diff --git a/src/Angle/Exec/Builtins.hs b/src/Angle/Exec/Builtins.hs index ae1de05..100a7df 100644 --- a/src/Angle/Exec/Builtins.hs +++ b/src/Angle/Exec/Builtins.hs @@ -79,9 +79,9 @@ import System.IO.Error ( tryIOError import System.Process (readProcess) import Angle.Exec.Error -import Angle.Exec.Scope import Angle.Exec.Types import Angle.Types.Lang +import Angle.Types.Scope emptyArgs :: ArgSig @@ -108,7 +108,7 @@ builtinVar name = (name, VarVal , varBuiltin = True }) -builtinsVars :: BindEnv Lambda +builtinsVars :: BindEnv LangIdent Lambda builtinsVars = bindEnvFromList $ map (builtinVar . LangIdent) builtins @@ -119,7 +119,7 @@ builtinValue name val = (name, VarVal , varBuiltin = True }) -builtinsValues :: BindEnv LangLit +builtinsValues :: BindEnv LangIdent LangLit builtinsValues = bindEnvFromList $ map (\(x,y) -> builtinValue (LangIdent x) y) builtinValues diff --git a/src/Angle/Exec/Exec.hs b/src/Angle/Exec/Exec.hs index dce502e..2fd904d 100644 --- a/src/Angle/Exec/Exec.hs +++ b/src/Angle/Exec/Exec.hs @@ -25,9 +25,9 @@ import Angle.Parse.Parser (program, evalParse) import Angle.Exec.Builtins import Angle.Exec.Error import Angle.Exec.Operations -import Angle.Exec.Scope import Angle.Exec.Types import Angle.Types.Lang +import Angle.Types.Scope updatePos :: SourceRef -> ExecIO () @@ -49,7 +49,7 @@ newScope = do put env { currentScope = newScope' } -lookupVar :: (Scope -> BindEnv a) -> LangIdent -> ExecIO (Maybe a) +lookupVar :: (Scope -> BindEnv LangIdent a) -> LangIdent -> ExecIO (Maybe a) lookupVar binds name = do currScope <- getScope case resolve binds name currScope of @@ -57,7 +57,7 @@ lookupVar binds name = do Just x -> return $ varDef x -lookupVarF :: (Scope -> BindEnv a) -> (LangIdent -> ExecError) -> LangIdent -> ExecIO a +lookupVarF :: (Scope -> BindEnv LangIdent a) -> (LangIdent -> ExecError) -> LangIdent -> ExecIO a lookupVarF binds err name = lookupVar binds name >>= maybe (throwExecError $ err name) return @@ -85,7 +85,7 @@ modifyScope f = do put env {currentScope=newScope'} -lookupVarCurrentScope :: (Scope -> BindEnv a) -> LangIdent -> ExecIO (Maybe (VarVal a)) +lookupVarCurrentScope :: (Scope -> BindEnv LangIdent a) -> LangIdent -> ExecIO (Maybe (VarVal a)) lookupVarCurrentScope binds name = do currScope <- liftM currentScope get if isDefinedIn binds name currScope @@ -104,7 +104,7 @@ assignVarLit n v = assignVar valueBindings handleBuiltinAssignLit assignVar - :: (Scope -> BindEnv a) + :: (Scope -> BindEnv LangIdent a) -> (LangIdent -> b -> ExecIO b) -- ^ Builtin handler function. -> (LangIdent -> VarVal b -> Scope -> Scope) -> LangIdent @@ -321,6 +321,14 @@ callFun x asClass args | isBuiltin x = callBuiltin x args callLambda :: Lambda -> Bool -> [Expr] -> ExecIO LangLit +callLambda l@(Lambda { lambdaScope = (Just s) }) asClass args + = do + cScope <- getScope + modifyScope (const s) + res <- callLambda l { lambdaScope = Nothing } asClass args + `catchAE` (\e -> modifyScope (const cScope) >> throwError e) + modifyScope (const cScope) + return res callLambda (Lambda { lambdaArgs=params , lambdaBody=body}) asClass args @@ -355,7 +363,13 @@ execSingStmt (StmtReturn x) = do isGlob <- liftM (isOutermostScope . currentScope) get if isGlob then throwExecError returnFromGlobalErr - else execExpr x >>= throwReturn + else do + res <- execExpr x + case res of + LitLambda lam -> do + sc <- getScope + throwReturn $ LitLambda lam { lambdaScope = Just sc } + y -> throwReturn y execSingStmt (StmtBreak x False) = case x of Nothing -> throwBreak Nothing @@ -525,7 +539,7 @@ assignVarBuiltinLambda = assignVarBuiltin - :: (Scope -> BindEnv a) + :: (Scope -> BindEnv LangIdent a) -> (LangIdent -> VarVal b -> Scope -> Scope) -> LangIdent -> b -- ^ Value to assign. diff --git a/src/Angle/Exec/Types/Internal.hs b/src/Angle/Exec/Types/Internal.hs index d035a98..47309cd 100644 --- a/src/Angle/Exec/Types/Internal.hs +++ b/src/Angle/Exec/Types/Internal.hs @@ -37,8 +37,8 @@ import Control.Monad.Trans.Except import Control.Monad.State import Angle.Exec.Error -import Angle.Exec.Scope import Angle.Types.Lang +import Angle.Types.Scope -- | Angle program execution monad. diff --git a/src/Angle/Parse/Parser/Internal.hs b/src/Angle/Parse/Parser/Internal.hs index 99b4bb6..3f2b508 100644 --- a/src/Angle/Parse/Parser/Internal.hs +++ b/src/Angle/Parse/Parser/Internal.hs @@ -238,7 +238,7 @@ structDefun = StructDefun <$> (string "defun " *> identName) <*> (Lambda <$> callList <* tokStmtBetween - <*> stmt) + <*> stmt <*> return Nothing) -- | Exception handling. @@ -436,7 +436,7 @@ lambda :: Parser Lambda lambda = parens $ do args <- callList <* tokNSpaced body <- stmt - return $ Lambda args body + return $ Lambda args body Nothing -- | Set of arguments for a function diff --git a/src/Angle/Types/Lang.hs b/src/Angle/Types/Lang.hs index 47d3a3a..7ea0022 100644 --- a/src/Angle/Types/Lang.hs +++ b/src/Angle/Types/Lang.hs @@ -102,6 +102,7 @@ module Angle.Types.Lang , typeAnnOf , ArgElt(..) , Lambda(..) + , Scope , enumType , allType ) where @@ -110,6 +111,10 @@ import Numeric (showFFloat) import System.IO (Handle) import Angle.Scanner (SourcePos, beginningOfFile) +import Angle.Types.Scope (GenScope) + + +type Scope = GenScope LangIdent LangLit Lambda -- | Wraps statements to allow for positional tracking as well @@ -245,13 +250,14 @@ showSynOpList = showSynSep " " ")" " " -- The body is the code that is executed upon successful invokation. data Lambda = Lambda { lambdaArgs :: ArgSig , lambdaBody :: Stmt + , lambdaScope :: Maybe Scope } deriving (Show, Eq) instance ShowSyn Lambda where - showSyn (Lambda args body@(SingleStmt _ _)) + showSyn (Lambda args body@(SingleStmt _ _) _) = concat ["(", showSyn args, " ", init $ showSyn body, ")"] - showSyn (Lambda args body) = concat ["(", showSyn args, " ", showSyn body, ")"] + showSyn (Lambda args body _) = concat ["(", showSyn args, " ", showSyn body, ")"] -- | An argument signature. @@ -477,7 +483,7 @@ instance ShowSyn Expr where showSyn (ExprList _) = error "showSyn - cannot show unevaluated list" showSyn (ExprRange{}) = error "showSyn - cannot show unevaluated range" showSyn (ExprLambdaCall x xs) = showSyn (LitLambda x) ++ " : (" ++ showSynArgs xs ++ ")" - showSyn (ExprParamExpand _) = error "showSyn - ExprParamExpand made it to showSyn" + showSyn (ExprParamExpand x) = ".." ++ showSyn x -- error "showSyn - ExprParamExpand made it to showSyn" -- | Represents names that can be assigned values. diff --git a/src/Angle/Exec/Scope.hs b/src/Angle/Types/Scope.hs similarity index 65% rename from src/Angle/Exec/Scope.hs rename to src/Angle/Types/Scope.hs index bab136a..aa878a4 100644 --- a/src/Angle/Exec/Scope.hs +++ b/src/Angle/Types/Scope.hs @@ -1,6 +1,8 @@ +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ImpredicativeTypes #-} {-| -Module : Angle.Exec.Scope -Description : Defines functions for working with Scopes. +Module : Angle.Types.Scope +Description : Defines functions for working with GenScopes. Copyright : Copyright (C) 2015 Ben Moon License : GNU GPL, version 3 Maintainer : GuiltyDolphin@gmail.com @@ -20,8 +22,8 @@ or any of the parent scopes. The outer-most scope is called the global scope and is accessible from all other parts of a program. -} -module Angle.Exec.Scope - ( Scope(..) +module Angle.Types.Scope + ( GenScope(..) , BindEnv(..) , VarVal(..) , bindEnvFromList @@ -42,38 +44,39 @@ import qualified Data.Map as M import Data.Maybe (isJust) import Data.Function (on) -import Angle.Types.Lang - -- | Convert a list to a binding environment. -bindEnvFromList :: [(LangIdent, VarVal a)] -> BindEnv a +bindEnvFromList :: (Ord n) => [(n, VarVal a)] -> BindEnv n a bindEnvFromList = BindEnv . M.fromList -emptyBindEnv :: BindEnv a +emptyBindEnv :: BindEnv n a emptyBindEnv = BindEnv M.empty -- | Binding environment. -newtype BindEnv a = BindEnv - { unBindEnv :: M.Map LangIdent (VarVal a) } +newtype BindEnv n a = BindEnv + { unBindEnv :: M.Map n (VarVal a) } deriving (Show, Eq) -type BindMap a = M.Map LangIdent (VarVal a) +type BindMap n a = M.Map n (VarVal a) -- | Contains variable-value bindings, along with a reference -- to a parent scope. -data Scope = Scope - { outerScope :: Maybe Scope -- ^ Parent scope, if any. - , valueBindings :: BindEnv LangLit - , lambdaBindings :: BindEnv Lambda +data GenScope n v f = Scope + { outerScope :: Maybe (GenScope n v f) -- ^ Parent scope, if any. + , valueBindings :: BindEnv n v + , lambdaBindings :: BindEnv n f } deriving (Show, Eq) +-- type GenScope n v f = (Ord n) => GScope n v f + + -- | True if the given scope has no parent scopes. -isOutermostScope :: Scope -> Bool +isOutermostScope :: GenScope n v f -> Bool isOutermostScope s = case outerScope s of Nothing -> True Just _ -> False @@ -81,36 +84,36 @@ isOutermostScope s = case outerScope s of -- | True if the scope contains a defition for the given -- identifier. -isDefinedIn :: (Scope -> BindEnv a) -> LangIdent -> Scope -> Bool +isDefinedIn :: (Ord n) => (GenScope n v f -> BindEnv n a) -> n -> GenScope n v f -> Bool isDefinedIn binds name scope = isJust $ lookupBind name $ binds scope -onBind :: (BindMap a -> b) -> BindEnv a -> b +onBind :: (BindMap n a -> b) -> BindEnv n a -> b onBind f = f . unBindEnv -withBind :: (BindMap a -> BindMap a) -> BindEnv a -> BindEnv a +withBind :: (BindMap n a -> BindMap n a) -> BindEnv n a -> BindEnv n a withBind f = toBind . onBind f -toBind :: BindMap a -> BindEnv a +toBind :: BindMap n a -> BindEnv n a toBind = BindEnv -onBinds :: (BindMap a -> BindMap a -> BindMap a) -> BindEnv a -> BindEnv a -> BindEnv a +onBinds :: (BindMap n a -> BindMap n a -> BindMap n a) -> BindEnv n a -> BindEnv n a -> BindEnv n a onBinds f x = toBind . (f `on` unBindEnv) x -- | Runs a function in the outer scope of that provided. -- -- Returns `Nothing' if no outer scope exists. -withOuterScope :: Scope -> (Scope -> a) -> Maybe a +withOuterScope :: GenScope n v f -> (GenScope n v f -> a) -> Maybe a withOuterScope sc f = liftM f (outerScope sc) --- | Finds the local-most Scope that contains a definition +-- | Finds the local-most GenScope that contains a definition -- for the specified identifier. -innerScopeDefining :: (Scope -> BindEnv a) -> LangIdent -> Scope -> Maybe Scope +innerScopeDefining :: (Ord n) => (GenScope n v f -> BindEnv n a) -> n -> GenScope n v f -> Maybe (GenScope n v f) innerScopeDefining binds name scope = if isDefinedIn binds name scope then Just scope @@ -121,7 +124,7 @@ innerScopeDefining binds name scope -- scope in which it is defined. -- -- Returns Nothing if no definition is found. -resolve :: (Scope -> BindEnv a) -> LangIdent -> Scope -> Maybe (VarVal a) +resolve :: (Ord n) => (GenScope n v f -> BindEnv n a) -> n -> GenScope n v f -> Maybe (VarVal a) resolve binds name scope = case innerScopeDefining binds name scope of Nothing -> Nothing @@ -131,7 +134,7 @@ resolve binds name scope = -- | A scope with no parent or bindings. -emptyScope :: Scope +emptyScope :: GenScope n v f emptyScope = Scope { outerScope = Nothing , valueBindings = emptyBindEnv @@ -141,28 +144,28 @@ emptyScope = Scope { -- | Run a function over the bindings of a scope. onLitBindings - :: (BindEnv LangLit -> BindEnv LangLit) -> Scope -> Scope + :: (BindEnv n v -> BindEnv n v) -> GenScope n v f -> GenScope n v f onLitBindings f scope = scope { valueBindings = f $ valueBindings scope } onFunBindings - :: (BindEnv Lambda -> BindEnv Lambda) -> Scope -> Scope + :: (BindEnv n f -> BindEnv n f) -> GenScope n v f -> GenScope n v f onFunBindings f scope = scope { lambdaBindings = f $ lambdaBindings scope } -insertVar :: LangIdent -> VarVal a -> BindEnv a -> BindEnv a +insertVar :: (Ord n) => n -> VarVal a -> BindEnv n a -> BindEnv n a insertVar = insertBind -- | Set the value definition for the given variable in the given -- scope. -setVarLitInScope :: LangIdent -> VarVal LangLit -> Scope -> Scope +setVarLitInScope :: (Ord n) => n -> VarVal v -> GenScope n v f -> GenScope n v f setVarLitInScope name val = onLitBindings (insertVar name val) -- | Set the lambda definition for the given variable in the given -- scope. -setVarFunInScope :: LangIdent -> VarVal Lambda -> Scope -> Scope +setVarFunInScope :: (Ord n) => n -> VarVal f -> GenScope n v f -> GenScope n v f setVarFunInScope name val = onFunBindings (insertVar name val) @@ -170,7 +173,7 @@ setVarFunInScope name val = onFunBindings (insertVar name val) -- | Merge the binding values of the scopes, favouring the first -- when a definition exists in both, but always favouring a -- definition over no definition. -mergeScope :: Scope -> Scope -> Scope +mergeScope :: (Ord n) => GenScope n v f -> GenScope n v f -> GenScope n v f mergeScope sc1 sc2 = let nLits = mergeBinds `on` valueBindings nFuns = mergeBinds `on` lambdaBindings @@ -180,24 +183,24 @@ mergeScope sc1 sc2 -- | Remove the value binding of a variable from the given scope. -deleteLitFromScope :: LangIdent -> Scope -> Scope +deleteLitFromScope :: (Ord n) => n -> GenScope n v f -> GenScope n v f deleteLitFromScope = onLitBindings . deleteBind -deleteBind :: LangIdent -> BindEnv a -> BindEnv a +deleteBind :: (Ord n) => n -> BindEnv n a -> BindEnv n a deleteBind = withBind . M.delete -mergeBinds :: BindEnv a -> BindEnv a -> BindEnv a +mergeBinds :: (Ord n) => BindEnv n a -> BindEnv n a -> BindEnv n a mergeBinds = onBinds M.union -lookupBind :: LangIdent -> BindEnv a -> Maybe (VarVal a) +lookupBind :: (Ord n) => n -> BindEnv n a -> Maybe (VarVal a) lookupBind = onBind . M.lookup -insertBind :: LangIdent -> VarVal a -> BindEnv a -> BindEnv a +insertBind :: (Ord n) => n -> VarVal a -> BindEnv n a -> BindEnv n a insertBind n = withBind . M.insert n @@ -205,7 +208,7 @@ insertBind n = withBind . M.insert n data VarVal a = VarVal { varDef :: Maybe a , varBuiltin :: Bool - } deriving (Show, Eq) + } deriving (Show, Eq, Ord) -- | Variable with no definitions. diff --git a/tests/Test/Angle/Exec/Exec.hs b/tests/Test/Angle/Exec/Exec.hs index 81be8e2..6ff75a8 100644 --- a/tests/Test/Angle/Exec/Exec.hs +++ b/tests/Test/Angle/Exec/Exec.hs @@ -187,14 +187,23 @@ testClassAdd x y = checkFail toRun where toRun = setupAddF ++ callShowSyn "addInts" [x, y] -testReturnSimple :: LangLit -> Property -testReturnSimple x = checkResEq toRun x +testReturnSimple :: NonLambda -> Property +testReturnSimple (NonLambda x) = checkResEq toRun x where toRun = setupReturnSimple ++ callShowSyn "returnSimple" [x] setupReturnSimple = defun "returnSimple" "x" "return x;" -testReturnIfEmbedded :: Bool -> LangLit -> LangLit -> Property -testReturnIfEmbedded p x y | p = checkResEq toRun x +newtype NonLambda = NonLambda { getNonLambda :: LangLit } + deriving (Show) + +instance Arbitrary NonLambda where + arbitrary = liftM NonLambda $ arbitrary `suchThat` + (\x -> case x of + LitLambda{} -> False + _ -> True) + +testReturnIfEmbedded :: Bool -> NonLambda -> NonLambda -> Property +testReturnIfEmbedded p (NonLambda x) (NonLambda y) | p = checkResEq toRun x | otherwise = checkResEq toRun y where toRun = setupReturnIfEmbedded ++ callShowSyn "returnIfEmbedded" [LitBool p, x, y] setupReturnIfEmbedded = defun "returnIfEmbedded" "p, x, y" "if p then return x; else return y;" diff --git a/tests/Test/Angle/Exec/Scope.hs b/tests/Test/Angle/Types/Scope.hs similarity index 97% rename from tests/Test/Angle/Exec/Scope.hs rename to tests/Test/Angle/Types/Scope.hs index a9c4b7c..66d5e01 100644 --- a/tests/Test/Angle/Exec/Scope.hs +++ b/tests/Test/Angle/Types/Scope.hs @@ -1,6 +1,6 @@ {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleInstances #-} -module Test.Angle.Exec.Scope +module Test.Angle.Types.Scope ( tests ) where diff --git a/tests/TestHelper.hs b/tests/TestHelper.hs index 501e339..4508f18 100644 --- a/tests/TestHelper.hs +++ b/tests/TestHelper.hs @@ -34,10 +34,10 @@ import Test.Tasty.QuickCheck import Angle.Parse.Helpers (evalParse, Parser) import Angle.Parse.Token (keywords) -import Angle.Exec.Scope import Angle.Exec.Types.Internal import Angle.Scanner (SourcePos(..)) import Angle.Types.Lang +import Angle.Types.Scope import Angle.Parse.Parser (program) import Angle.Exec.Exec (execStmt) @@ -106,8 +106,8 @@ instance Arbitrary Lambda where arbitrary = do args <- arbitrary body <- arbitrary - return Lambda { lambdaArgs=args, lambdaBody=body} - shrink (Lambda x y) = shrink2 Lambda x y + return Lambda { lambdaArgs=args, lambdaBody=body, lambdaScope = Nothing} + shrink (Lambda x y z) = shrink3 Lambda x y z instance Arbitrary Expr where @@ -303,7 +303,7 @@ instance (Arbitrary a) => Arbitrary (TinyList a) where shrink (TinyList xs) = shrink1 TinyList xs -instance (Arbitrary a) => Arbitrary (BindEnv a) where +instance (Arbitrary a) => Arbitrary (BindEnv LangIdent a) where arbitrary = liftArby bindEnvFromList shrink (BindEnv x) = shrink1 bindEnvFromList (M.toList x) diff --git a/tests/TestSuite.hs b/tests/TestSuite.hs index 071aa15..e853b9b 100644 --- a/tests/TestSuite.hs +++ b/tests/TestSuite.hs @@ -7,7 +7,7 @@ import qualified Test.Angle.Parse.Helpers as Helpers import qualified Test.Angle.Parse.Parser as Parser import qualified Test.Angle.Parse.Token as Token import qualified Test.Angle.Exec.Operations as Operations -import qualified Test.Angle.Exec.Scope as Scope +import qualified Test.Angle.Types.Scope as Scope import qualified Test.Angle.Exec.Exec as Exec import qualified Test.Angle.Exec.Types as ExecTypes