atomo-0.2.2: src/Atomo/Helpers.hs
module Atomo.Helpers where
import "monads-fd" Control.Monad.State
import Data.Dynamic
import Data.IORef
import qualified Data.Text as T
import qualified Data.Vector as V
import Atomo.Environment
import Atomo.Method
import Atomo.Pattern
import Atomo.Types
infixr 0 =:, =::
-- | Define a method as an action returning a value.
(=:) :: Pattern -> VM Value -> VM ()
pat =: vm = define pat (EVM Nothing Nothing vm)
-- | Set a slot to a given value.
(=::) :: Pattern -> Value -> VM ()
pat =:: v = define pat (Primitive Nothing v)
-- | Define a method that evaluates e.
(=:::) :: Pattern -> Expr -> VM ()
pat =::: e = define pat e
-- | Find a value, searching through an object's delegates, and throwing
-- @\@could-not-find:in:@ if it is not found.
findValue :: String -> (Value -> Bool) -> Value -> VM Value
findValue _ t v | t v = return v
findValue d t v = findValue' t v >>= maybe die return
where
die = throwError (ValueNotFound d v)
-- | Same as `findValue', but returning Nothing instead of failing
findValue' :: (Value -> Bool) -> Value -> VM (Maybe Value)
findValue' t v | t v = return (Just v)
findValue' t (Reference r) = do
o <- liftIO (readIORef r)
findDels (oDelegates o)
where
findDels [] = return Nothing
findDels (d:ds) = do
f <- findValue' t d
case f of
Nothing -> findDels ds
Just v -> return (Just v)
findValue' _ _ = return Nothing
-- | `findValue' for `Block'
findBlock :: Value -> VM Value
findBlock v
| isBlock v = return v
| otherwise = findValue "Block" isBlock v
-- | `findValue' for `Boolean'
findBoolean :: Value -> VM Value
findBoolean v
| isBoolean v = return v
| otherwise = findValue "Boolean" isBoolean v
-- | `findValue' for `Char'
findChar :: Value -> VM Value
findChar v
| isChar v = return v
| otherwise = findValue "Char" isChar v
-- | `findValue' for `Continuation'
findContinuation :: Value -> VM Value
findContinuation v
| isContinuation v = return v
| otherwise = findValue "Continuation" isContinuation v
-- | `findValue' for `Double'
findDouble :: Value -> VM Value
findDouble v
| isDouble v = return v
| otherwise = findValue "Double" isDouble v
-- | `findValue' for `Expression'
findExpression :: Value -> VM Value
findExpression v
| isExpression v = return v
| otherwise = findValue "Expression" isExpression v
-- | `findValue' for `Haskell'
findHaskell :: Value -> VM Value
findHaskell v
| isHaskell v = return v
| otherwise = findValue "Haskell" isHaskell v
-- | `findValue' for `Integer'
findInteger :: Value -> VM Value
findInteger v
| isInteger v = return v
| otherwise = findValue "Integer" isInteger v
-- | `findValue' for `List'
findList :: Value -> VM Value
findList v
| isList v = return v
| otherwise = findValue "List" isList v
-- | `findValue' for `Message'
findMessage :: Value -> VM Value
findMessage v
| isMessage v = return v
| otherwise = findValue "Message" isMessage v
-- | `findValue' for `Method''
findMethod' :: Value -> VM Value
findMethod' v
| isMethod v = return v
| otherwise = findValue "Method" isMethod v
-- | `findValue' for `Particle'
findParticle :: Value -> VM Value
findParticle v
| isParticle v = return v
| otherwise = findValue "Particle" isParticle v
-- | `findValue' for `Process'
findProcess :: Value -> VM Value
findProcess v
| isProcess v = return v
| otherwise = findValue "Process" isProcess v
-- | `findValue' for `Pattern'
findPattern :: Value -> VM Value
findPattern v
| isPattern v = return v
| otherwise = findValue "Pattern" isPattern v
-- | `findValue' for `Rational'
findRational :: Value -> VM Value
findRational v
| isRational v = return v
| otherwise = findValue "Rational" isRational v
-- | `findValue' for `Reference'
findReference :: Value -> VM Value
findReference v
| isReference v = return v
| otherwise = findValue "Reference" isReference v
-- | `findValue' for `String'
findString :: Value -> VM Value
findString v
| isString v = return v
| otherwise = findValue "String" isString v
-- | Find a String given an expression to evaluate.
getString :: Expr -> VM String
getString e = eval e >>= liftM (fromText . fromString) . findString
-- | Find a Data.Text.Text given an expression to evaluate.
getText :: Expr -> VM T.Text
getText e = eval e >>= findString >>= \(String t) -> return t
-- | Find a list of values, given an expression to evaluate.
getList :: Expr -> VM [Value]
getList = liftM V.toList . getVector
-- | Find a VVector, given an expression to evaluate.
getVector :: Expr -> VM VVector
getVector e = eval e
>>= findList
>>= \(List v) -> return v
-- | Dispatch a single message to the current toplevel.
here :: String -> VM Value
here n = gets top >>= dispatch . single n
-- | if-then-else based on a VM action yielding a Boolean Value.
ifVM :: VM Value -> VM a -> VM a -> VM a
ifVM c a b = do
r <- c
if r == Boolean True then a else b
-- | if-then-else based on a VM action yielding a Bool.
ifVM' :: VM Bool -> VM a -> VM a -> VM a
ifVM' c a b = do
r <- c
if r then a else b
-- | if-then-else based on an expression to evaluate.
ifE :: Expr -> VM a -> VM a -> VM a
ifE = ifVM . eval
-- | Get a value's object.
referenceTo :: Value -> VM Value
{-# INLINE referenceTo #-}
referenceTo = liftM Reference . orefFor
-- | Call a block with the given arguments. Creates a scope, checks that its
-- argument patterns match, and executes it with the bindings.
callBlock :: Value -> [Value] -> VM Value
callBlock (Block s ps es) vs = do
is <- gets primitives
checkArgs is ps vs
doBlock (toMethods . concat $ zipWith bindings' ps vs) s es
where
checkArgs _ [] _ = return (particle "ok")
checkArgs _ _ [] = throwError (BlockArity (length ps) (length vs))
checkArgs is (p:ps') (v:vs')
| match is Nothing p v = checkArgs is ps' vs'
| otherwise = throwError (Mismatch p v)
callBlock x _ = raise ["not-a-block"] [x]
-- | Evaluate multiple expressions given a context and bindings for the
-- toplevel object.
doBlock :: MethodMap -> Value -> [Expr] -> VM Value
{-# INLINE doBlock #-}
doBlock bms s es = do
blockScope <- newObject $ \o -> o
{ oDelegates = [s]
, oMethods = (bms, emptyMap)
}
withTop blockScope (evalAll es)
-- | Get the object backing a value.
objectFor :: Value -> VM Object
{-# INLINE objectFor #-}
objectFor v = orefFor v >>= liftIO . readIORef
-- | Does one value delegate to another?
delegatesTo :: Value -> Value -> VM Bool
delegatesTo f t = do
o <- objectFor f
delegatesTo' (oDelegates o)
where
delegatesTo' [] = return False
delegatesTo' (d:ds)
| t `elem` (d:ds) = return True
| otherwise = do
o <- objectFor d
delegatesTo' (oDelegates o ++ ds)
-- | Is one value an instance of, equal to, or a delegation to another?
--
-- For example, 1 is-a?: Integer, but 1 does not delegates-to?: Integer
isA :: Value -> Value -> VM Bool
isA x y = do
xr <- orefFor x
yr <- orefFor y
if xr == yr
then return True
else do
ds <- liftM oDelegates (objectFor x)
isA' ds
where
isA' [] = return False
isA' (d:ds) = do
di <- isA d y
if di
then return True
else isA' ds
-- | Attempt conversion from a Haskell Value, given the type we expect as
-- a string.
--
-- If conversion fails, raises @\@dynamic-needed:@ with the given string.
fromHaskell :: Typeable a => String -> Value -> VM a
fromHaskell t (Haskell d) =
case fromDynamic d of
Just a -> return a
Nothing -> raise ["dynamic-needed"] [string t]
fromHaskell t _ = raise ["dynamic-needed"] [string t]
-- | Convert an Atomo Haskell dynamic value into its value, erroring on
-- failure.
fromHaskell' :: Typeable a => String -> Value -> a
fromHaskell' t (Haskell d) =
case fromDynamic d of
Just a -> a
Nothing -> error ("needed Haskell value of type " ++ t)
fromHaskell' t _ = error ("needed haskell value of type " ++ t)
-- | `toPattern', raising @\@unknown-pattern:@ if conversion fails.
toPattern' :: Expr -> VM Pattern
toPattern' = tryPattern toPattern
-- | `toDefinePattern', raising @\@unknown-pattern:@ if conversion fails.
toDefinePattern' :: Expr -> VM Pattern
toDefinePattern' = tryPattern toDefinePattern
-- | `toRolePattern', raising @\@unknown-pattern:@ if conversion fails.
toRolePattern' :: Expr -> VM Pattern
toRolePattern' = tryPattern toRolePattern
-- | `toMacroPattern', raising @\@unknown-pattern:@ if conversion fails.
toMacroPattern' :: Expr -> VM Pattern
toMacroPattern' = tryPattern toMacroPattern
-- | Try a given pattern conversion, raising @\@unknown-pattern:@ if conversion
-- fails.
tryPattern :: (Expr -> Maybe Pattern) -> Expr -> VM Pattern
tryPattern c e =
case c e of
Nothing -> raise ["unknown-pattern"] [Expression e]
Just p -> return p
-- | Fill in the empty values of a particle. The number of values missing
-- is expected to be equal to the number of values provided.
completeKP :: [Maybe Value] -> [Value] -> [Value]
completeKP [] _ = []
completeKP (Nothing:mvs') (v:vs') = v : completeKP mvs' vs'
completeKP (Just v:mvs') vs' = v : completeKP mvs' vs'
completeKP mvs' vs' = error $ "impossible: completeKP on " ++ show (mvs', vs')