163 lines
5.3 KiB
Haskell
163 lines
5.3 KiB
Haskell
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
module Propa.Prolog.Unify
|
|
( run
|
|
) where
|
|
|
|
import Control.Applicative
|
|
import Control.Monad
|
|
import Data.Foldable
|
|
import Data.List
|
|
import Data.Tuple
|
|
|
|
import Control.Monad.Trans.Class
|
|
import Control.Monad.Trans.State
|
|
import qualified Data.Map as Map
|
|
import qualified Data.Set as Set
|
|
import qualified Data.Text as T
|
|
|
|
import Propa.Prolog.Types
|
|
|
|
-- General utility functions
|
|
|
|
-- | Start at a value and follow the map's entries until the end of the chain of
|
|
-- references.
|
|
follow :: (Ord a) => (b -> Maybe a) -> Map.Map a b -> b -> b
|
|
follow f m b = maybe b (follow f m) $ (m Map.!?) =<< f b
|
|
|
|
-- | Deduplicates the elements of a finite list. Doesn't preserve the order of
|
|
-- the elements. Doesn't work on infinite lists.
|
|
deduplicate :: (Ord a) => [a] -> [a]
|
|
deduplicate = Set.toList . Set.fromList
|
|
|
|
-- Now the fun begins...
|
|
|
|
data Context = Context
|
|
{ cDb :: Db T.Text
|
|
, cVarIdx :: Int
|
|
, cTerms :: Map.Map Int (Term Int)
|
|
} deriving (Show)
|
|
|
|
newContext :: [Def T.Text] -> Context
|
|
newContext db = Context db 0 Map.empty
|
|
|
|
bindTerm :: Int -> Term Int -> UniM ()
|
|
bindTerm k v = modify $ \c -> c{cTerms = Map.insert k v $ cTerms c}
|
|
|
|
-- | Look up a variable, first repeatedly in the var map and then the term map.
|
|
-- Returns statements unchanged.
|
|
--
|
|
-- If this returns a variable, then that variable is not bound.
|
|
lookupTerm :: Term Int -> UniM (Term Int)
|
|
lookupTerm t = do
|
|
c <- get
|
|
pure $ follow tVar (cTerms c) t
|
|
|
|
-- | A simple state monad transformer over the list monad for easy backtracking.
|
|
-- Needs to be changed when implementing cuts.
|
|
type UniM = StateT Context []
|
|
|
|
varMap :: (Foldable a) => a T.Text -> UniM (Map.Map T.Text Int)
|
|
varMap a = do
|
|
c <- get
|
|
let i = cVarIdx c
|
|
vars = deduplicate $ toList a
|
|
vmap = Map.fromList $ zip vars [i..]
|
|
put c{cVarIdx = i + Map.size vmap}
|
|
pure vmap
|
|
|
|
-- | Convert a definition's variables to unique integers that are not already in
|
|
-- use in the current context.
|
|
understand :: (Functor a, Foldable a) => a T.Text -> UniM (a Int, Map.Map T.Text Int)
|
|
understand a = do
|
|
vmap <- varMap a
|
|
pure (fmap (vmap Map.!) a, vmap)
|
|
|
|
satisfyStat :: Stat Int -> UniM ()
|
|
satisfyStat stat = do
|
|
c <- get
|
|
(Def dStat dStats, _) <- understand =<< lift (cDb c)
|
|
unifyStat stat dStat
|
|
satisfyStats dStats
|
|
|
|
satisfyStats :: [Stat Int] -> UniM ()
|
|
satisfyStats = traverse_ satisfyStat
|
|
|
|
unifyStat :: Stat Int -> Stat Int -> UniM ()
|
|
unifyStat (Stat name1 args1) (Stat name2 args2) = do
|
|
guard $ name1 == name2
|
|
unifyTerms args1 args2
|
|
|
|
unifyTerm :: Term Int -> Term Int -> UniM ()
|
|
unifyTerm t1 t2 = do
|
|
t1' <- lookupTerm t1
|
|
t2' <- lookupTerm t2
|
|
case (t1', t2') of
|
|
(TStat s1, TStat s2) -> unifyStat s1 s2
|
|
(TInt i1, TInt i2) -> guard $ i1 == i2
|
|
(TVar v, t) -> bindTerm v t
|
|
(t, TVar v) -> bindTerm v t
|
|
(_, _) -> empty
|
|
|
|
unifyTerms :: [Term Int] -> [Term Int] -> UniM ()
|
|
unifyTerms t1 t2 = do
|
|
guard $ length t1 == length t2
|
|
sequenceA_ $ zipWith unifyTerm t1 t2
|
|
|
|
-- Figuring out how to display the result of the unification
|
|
|
|
-- | An infinite list of possible variable names: @A@, @B@, ..., @A1@, @B1@,
|
|
-- ..., @A2@, ...
|
|
varNames :: [T.Text]
|
|
varNames = do
|
|
num <- "" : map (T.pack . show) [(1::Integer)..]
|
|
char <- alphabet
|
|
pure $ char <> num
|
|
where
|
|
alphabet = map T.singleton ['A'..'Z']
|
|
|
|
-- | Find a naming (Map from integer to name) for all variables in a list of
|
|
-- terms based on the original variable names and the variable mapping. Attempts
|
|
-- to map variables to known variables instead of a common unknown variable.
|
|
findVarNaming :: Map.Map T.Text Int -> Map.Map Int (Term Int) -> [Term Int] -> Map.Map Int T.Text
|
|
findVarNaming known vars terms =
|
|
let knownLookedUp :: Map.Map T.Text Int
|
|
knownLookedUp = Map.mapMaybe (tVar . follow tVar vars . TVar) known
|
|
knownNaming = Map.fromList $ reverse $ map swap $ Map.toList knownLookedUp
|
|
knownNames = Map.keysSet known
|
|
knownVars = Map.keysSet knownNaming
|
|
termVars = Set.fromList $ concatMap toList terms
|
|
unknownVars = termVars Set.\\ knownVars
|
|
availVarNames = filter (not . (`Set.member` knownNames)) varNames
|
|
unknownNaming = Map.fromList $ zip (sort $ Set.toList unknownVars) availVarNames
|
|
in knownNaming <> unknownNaming
|
|
|
|
-- | Recursively set variables to their most resolved term.
|
|
resolveVars :: Term Int -> UniM (Term Int)
|
|
resolveVars t = do
|
|
t2 <- lookupTerm t
|
|
case t2 of
|
|
(TVar v) -> pure $ TVar v
|
|
(TInt i) -> pure $ TInt i
|
|
(TStat (Stat name args)) -> TStat . Stat name <$> traverse resolveVars args
|
|
|
|
-- | Helper type so I can resolve variables in multiple statements
|
|
-- simultaneously.
|
|
newtype Stats a = Stats { unStats :: [Stat a] }
|
|
|
|
instance Functor Stats where
|
|
fmap f (Stats ts) = Stats $ fmap (fmap f) ts
|
|
|
|
instance Foldable Stats where
|
|
foldMap f (Stats ts) = foldMap (foldMap f) ts
|
|
|
|
run :: Db T.Text -> [Stat T.Text] -> [Map.Map T.Text (Term T.Text)]
|
|
run db stats = map fst $ runStateT helper $ newContext db
|
|
where
|
|
helper = do
|
|
(stats2, vmap) <- understand $ Stats stats
|
|
satisfyStats $ unStats stats2
|
|
tmap <- traverse (resolveVars . TVar) vmap
|
|
c <- get
|
|
let naming = findVarNaming vmap (cTerms c) $ Map.elems tmap
|
|
pure $ fmap (naming Map.!) <$> tmap
|