module Language.Sifflet.Expr ( ArgSpec(..) , aspecsLookup , EvalResult, EvalRes(EvalOk, EvalError, EvalUntried) , exprToValue, valueToLiteral, valueToLiteral' , Symbol(..) , OStr, OBool, OChar , Expr(..), eSymbol, eSym, eInt, eString, eChar, eFloat , toLambdaExpr , callToApp, mapply , appToCall, mcall , exprIsAtomic , exprIsCompound , eBool, eFalse, eTrue, eIf , eList, eCall , exprIsLiteral , exprSymbols, exprVarNames , Operator(..) , Precedence , OperatorGrouping(..) , Value(..), valueFunction , Functions(..) , Function(..), functionName, functionNArgs , functionArgSpecs , functionArgTypes, functionResultType, functionArgResultTypes , functionType , functionArgNames, functionBody, functionImplementation , FunctionDefTuple, functionToDef, functionFromDef , FunctionImpl(..) , TypeVarName, TypeConsName, Type(..) , typeBool, typeChar, typeNum, typeString , typeList, typeFunction , Env, emptyEnv, makeEnv, extendEnv, envInsertL, envPop , envIns, envSet, envGet , envGetFunction, envLookup, envLookupFunction , envSymbols, envFunctionSymbols, envFunctions , eval, evalWithLimit, stackSize, apply , newUndefinedFunction , 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, foldl, map, null) import Data.List as List import Data.Number.Sifflet import Data.Sifflet.Tree as T import Text.Sifflet.Pretty import Text.Sifflet.Repr () import Language.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. newtype Symbol = Symbol String 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 -- -- Function calls have two kinds: -- 1. ECall: -- restricted to the case where the function expression -- is just a symbol, since otherwise it will be hard to visualize. -- 2. EApp: allows any expression to be the function, -- but is applied to only one argument. -- For now, the type checker will convert ECall expressions to -- EApp expressions. Ultimately, the two variants ought to be -- unified. -- -- 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] | ELambda Symbol Expr | EApp Expr Expr -- ^ apply function to argument | 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) ELambda x body -> par "lambda" [repr x , "->", repr body] EApp f arg -> par (repr f) [repr arg] ECall (Symbol fname) args -> par fname (map repr args) EOp op left right -> unwords [repr left, opName op, repr right] EGroup e' -> "(" ++ repr e' ++ ")" -- | Try to convert the arguments and body of a function to a lambda expression. -- Fails if there are no arguments, since a lambda expression requires one. -- If there are multiple arguments, then we get a nested lambda expression. toLambdaExpr :: [String] -> Expr -> SuccFail Expr toLambdaExpr args body = case args of [] -> Fail "toLambdaExpr: no arguments; at least one needed" a:[] -> Succ $ ELambda (Symbol a) body a:as -> do { expr <- toLambdaExpr as body ; Succ $ ELambda (Symbol a) expr } -- | Convert an ECall expression to an EApp expression callToApp :: Expr -> Expr callToApp expr = case expr of ECall fsym args -> mapply (ESymbol fsym) args _ -> error "callToApp: requires ECall expression" -- | Helper for callToApp, but may have other uses. -- Creates an EApp expression representing a function call -- with possibly many arguments. mapply :: Expr -> [Expr] -> Expr mapply fexpr args = case args of [] -> error "mapply: no argument, cannot happen" arg:[] -> EApp fexpr arg arg:args' -> mapply (EApp fexpr arg) args' -- | Convert an EApp expression to an ECall expression appToCall :: Expr -> Expr appToCall expr = case expr of EApp f arg -> mcall f [arg] _ -> error "appToCall: requires an EApp expression" -- | Helper for appToCall, but may have other uses. -- Creates an ECall expression. mcall :: Expr -> [Expr] -> Expr mcall f args = case f of ESymbol fsym -> ECall fsym args EApp g arg -> mcall g (arg:args) _ -> error "mcall: function must be represented as a symbol or an EApp" -- | 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 ELambda _ body -> exprIsExtended body 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) -- 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) = repr f repr (VList vs) = reprList "[" ", " "]" vs valueFunction :: Value -> Function valueFunction value = case value of VFun function -> function _ -> error "valueFunction: non-function value" -- 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 -- | 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] -- | Type variable name type TypeVarName = String -- | Type constructor name type TypeConsName = String -- | A Type is either a type variable or a constructed type -- with a constructor and a list of type parameters data Type = TypeVar TypeVarName -- named type variable | TypeCons TypeConsName [Type] -- constructed type deriving (Eq) instance Show Type where show (TypeVar vname) = vname show (TypeCons ctor ts) = case ts of [] -> ctor (t:ts') -> case ctor of "List" -> "[" ++ show t ++ "]" "Function" -> "(" ++ show t ++ " -> " ++ show (head ts') ++ ")" _ -> "(" ++ ctor ++ (unwords (map show ts)) ++ ")" typeToArity :: Type -> Int typeToArity atype = case atype of TypeCons "Function" [_, next] -> 1 + typeToArity next _ -> 0 -- Primitive types primType :: TypeConsName -> Type primType tname = TypeCons tname [] typeBool, typeChar, typeNum, typeString :: Type typeBool = primType "Bool" typeChar = primType "Char" typeNum = primType "Num" typeString = primType "String" -- Built-in compound types typeList :: Type -> Type typeList t = TypeCons "List" [t] -- | The type of a function, from its argument types and result type, -- where (a -> b) is represented as TypeCons "Function" [a, b]. -- Note that for n-ary functions, n > 2 implies nested function types: -- (a -> b -> c) is represented as -- TypeCons "Function" [a, TypeCons "Function" [b, c]], etc. typeFunction :: [Type] -> Type -> Type typeFunction atypes rtype = let func a b = TypeCons "Function" [a, b] in case atypes of [] -> error "typeFunction: empty argument type list" atype:[] -> func atype rtype atype:atypes' -> func atype (typeFunction atypes' rtype) -- | A collection of functions, typically to be saved or exported -- or read from a file newtype Functions = Functions [Function] deriving (Eq, Show) -- | A function may have a name and always has an implementation data Function = Function (Maybe String) -- function name [Type] -- argument types Type -- result type FunctionImpl -- implementation deriving (Show) data ArgSpec = ArgSpec {argName :: String, -- argument name argInlets :: Int -- number of inputs } deriving (Eq, Show) -- | Try to find the number of inlets for an argument -- from a list of ArgSpec aspecsLookup :: String -> [ArgSpec] -> Maybe Int aspecsLookup name specs = case specs of [] -> Nothing s:ss -> if argName s == name then Just (argInlets s) else aspecsLookup name ss functionArgSpecs :: Function -> [ArgSpec] functionArgSpecs f@(Function _ argTypes _ _) = [ArgSpec {argName = n, argInlets = typeToArity t} | (n, t) <- zip (functionArgNames f) argTypes] 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 f = let name = functionName f in case functionImplementation f of Primitive _ -> "" Compound _args _body -> "" newUndefinedFunction :: String -> [String] -> Function newUndefinedFunction name argnames = let (atypes, rtype) = undefinedTypes argnames impl = Compound argnames EUndefined in Function (Just name) atypes rtype impl undefinedTypes :: [String] -> ([Type], Type) undefinedTypes argnames = let atypes = [TypeVar ('_' : name) | name <- argnames] rtype = TypeVar "_result" in (atypes, rtype) functionName :: Function -> String functionName (Function mname _ _ _) = case mname of Just name -> name Nothing -> "(unnamed)" functionNArgs :: Function -> Int functionNArgs = length . functionArgTypes functionArgTypes :: Function -> [Type] functionArgTypes (Function _ argtypes _ _) = argtypes functionResultType :: Function -> Type functionResultType (Function _ _ rtype _) = rtype -- | Type type of a function, a tuple of (arg types, result type) functionArgResultTypes :: Function -> ([Type], Type) -- (args., result type) functionArgResultTypes f = (functionArgTypes f, functionResultType f) -- | The type of a function, -- where (a -> b) is represented as TypeCons "Function" [a, b] functionType :: Function -> Type functionType (Function _ argTs resultT _) = typeFunction argTs resultT 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], [Type], Type, 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 ELambda _ _ -> error "evalWithLimit: not implemented for lambda expr" 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" [typeList (TypeVar "a")] typeBool primNull, prim "head" [typeList (TypeVar "b")] (TypeVar "b") primHead, prim "tail" [typeList (TypeVar "c")] (typeList (TypeVar "c")) primTail, prim ":" [TypeVar "d", typeList (TypeVar "d")] (typeList (TypeVar "d")) primCons ] type PFun = [Value] -> EvalResult -- Primitive functions of arbitrary type prim :: String -> [Type] -> Type -> 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" _ -> err "wrong type or number of arguments" in prim name [typeNum, typeNum] typeNum 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 "/" [typeNum, typeNum] typeNum 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 [typeNum, typeNum] typeNum 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 [typeNum] typeNum 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 [typeNum, typeNum] typeBool 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 [typeNum] typeBool impl baseEnv :: Env baseEnv = makeEnv (map functionName primitiveFunctions) (map VFun primitiveFunctions) -- | Given an expression, return the list of names of variables -- occurring in 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] ELambda x body -> nub (x : exprSymbols body) 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]