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