111 lines
3.6 KiB
Haskell
Raw Normal View History

2026-01-30 10:27:26 +00:00
{-# HLINT ignore "Redundant flip" #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
2026-01-30 16:16:06 +00:00
{-# LANGUAGE BlockArguments #-}
2026-01-30 10:27:26 +00:00
module Datalog.DatalogDB where
import Control.Exception.Base
import Data.Set (Set)
import Data.Set qualified as Set
import Data.Text (Text)
2026-01-30 14:46:09 +00:00
import Datalog.DatalogParser (Literal (..), Term (..), Statement)
2026-01-30 10:27:26 +00:00
import Text.Megaparsec (ParseErrorBundle)
2026-01-30 14:46:09 +00:00
import Data.Void
data Relation = Relation
{ _name :: RelationId
, _arity :: Int
, _tuples :: Set [Constant]
, _rules :: [RelationRule]
}
deriving (Show, Eq)
data RelationRule = RelationRule
{ headVariables :: [Text]
, bodyElements :: [RuleBodyElement]
}
deriving (Show, Eq)
data RuleBodyElement = RuleBodyElement
{ _subRelationId :: RelationId
, _ruleElements :: [RuleElement]
}
deriving (Show, Eq)
data RuleElement -- entry occurring in a head or body relation - constant or variable
= RuleElementConstant Constant
| RuleElementVariable Int
deriving (Show, Eq)
data DatalogDBException
= CannotParseStatementException Text (ParseErrorBundle Text Void)
| NonFactException Text Statement
| NonRuleException Text Statement
| NonQueryException Text Statement
| BadArityException Text Int
| VariableLookupException Text [Text]
| UnexpectedConstantException Constant
deriving (Show)
instance Exception DatalogDBException
-- Our constants will be the terms of the Datalog grammar - ints/variables/symbols
type Constant = Term
type RelationId = Text
2026-01-30 10:27:26 +00:00
class DatalogDB db where
emptyDB :: db
lookupRelation :: db -> Text -> Maybe Relation
insertRelation :: db -> Relation -> db
2026-01-30 12:53:40 +00:00
addConstants :: db -> Set Constant -> db
2026-01-30 14:37:45 +00:00
lookupRelationArity :: DatalogDB db => Text -> db -> Int -> Set [Constant] -> Relation
lookupRelationArity relationName db newArity tuples =
case lookupRelation db relationName of
Nothing -> Relation relationName newArity tuples []
Just relation ->
if _arity relation == newArity then
let newTuples = Set.union tuples $ _tuples relation
in relation { _tuples = newTuples }
else throw $ BadArityException relationName newArity
2026-01-30 16:16:06 +00:00
lookupRelationArity0 :: DatalogDB db => Text -> db -> Int -> (Relation -> Relation) -> db
lookupRelationArity0 relationName db newArity update =
insertRelation db (update newRelation)
where
newRelation = case lookupRelation db relationName of
Nothing -> Relation relationName newArity Set.empty []
Just relation ->
if _arity relation == newArity then relation
else throw $ BadArityException relationName newArity
2026-01-30 14:37:45 +00:00
addFact :: (DatalogDB db) => Literal -> db -> db
addFact (Literal neg relationName terms) db =
insertRelation (addConstants db extraConstants) newRelation
where
newArity = length terms
newRelation = lookupRelationArity relationName db newArity (Set.singleton terms)
extraConstants = Set.fromList terms
2026-01-30 16:16:06 +00:00
lookupRelation00 :: DatalogDB db =>
Text -> db -> Int -> (Relation -> Relation) -> db
lookupRelation00 relationName db newArity update =
insertRelation db (update newRelation)
where
newRelation = case lookupRelation db relationName of
Nothing -> Relation relationName newArity Set.empty []
Just relation ->
if _arity relation == newArity then
relation
else throw $ BadArityException relationName newArity
lookupRelation000 :: DatalogDB db =>
Text -> db -> Int -> Set [Constant] -> (Relation -> Relation) -> db
lookupRelation000 relationName db newArity tuples update =
lookupRelation00 relationName db newArity \relation ->
update relation {
_tuples = Set.union tuples $ _tuples relation
}