246 lines
9.8 KiB
Haskell
Raw Normal View History

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)
data RelationRule = RelationRule {
2026-01-26 12:07:41 +00:00
headVariables :: [Text],
2026-01-23 18:12:47 +00:00
body :: [(Relation, [RuleElement])]
2026-01-22 18:00:52 +00:00
} deriving (Show, Eq)
2026-01-21 18:01:06 +00:00
data Relation = Relation {
2026-01-26 12:20:19 +00:00
_name :: Text,
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
-- newtype RelationId = RelationId Text
-- deriving (Eq, Ord, Show)
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
-- newtype Constant = Constant Text
-- deriving (Eq, Ord, Show)
2026-01-21 18:01:06 +00:00
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-23 18:12:47 +00:00
newRelation = lookupRelation relationName relationMap newArity terms
newRelationMap = Map.insert relationName newRelation relationMap
newConstantSet = Set.union constantSet $ Set.fromList terms
lookupRelation :: RelationId -> Map RelationId Relation -> Int -> [Term] -> Relation
lookupRelation relationName relationMap newArity terms =
case Map.lookup relationName relationMap of
2026-01-26 12:20:19 +00:00
Nothing -> Relation relationName newArity (Set.singleton terms) []
2026-01-23 18:12:47 +00:00
Just relation ->
if (_arity relation == newArity)
2026-01-26 12:20:19 +00:00
then Relation relationName (length terms) (Set.singleton terms) []
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
subRelation = lookupRelation subRelationName relationMap newArity terms
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
relation = lookupRelation relationName (relations (_db context)) newArity terms
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
-- toBodyConstraint :: (Relation, [RuleElement]) -> BodyConstraint
-- toBodyConstraint (subRelation, ruleElements) =
-- BodyConstraint {
-- _relation = subRelation,
-- _elements = toConstraintElement <$> ruleElements
-- }
newRule = RelationRule {
2026-01-26 12:07:41 +00:00
headVariables = extractVarName <$> variables',
2026-01-23 18:12:47 +00:00
body = fromBodyConstraint <$> (_bodyConstraints context'')
} where
fromBodyConstraint :: BodyConstraint -> (Relation, [RuleElement])
fromBodyConstraint (BodyConstraint subRelation elements) =
(subRelation, toRuleElement <$> elements)
toRuleElement :: ConstraintElement -> RuleElement
toRuleElement (ConstraintElementConstant constant) = RuleElementConstant constant
toRuleElement (ConstraintElementIndex index) = variables' !! index
-- input is [(Relation, [RuleElement])]
-- bodyConstraint has
-- _relation :: Relation,
-- _elements :: [ConstraintElement]
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