238 lines
8.7 KiB
Haskell
Raw Normal View History

2026-01-21 11:24:30 +00:00
{-# HLINT ignore "Redundant flip" #-}
2026-01-21 17:11:13 +00:00
{-# LANGUAGE ImportQualifiedPost #-}
2026-01-27 16:41:33 +00:00
{-# LANGUAGE OverloadedStrings #-}
2026-01-23 18:12:47 +00:00
{-# LANGUAGE ScopedTypeVariables #-}
2026-01-27 16:41:33 +00:00
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
2026-01-21 11:24:30 +00:00
module Datalog.NaiveDatabase where
2026-01-27 16:41:33 +00:00
import Control.Exception.Base
import Data.List
2026-01-21 11:24:30 +00:00
import Data.Map (Map)
2026-01-27 16:41:33 +00:00
import Data.Map qualified as Map
import Data.Maybe
2026-01-21 11:24:30 +00:00
import Data.Set (Set)
2026-01-21 17:11:13 +00:00
import Data.Set qualified as Set
import Data.Text (Text)
import Data.Void
2026-01-27 16:41:33 +00:00
import Datalog.DatalogParser (Head (HeadSingle), Literal (..), Statement (..), Term (..), parseDatalog)
import Text.Megaparsec (ParseErrorBundle)
2026-01-21 11:24:30 +00:00
2026-01-27 16:41:33 +00:00
data Value
= ValueInt Int
| ValueSymbol String
2026-01-21 11:24:30 +00:00
2026-01-27 16:41:33 +00:00
data NaiveDatabase = NaiveDatabase
{ relations :: Map RelationId Relation
, constants :: Set Constant
}
2026-01-21 17:11:13 +00:00
2026-01-27 16:41:33 +00:00
data RuleElement -- entry occurring in a head or body relation - constant or variable
= RuleElementConstant Constant
2026-01-27 18:40:05 +00:00
| RuleElementVariable Int
2026-01-22 18:00:52 +00:00
deriving (Show, Eq)
2026-01-27 16:41:33 +00:00
data RuleBodyElement = RuleBodyElement
{ _subRelationId :: RelationId
, _ruleElements :: [RuleElement]
}
deriving (Show, Eq)
2026-01-27 12:41:23 +00:00
2026-01-27 16:41:33 +00:00
data RelationRule = RelationRule
{ headVariables :: [Text]
, bodyElements :: [RuleBodyElement]
}
deriving (Show, Eq)
2026-01-22 18:00:52 +00:00
2026-01-27 16:41:33 +00:00
data Relation = Relation
{ _name :: RelationId
, _arity :: Int
, _tuples :: Set [Constant]
, _rules :: [RelationRule]
}
deriving (Show, Eq)
2026-01-21 18:01:06 +00:00
2026-01-22 10:57:11 +00:00
-- Our constants will be the terms of the Datalog grammar - ints/variables/symbols
type Constant = Term
type RelationId = Text
2026-01-21 17:11:13 +00:00
emptyDB :: NaiveDatabase
2026-01-27 16:41:33 +00:00
emptyDB =
NaiveDatabase
{ relations = Map.empty
, constants = Set.empty -- the Herbrand universe
}
2026-01-21 17:11:13 +00:00
withFacts :: [Text] -> NaiveDatabase
withFacts facts =
2026-01-28 10:02:25 +00:00
foldr addFact emptyDB (extractFact <$> facts)
2026-01-27 16:41:33 +00:00
where
extractFact :: Text -> Literal
extractFact factText =
2026-01-27 17:33:44 +00:00
case parseDatalog factText of
2026-01-27 16:41:33 +00:00
Right (Fact fact) -> fact
Right otherStatement -> throw $ NonFactException factText otherStatement
Left ex -> throw $ CannotParseStatementException factText ex
2026-01-28 10:02:25 +00:00
addFact :: Literal -> NaiveDatabase -> NaiveDatabase
addFact (Literal neg relationName terms) (NaiveDatabase relationMap constantSet) =
2026-01-27 16:41:33 +00:00
NaiveDatabase newRelationMap newConstantSet
where
newArity = length terms
newRelation = lookupRelation relationName relationMap newArity (Set.singleton terms)
newRelationMap = Map.insert relationName newRelation relationMap
newConstantSet = Set.union constantSet $ Set.fromList terms
2026-01-23 18:12:47 +00:00
2026-01-26 18:28:15 +00:00
lookupRelation :: RelationId -> Map RelationId Relation -> Int -> Set [Term] -> Relation
lookupRelation relationName relationMap newArity tuples =
2026-01-27 16:41:33 +00:00
case Map.lookup relationName relationMap of
Nothing -> Relation relationName newArity tuples []
Just relation ->
2026-01-27 17:33:44 +00:00
if _arity relation == newArity then
2026-01-27 16:41:33 +00:00
let newTuples = Set.union tuples $ _tuples relation
in Relation relationName newArity newTuples []
else throw $ BadArityException relationName newArity
data BodyConstraint = BodyConstraint
2026-01-28 10:02:25 +00:00
{ _subRelation :: Relation
2026-01-27 18:40:05 +00:00
, _elements :: [RuleElement]
2026-01-27 16:41:33 +00:00
}
2026-01-23 18:12:47 +00:00
2026-01-27 16:41:33 +00:00
data RuleContext = RuleContext
{ __relation :: Relation
2026-01-27 17:55:19 +00:00
, _variableNames :: [Text]
, _headEntries :: [RuleElement]
2026-01-27 16:41:33 +00:00
, _bodyConstraints :: [BodyConstraint]
, _db :: NaiveDatabase
}
2026-01-23 18:12:47 +00:00
2026-01-22 17:33:49 +00:00
withFactsAndRules :: [Text] -> [Text] -> NaiveDatabase
2026-01-27 18:01:30 +00:00
withFactsAndRules facts = foldr (addRule . extractRule) (withFacts facts)
2026-01-27 16:41:33 +00:00
where
extractRule :: Text -> (Literal, [Literal])
extractRule ruleText =
2026-01-27 17:27:18 +00:00
case parseDatalog ruleText of
2026-01-27 16:41:33 +00:00
Right (Rule (HeadSingle lhs) rhs) -> (lhs, rhs)
Right otherStatement -> throw $ NonRuleException ruleText otherStatement
Left ex -> throw $ CannotParseStatementException ruleText ex
digestHead :: NaiveDatabase -> Relation -> Literal -> RuleContext
digestHead db relation (Literal neg relationName terms) =
RuleContext
{ __relation = relation
2026-01-27 18:40:05 +00:00
, _variableNames = variableNames
, _headEntries = entries'
2026-01-27 16:41:33 +00:00
, _bodyConstraints = []
, _db = NaiveDatabase relationMap' constants'
}
where
2026-01-27 18:40:05 +00:00
variableNames = extractVariableNames terms
headTermToElement :: Term -> RuleElement
headTermToElement (Var name) =
case (elemIndex name) variableNames of
Just index -> RuleElementVariable index
Nothing -> throw $ VariableLookupException name variableNames
headTermToElement constant = RuleElementConstant constant
2026-01-27 16:41:33 +00:00
relationMap :: Map RelationId Relation = relations db
relationMap' = Map.insert relationName relation relationMap
2026-01-27 18:40:05 +00:00
extraVariables = headTermToElement <$> terms
entries' = nub extraVariables
2026-01-28 11:49:21 +00:00
extraConstants = mapMaybe extractConstant entries' where
extractConstant :: RuleElement -> Maybe Constant
extractConstant (RuleElementConstant constant) = Just constant
extractConstant _ = Nothing
2026-01-27 16:41:33 +00:00
constants' = Set.union (constants db) $ Set.fromList extraConstants
2026-01-28 10:02:25 +00:00
digestBody :: Literal -> RuleContext -> RuleContext
2026-01-28 11:49:21 +00:00
digestBody (Literal neg subRelationName subTerms) context =
2026-01-27 16:41:33 +00:00
RuleContext
2026-01-28 11:49:21 +00:00
{ __relation = __relation context
2026-01-27 18:40:05 +00:00
, _variableNames = variableNames
2026-01-28 11:49:21 +00:00
, _headEntries = _headEntries context
, _bodyConstraints = newConstraint : constraints
2026-01-27 16:41:33 +00:00
, _db = NaiveDatabase relationMap' constants'
}
where
2026-01-28 11:49:21 +00:00
variableNames = nub $ _variableNames context ++ extractVariableNames subTerms
newArity = length subTerms
2026-01-27 16:41:33 +00:00
subRelation = lookupRelation subRelationName relationMap newArity Set.empty
relationMap :: Map RelationId Relation = relations (_db context)
relationMap' = Map.insert subRelationName subRelation relationMap
2026-01-28 11:49:21 +00:00
-- extraVariables = bodyTermToElement <$> terms where
-- bodyTermToElement :: Term -> RuleElement
-- bodyTermToElement (Var name) =
-- case (elemIndex name) variableNames of
-- Just index -> RuleElementVariable index
-- Nothing -> throw $ VariableLookupException name variableNames
-- bodyTermToElement constant = RuleElementConstant constant
extraConstants = mapMaybe constantFromTerm subTerms where
constantFromTerm :: Term -> Maybe Constant
constantFromTerm (Var _) = Nothing
constantFromTerm constant = Just constant
-- variables' = nub $ _headEntries context ++ extraVariables
2026-01-27 16:41:33 +00:00
constants' = Set.union (constants (_db context)) $ Set.fromList extraConstants
constraints = _bodyConstraints context
2026-01-28 11:49:21 +00:00
newConstraint = BodyConstraint subRelation subRuleElements where
subRuleElements = toRuleElement <$> subTerms
toRuleElement :: Term -> RuleElement
toRuleElement (Var varName) = RuleElementVariable $ lookupVariable varName
toRuleElement constant = RuleElementConstant constant
lookupVariable :: Text -> Int
lookupVariable varName =
case (elemIndex varName) variableNames of
Just index -> index
Nothing -> throw $ VariableLookupException varName variableNames
2026-01-28 10:06:40 +00:00
addRule :: (Literal, [Literal]) -> NaiveDatabase -> NaiveDatabase
2026-01-27 16:41:33 +00:00
addRule (ruleHead, body) db =
2026-01-27 18:40:05 +00:00
NaiveDatabase relationMap' constants'
2026-01-27 16:41:33 +00:00
where
relationName = predName ruleHead
terms = arguments ruleHead
newArity = length terms
relation = lookupRelation relationName (relations db) newArity Set.empty
2026-01-27 18:01:30 +00:00
context = digestHead db relation ruleHead
2026-01-28 10:02:25 +00:00
context' = foldr digestBody context body
2026-01-27 18:01:30 +00:00
db' = _db context'
2026-01-27 16:41:33 +00:00
relationMap = relations db'
newRule =
RelationRule
2026-01-27 18:40:05 +00:00
{ headVariables = _variableNames context'
, bodyElements = toRuleBodyElement <$> _bodyConstraints context'
} where
toRuleBodyElement :: BodyConstraint -> RuleBodyElement
toRuleBodyElement (BodyConstraint subRelation elements) =
RuleBodyElement {
_subRelationId = _name subRelation
, _ruleElements = elements
}
2026-01-27 16:41:33 +00:00
relation' =
Relation
{ _name = _name relation
, _arity = newArity
, _tuples = _tuples relation
2026-01-27 17:27:18 +00:00
, _rules = newRule : _rules relation
2026-01-23 18:12:47 +00:00
}
2026-01-27 18:40:05 +00:00
relationMap' = Map.insert relationName relation' relationMap
2026-01-27 16:41:33 +00:00
constants' = constants db'
2026-01-27 17:55:19 +00:00
extractVariableNames :: [Term] -> [Text]
extractVariableNames = mapMaybe extractVariableName where
extractVariableName :: Term -> Maybe Text
extractVariableName (Var name) = Just name
extractVariableName _ = Nothing
2026-01-23 18:12:47 +00:00
2026-01-21 17:11:13 +00:00
query :: NaiveDatabase -> Text -> Text
query db qText =
2026-01-27 17:27:18 +00:00
case parseDatalog qText of
2026-01-21 17:11:13 +00:00
Right (Query texts literals) -> "#NYI"
Right otherStatement -> throw $ NonQueryException qText otherStatement
Left ex -> throw $ CannotParseStatementException qText ex
data NaiveDatabaseException
2026-01-27 16:41:33 +00:00
= CannotParseStatementException Text (ParseErrorBundle Text Void)
| NonFactException Text Statement
| NonRuleException Text Statement
| NonQueryException Text Statement
| BadArityException Text Int
2026-01-27 18:40:05 +00:00
| VariableLookupException Text [Text]
2026-01-27 16:41:33 +00:00
| UnexpectedConstantException Constant
2026-01-21 17:11:13 +00:00
deriving (Show)
instance Exception NaiveDatabaseException