atomo-0.4: src/Atomo/Method.hs
module Atomo.Method
( addMethod
, elemsMap
, emptyMap
, insertMethod
, insertMap
, lookupMap
, memberMap
, noMethods
, nullMap
, toMethods
) where
import Data.List (elemIndices)
import Data.Maybe (isJust)
import qualified Data.IntMap as M
import Atomo.Types
-- referring to the left side:
-- LT = is higher-precision
-- GT = is lower-precision
comparePrecision :: Pattern -> Pattern -> Ordering
comparePrecision (PNamed _ a) (PNamed _ b) =
comparePrecision a b
comparePrecision (PNamed _ a) b = comparePrecision a b
comparePrecision a (PNamed _ b) = comparePrecision a b
comparePrecision PAny PAny = EQ
comparePrecision PThis PThis = EQ
comparePrecision (PMatch a@(Object {})) (PMatch b@(Object {}))
| delegatesTo a b = LT
| delegatesTo a b = GT
| otherwise = EQ
comparePrecision (PMatch _) (PMatch _) = EQ
comparePrecision (PList as) (PList bs) =
comparePrecisions as bs
comparePrecision (PPMKeyword _ as) (PPMKeyword _ bs) =
comparePrecisions as bs
comparePrecision (PHeadTail ah at) (PHeadTail bh bt) =
comparePrecisions [ah, at] [bh, bt]
comparePrecision (PMessage (Single { mTarget = at })) (PMessage (Single { mTarget = bt })) =
comparePrecision at bt
comparePrecision (PMessage (Keyword { mTargets = as })) (PMessage (Keyword { mTargets = bs })) =
compareHeads as bs
comparePrecision (PObject _) (PObject _) = EQ
comparePrecision PAny _ = GT
comparePrecision _ PAny = LT
comparePrecision PThis (PMatch (Object {})) = LT
comparePrecision (PMatch (Object {})) PThis = GT
comparePrecision (PMatch _) _ = LT
comparePrecision _ (PMatch _) = GT
comparePrecision (PExpr a) (PExpr b) = exprPrecision 0 a b
comparePrecision (PExpr _) _ = LT
comparePrecision _ (PExpr _) = GT
comparePrecision (PList _) _ = LT
comparePrecision _ (PList _) = GT
comparePrecision (PPMKeyword _ _) _ = LT
comparePrecision _ (PPMKeyword _ _) = GT
comparePrecision (PHeadTail _ _) _ = LT
comparePrecision _ (PHeadTail _ _) = GT
comparePrecision PThis _ = LT
comparePrecision _ PThis = GT
comparePrecision (PObject _) _ = LT
comparePrecision _ (PObject _) = GT
comparePrecision _ _ = GT
compareHeads :: [Pattern] -> [Pattern] -> Ordering
compareHeads [a] [b] = comparePrecision a b
compareHeads (a:as) (b:bs) =
case comparePrecision a b of
EQ -> compareHeads as bs
x -> x
compareHeads a b = error $ "impossible: compareHeads on " ++ show (a, b)
comparePrecisions :: [Pattern] -> [Pattern] -> Ordering
comparePrecisions = comparePrecisionsWith comparePrecision
comparePrecisionsWith :: (a -> a -> Ordering) -> [a] -> [a] -> Ordering
comparePrecisionsWith cmp as bs =
compare gt lt
where
compared = zipWith cmp as bs
gt = length $ elemIndices GT compared
lt = length $ elemIndices LT compared
delegatesTo :: Value -> Value -> Bool
delegatesTo (Object { oDelegates = ds }) t =
t `elem` ds || any (`delegatesTo` t) ds
delegatesTo _ _ = False
exprPrecision :: Int -> Expr -> Expr -> Ordering
exprPrecision 0 (EUnquote {}) (EUnquote {}) = EQ
exprPrecision 0 (EUnquote {}) _ = GT
exprPrecision 0 _ (EUnquote {}) = LT
exprPrecision n (EDefine { eExpr = a }) (EDefine { eExpr = b }) =
exprPrecision n a b
exprPrecision n (ESet { eExpr = a }) (ESet { eExpr = b }) =
exprPrecision n a b
exprPrecision n (EDispatch { eMessage = am@(Keyword {}) }) (EDispatch { eMessage = bm@(Keyword {}) }) =
comparePrecisionsWith (exprPrecision n) (mTargets am) (mTargets bm)
exprPrecision n (EDispatch { eMessage = am@(Single {}) }) (EDispatch { eMessage = bm@(Single {}) }) =
exprPrecision n (mTarget am) (mTarget bm)
exprPrecision n (EBlock { eContents = as }) (EBlock { eContents = bs }) =
comparePrecisionsWith (exprPrecision n) as bs
exprPrecision n (EList { eContents = as }) (EList { eContents = bs }) =
comparePrecisionsWith (exprPrecision n) as bs
exprPrecision n (EMacro { eExpr = a }) (EMacro { eExpr = b }) =
exprPrecision n a b
exprPrecision n (EParticle { eParticle = ap' }) (EParticle { eParticle = bp }) =
case (ap', bp) of
(Keyword { mTargets = ames }, Keyword { mTargets = bmes }) ->
comparePrecisionsWith (exprPrecision n) (firsts ames bmes) (seconds ames bmes)
_ -> EQ
where
pairs ames bmes = map (\(Just a, Just b) -> (a, b)) $ filter (\(a, b) -> isJust a && isJust b) $ zip ames bmes
firsts ames = fst . unzip . pairs ames
seconds ames = fst . unzip . pairs ames
exprPrecision n (EQuote { eExpr = a }) (EQuote { eExpr = b }) =
exprPrecision (n + 1) a b
exprPrecision _ _ _ = EQ
-- | Insert a method into a MethodMap based on its pattern's ID and precision.
addMethod :: Method -> MethodMap -> MethodMap
addMethod m mm =
M.insertWith (\[m'] ms -> insertMethod m' ms) key [m] mm
where
key = mID $ mPattern m
-- | Insert a method into a list of existing methods most precise goes first,
-- equivalent patterns are replaced.
insertMethod :: Method -> [Method] -> [Method]
insertMethod x [] = [x]
insertMethod x (y:ys)
| mPattern x `samePattern` mPattern y = x : ys
| otherwise =
case comparePrecision (PMessage (mPattern x)) (PMessage (mPattern y)) of
-- stop at LT so it's after all of the definitons before this one
LT -> x : y : ys
-- keep looking if we're EQ or GT
_ -> y : insertMethod x ys
-- | Like ==, but ignore optionals.
samePattern :: Message Pattern -> Message Pattern -> Bool
samePattern (Single { mID = ai, mTarget = at })
(Single { mID = bi, mTarget = bt }) =
ai == bi && at == bt
samePattern (Keyword { mID = ai, mTargets = ats })
(Keyword { mID = bi, mTargets = bts }) =
ai == bi && ats == bts
samePattern _ _ = False
-- | Convert a list of slots to a MethodMap.
toMethods :: [(Message Pattern, Value)] -> MethodMap
toMethods = foldl (\ss (p, v) -> addMethod (Slot p v) ss) emptyMap
-- | A pair of two empty MethodMaps; one for single methods and one for keyword
-- methods.
noMethods :: (MethodMap, MethodMap)
noMethods = (M.empty, M.empty)
-- | An empty MethodMap.
emptyMap :: MethodMap
emptyMap = M.empty
-- | Find methods in a MethodMap by the pattern ID.
lookupMap :: Int -> MethodMap -> Maybe [Method]
lookupMap = M.lookup
-- | Is a MethodMap empty?.
nullMap :: MethodMap -> Bool
nullMap = M.null
-- | All of the methods in a MethodMap.
elemsMap :: MethodMap -> [[Method]]
elemsMap = M.elems
-- | Is a key set in a map?
memberMap :: Int -> MethodMap -> Bool
memberMap = M.member
-- | Insert a method into a MethodMap, replacing all other methods with the
-- same ID.
insertMap :: Method -> MethodMap -> MethodMap
insertMap m mm = M.insert key [m] mm
where
key = mID $ mPattern m