2026-01-21 11:24:30 +00:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
|
{-# LANGUAGE TypeApplications #-}
|
|
|
|
|
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
|
|
|
|
|
|
|
|
|
|
{-# HLINT ignore "Redundant flip" #-}
|
2026-01-21 17:11:13 +00:00
|
|
|
{-# LANGUAGE ImportQualifiedPost #-}
|
2026-01-23 18:12:47 +00:00
|
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
2026-01-21 11:24:30 +00:00
|
|
|
module Datalog.NaiveDatabase where
|
|
|
|
|
|
|
|
|
|
import Data.Map (Map)
|
|
|
|
|
import Data.Set (Set)
|
2026-01-21 17:11:13 +00:00
|
|
|
import Data.Set qualified as Set
|
|
|
|
|
import Data.Text (Text)
|
2026-01-23 18:12:47 +00:00
|
|
|
import Datalog.DatalogParser(parseDatalog, Literal(..), Statement(..), Term (..), Head (HeadSingle))
|
2026-01-21 17:11:13 +00:00
|
|
|
import qualified Data.Map as Map
|
|
|
|
|
import Text.Megaparsec (ParseErrorBundle)
|
|
|
|
|
import Data.Void
|
|
|
|
|
import Control.Exception.Base
|
2026-01-23 18:12:47 +00:00
|
|
|
import Data.List
|
|
|
|
|
import Data.Maybe
|
2026-01-21 11:24:30 +00:00
|
|
|
|
|
|
|
|
data Value =
|
|
|
|
|
ValueInt Int |
|
|
|
|
|
ValueSymbol String
|
|
|
|
|
|
|
|
|
|
data NaiveDatabase = NaiveDatabase {
|
2026-01-21 18:01:06 +00:00
|
|
|
relations :: Map RelationId Relation,
|
2026-01-22 10:57:11 +00:00
|
|
|
constants :: Set Constant
|
2026-01-21 17:11:13 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-23 18:12:47 +00:00
|
|
|
data RuleElement = -- entry occurring in a head or body relation - constant or variable
|
|
|
|
|
RuleElementConstant Constant |
|
|
|
|
|
RuleElementVariable Text
|
2026-01-22 18:00:52 +00:00
|
|
|
deriving (Show, Eq)
|
|
|
|
|
|
2026-01-27 12:41:23 +00:00
|
|
|
data RuleBodyElement = RuleBodyElement {
|
2026-01-27 14:22:43 +00:00
|
|
|
_subRelationId :: RelationId,
|
2026-01-27 12:41:23 +00:00
|
|
|
_ruleElements :: [RuleElement]
|
|
|
|
|
} deriving (Show, Eq)
|
|
|
|
|
|
2026-01-22 18:00:52 +00:00
|
|
|
data RelationRule = RelationRule {
|
2026-01-26 12:07:41 +00:00
|
|
|
headVariables :: [Text],
|
2026-01-27 12:41:23 +00:00
|
|
|
bodyElements :: [RuleBodyElement]
|
2026-01-22 18:00:52 +00:00
|
|
|
} deriving (Show, Eq)
|
|
|
|
|
|
2026-01-21 18:01:06 +00:00
|
|
|
data Relation = Relation {
|
2026-01-27 14:22:43 +00:00
|
|
|
_name :: RelationId,
|
2026-01-23 18:12:47 +00:00
|
|
|
_arity :: Int,
|
|
|
|
|
_tuples :: Set [Constant],
|
|
|
|
|
_rules :: [RelationRule]
|
2026-01-22 14:25:09 +00:00
|
|
|
} 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
|
|
|
|
|
emptyDB = NaiveDatabase {
|
|
|
|
|
relations = Map.empty,
|
2026-01-22 10:57:11 +00:00
|
|
|
constants = Set.empty -- the Herbrand universe
|
2026-01-21 17:11:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
withFacts :: [Text] -> NaiveDatabase
|
|
|
|
|
withFacts facts =
|
|
|
|
|
foldl addFact emptyDB (extractFact <$> facts) where
|
|
|
|
|
extractFact:: Text -> Literal
|
|
|
|
|
extractFact factText =
|
|
|
|
|
case (parseDatalog factText) of
|
|
|
|
|
Right (Fact fact) -> fact
|
|
|
|
|
Right otherStatement -> throw $ NonFactException factText otherStatement
|
|
|
|
|
Left ex -> throw $ CannotParseStatementException factText ex
|
|
|
|
|
addFact :: NaiveDatabase -> Literal -> NaiveDatabase
|
2026-01-23 18:12:47 +00:00
|
|
|
addFact (NaiveDatabase relationMap constantSet) (Literal neg relationName terms) =
|
|
|
|
|
NaiveDatabase newRelationMap newConstantSet where
|
2026-01-22 14:25:09 +00:00
|
|
|
newArity = length terms
|
2026-01-26 18:28:15 +00:00
|
|
|
newRelation = lookupRelation relationName relationMap newArity (Set.singleton terms)
|
2026-01-23 18:12:47 +00:00
|
|
|
newRelationMap = Map.insert relationName newRelation relationMap
|
|
|
|
|
newConstantSet = Set.union constantSet $ Set.fromList terms
|
|
|
|
|
|
2026-01-26 18:28:15 +00:00
|
|
|
lookupRelation :: RelationId -> Map RelationId Relation -> Int -> Set [Term] -> Relation
|
|
|
|
|
lookupRelation relationName relationMap newArity tuples =
|
2026-01-23 18:12:47 +00:00
|
|
|
case Map.lookup relationName relationMap of
|
2026-01-26 18:28:15 +00:00
|
|
|
Nothing -> Relation relationName newArity tuples []
|
2026-01-23 18:12:47 +00:00
|
|
|
Just relation ->
|
|
|
|
|
if (_arity relation == newArity)
|
2026-01-26 18:28:15 +00:00
|
|
|
then
|
|
|
|
|
let newTuples = Set.union tuples $ _tuples relation
|
|
|
|
|
in Relation relationName newArity newTuples []
|
2026-01-23 18:12:47 +00:00
|
|
|
else throw $ BadArityException relationName newArity
|
|
|
|
|
|
|
|
|
|
data ConstraintElement = -- entry occurring in a rule body constraint - constant or variable index
|
|
|
|
|
ConstraintElementConstant Constant |
|
|
|
|
|
ConstraintElementIndex Int
|
|
|
|
|
deriving (Show, Eq)
|
|
|
|
|
|
|
|
|
|
data BodyConstraint = BodyConstraint {
|
|
|
|
|
_relation :: Relation,
|
|
|
|
|
_elements :: [ConstraintElement]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data RuleContext = RuleContext {
|
|
|
|
|
__relation :: Relation,
|
|
|
|
|
_headVariables :: [RuleElement],
|
|
|
|
|
_bodyConstraints :: [BodyConstraint],
|
|
|
|
|
_db :: NaiveDatabase
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toElement :: Term -> RuleElement
|
|
|
|
|
-- toElement constant@(Var _) = RuleElementConstant constant
|
|
|
|
|
-- toElement constant@(Num _) = RuleElementConstant constant
|
|
|
|
|
toElement (Var name) = RuleElementVariable name
|
|
|
|
|
toElement constant = RuleElementConstant constant
|
|
|
|
|
|
|
|
|
|
maybeConstant :: RuleElement -> Maybe Constant
|
|
|
|
|
maybeConstant (RuleElementConstant constant) = Just constant
|
|
|
|
|
maybeConstant _ = Nothing
|
2026-01-21 17:11:13 +00:00
|
|
|
|
2026-01-22 17:33:49 +00:00
|
|
|
withFactsAndRules :: [Text] -> [Text] -> NaiveDatabase
|
|
|
|
|
withFactsAndRules facts rules =
|
|
|
|
|
foldl addRule (withFacts facts) (extractRule <$> rules) where
|
|
|
|
|
extractRule:: Text -> (Literal, [Literal])
|
|
|
|
|
extractRule ruleText =
|
|
|
|
|
case (parseDatalog ruleText) of
|
2026-01-22 18:00:52 +00:00
|
|
|
Right (Rule (HeadSingle lhs) rhs) -> (lhs, rhs)
|
2026-01-22 17:33:49 +00:00
|
|
|
Right otherStatement -> throw $ NonRuleException ruleText otherStatement
|
|
|
|
|
Left ex -> throw $ CannotParseStatementException ruleText ex
|
2026-01-23 18:12:47 +00:00
|
|
|
digestHead :: RuleContext -> Literal -> RuleContext
|
|
|
|
|
digestHead context (Literal neg relationName terms) =
|
|
|
|
|
RuleContext {
|
|
|
|
|
__relation = relation,
|
|
|
|
|
_headVariables = variables',
|
|
|
|
|
_bodyConstraints = _bodyConstraints context,
|
|
|
|
|
_db = NaiveDatabase relationMap' constants'
|
|
|
|
|
} where
|
|
|
|
|
newArity = length terms
|
|
|
|
|
relation = __relation context
|
|
|
|
|
relationMap :: Map RelationId Relation = (relations (_db context))
|
|
|
|
|
relationMap' = Map.insert relationName relation relationMap
|
|
|
|
|
extraVariables = toElement <$> terms
|
|
|
|
|
extraConstants = catMaybes $ maybeConstant <$> extraVariables
|
|
|
|
|
variables' = nub $ _headVariables context ++ extraVariables
|
|
|
|
|
constants' = Set.union (constants (_db context)) $ Set.fromList extraConstants
|
|
|
|
|
digestBody :: RuleContext -> Literal -> RuleContext
|
|
|
|
|
digestBody context (Literal neg subRelationName terms) =
|
|
|
|
|
RuleContext {
|
|
|
|
|
__relation = relation,
|
|
|
|
|
_headVariables = variables',
|
|
|
|
|
_bodyConstraints = constraints',
|
|
|
|
|
_db = NaiveDatabase relationMap' constants'
|
|
|
|
|
} where
|
|
|
|
|
relation = __relation context
|
|
|
|
|
newArity = length terms
|
2026-01-26 18:28:15 +00:00
|
|
|
subRelation = lookupRelation subRelationName relationMap newArity Set.empty
|
2026-01-23 18:12:47 +00:00
|
|
|
relationMap :: Map RelationId Relation = relations (_db context)
|
|
|
|
|
relationMap' = Map.insert subRelationName subRelation relationMap
|
|
|
|
|
extraVariables = toElement <$> terms
|
|
|
|
|
extraConstants = catMaybes $ maybeConstant <$> extraVariables
|
|
|
|
|
variables' = nub $ _headVariables context ++ extraVariables
|
|
|
|
|
constants' = Set.union (constants (_db context)) $ Set.fromList extraConstants
|
|
|
|
|
constraints = _bodyConstraints context
|
|
|
|
|
newConstraint = BodyConstraint subRelation (toConstraint <$> terms)
|
|
|
|
|
constraints' = constraints ++ [newConstraint]
|
|
|
|
|
varIndex :: Text -> Int
|
|
|
|
|
varIndex name =
|
|
|
|
|
case elemIndex (RuleElementVariable name) variables' of
|
|
|
|
|
Just index -> index
|
|
|
|
|
Nothing -> throw $ VariableLookupException name variables'
|
|
|
|
|
toConstraint :: Term -> ConstraintElement
|
|
|
|
|
-- toConstraint thing = ConstraintElementIndex 0
|
|
|
|
|
toConstraint (Var name) = ConstraintElementIndex (varIndex name)
|
|
|
|
|
toConstraint constant = ConstraintElementConstant constant
|
2026-01-22 18:00:52 +00:00
|
|
|
addRule :: NaiveDatabase -> (Literal, [Literal]) -> NaiveDatabase
|
2026-01-23 18:12:47 +00:00
|
|
|
addRule db (ruleHead, body) =
|
|
|
|
|
NaiveDatabase newRelationMap constants' where
|
|
|
|
|
relationName = predName ruleHead
|
|
|
|
|
terms = arguments ruleHead
|
|
|
|
|
newArity = length terms
|
2026-01-26 18:28:15 +00:00
|
|
|
relation = lookupRelation relationName (relations (_db context)) newArity Set.empty
|
2026-01-23 18:12:47 +00:00
|
|
|
context = RuleContext {
|
|
|
|
|
__relation = relation,
|
|
|
|
|
_headVariables = [],
|
|
|
|
|
_bodyConstraints = [],
|
|
|
|
|
_db = db
|
|
|
|
|
}
|
|
|
|
|
context' = digestHead context ruleHead
|
|
|
|
|
context'' = foldl digestBody context' body
|
|
|
|
|
db' = _db context''
|
|
|
|
|
relationMap = relations db'
|
|
|
|
|
variables' = _headVariables context''
|
|
|
|
|
varIndex :: Text -> Int -- TODO unify with the above
|
|
|
|
|
varIndex name =
|
|
|
|
|
case elemIndex (RuleElementVariable name) variables' of
|
|
|
|
|
Just index -> index
|
|
|
|
|
Nothing -> throw $ VariableLookupException name variables'
|
|
|
|
|
toConstraintElement :: RuleElement -> ConstraintElement
|
|
|
|
|
toConstraintElement (RuleElementConstant constant) = ConstraintElementConstant constant
|
|
|
|
|
toConstraintElement (RuleElementVariable varName) = ConstraintElementIndex (varIndex varName)
|
2026-01-26 12:07:41 +00:00
|
|
|
extractVarName :: RuleElement -> Text
|
|
|
|
|
extractVarName (RuleElementVariable varName) = varName
|
|
|
|
|
extractVarName (RuleElementConstant constant) = throw $ UnexpectedConstantException constant
|
2026-01-23 18:12:47 +00:00
|
|
|
newRule = RelationRule {
|
2026-01-26 12:07:41 +00:00
|
|
|
headVariables = extractVarName <$> variables',
|
2026-01-27 12:41:23 +00:00
|
|
|
bodyElements = fromBodyConstraint <$> (_bodyConstraints context'')
|
2026-01-23 18:12:47 +00:00
|
|
|
} where
|
2026-01-27 12:41:23 +00:00
|
|
|
fromBodyConstraint :: BodyConstraint -> RuleBodyElement
|
2026-01-23 18:12:47 +00:00
|
|
|
fromBodyConstraint (BodyConstraint subRelation elements) =
|
2026-01-27 12:41:23 +00:00
|
|
|
RuleBodyElement {
|
2026-01-27 14:22:43 +00:00
|
|
|
_subRelationId = _name subRelation,
|
2026-01-27 12:41:23 +00:00
|
|
|
_ruleElements = toRuleElement <$> elements
|
|
|
|
|
}
|
2026-01-23 18:12:47 +00:00
|
|
|
toRuleElement :: ConstraintElement -> RuleElement
|
|
|
|
|
toRuleElement (ConstraintElementConstant constant) = RuleElementConstant constant
|
|
|
|
|
toRuleElement (ConstraintElementIndex index) = variables' !! index
|
|
|
|
|
relation' = Relation {
|
2026-01-26 12:20:19 +00:00
|
|
|
_name = _name relation,
|
2026-01-23 18:12:47 +00:00
|
|
|
_arity = newArity,
|
|
|
|
|
_tuples = _tuples relation,
|
|
|
|
|
_rules = (_rules relation) ++ [newRule]
|
|
|
|
|
}
|
|
|
|
|
newRelationMap = Map.insert relationName relation' relationMap
|
|
|
|
|
constants' = constants db'
|
|
|
|
|
|
2026-01-21 17:11:13 +00:00
|
|
|
query :: NaiveDatabase -> Text -> Text
|
|
|
|
|
query db qText =
|
|
|
|
|
case (parseDatalog qText) of
|
|
|
|
|
Right (Query texts literals) -> "#NYI"
|
|
|
|
|
Right otherStatement -> throw $ NonQueryException qText otherStatement
|
|
|
|
|
Left ex -> throw $ CannotParseStatementException qText ex
|
|
|
|
|
|
|
|
|
|
data NaiveDatabaseException
|
|
|
|
|
= CannotParseStatementException Text (ParseErrorBundle Text Void) |
|
|
|
|
|
NonFactException Text Statement |
|
2026-01-22 17:33:49 +00:00
|
|
|
NonRuleException Text Statement |
|
2026-01-22 14:25:09 +00:00
|
|
|
NonQueryException Text Statement |
|
2026-01-23 18:12:47 +00:00
|
|
|
BadArityException Text Int |
|
2026-01-26 12:07:41 +00:00
|
|
|
VariableLookupException Text [RuleElement] |
|
|
|
|
|
UnexpectedConstantException Constant
|
2026-01-21 17:11:13 +00:00
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
|
|
instance Exception NaiveDatabaseException
|