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
|