packages feed

atomo-0.4: src/Atomo/Pattern.hs

module Atomo.Pattern
    ( bindings
    , bindings'
    , match
    , matchAll
    , toPattern
    , toDefinePattern
    , toRolePattern
    , toMacroPattern
    , toMacroRole
    ) where

import Control.Monad (forM, liftM)
import Data.Char (isUpper)
import Data.Maybe (fromJust, isJust)
import qualified Data.Text as T
import qualified Data.Vector as V

import Atomo.Method
import Atomo.Types


-- | Check if a value matches a given pattern.
match :: IDs -> Maybe Value -> Pattern -> Value -> Bool
{-# NOINLINE match #-}
match ids (Just r) PThis y@(Object { oDelegates = ds }) =
    r == y || any (match ids (Just r) (PMatch r)) ds
match ids (Just r) PThis y =
    match ids (Just r) (PMatch r) (objectFrom ids y)
match _ _ (PMatch x) y | x == y = True
match ids r x@(PMatch _) (Object { oDelegates = ds }) =
    any (match ids r x) ds
match ids r x@(PMatch (Object {})) y =
    match ids r x (objectFrom ids y)
match ids r
    (PMessage (Single { mTarget = p }))
    ( Message (Single { mTarget = t })) =
    match ids r p t
match ids r
    (PMessage (Keyword { mTargets = ps }))
    ( Message (Keyword { mTargets = ts })) =
    matchAll ids r ps ts
match ids r (PInstance p) (Object { oDelegates = ds }) =
    any (match ids r p) ds
match ids r (PInstance p) v = match ids r p v
match _ _ (PStrict (PMatch x)) v = x == v
match ids r (PStrict p) v = match ids r p v
match ids r (PVariable p) (Tuple t) = match ids r p (List t)
match ids r (PVariable p) v = match ids r p (list [v])
match ids r (PNamed _ p) v = match ids r p v
match _ _ PAny _ = True
match ids r (PList ps) (List v) = matchAll ids r ps (V.toList v)
match ids r (PHeadTail hp tp) (List vs) =
    V.length vs > 0 && match ids r hp h && match ids r tp t
  where
    h = V.head vs
    t = List (V.tail vs)
match ids r (PHeadTail hp tp) (String t) | not (T.null t) =
    match ids r hp (Character (T.head t)) && match ids r tp (String (T.tail t))
match ids r (PTuple ps) (Tuple v) = matchAll ids r ps (V.toList v)
match ids r (PPMKeyword ans aps) (Particle (Keyword { mNames = bns, mTargets = mvs })) =
    ans == bns && matchParticle ids r aps mvs
match ids r p (Object { oDelegates = ds }) =
    any (match ids r p) ds
match _ _ p (Expression e) = macroMatch p e
match _ _ _ _ = False

macroMatch :: Pattern -> Expr -> Bool
macroMatch PEDispatch (EDispatch {}) = True
macroMatch PEOperator (EOperator {}) = True
macroMatch PEPrimitive (EPrimitive {}) = True
macroMatch PEBlock (EBlock {}) = True
macroMatch PEList (EList {}) = True
macroMatch PETuple (ETuple {}) = True
macroMatch PEMacro (EMacro {}) = True
macroMatch PEParticle (EParticle {}) = True
macroMatch PETop (ETop {}) = True
macroMatch PEQuote (EQuote {}) = True
macroMatch PEUnquote (EUnquote {}) = True
macroMatch (PExpr a) b = matchExpr 0 a b
macroMatch _ _ = False

-- | Match multiple patterns with multiple values.
matchAll :: IDs -> Maybe Value -> [Pattern] -> [Value] -> Bool
matchAll _ _ [] [] = True
matchAll ids mr (p:ps) (v:vs) = match ids mr p v && matchAll ids mr ps vs
matchAll _ _ _ _ = False

matchEParticle :: Int -> [Maybe Expr] -> [Maybe Expr] -> Bool
matchEParticle _ [] [] = True
matchEParticle n (Just a:as) (Just b:bs) =
    matchExpr n a b && matchEParticle n as bs
matchEParticle n (Nothing:as) (Nothing:bs) = matchEParticle n as bs
matchEParticle _ _ _ = False

matchExpr :: Int -> Expr -> Expr -> Bool
matchExpr 0 (EUnquote { eExpr = EDispatch { eMessage = Single {} } }) _ = True
matchExpr 0 (EUnquote { eExpr = EDispatch { eMessage = Keyword { mTargets = [ETop {}, pat] } } }) e
    | isJust mp = macroMatch (fromJust mp) e --matchExpr 0 pat e
  where
    mp = toMacroRole pat
matchExpr n (EUnquote { eExpr = a }) (EUnquote { eExpr = b }) =
    matchExpr (n - 1) a b
matchExpr n (EDefine { emPattern = ap', eExpr = a }) (EDefine { emPattern = bp, eExpr = b }) =
    ap' == bp && matchExpr n a b
matchExpr n (ESet { ePattern = ap', eExpr = a }) (ESet { ePattern = bp, eExpr = b }) =
    ap' == bp && matchExpr n a b
matchExpr n (EDispatch { eMessage = am@(Keyword {}) }) (EDispatch { eMessage = bm@(Keyword {}) }) =
    mID am == mID bm && length (mTargets am) == length (mTargets bm) && and (zipWith (matchExpr n) (mTargets am) (mTargets bm))
matchExpr n (EDispatch { eMessage = am@(Single {}) }) (EDispatch { eMessage = bm@(Single {}) }) =
    mID am == mID bm && matchExpr n (mTarget am) (mTarget bm)
matchExpr n (EBlock { eArguments = aps, eContents = as }) (EBlock { eArguments = bps, eContents = bs }) =
    aps == bps && length as == length bs && and (zipWith (matchExpr n) as bs)
matchExpr n (EList { eContents = as }) (EList { eContents = bs }) =
    length as == length bs && and (zipWith (matchExpr n) as bs)
matchExpr n (ETuple { eContents = as }) (ETuple { eContents = bs }) =
    length as == length bs && and (zipWith (matchExpr n) as bs)
matchExpr n (EMacro { emPattern = ap', eExpr = a }) (EMacro { emPattern = bp, eExpr = b }) =
    ap' == bp && matchExpr n a b
matchExpr n (EParticle { eParticle = ap' }) (EParticle { eParticle = bp }) =
    case (ap', bp) of
        (Keyword { mNames = ans, mTargets = ames }, Keyword { mNames = bns, mTargets = bmes }) ->
            ans == bns && matchEParticle n ames bmes
        _ -> ap' == bp
matchExpr n (EQuote { eExpr = a }) (EQuote { eExpr = b }) =
    matchExpr (n + 1) a b
matchExpr _ a b = a == b

matchParticle :: IDs -> Maybe Value -> [Pattern] -> [Maybe Value] -> Bool
matchParticle _ _ [] [] = True
matchParticle ids mr (PAny:ps) (Nothing:mvs) = matchParticle ids mr ps mvs
matchParticle ids mr (PNamed _ p:ps) mvs = matchParticle ids mr (p:ps) mvs
matchParticle ids mr (p:ps) (Just v:mvs) =
    match ids mr p v && matchParticle ids mr ps mvs
matchParticle _ _ _ _ = False

-- | Given a pattern and a message, return the bindings from the pattern.
bindings :: Message Pattern -> Message Value -> MethodMap
bindings (Single { mTarget = p }) (Single { mTarget = t }) =
    toMethods (bindings' p t)
bindings (Keyword { mTargets = ps }) (Keyword { mTargets = ts }) =
    toMethods $ concat (zipWith bindings' ps ts)
bindings p m = error $ "impossible: bindings on " ++ show (p, m)

-- | Given a pattern and avalue, return the bindings as a list of pairs.
bindings' :: Pattern -> Value -> [(Message Pattern, Value)]
bindings' (PNamed n p) v = (single n PThis, v) : bindings' p v
bindings' (PPMKeyword _ ps) (Particle (Keyword { mTargets = mvs })) =
    concatMap (\(p, Just v) -> bindings' p v)
    $ filter (isJust . snd)
    $ zip ps mvs
bindings' (PList ps) (List vs) = concat (zipWith bindings' ps (V.toList vs))
bindings' (PHeadTail hp tp) (List vs) =
    bindings' hp h ++ bindings' tp t
  where
    h = V.head vs
    t = List (V.tail vs)
bindings' (PHeadTail hp tp) (String t) | not (T.null t) =
    bindings' hp (Character (T.head t)) ++ bindings' tp (String (T.tail t))
bindings' (PTuple ps) (Tuple vs) = concat (zipWith bindings' ps (V.toList vs))
bindings' (PExpr a) (Expression b) = exprBindings 0 a b
bindings' (PInstance p) (Object { oDelegates = ds }) =
    concatMap (bindings' p) ds
bindings' (PInstance p) v = bindings' p v
bindings' (PStrict p) v = bindings' p v
bindings' (PVariable p) (Tuple t) = bindings' p (List t)
bindings' (PVariable p) v = bindings' p (list [v])
bindings' p (Object { oDelegates = ds }) =
    concatMap (bindings' p) ds
bindings' _ _ = []


exprBindings :: Int -> Expr -> Expr -> [(Message Pattern, Value)]
exprBindings 0 (EUnquote { eExpr = EDispatch { eMessage = Single { mName = n } } }) e =
    [(single n PThis, Expression e)]
exprBindings 0 (EUnquote { eExpr = EDispatch { eMessage = Keyword { mNames = [n] } } }) e =
    [(single n PThis, Expression e)]
exprBindings n (EUnquote { eExpr = a }) (EUnquote { eExpr = b }) =
    exprBindings (n - 1) a b
exprBindings n (EDefine { eExpr = a }) (EDefine { eExpr = b }) =
    exprBindings n a b
exprBindings n (ESet { eExpr = a }) (ESet { eExpr = b }) =
    exprBindings n a b
exprBindings n (EDispatch { eMessage = am@(Keyword {}) }) (EDispatch { eMessage = bm@(Keyword {}) }) =
    concat $ zipWith (exprBindings n) (mTargets am) (mTargets bm)
exprBindings n (EDispatch { eMessage = am@(Single {}) }) (EDispatch { eMessage = bm@(Single {}) }) =
    exprBindings n (mTarget am) (mTarget bm)
exprBindings n (EBlock { eContents = as }) (EBlock { eContents = bs }) =
    concat $ zipWith (exprBindings n) as bs
exprBindings n (EList { eContents = as }) (EList { eContents = bs }) =
    concat $ zipWith (exprBindings n) as bs
exprBindings n (ETuple { eContents = as }) (ETuple { eContents = bs }) =
    concat $ zipWith (exprBindings n) as bs
exprBindings n (EMacro { eExpr = a }) (EMacro { eExpr = b }) =
    exprBindings n a b
exprBindings n (EParticle { eParticle = ap' }) (EParticle { eParticle = bp }) =
    case (ap', bp) of
        (Keyword { mNames = _, mTargets = ames }, Keyword { mNames = _, mTargets = bmes }) ->
            concatMap (\(Just a, Just b) -> exprBindings n a b)
            $ filter (isJust . fst)
            $ zip ames bmes
        _ -> []
exprBindings n (EQuote { eExpr = a }) (EQuote { eExpr = b }) =
    exprBindings (n + 1) a b
exprBindings _ _ _ = []

-- | Convert an expression to the pattern match it represents.
toPattern :: Expr -> Maybe Pattern
toPattern (EDispatch { eMessage = Keyword { mNames = ["."], mTargets = [h, t] } }) = do
    hp <- toPattern h
    tp <- toPattern t
    return (PHeadTail hp tp)
toPattern (EDispatch { eMessage = Keyword { mNames = ["->"], mTargets = [ETop {}, o] } }) = do
    liftM PInstance (toPattern o)
toPattern (EDispatch { eMessage = Keyword { mNames = ["=="], mTargets = [ETop {}, o] } }) = do
    liftM PStrict (toPattern o)
toPattern (EDispatch { eMessage = Keyword { mNames = ["..."], mTargets = [ETop {}, o] } }) = do
    liftM PVariable (toPattern o)
toPattern (EDispatch { eMessage = Keyword { mNames = [n], mTargets = [ETop {}, x] } }) = do
    p <- toPattern x
    return (PNamed n p)
toPattern (EDispatch { eMessage = Keyword { mNames = ns, mTargets = ts } }) =
    return (pkeyword ns (map PObject ts))
toPattern (EDispatch { eMessage = Single { mName = "_" } }) =
    return PAny
toPattern (EDispatch { eMessage = Single { mName = n, mTarget = ETop {} } }) =
    return (PNamed n PAny)
toPattern (EDispatch { eMessage = Single { mTarget = d@(EDispatch {}), mName = n } }) =
    return (psingle n (PObject d))
toPattern (EList { eContents = es }) = do
    ps <- mapM toPattern es
    return (PList ps)
toPattern (ETuple { eContents = es }) = do
    ps <- mapM toPattern es
    return (PTuple ps)
toPattern (EParticle { eParticle = Single { mName = n } }) =
    return (PMatch (particle n))
toPattern (EParticle { eParticle = Keyword { mNames = ns, mTargets = mes } }) = do
    ps <- forM mes $ \me ->
        case me of
            Nothing -> return PAny
            Just e -> toPattern e

    return (PPMKeyword ns ps)
toPattern (EQuote { eExpr = e }) = return (PExpr e)
toPattern (EPrimitive { eValue = v }) =
    return (PMatch v)
toPattern (ETop {}) =
    return (PObject (ETop Nothing))
toPattern b@(EBlock {}) =
    return (PObject (EDispatch Nothing (keyword ["call-in"] [b, ETop Nothing])))
toPattern _ = Nothing

-- | Convert an expression into a definition's message pattern.
toDefinePattern :: Expr -> Maybe (Message Pattern)
toDefinePattern (EDispatch { eMessage = Single { mName = n, mTarget = t, mOptionals = os } }) = do
    p <- toRolePattern t
    return (single' n p (map (\(Option oi on oe) -> Option oi on (PObject oe)) os))
toDefinePattern (EDispatch { eMessage = Keyword { mNames = ns, mTargets = ts, mOptionals = os } }) = do
    ps <- mapM toRolePattern ts
    return (keyword' ns ps (map (\(Option oi on oe) -> Option oi on (PObject oe)) os))
toDefinePattern _ = Nothing

-- | Convert an expression into a pattern-match for use as a message's role.
toRolePattern :: Expr -> Maybe Pattern
toRolePattern (EDispatch { eMessage = Keyword { mNames = ["->"], mTargets = [ETop {}, o] } }) = do
    liftM PInstance (toRolePattern o)
toRolePattern (EDispatch { eMessage = Keyword { mNames = ["=="], mTargets = [ETop {}, o] } }) = do
    liftM PStrict (toRolePattern o)
toRolePattern (EDispatch { eMessage = Keyword { mNames = ["..."], mTargets = [ETop {}, o] } }) = do
    liftM PVariable (toPattern o)
toRolePattern (EDispatch { eMessage = Keyword { mNames = [n], mTargets = [ETop {}, x] } }) = do
    p <- toRolePattern x
    return (PNamed n p)
toRolePattern d@(EDispatch { eMessage = Single { mTarget = ETop {}, mName = n } })
    | isUpper (head n) = return (PObject d)
    | n == "_" = return PAny
    | otherwise = return (PNamed n PAny)
toRolePattern d@(EDispatch { eMessage = Single { mTarget = (EDispatch {}) } }) =
    return (PObject d)
toRolePattern p = toPattern p

-- | Convert an expression into a macro's message pattern.
toMacroPattern :: Expr -> Maybe (Message Pattern)
toMacroPattern (EDispatch { eMessage = Single { mName = n, mTarget = t, mOptionals = os } }) = do
    p <- toMacroRole t
    return (single' n p (map macroOptional os))
toMacroPattern (EDispatch { eMessage = Keyword { mNames = ns, mTargets = ts, mOptionals = os } }) = do
    ps <- mapM toMacroRole ts
    return (keyword' ns ps (map macroOptional os))
toMacroPattern _ = Nothing

macroOptional :: Option Expr -> Option Pattern
macroOptional (Option i n e) = Option i n (PExpr e)

-- | Convert an expression into a pattern-match for use as a macro's role.
toMacroRole :: Expr -> Maybe Pattern
toMacroRole (EDispatch _ (Single { mName = "Dispatch" })) = Just PEDispatch
toMacroRole (EDispatch _ (Single { mName = "Operator" })) = Just PEOperator
toMacroRole (EDispatch _ (Single { mName = "Primitive" })) = Just PEPrimitive
toMacroRole (EDispatch _ (Single { mName = "Block" })) = Just PEBlock
toMacroRole (EDispatch _ (Single { mName = "List" })) = Just PEList
toMacroRole (EDispatch _ (Single { mName = "Tuple" })) = Just PETuple
toMacroRole (EDispatch _ (Single { mName = "Macro" })) = Just PEMacro
toMacroRole (EDispatch _ (Single { mName = "ForMacro" })) = Just PEForMacro
toMacroRole (EDispatch _ (Single { mName = "Particle" })) = Just PEParticle
toMacroRole (EDispatch _ (Single { mName = "Top" })) = Just PETop
toMacroRole (EDispatch _ (Single { mName = "Quote" })) = Just PEQuote
toMacroRole (EDispatch _ (Single { mName = "Unquote" })) = Just PEUnquote
toMacroRole (EDispatch _ (Single { mName = "MacroQuote" })) = Just PEMacroQuote
toMacroRole (EDispatch _ (Single { mName = "Match" })) = Just PEMatch
toMacroRole (EDispatch { eMessage = Keyword { mNames = [n], mTargets = [ETop {}, x] } }) = do
    p <- toMacroRole x
    return (PNamed n p)
toMacroRole (ETop {}) = Just PAny
toMacroRole p = toPattern p