module Sifflet.Language.Expr ( exprToValue, valueToLiteral, valueToLiteral' , Symbol(..) , OStr, OBool, OChar , Expr(..), eSymbol, eSym, eInt, eString, eChar, eFloat , exprIsAtomic , exprIsCompound , eBool, eFalse, eTrue, eIf , eList, eCall , exprIsLiteral , exprSymbols, exprVarNames , Operator(..) , Precedence , OperatorGrouping(..) , ExprTree, ExprNode(..), ExprNodeLabel(..) , exprNodeIoletCounter -- needs work ****** get rid of it??? , exprToTree, treeToExpr, exprToReprTree , EvalResult, EvalRes(EvalOk, EvalError, EvalUntried) , evalTree, unevalTree , Value(..), valueFunction , Functions(..) , Function(..), functionName, functionNArgs , functionArgTypes, functionResultType, functionType , functionArgNames, functionBody, functionImplementation , FunctionDefTuple, functionToDef, functionFromDef , FunctionImpl(..) , VpType(..), typeMatch, typeCheck, vpTypeOf , TypeEnv, emptyTypeEnv , Env, emptyEnv, makeEnv, extendEnv, envInsertL, envPop , envIns, envSet, envGet , envGetFunction, envLookup, envLookupFunction , envSymbols, envFunctionSymbols, envFunctions , eval, apply , decideTypes, newUndefinedFunction, undefinedTypes , ePlus, eTimes, eMinus, eDiv, eMod , eAdd1, eSub1 , eEq, eNe, eGt, eGe, eLt, eLe , eZerop, ePositivep, eNegativep , baseEnv) where -- drop this after debugging: import System.IO.Unsafe(unsafePerformIO) import Data.Map as Map hiding (filter, map, null) import Data.List as List import Data.Number.Sifflet import Sifflet.Data.Tree as T import Sifflet.Text.Pretty import Sifflet.Text.Repr () import Sifflet.Util -- | Transform a numerical expression into its negation, -- e.g., 5 --> (-5). -- Fails if the expression is not an ENumber. eNegate :: Expr -> SuccFail Expr eNegate expr = case expr of ENumber n -> Succ $ ENumber (negate n) _ -> Fail $ "eNegate: cannot handle" ++ show expr -- Symbols have names, and may or may not have values, -- but the value is stored in an environment, not in the symbol itself. data Symbol = Symbol String -- symbol name deriving (Eq, Read, Show) instance Pretty Symbol where pretty (Symbol s) = s instance Repr Symbol where repr (Symbol s) = s -- The Haskell representations of V's primitive data types. -- Data.Number.Sifflet.Number represents exact and inexact numbers. type OStr = String type OBool = Bool type OChar = Char -- | A more highly "parsed" type of expression -- -- I've restricted function calls to the case where the function expression -- is just a symbol, since otherwise it will be hard to visualize. -- But with some thought, it may be possible to generalize -- this to -- ECall [Expr] -- (function:args) -- The constructors EOp and EGroup are not used in Sifflet itself, -- but they are needed for export to Python, Haskell, and similar languages; -- they allow a distinction between operators and functions, and -- wrapping expressions in parentheses. -- EGroup e represents parentheses used for grouping: (e); -- it is not used for other cases of parentheses, e.g., -- around the argument list in a function call.] data Expr = EUndefined | ESymbol Symbol | EBool Bool | EChar Char | ENumber Number | EString String | EIf Expr Expr Expr -- ^ if test branch1 branch2 | EList [Expr] | ECall Symbol [Expr] -- ^ function name, arglist | EOp Operator Expr Expr -- ^ binary operator application | EGroup Expr -- ^ grouping parentheses deriving (Eq, Show) instance Repr Expr where repr e = case e of EUndefined -> "*undefined*" ESymbol s -> repr s EBool b -> repr b EChar c -> repr c ENumber n -> repr n EString s -> show s EIf t a b -> par "if" (map repr [t, a, b]) EList xs -> if exprIsLiteral e then reprList "[" ", " "]" xs else error ("Expr.repr: EList expression is non-literal: " ++ show e) -- check *** was: par "EList" (map repr items) ECall (Symbol fname) args -> par fname (map repr args) EOp op left right -> unwords [repr left, opName op, repr right] EGroup e' -> "(" ++ repr e' ++ ")" -- | An Expr is "extended" if it uses the extended constructors -- EOp or EGroup. In pure Sifflet, no extended Exprs are used. exprIsExtended :: Expr -> Bool exprIsExtended e = case e of EOp _ _ _ -> True EGroup _ -> True EIf t a b -> exprIsExtended t || exprIsExtended a || exprIsExtended b EList xs -> any exprIsExtended xs ECall (Symbol _) args -> any exprIsExtended args _ -> False -- | Is an Expr a literal? A literal is a boolean, character, number, string, -- or list of literals. We (should) only allow user input expressions -- to be literal expressions. exprIsLiteral :: Expr -> Bool exprIsLiteral e = case e of EBool _ -> True EChar _ -> True ENumber _ -> True EString _ -> True EList es -> all exprIsLiteral es -- Shouldn't we say that -- EGroup e' *not* a literal, even if e' is a literal? -- But consider carefully the effect on exprIsAtomic and ()'s removal. EGroup e' -> True -- or False, or exprIsLiteral e' ??? _ -> False -- | Is an expression atomic? -- Atomic expressions do not need parentheses in any reasonable language, -- because there is nothing to be grouped (symbols, literals) -- or in the case of lists, they already have brackets -- which separate them from their neighbors. -- -- All lists are atomic, even if they are not literals, -- because (for example) we can remove parentheses -- from ([a + b, 7]) exprIsAtomic :: Expr -> Bool exprIsAtomic e = case e of ESymbol _ -> True EList _ -> True _ -> exprIsLiteral e -- | Compound = non-atomic exprIsCompound :: Expr -> Bool exprIsCompound = not . exprIsAtomic eSymbol, eSym :: String -> Expr eSymbol = ESymbol . Symbol eSym = eSymbol eInt :: Integer -> Expr eInt = ENumber . Exact eString :: OStr -> Expr eString = EString eChar :: OChar -> Expr eChar = EChar eFloat :: Double -> Expr eFloat = ENumber . Inexact eBool :: Bool -> Expr eBool = EBool eFalse, eTrue :: Expr eFalse = eBool False eTrue = eBool True eIf :: Expr -> Expr -> Expr -> Expr eIf = EIf eList :: [Expr] -> Expr eList = EList -- | Example: -- ePlus_2_3 = eCall "+" [eInt 2, eInt 3] eCall :: String -> [Expr] -> Expr eCall = ECall . Symbol -- | An operator, such as * or + -- An operator is associative, like +, if (a + b) + c == a + (b + c). -- Its grouping is left to right if (a op b op c) means (a op b) op c; -- right to left if (a op b op c) means a op (b op c). -- Most operators group left to right. data Operator = Operator {opName :: String , opPrec :: Precedence , opAssoc :: Bool -- ^ associative? , opGrouping :: OperatorGrouping } deriving (Eq, Show) instance Pretty Operator where pretty = opName -- | Operator priority, normally is > 0 or >= 0, -- but does that really matter? I think not. type Precedence = Int -- | Operator grouping: left to right or right to left, -- or perhaps not at all data OperatorGrouping = GroupLtoR | GroupRtoL | GroupNone deriving (Eq, Show) -- | -- EXPRESSION TREES -- For pure Sifflet, so not defined for extended expressions. type ExprTree = Tree ExprNode data ExprNode = ENode ExprNodeLabel EvalResult deriving (Eq, Show) data ExprNodeLabel = NUndefined | NSymbol Symbol | -- formerly NLit Value NBool Bool | NChar Char | NNumber Number | NString String | NList [Expr] -- ??? deriving (Eq, Show) instance Repr ExprNode where reprl (ENode label evalRes) = case label of NUndefined -> case evalRes of EvalUntried -> ["undefined"] EvalError e -> ["undefined", "error: " ++ e] EvalOk _ -> errcats ["reprl of ExprNode: NUndefined with EvalOk", "should not happen!"] NSymbol s -> case evalRes of EvalOk v -> [repr s, repr v] EvalError e -> [repr s, "error: " ++ e] EvalUntried -> reprl s -- NLit l -> reprl l NBool b -> reprl b NChar c -> reprl c NNumber n -> reprl n NString s -> [show s] NList es -> reprl (EList es) -- check *** -- This was -- exprNodeIoletCounter :: Env -> IoletCounter ExprNode -- but IoletCounter is not available here, so use equivalent type. -- Returns (no. of inlets, no. of outlets) exprNodeIoletCounter :: Env -> ExprNode -> (Int, Int) exprNodeIoletCounter env (ENode nodeLabel _nodeResult) = case nodeLabel of NUndefined -> (0, 1) NSymbol (Symbol "if") -> (3, 1) NSymbol (Symbol s) -> case envLookup env s of Nothing -> (0, 1) -- probably a parameter of the function Just value -> case value of VFun function -> (functionNArgs function, 1) _ -> (0, 1) -- symbol bound to non-function value _ -> (0, 1) exprToTree :: Expr -> ExprTree exprToTree expr = let leafnode :: ExprNodeLabel -> T.Tree ExprNode leafnode e = node e [] node :: ExprNodeLabel -> [T.Tree ExprNode] -> T.Tree ExprNode node e ts = T.Node (ENode e EvalUntried) ts errext = error ("exprToTree: extended expr: " ++ show expr) in case expr of -- EUndefined, ESymbol, and literals map directly -- to NUndefined, NSymbol, E(literal-type) EUndefined -> leafnode NUndefined ESymbol s -> leafnode (NSymbol s) -- Literals EBool b -> leafnode (NBool b) EChar c -> leafnode (NChar c) ENumber n -> leafnode (NNumber n) EString s -> leafnode (NString s) -- EIf maps to symbol "if" at the root, 3 subtrees EIf t a b -> node (NSymbol (Symbol "if")) (map exprToTree [t, a, b]) -- ECall maps to symbol f (function name) at the root, -- each argument forms a subtree ECall f args -> node (NSymbol f) (map exprToTree args) EList xs -> leafnode (NList xs) -- Extended Exprs not supported! EGroup _ -> errext EOp _ _ _ -> errext -- | Convert an expression tree (back) to an expression -- It will not give back the *same* expression in the case of an EList. treeToExpr :: ExprTree -> Expr treeToExpr (T.Node (ENode label _) trees) = let wrong msg = errcat ["treeToExpr: ", msg, ": node label = ", show label, "; trees = ", show trees] lit e = if null trees then e else wrong "literal node with non-empty subtrees" in case label of NUndefined -> EUndefined NBool b -> lit (EBool b) NChar c -> lit (EChar c) NNumber n -> lit (ENumber n) NString s -> lit (EString s) NList xs -> lit (EList xs) NSymbol s -> if s == Symbol "if" then case trees of [q, a, b] -> EIf (treeToExpr q) (treeToExpr a) (treeToExpr b) _ -> wrong "'if' node with /= 3 subtrees" else -- VVV Do I really need to distinguish these two cases? if null trees then -- s = terminal symbol ESymbol s else -- s = function symbol in function call ECall s (map treeToExpr trees) -- Convert an expression to a repr tree (of string elements) -- (Why?) exprToReprTree :: Expr -> Tree String exprToReprTree = fmap repr . exprToTree -- Evaluation results (or non-results) type EvalResult = EvalRes Value data EvalRes e = EvalOk e | EvalError String | EvalUntried deriving (Eq, Show) instance Monad EvalRes where EvalOk value >>= f = f value EvalError e >>= _f = EvalError e EvalUntried >>= _f = EvalUntried return = EvalOk fail = EvalError -- Evaluate an expression tree showing the evaluation at each node. -- There's a lot of redundancy in this computation, but does it matter? evalTree :: ExprTree -> Env -> ExprTree evalTree atree env = evalTreeWithLimit atree env stackSize evalTreeWithLimit :: ExprTree -> Env -> Int -> ExprTree evalTreeWithLimit atree env stacksize = let T.Node root subtrees = atree ss' = pred stacksize in case root of ENode (NSymbol (Symbol "if")) _ -> case subtrees of [tt, ta, tb] -> let tt' = evalTreeWithLimit tt env ss' ENode _ testResult = rootLabel tt' subEval subtree = let subtree' = evalTreeWithLimit subtree env ss' ENode _ subresult = rootLabel subtree' in (subresult, subtree') ifNode result = ENode (NSymbol (Symbol "if")) result in case testResult of EvalOk (VBool True) -> let (taValue, ta') = subEval ta in T.Node (ifNode taValue) [tt', ta', tb] EvalOk (VBool False) -> let (tbValue, tb') = subEval tb in T.Node (ifNode tbValue) [tt', ta, tb'] EvalError msg -> T.Node (ifNode (EvalError msg)) [tt', ta, tb] _ -> errcats ["evalTreeWithLimit (if):", "unexpected test result"] _ -> error "evalTreeWithLimit: if: wrong number of subtrees" ENode rootOper _ -> T.Node (ENode rootOper (evalWithLimit (treeToExpr atree) env ss')) [evalTreeWithLimit s env ss' | s <- subtrees] -- remove the values from the ExprNodes -- "inverse" of evalTree unevalTree :: ExprTree -> ExprTree unevalTree atree = let unevalNode (ENode oper _) = ENode oper EvalUntried in fmap unevalNode atree -- VALUES AND EVALUATION data Value = VBool OBool | VChar OChar | VNumber Number | VString OStr | VFun Function | VList [Value] deriving (Eq, Show) -- no Read for Function instance Repr Value where repr (VBool b) = show b repr (VChar c) = show c repr (VNumber n) = show n repr (VString s) = show s repr (VFun f) = show f repr (VList vs) = reprList "[" ", " "]" vs valueFunction :: Value -> Function valueFunction value = case value of VFun function -> function _ -> error "valueFunction: non-function value" -- | The value of an expression in the base environment. exprToValue :: Expr -> SuccFail Value exprToValue expr = case eval expr baseEnv of EvalOk value -> Succ value EvalError msg -> Fail msg EvalUntried -> error "exprToValue: eval resulted in EvalUntried" valueToLiteral :: Value -> SuccFail Expr valueToLiteral v = case v of VBool b -> Succ $ EBool b VChar c -> Succ $ EChar c VNumber n -> Succ $ ENumber n VString s -> Succ $ EString s -- VList [] -> Succ $ EList [] -- VV Should this be fixed? VV -- VList _ -> Fail "cannot convert non-empty list to literal expression" VList vs -> mapM valueToLiteral vs >>= Succ . EList VFun _f -> Fail "cannot convert function to literal expression" valueToLiteral' :: Value -> Expr valueToLiteral' v = case valueToLiteral v of Fail msg -> error ("valueToLiteral: " ++ msg) Succ e -> e -- | Convert a literal expression to the value it represents. -- It is an error if the expression is non-literal. -- See exprIsLiteral. literalToValue :: Expr -> Value literalToValue e = case e of EBool b -> VBool b EChar c -> VChar c ENumber n -> VNumber n EString s -> VString s EList es -> if exprIsLiteral e then VList (map literalToValue es) else errcats ["literalToValue: ", "non-literal list expression: ", show e] _ -> errcats ["literalToValue: non-literal or extended expression: " , show e] data VpType = VpTypeBool | VpTypeChar | VpTypeNum | VpTypeString | VpTypeList VpType -- list with fixed type of elements | VpTypeFunction [VpType] VpType -- argument, result types | VpTypeVar String -- named type variable deriving (Eq, Show) type TypeEnv = Map String VpType emptyTypeEnv :: TypeEnv emptyTypeEnv = Map.empty -- | Try to match a single type and value, -- may result in binding a type variable in a new environment -- or just the old environment typeMatch :: VpType -> Value -> TypeEnv -> SuccFail TypeEnv typeMatch vptype value env = let sorry x etype = Fail $ repr x ++ ": " ++ etype ++ " expected" in case (vptype, value) of -- easy cases (VpTypeBool, VBool _) -> Succ env (VpTypeBool, x) -> sorry x "True or False" (VpTypeChar, VChar _) -> Succ env (VpTypeChar, x) -> sorry x "character" (VpTypeNum, VNumber _) -> Succ env (VpTypeNum, x) -> sorry x "number" (VpTypeString, VString _) -> Succ env (VpTypeString, x) -> sorry x "string" -- VV Harder -- VV Are the avalues below supposed to be equal to the value above? (VpTypeVar name, avalue) -> case Map.lookup name env of Nothing -> -- bind type variable vpTypeOf avalue >>= \ vtype -> Succ $ Map.insert name vtype env Just concreteType -> typeMatch concreteType avalue env (VpTypeList etype, VList lvalues) -> case lvalues of [] -> Succ env v:vs -> typeMatch etype v env >>= typeMatch (VpTypeList etype) (VList vs) (VpTypeFunction _atypes _rtype, _) -> -- this will require matching type variables with type variables! error "typeMatch: unimplemented case for VpTypeFunction" _ -> Fail $ "type mismatch: " ++ show (vptype, value) -- | Determine the type of a value. -- May result in a type variable. vpTypeOf :: Value -> SuccFail VpType vpTypeOf v = case v of VBool _ -> Succ VpTypeBool VChar _ -> Succ VpTypeChar VNumber _ -> Succ VpTypeNum VString _ -> Succ VpTypeString VFun (Function _ atypes rtype _) -> Succ $ VpTypeFunction atypes rtype VList [] -> Succ $ VpTypeList $ VpTypeVar "list_element" VList (x:xs) -> do xtype <- vpTypeOf x xstypes <- mapM vpTypeOf xs if filter (/= xtype) xstypes == [] then Succ $ VpTypeList xtype else Fail "list with diverse element types" -- | Check whether the values agree with the types (which may be abstract) -- -- This is *probably* too lenient in the case of type variables: -- it can pass a mixed-type list. typeCheck :: [String] -> [VpType] -> [Value] -> SuccFail [Value] typeCheck names types values = let check :: TypeEnv -> [String] -> [VpType] -> [Value] -> SuccFail [Value] check _ [] [] [] = Succ [] check env (n:ns) (t:ts) (v:vs) = case typeMatch t v env of Succ env' -> check env' ns ts vs >>= Succ . (v:) Fail msg -> Fail $ "For variable " ++ n ++ ":\n" ++ msg check _ _ _ _ = error "typeCheck: mismatched list lengths" in check empty names types values -- | A collection of functions, typically to be saved or exported -- or read from a file data Functions = Functions [Function] deriving (Eq, Show) -- | A function may have a name and always has an implementation data Function = Function (Maybe String) -- function name [VpType] -- argument types VpType -- result type FunctionImpl -- implementation deriving (Show) data FunctionImpl = Primitive ([Value] -> EvalResult) -- a Haskell function | Compound [String] Expr -- arguments, body instance Show FunctionImpl where show (Primitive _) = "" show (Compound args body) = concat ["Compound function, args = " ++ show args ++ "; body = " ++ show body] instance Read FunctionImpl where readsPrec _ _ = error "readsPrec not implemented for FunctionImpl" instance Repr Function where repr (Function mname _ _ _) = case mname of Nothing -> "" Just name -> "" newUndefinedFunction :: String -> [String] -> Function newUndefinedFunction name argnames = let (atypes, rtype) = undefinedTypes argnames impl = Compound argnames EUndefined in Function (Just name) atypes rtype impl functionName :: Function -> String functionName (Function mname _ _ _) = case mname of Just name -> name Nothing -> "anonymous function" functionNArgs :: Function -> Int functionNArgs = length . functionArgTypes functionArgTypes :: Function -> [VpType] functionArgTypes (Function _ argtypes _ _) = argtypes functionResultType :: Function -> VpType functionResultType (Function _ _ rtype _) = rtype -- | Type type of a function, a tuple of (arg types, result type) functionType :: Function -> ([VpType], VpType) -- (args., result type) functionType f = (functionArgTypes f, functionResultType f) functionImplementation :: Function -> FunctionImpl functionImplementation (Function _ _ _ impl) = impl functionArgNames :: Function -> [String] functionArgNames f = case functionImplementation f of Primitive _ -> ["dummy" | _t <- functionArgTypes f] Compound args _body -> args type FunctionDefTuple = (String, [String], [VpType], VpType, Expr) functionToDef :: Function -> FunctionDefTuple functionToDef (Function mname argTypes resType impl) = case impl of Primitive _ -> error "functionToDef: primitive function" Compound argNames body -> case mname of Nothing -> error "functionToDef: unnamed function" Just name -> (name, argNames, argTypes, resType, body) functionFromDef :: FunctionDefTuple -> Function functionFromDef (name, argNames, argTypes, resType, body) = Function (Just name) argTypes resType (Compound argNames body) functionBody :: Function -> Expr functionBody f = case functionImplementation f of Primitive _fp -> errcats ["functionBody:", "no body available for primitive function"] Compound _args body -> body -- | We need to be able to say functions are equal (or not) in order -- to tell if environments are equal or not, in order to know whether -- there are unsaved changes. This is tricky since the primitive -- function implementations do not instantiate Eq, so if it's -- primitive == primitive? we go by the names alone (there's nothing -- else to go by). Otherwise all the parts must be equal. instance Eq Function where f1 == f2 = let Function mname1 atypes1 anames1 impl1 = f1 Function mname2 atypes2 anames2 impl2 = f2 in case (impl1, impl2) of (Primitive _, Primitive _) -> mname1 == mname2 (Compound args1 body1, Compound args2 body2 ) -> mname1 == mname2 && atypes1 == atypes2 && anames1 == anames2 && args1 == args2 && body1 == body2 _ -> False -- | An Environment contains variable bindings and may be linked to -- a next environment -- -- Perhaps it may also be used to generate Vp type variables (with int id's) type EnvFrame = Map String Value type Env = [EnvFrame] -- should be NON-empty type Binding = (String, Value) emptyEnv :: Env emptyEnv = makeEnv [] [] makeEnv :: [String] -> [Value] -> Env makeEnv names values = extendEnv names values [] extendEnv :: [String] -> [Value] -> Env -> Env extendEnv names values env = fromList (zip names values) : env -- | Insert names and values from lists into an environment envInsertL :: Env -> [String] -> [Value] -> Env envInsertL env names values = case env of [] -> error "envInsertL: empty list" f : fs -> let ins :: EnvFrame -> Binding -> EnvFrame ins frame (name, value) = Map.insert name value frame in foldl ins f (zip names values) : fs envIns :: Env -> String -> Value -> Env envIns env name value = case env of [] -> error "envIns: empty list" f : fs -> Map.insert name value f : fs envSet :: Env -> String -> Value -> Env envSet env name value = -- If name is bound in some map in the environment, update the binding -- in that map; otherwise insert it into the "front" map let loop :: Env -> Maybe Env loop env1 = case env1 of [] -> Nothing f:fs -> case Map.lookup name f of Just _ -> Just (envIns env1 name value) Nothing -> do -- in the Maybe monad: fs' <- loop fs return (f:fs') in case loop env of Just result -> result Nothing -> envIns env name value -- | Get the value of a variable from an environment envGet :: Env -> String -> Value envGet env name = case envLookup env name of Just value -> value Nothing -> errcats ["envGet: unbound variable:", name] envGetFunction :: Env -> String -> Function envGetFunction env name = func where VFun func = envGet env name envLookup :: Env -> String -> Maybe Value envLookup env name = case env of [] -> Nothing f:fs -> case Map.lookup name f of Just value -> Just value Nothing -> envLookup fs name envLookupFunction :: Env -> String -> Maybe Function envLookupFunction env name = case envLookup env name of Nothing -> Nothing Just value -> case value of VFun function -> Just function _ -> Nothing -- | List of all symbols bound in the environment envSymbols :: Env -> [String] envSymbols env = case env of [] -> [] f : fs -> keys f ++ envSymbols fs -- | List of all symbols bound to functions in the environment envFunctionSymbols :: Env -> [String] envFunctionSymbols env = let isFunction s = case envGet env s of VFun _ -> True _ -> False in [s | s <- envSymbols env, isFunction s] -- | All the functions in the environment envFunctions :: Env -> Functions envFunctions env = Functions (map (envGetFunction env) (envFunctionSymbols env)) -- | Return to the environment prior to an extendEnv envPop :: Env -> Env envPop env = case env of [] -> error "envPop: empty list" _f:fs -> fs unbound :: String -> Env -> Bool unbound name env = envLookup env name == Nothing -- EVALUATING EXPRESSIONS -- Limit the stack size for recursion, since we are helping -- novice programmers to learn stackSize :: Int stackSize = 1000 eval :: Expr -> Env -> EvalResult eval expr env = evalWithLimit expr env stackSize evalWithLimit :: Expr -> Env -> Int -> EvalResult -- Evaluate an expression in an environment with a limited stack evalWithLimit expr env stacksize = if stacksize <= 0 then EvalError "stack overflow" else let stacksize' = pred stacksize in case expr of EUndefined -> EvalError "undefined" ESymbol (Symbol name) -> case envLookup env name of Nothing -> EvalError $ "unbound variable: " ++ name Just value -> EvalOk value EBool b -> EvalOk (VBool b) EChar c -> EvalOk (VChar c) ENumber n -> EvalOk (VNumber n) EString n -> EvalOk (VString n) EIf t a b -> case evalWithLimit t env stacksize' of EvalOk (VBool True) -> evalWithLimit a env stacksize' EvalOk (VBool False) -> evalWithLimit b env stacksize' result -> result ECall fsym args -> -- evaluating a function call -- I assume that call expressions have *symbols* for the -- functions. -- To relax this assumption: change the definition of ECall, -- but how will you visualize it? case evalWithLimit (ESymbol fsym) env stacksize' of EvalOk f -> case mapM (\ a -> evalWithLimit a env stacksize') args of EvalOk argvalues -> apply f argvalues env stacksize' -- why doesn't this work? err -> err EvalError e -> EvalError e EvalUntried -> EvalUntried err -> err EList elist -> case mapM (\ elt -> evalWithLimit elt env stacksize') elist of EvalOk values -> EvalOk (VList values) EvalError e -> EvalError e EvalUntried -> EvalUntried _ -> errcats ["evalWithLimit: extended expression not supported", show expr] -- | Apply a function fvalue to a list of actual arguments args -- in an environment env and with a limited stack size stacksize apply :: Value -> [Value] -> Env -> Int -> EvalResult apply fvalue args env stacksize = case fvalue of VFun f -> case functionImplementation f of Primitive pf -> pf args Compound formalArgs body -> evalWithLimit body (extendEnv formalArgs args env) stacksize not_a_function -> EvalError ("apply: first arg is not a function: " ++ show not_a_function) -- Shortcuts for making expressions that call the primitive functions ePlus :: Expr -> Expr -> Expr ePlus e1 e2 = eCall "+" [e1, e2] eTimes :: Expr -> Expr -> Expr eTimes e1 e2 = eCall "*" [e1, e2] eMinus, eDiv, eMod :: Expr -> Expr -> Expr eMinus e1 e2 = eCall "-" [e1, e2] eDiv e1 e2 = eCall "div" [e1, e2] eMod e1 e2 = eCall "mod" [e1, e2] eAdd1, eSub1 :: Expr -> Expr eAdd1 e1 = eCall "add1" [e1] eSub1 e1 = eCall "sub1" [e1] eEq, eNe, eGt, eGe, eLt, eLe :: Expr -> Expr -> Expr eEq e1 e2 = eCall "==" [e1, e2] eNe e1 e2 = eCall "/=" [e1, e2] eGt e1 e2 = eCall ">" [e1, e2] eGe e1 e2 = eCall ">=" [e1, e2] eLt e1 e2 = eCall "<" [e1, e2] eLe e1 e2 = eCall "<=" [e1, e2] eZerop, ePositivep, eNegativep :: Expr -> Expr eZerop e1 = eCall "zero?" [e1] ePositivep e1 = eCall "positive?" [e1] eNegativep e1 = eCall "negative?" [e1] -- A good base environment to get started with primitiveFunctions :: [Function] primitiveFunctions = [ -- Arithmetic primN2N "+" (+), -- Number (+) primN2N "-" (-), primN2N "*" (*), primIntDiv, primIntMod, primFloatDiv, primN1N "add1" succ, primN1N "sub1" pred, -- Comparison primN2B "==" (==), primN2B "/=" (/=), primN2B ">" (>), primN2B ">=" (>=), primN2B "<" (<), primN2B "<=" (<=), primN1B "zero?" eqZero, primN1B "positive?" gtZero, primN1B "negative?" ltZero, -- List operations -- null xs tells if xs is an empty list prim "null" [VpTypeList (VpTypeVar "a")] VpTypeBool primNull, prim "head" [VpTypeList (VpTypeVar "c")] (VpTypeVar "c") primHead, prim "tail" [VpTypeList (VpTypeVar "c")] (VpTypeList (VpTypeVar "c")) primTail, prim ":" [VpTypeVar "d", VpTypeList (VpTypeVar "d")] (VpTypeList (VpTypeVar "d")) primCons ] type PFun = [Value] -> EvalResult -- Primitive functions of arbitrary type prim :: String -> [VpType] -> VpType -> PFun -> Function prim name atypes rtype = Function (Just name) atypes rtype . Primitive -- Primitive arithmetic functions -- | Integer div and mod operations, for exact integers only. -- Using an inexact (floating point) argument is an error, -- even if the argument is "equal" to an integer (e.g., 5.0). -- Division (div or mod) by zero is an error. primIntDivMod :: String -> (Number -> Number -> Number) -> Function primIntDivMod name oper = let func args = let err msg = EvalError $ concat [name, ": ", msg, " (", show args, ")"] in case args of [VNumber a, VNumber b] -> if b == 0 then err "zero divisor" else if isExact a && isExact b then EvalOk $ VNumber (oper a b) else err "arguments must be exact numbers" _ -> error "wrong type or number of arguments" in prim name [VpTypeNum, VpTypeNum] VpTypeNum func primIntDiv, primIntMod :: Function primIntDiv = primIntDivMod "div" div primIntMod = primIntDivMod "mod" mod -- | Floating point division. -- Integer arguments are coerced to floating point, -- and the result is always floating point. -- operands are ints. -- x / 0 is NaN if x == 0, Infinity if x > 0, -Infinity if x < 0. primFloatDiv :: Function primFloatDiv = let divide args = case args of [VNumber x, VNumber y] -> EvalOk $ VNumber (x / y) _ -> EvalError $ "/: invalid args: " ++ show args in prim "/" [VpTypeNum, VpTypeNum] VpTypeNum divide -- Primitive functions for lists primArgCountError :: String -> EvalResult primArgCountError name = errcat [name, ": wrong number of arguments in primitive function"] -- Some of the type-checking in these primitive functions -- shouldn't be necessary, if Sifflet knew the types of the -- functions and could do type inference and check input values. primNull :: PFun primNull args = case args of [VList list] -> EvalOk $ VBool (List.null list) [_] -> EvalError "null: not a list" _ -> primArgCountError "primNull" primHead :: PFun primHead args = case args of [VList (x : _xs)] -> EvalOk x [VList []] -> EvalError "head: empty list" [_] -> EvalError "head: not a list" _ -> primArgCountError "primHead" primTail :: PFun primTail args = case args of [VList (_x : xs)] -> EvalOk $ VList xs [VList []] -> EvalError "tail: empty list" [_] -> EvalError "tail: not a list" _ -> primArgCountError "primTail" primCons :: PFun primCons args = case args of [x, VList xs] -> EvalOk $ VList (x:xs) [_, _] -> EvalError "cons: second argument not a list" _ -> primArgCountError "primCons" -- Functions for constructing Functions of common types -- | Primitive function with 2 number arguments yield an number value -- fn = Number function to implement for Number operands. primN2N :: String -> (Number -> Number -> Number) -> Function primN2N name fn = let impl args = case args of [VNumber x, VNumber y] -> EvalOk $ VNumber (fn x y) _ -> EvalError $ name ++ ": invalid args: " ++ show args in prim name [VpTypeNum, VpTypeNum] VpTypeNum impl -- | Primitive unary functions number to number primN1N :: String -> (Number -> Number) -> Function primN1N name fn = let impl args = case args of [VNumber x] -> EvalOk $ VNumber (fn x) _ -> EvalError $ name ++ ": invalid args: " ++ show args in prim name [VpTypeNum] VpTypeNum impl -- Primitive frunctions with 2 number args and a boolean result primN2B :: String -> (Number -> Number -> OBool) -> Function primN2B name fn = let impl args = case args of [VNumber x, VNumber y] -> EvalOk $ VBool (fn x y) _ -> EvalError $ name ++ ": invalid args: " ++ show args in prim name [VpTypeNum, VpTypeNum] VpTypeBool impl -- Primitive unary functions number to boolean primN1B :: String -> (Number -> Bool) -> Function primN1B name fn = let impl args = case args of [VNumber x] -> EvalOk $ VBool (fn x) _ -> EvalError $ name ++ ": invalid args: " ++ show args in prim name [VpTypeNum] VpTypeBool impl baseEnv :: Env baseEnv = makeEnv (map functionName primitiveFunctions) (map VFun primitiveFunctions) -- | Given an expression, return the list of names of variables -- occurring n the expression exprSymbols :: Expr -> [Symbol] exprSymbols expr = nub $ case expr of EUndefined -> [] -- is *not* a variable ESymbol s -> [s] EIf t a b -> nub $ concat [exprSymbols t, exprSymbols a, exprSymbols b] ECall f args -> case args of [] -> [f] a:as -> nub $ concat [exprSymbols a, exprSymbols (ECall f as)] EList items -> nub $ concatMap exprSymbols items _ -> if exprIsExtended expr then errcats ["exprSymbols: extended expr not supported:", show expr] else [] -- literal types bool, char, number, string -- | exprVarNames expr returns the names of variables in expr -- that are UNBOUND in the base environment. This may not be ideal, -- but it's a start. exprVarNames :: Expr -> [String] exprVarNames expr = [name | (Symbol name) <- exprSymbols expr, unbound name baseEnv] -- | decideTypes tries to find the argument types and return type -- of an expression considered as the body of a function, -- at the same time checking for consistency of inputs and -- outputs between the parts of the expression. -- It returns Right (argtypes, returntype) if successful; -- Left errormessage otherwise. decideTypes :: Expr -> [String] -> Env -> Either String ([VpType], VpType) decideTypes expr args _env = unsafePerformIO $ do { print "Fudged the decideTypes" ; print expr ; return (if True then Right (undefinedTypes args) else Left "decideTypes: not implemented") } undefinedTypes :: [String] -> ([VpType], VpType) undefinedTypes argnames = let atypes = [VpTypeVar ('_' : name) | name <- argnames] rtype = VpTypeVar "_result" in (atypes, rtype)