diff --git a/Main.hs b/Main.hs index 5c1d7be..7174ea8 100644 --- a/Main.hs +++ b/Main.hs @@ -1,6 +1,5 @@ -- {-# LANGUAGE DeriveDataTypeable #-} -- {-# LANGUAGE DeriveGeneric #-} --- {-# LANGUAGE RecursiveDo #-} -- {-# LANGUAGE TemplateHaskell #-} -- {-# LANGUAGE ViewPatterns #-} @@ -8,16 +7,13 @@ module Main where -- import Control.Applicative -- import Control.Monad --- import Control.Monad.Error.Class -- import Control.Monad.Error.Lens --- import Control.Monad.IO.Class -- import Control.Monad.Trans.Either -- import Control.Monad.Trans.RWS import Data.Binary.Get -- import Data.Bits import qualified Data.ByteString.Lazy as BSL -- import Data.Foldable (toList) --- import Data.IORef -- import Data.List -- import Data.List.NonEmpty (NonEmpty(..)) -- import qualified Data.List.NonEmpty as NE @@ -34,6 +30,7 @@ import System.Environment import Masque.AST import Masque.Equality +import Masque.Eval import Masque.Monte import Masque.Objects import Masque.Vats @@ -49,9 +46,6 @@ import Masque.Vats -- -- | Lenses on other data types -- -- These are destined for various upstreams at some point. -- --- first :: Lens' (NonEmpty a) a --- first = lens (\(a :| _) -> a) (\(_ :| as) -> (:| as)) --- -- final :: Lens' (NonEmpty a) a -- final = lens g s -- where @@ -253,15 +247,6 @@ import Masque.Vats -- -- -- | Evaluation helpers -- --- scoped :: Monte a -> Monte a --- scoped action = --- bracketEitherT push pop (const action) --- where --- push = envStack %= (Env M.empty NE.<|) --- -- Only works as long as the environment stack isn't overpopped during the --- -- scoped action. Shouldn't happen. --- pop _ = envStack %= (\(_ :| (a:as)) -> a :| as) --- -- stashingScope :: NonEmpty Env -> Monte a -> Monte a -- stashingScope es action = bracketEitherT open (envStack .=) (const action) -- where @@ -270,16 +255,6 @@ import Masque.Vats -- envStack .= es -- return stashed -- --- varBinding :: String -> Obj -> Monte () --- varBinding name obj = do --- slotRef <- liftIO $ newIORef obj --- -- XXX use an actual guard next time! --- guardRef <- liftIO $ newIORef NullObj --- envStack . first . unEnv . at name ?= VarBind slotRef guardRef --- --- defBinding :: String -> Obj -> Monte () --- defBinding name obj = envStack . first . unEnv . at name ?= DefBind obj --- -- newEjector :: Monte Obj -- newEjector = do -- u <- liftIO newUnique @@ -319,119 +294,6 @@ import Masque.Vats -- maybe (left Unsettled) resolve mobj -- resolve obj = return obj -- --- -- | Unification and evaluation --- --- unifyEject :: Obj -> Obj -> Pattern -> Monte () --- unifyEject _ _ (BindPattern _) = undefined --- unifyEject obj _ (Final n Null) = defBinding n obj --- unifyEject obj ej (Final n g) = do --- g' <- eval g --- obj' <- call g' "coerce" [obj, ej] --- defBinding n obj' --- unifyEject _ _ (Ignore Null) = return () --- unifyEject obj ej (Ignore g) = do --- g' <- eval g --- void $ call g' "coerce" [obj, ej] --- unifyEject (ConstListObj os) ej (ListPattern ps) --- | Seq.length os == length ps = forM_ (zip os' ps) $ \(o, p) -> unifyEject o ej p --- where os' = toList os --- unifyEject _ ej (ListPattern _) = fire ej NullObj --- -- XXX need to generate slots here --- unifyEject obj _ (Var n _) = varBinding n obj --- unifyEject obj ej (Via expr p) = do --- examiner <- eval expr --- examined <- call examiner "run" [obj, ej] --- unifyEject examined ej p --- --- unify :: Obj -> Pattern -> Monte Bool --- unify _ (BindPattern _) = undefined --- unify obj (Final n Null) = defBinding n obj >> return True --- unify obj (Final n g) = do --- g' <- eval g --- obj' <- call g' "coerce" [obj, NullObj] --- defBinding n obj' --- return True --- unify _ (Ignore Null) = return True --- unify obj (Ignore g) = do --- g' <- eval g --- void $ call g' "coerce" [obj, NullObj] --- return True --- unify (ConstListObj os) (ListPattern ps) --- | Seq.length os == length ps = do --- unified <- forM (zip os' ps) $ uncurry unify --- return $ and unified --- where os' = toList os --- unify _ (ListPattern _) = return False --- unify obj (Var n _) = varBinding n obj >> return True --- unify obj (Via expr p) = do --- examiner <- eval expr --- examined <- call examiner "run" [obj, NullObj] --- unify examined p --- --- eval :: Node -> Monte Obj --- eval Null = return NullObj --- eval (CharNode c) = return $ CharObj c --- eval (DoubleNode d) = return $ DoubleObj d --- eval (IntNode i) = return $ IntObj i --- eval (StrNode s) = return $ StrObj s --- eval (Tuple t) = do --- objs <- mapM eval t --- return . ConstListObj $ Seq.fromList objs --- eval (Assign name node) = do --- obj <- eval node --- binding <- getBinding name --- case binding of --- DefBind _ -> left $ BadWrite name --- -- XXX invoke guard here --- VarBind slotRef _ -> do --- liftIO $ writeIORef slotRef obj --- return obj --- eval (BindingNode _) = return BindingObj --- eval (Call o v as) = do --- o' <- eval o --- StrObj v' <- eval v --- ConstListObj as' <- eval as --- call o' v' $ toList as' --- eval (Def p ej expr) = do --- rvalue <- eval expr --- ej' <- eval ej --- unifyEject rvalue ej' p --- return rvalue --- eval (Escape p n _ _) = scoped $ do --- ej@(EjectorObj u) <- newEjector --- success <- unify ej p --- if success then catchEjector u $ eval n else left Unknown --- eval (Finally node atLast) = bracketEitherT before after return --- where --- before = scoped $ eval node --- after obj = scoped $ eval atLast >> return obj --- eval (If i t f) = do --- test <- eval i --- BoolObj b <- resolve test --- scoped $ eval $ if b then t else f --- eval (Hide n) = scoped $ eval n --- eval (Noun name) = getName name --- eval (Object _ p _ (Script _ methods matchers)) = mdo --- u <- liftIO newUnique --- let rv = UserObj u objName env methodMap matcherList --- success <- unify rv p --- env <- uses envStack $ \es -> Env (M.unions (map _unEnv (NE.toList es))) --- if success then return rv else left Unknown --- where --- methodMap = M.fromListWith (++) methodList --- methodList = [(verb, [(ListPattern p', n)]) | Method _ (StrNode verb) p' _ n <- methods ] --- matcherList = [(p', n) | Matcher p' n <- matchers ] --- objName = case p of --- Final name _ -> name --- _ -> "_" --- eval (Sequence ns) = do --- os <- mapM eval ns --- return $ if null os then NullObj else last os --- eval (Try n p h) = scoped $ catchError (eval n) $ \_ -> do --- success <- unify NullObj p --- if success then eval h else left Unknown --- eval n = error $ "Couldn't evaluate node: " ++ show n --- -- -- | Scope creation -- -- coreScope :: M.Map String Obj diff --git a/Masque/Ejectors.hs b/Masque/Ejectors.hs new file mode 100644 index 0000000..2f27250 --- /dev/null +++ b/Masque/Ejectors.hs @@ -0,0 +1,22 @@ +module Masque.Ejectors where + +import Control.Monad.Error.Class +import Control.Monad.Trans.Either +import Data.Unique + +import Masque.Monte +import Masque.Objects + +-- | Run an action, catching a single specified ejector. If caught, the +-- ejector's payload will be run through the given handler action. +catchEjector :: Unique -> Monte Obj -> (Obj -> Monte Obj) -> Monte Obj +catchEjector u action handler = catchError action $ \err -> + case err of + Ejecting u' obj | u == u' -> handler obj + _ -> left err + +-- | Like Monte m`throw.eject(ej, problem)`. +throwEject :: Obj -> Obj -> Monte () +throwEject ej problem = left $ case ej of + EjectorObj u -> Ejecting u problem + _ -> Exception problem diff --git a/Masque/Equality.hs b/Masque/Equality.hs index 3e661bb..9f1ddde 100644 --- a/Masque/Equality.hs +++ b/Masque/Equality.hs @@ -25,7 +25,7 @@ sameEver left right = case (left, right) of bools <- zipWithM zippingTuples xs ys return $ and bools -- Should consider auditors, not just object identity. - (UserObj x _ _ _ _ _, UserObj y _ _ _ _ _) -> return $ x == y + (UserObj x _ _ _ _, UserObj y _ _ _ _) -> return $ x == y (_, _) -> return False where zippingTuples (k, v) (k', v') = do diff --git a/Masque/Eval.hs b/Masque/Eval.hs new file mode 100644 index 0000000..f716023 --- /dev/null +++ b/Masque/Eval.hs @@ -0,0 +1,186 @@ +{-# LANGUAGE RecursiveDo #-} + +-- | The actual evaluation strategies for anything resembling +-- `Expr -> Monte Obj`. +module Masque.Eval where + +import Control.Lens +import Control.Monad +import Control.Monad.Error.Class +import Control.Monad.IO.Class +import Control.Monad.Trans.Either +import Data.Foldable (toList) +import Data.IORef +import Data.List.NonEmpty (NonEmpty(..)) +import qualified Data.List.NonEmpty as NE +import qualified Data.Map as M +import qualified Data.Sequence as Seq +import Data.Unique + +import Masque.AST +import Masque.Ejectors +import Masque.Monte +import Masque.Objects + +-- | Smart constructor for a final binding; perform the guarding operation as +-- part of the assignment. +finalBinding :: Obj -> Obj -> Obj -> Monte Binding +finalBinding specimen guard ej = do + specimen <- call guard "coerce" [specimen, ej] [] + return $ FinalBinding specimen guard + +-- | Smart constructor for a var binding; perform the guarding operation as +-- part of the assignment. +varBinding :: Obj -> Obj -> Obj -> Monte Binding +varBinding specimen guard ej = do + specimen <- call guard "coerce" [specimen, ej] [] + valueRef <- liftIO $ newIORef specimen + return $ VarBinding valueRef guard + +first :: Lens' (NonEmpty a) a +first = lens (\(a :| _) -> a) (\(_ :| as) -> (:| as)) + +-- | Bind a name to a binding within the current scope. +bindName :: String -> Binding -> Monte () +bindName name binding = envStack . first . unEnv . at name ?= binding + +-- | Unify a pattern with a specimen and ejector in the current scope. +unify :: Patt -> Obj -> Obj -> Monte () +unify (BindPatt name) specimen _ = bindName name (FullBinding specimen) +unify (FinalPatt name NullExpr) specimen _ = bindName name (FinalAnyBinding specimen) +unify (FinalPatt name guardExpr) specimen ej = do + guard <- eval guardExpr + binding <- finalBinding specimen guard ej + bindName name binding +unify (IgnorePatt NullExpr) _ _ = return () +unify (IgnorePatt guardExpr) specimen ej = do + guard <- eval guardExpr + void $ call guard "coerce" [specimen, ej] [] +unify (ListPatt patts) (ConstListObj specimens) ej + | length patts == Seq.length specimens = + void $ zipWithM_ (\patt specimen -> unify patt specimen ej) patts (toList specimens) +unify (ListPatt _) _ ej = throwEject ej (StrObj "List pattern failed") +unify (VarPatt name NullExpr) specimen _ = do + valueRef <- liftIO $ newIORef specimen + bindName name (VarAnyBinding valueRef) +unify (VarPatt name guardExpr) specimen ej = do + guard <- eval guardExpr + binding <- varBinding specimen guard ej + bindName name binding +unify (ViaPatt transformExpr patt) specimen ej = do + transformer <- eval transformExpr + specimen' <- call transformer "run" [specimen, ej] [] + unify patt specimen' ej + +-- | Evaluate a closed Monte expression in the current scope. +eval :: Expr -> Monte Obj +eval NullExpr = return NullObj +eval (CharExpr c) = return $ CharObj c +eval (DoubleExpr d) = return $ DoubleObj d +eval (IntExpr i) = return $ IntObj i +eval (StrExpr s) = return $ StrObj s +eval (AssignExpr name expr) = do + rhs <- eval expr + binding <- lookupName name + case binding of + FullBinding bindingObj -> do + -- bindingObj.get().put(rhs) + slot <- call bindingObj "get" [] [] + void $ call slot "put" [rhs] [] + VarBinding valueRef guard -> do + specimen <- call guard "coerce" [rhs, NullObj] [] + liftIO $ writeIORef valueRef specimen + VarAnyBinding valueRef -> liftIO $ writeIORef valueRef rhs + _ -> do + names <- namesInScope + left $ BadWrite name names + return rhs +eval (BindingExpr name) = do + binding <- lookupName name + return $ case binding of + FullBinding bindingObj -> bindingObj + _ -> error "eval: BindingExpr: Can't promote bindings yet" +eval (CallExpr objExpr verb argExprs namedArgExprs) = do + obj <- eval objExpr + args <- mapM eval argExprs + namedArgs <- forM namedArgExprs $ \(NamedExpr k v) -> do + key <- eval k + value <- eval v + return (key, value) + call obj verb args namedArgs +eval (DefExpr patt ejExpr rhsExpr) = do + rhs <- eval rhsExpr + ej <- eval ejExpr + unify patt rhs ej + return rhs +eval (EscapeOnlyExpr patt expr) = do + u <- liftIO newUnique + catchEjector u (body u) return + where + body u = withFreshScope $ do + unify patt (EjectorObj u) NullObj + eval expr +eval (EscapeExpr patt expr catchPatt catchExpr) = do + u <- liftIO newUnique + catchEjector u (body u) catchBody + where + body u = withFreshScope $ do + unify patt (EjectorObj u) NullObj + eval expr + catchBody problem = withFreshScope $ do + unify catchPatt problem NullObj + eval catchExpr +eval (FinallyExpr expr atLast) = bracketEitherT before after return + where + before = withFreshScope $ eval expr + after obj = withFreshScope $ eval atLast >> return obj +eval (IfExpr condExpr consExpr altExpr) = withFreshScope $ do + cond <- eval condExpr + b <- unwrapBool cond + eval $ if b then consExpr else altExpr +eval (HideExpr expr) = withFreshScope $ eval expr +eval (NounExpr name) = do + binding <- lookupName name + case binding of + FullBinding bindingObj -> do + -- bindingObj.get().get() + slot <- call bindingObj "get" [] [] + call slot "get" [] [] + VarBinding valueRef _ -> liftIO $ readIORef valueRef + VarAnyBinding valueRef -> liftIO $ readIORef valueRef + FinalBinding value _ -> return value + FinalAnyBinding value -> return value +eval (ObjectExpr _ patt asAuditor auditors methods matchers) = mdo + -- Use recursive do to tie the self-referential knot of objects. We give + -- the object a reference to its environment, but trust the user to not + -- specify an object with ill-founded recursion in its pattern. + u <- liftIO newUnique + let rv = UserObj u objName env methodMap matcherList + unify patt rv NullObj + env <- uses envStack $ \es -> Env (M.unions (map _unEnv (NE.toList es))) + return rv + where + methodMap = M.fromListWith (++) methodList + -- We need more structured methods and matchers. + methodList = [(verb, [(ListPatt p', n)]) | MethodExpr _ verb p' _ _ n <- methods ] + matcherList = [(p', n) | MatcherExpr p' n <- matchers ] + objName = case patt of + FinalPatt name _ -> name + VarPatt name _ -> "var " ++ name + _ -> "_" +eval (SequenceExpr exprs) = do + values <- mapM eval exprs + return $ if null exprs then NullObj else last values +eval (TryExpr expr catchPatt catchBody) = catchError try recover + where + try = withFreshScope $ eval expr + -- This should eventually respect the information-hiding schemes that + -- Typhon has adopted. + recover ej@Ejecting{} = left ej + recover _ = withFreshScope $ do + unify catchPatt (StrObj "") NullObj + eval catchBody + +-- | Deliver a message immediately to an object. +call :: Obj -> String -> [Obj] -> [(Obj, Obj)] -> Monte Obj +call obj verb args namedArgs = error "Not written yet" diff --git a/Masque/Monte.hs b/Masque/Monte.hs index 7b494eb..5e85d4c 100644 --- a/Masque/Monte.hs +++ b/Masque/Monte.hs @@ -7,6 +7,7 @@ import Control.Monad.Trans.Either import Control.Monad.Trans.State import Data.Foldable (toList) import Data.List.NonEmpty (NonEmpty(..)) +import qualified Data.List.NonEmpty as NE import qualified Data.Map as M import qualified Data.Set as S import Data.Unique @@ -18,15 +19,17 @@ data Err = Refused | WrongType | Unsettled | Ejecting Unique Obj - | BadWrite String + | Exception Obj + | BadWrite String (S.Set String) | BadName String (S.Set String) instance Show Err where show Refused = "Refused" show WrongType = "WrongType" show Unsettled = "Unsettled" - show (BadWrite name) = "BadWrite " ++ show name show (Ejecting _ _) = "Ejecting" + show (Exception _) = "Exception" + show (BadWrite name _) = "BadWrite " ++ show name show (BadName name _) = "BadName " ++ show name @@ -83,6 +86,17 @@ namesInScope = do envs <- use envStack return $ S.unions (map (M.keysSet . _unEnv) (toList envs)) +-- | Run a Monte action within a fresh scope. Names defined by the action will +-- be discarded afterwards. +withFreshScope :: Monte a -> Monte a +withFreshScope action = + bracketEitherT push pop (const action) + where + push = envStack %= (Env M.empty NE.<|) + -- Only works as long as the environment stack isn't overpopped during the + -- scoped action. Shouldn't happen. + pop _ = envStack %= (\(_ :| (a:as)) -> a :| as) + -- | Lookup a name in the current scope. maybeLookupName :: String -> Monte (Maybe Binding) maybeLookupName name = preuse $ envStack . traverse . unEnv . ix name diff --git a/Masque/Objects.hs b/Masque/Objects.hs index f8d27d0..88515de 100644 --- a/Masque/Objects.hs +++ b/Masque/Objects.hs @@ -21,7 +21,7 @@ data Obj = NullObj | EjectorObj Unique | ConstListObj (Seq.Seq Obj) | ConstMapObj [(Obj, Obj)] - | UserObj Unique String String Env (M.Map String [(Patt, Expr)]) [(Patt, Expr)] + | UserObj Unique String Env (M.Map String [(Patt, Expr)]) [(Patt, Expr)] instance Show Obj where show NullObj = "null" @@ -34,7 +34,7 @@ instance Show Obj where show (ConstListObj objs) = "[" ++ intercalate "," (map show (toList objs)) ++ "]" show (ConstMapObj pairs) = let showPair (k, v) = show k ++ " => " ++ show v in "[" ++ intercalate "," (map showPair pairs) ++ "]" - show (UserObj _ name _ _ _ _) = "<" ++ name ++ ">" + show (UserObj _ name _ _ _) = "<" ++ name ++ ">" data Binding = FinalAnyBinding Obj | FinalBinding Obj Obj diff --git a/masque.cabal b/masque.cabal index f6ed670..1877ae7 100644 --- a/masque.cabal +++ b/masque.cabal @@ -21,6 +21,7 @@ executable masque main-is: Main.hs other-modules: Masque.AST , Masque.Equality + , Masque.Eval , Masque.Objects , Masque.Vats other-extensions: DeriveDataTypeable,