sifflet-2.3.0: lib/Language/Sifflet/Expr.hs
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 Functor EvalRes where
fmap f (EvalOk v) = EvalOk (f v)
fmap _ (EvalError s) = EvalError s
fmap _ EvalUntried = EvalUntried
instance Applicative EvalRes where
pure = EvalOk
EvalOk f <*> EvalOk v = EvalOk (f v)
EvalOk _ <*> EvalError s = EvalError s
EvalOk _ <*> EvalUntried = EvalUntried
-- It seems that the other cases should be consistent
-- with evalWithLimit for ECall below,
-- whatever that means ...
EvalError s <*> _ = EvalError s
EvalUntried <*> _ = EvalUntried
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 _) = "<primitive function>"
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 _ -> "<primfunc " ++ name ++ ">"
Compound _args _body -> "<func " ++ name ++ ">"
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'
-- Should there be another case here, where we get
-- EvalOk(something not boolean) and this should be
-- an error?
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]