angle-0.8.1.0: src/Angle/Exec/Exec.hs
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-|
Module : Angle.Exec.Exec
Description : Controls execution of an Angle program.
Copyright : Copyright (C) 2015 Ben Moon
License : GNU GPL, version 3
Maintainer : GuiltyDolphin@gmail.com
Stability : alpha
All programs in Angle can be expressed as a single statement
(usually a multi-statement) and as such all programs are executed
via this one statement.
-}
module Angle.Exec.Exec
( execStmt
) where
import Control.Applicative
import Control.Monad
import Control.Monad.IO.Class (liftIO)
import Data.Maybe (isNothing, fromMaybe)
import System.Directory (findFile, canonicalizePath)
import System.FilePath (takeDirectory)
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
bindArgs :: [Expr] -> ArgSig -> ExecIO ()
bindArgs args (ArgSig
{ stdArgs=params
, catchAllArg=catchParam})
= do
let toCheck = zip args params
la = length args
lp = length params
when ((la > lp && isNothing catchParam) || la < lp)
(throwExecError $ wrongNumberOfArgumentsErr lp la)
catchBind <- case catchParam of
Nothing -> return []
Just cp -> do
let toCatch = drop lp args
res <- mapM execExpr toCatch
return [(catchArgName cp, LitList res)]
vals <- mapM (uncurry checkArg) toCheck
case catchParam of
Nothing -> return ()
Just catcher ->
case catchArgConstr catcher of
Nothing -> return ()
Just (ConstrRef cstr constrArgs) ->
case catchBind of
[] -> return ()
[(_, r)] -> checkSatConstr r (Just cstr) (fromMaybe [] constrArgs)
_ -> undefined
let toBindFuns = map fst $ filter (isAnnFun . snd) vals
toBindLits = map fst $ filter (isAnnLit . snd) vals
toBindAny = map fst (filter (isAnnAny . snd) vals) ++ catchBind
isAnnFun AnnFun = True
isAnnFun _ = False
isAnnLit AnnLit = True
isAnnLit _ = False
isAnnAny AnnAny = True
isAnnAny _ = False
newScope
forM_ toBindFuns (\(x, LitLambda l) -> assignVarFunLocal x l)
forM_ toBindLits (uncurry assignVarLitLocal)
forM_ toBindAny (uncurry assignVarLitLocal)
return ()
-- | Make sure the argument satisfies any
-- class or type restrictions placed upon it.
checkArg :: Expr -> ArgElt -> ExecIO ((LangIdent, LangLit), AnnType)
checkArg ex (ArgElt {argEltConstr=constr, argEltType=typ
, argEltName=name}) = do
v <- execExpr ex
let cstr = fmap getConstrRef constr
checkSatConstr v cstr (fromMaybe [] $ maybe Nothing constrRefArgs constr)
checkSatType v typ
return ((name, v), typ)
checkSatConstr :: LangLit -> Maybe LangIdent -> [Expr] -> ExecIO ()
checkSatConstr _ Nothing _ = return ()
checkSatConstr v (Just clsName) constrArgs = do
res <- satConstr v clsName constrArgs
unless res (throwExecError $ typeExpectConstrErr v clsName)
execConstr :: LangLit -> LangIdent -> [Expr] -> ExecIO LangLit
execConstr val clsName args = do
cls <- lookupVarFunLocal clsName
res <- callLambda cls True $ ExprLit val : args
case res of
x@(LitBool _) -> return x
y -> throwExecError
$ typeConstrWrongReturnErr clsName (typeOf y)
-- | Sets the 'as_constr' variable to true if the function is
-- being called as a class, and cleans up afterwords.
withClass :: ExecIO a -> ExecIO a
withClass s = do
assignVarBuiltinLit (LangIdent "as_constr") (LitBool True)
res <- s `catchAE` (\e -> setClassFalse >> throwAE e)
setClassFalse
return res
where
setClassFalse = assignVarBuiltinLit (LangIdent "as_constr") (LitBool False)
-- | True if the literal is of the specified annotation type.
satType :: LangLit -> AnnType -> Bool
satType _ AnnAny = True
satType (LitLambda _) AnnFun = True
satType (LitLambda _) AnnLit = False
satType _ AnnLit = True
satType _ _ = False
-- | Fails if @satType@ returns false with the same arguments.
checkSatType :: CanErrorWithPos m => LangLit -> AnnType -> m ()
checkSatType val typ = do
let res = satType val typ
unless res $ throwExecError $ typeAnnWrongErr typ $ typeAnnOf val
-- | True if the given constraint returns true when
-- passed the literal.
satConstr :: LangLit -> LangIdent -> [Expr] -> ExecIO Bool
satConstr val clsName constrArgs = do
(LitBool res) <- execConstr val clsName constrArgs
return res
-- | Expand any ExprParamExpand expressions in an argument list.
expandParams :: [Expr] -> ExecIO [Expr]
expandParams [] = return []
expandParams (x:xs)
= case x of
ExprParamExpand n -> do
val <- lookupVarLitLocal n
case val of
LitList vs -> liftM (map ExprLit vs ++) $ expandParams xs
_ -> liftM (x:) $ expandParams xs
_ -> liftM (x:) $ expandParams xs
execExpr :: Expr -> ExecIO LangLit
execExpr (ExprLit x@(LitRange{})) = checkLitRange x >> returnVal x
execExpr (ExprLit (LitClosure lam)) = do
currScope <- getScope
returnVal . LitLambda $ lam { lambdaScope = Just currScope }
execExpr (ExprLit x) = returnVal x
execExpr (ExprIdent x) = lookupVarLitLocal x
execExpr (ExprFunIdent x) = liftM LitLambda $ lookupVarFunLocal x
execExpr (ExprOp x) = execOp x
execExpr (ExprFunCall name asClass args) = execFunCall name asClass args
execExpr (ExprList xs) = liftM LitList $ mapM execExpr xs
execExpr (ExprLambdaCall f xs) = callPureLambda f False xs
execExpr x@(ExprRange{}) = do
r <- execExprRange x
checkLitRange r
returnVal r
execExpr _ = undefined
callPureLambda :: Lambda -> Bool -> [Expr] -> ExecIO LangLit
callPureLambda lam asClass exprs = do
pushEnvCall (LangIdent "LAMBDA")
res <- callLambda lam asClass exprs
popEnvCall
return res
execExprRange :: Expr -> ExecIO LangLit
execExprRange (ExprRange x Nothing Nothing)
= liftM3 LitRange (execExpr x) (return Nothing) (return Nothing)
execExprRange (ExprRange x (Just y) Nothing)
= liftM3 LitRange (execExpr x) (liftM Just $ execExpr y) (return Nothing)
execExprRange (ExprRange x Nothing (Just z))
= liftM3 LitRange (execExpr x) (return Nothing) (liftM Just $ execExpr z)
execExprRange (ExprRange x (Just y) (Just z))
= liftM3 LitRange (execExpr x) (liftM Just $ execExpr y) (liftM Just $ execExpr z)
execExprRange _ = error "excExprRange: passed non-range expression"
execFunCall :: LangIdent -> Bool -> [Expr] -> ExecIO LangLit
execFunCall = callFun
execOp :: LangOp -> ExecIO LangLit
execOp (SpecOp op expr) = execSpecOp op expr
execOp (MultiOp op exprs) = execMultiOp op exprs
execSpecOp :: Op -> Expr -> ExecIO LangLit
execSpecOp OpNeg x = execExpr x >>= negLit
execSpecOp OpNot x = execExpr x >>= notLit
execSpecOp x _ = throwImplementationErr $ "execSpecOp - not a SpecOp: " ++ show x
execMultiOp :: Op -> [Expr] -> ExecIO LangLit
execMultiOp OpAdd xs = withMultiOp xs addLit
execMultiOp OpAnd xs = withMultiOp xs andLit
execMultiOp OpConcat xs = withMultiOp xs concatLit
execMultiOp OpDiv xs = withMultiOp xs divLit
execMultiOp OpEq xs = withMultiOp xs eqLit
execMultiOp OpExp xs = withMultiOp xs expLit
execMultiOp OpGreater xs = withMultiOp xs greaterLit
execMultiOp OpGreaterEq xs = withMultiOp xs greaterEqLit
execMultiOp OpLess xs = withMultiOp xs lessLit
execMultiOp OpLessEq xs = withMultiOp xs lessEqLit
execMultiOp OpMult xs = withMultiOp xs multLit
execMultiOp OpOr xs = withMultiOp xs orLit
execMultiOp OpSub xs = withMultiOp xs subLit
execMultiOp (UserOp _) _ = throwImplementationErr "execMultiOp: implement user operators"
execMultiOp x _ = throwImplementationErr $ "execMultiOp - not a multiOp: " ++ show x
withMultiOp :: [Expr] -> ([LangLit] -> ExecIO LangLit) -> ExecIO LangLit
withMultiOp xs f = mapM execExpr xs >>= f
callFun :: LangIdent -> Bool -> [Expr] -> ExecIO LangLit
callFun x asClass args
| isBuiltin x = callBuiltin x args
| otherwise = do
l <- lookupVarFunLocal x
pushEnvCall x
res <- callLambda l asClass args
popEnvCall
return res
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
= do
fullArgs <- expandParams args
bindArgs fullArgs params
let f = if asClass then withClass else id
res <- f (execStmt body `catchReturn` return)
upScope
return res
-- | Executes a single statement.
execStmt :: Stmt -> ExecIO LangLit
execStmt s@(SingleStmt x pos) = updateStmt s >> updatePos pos >> execSingStmt x
execStmt (MultiStmt (x@(SingleStmt (StmtReturn _) _):_)) = execStmt x
execStmt (MultiStmt []) = return LitNull
execStmt (MultiStmt [x]) = execStmt x
execStmt (MultiStmt (x:xs)) = execStmt x >> execStmt (MultiStmt xs)
assignment
:: (LangIdent -> Lambda -> ExecIO ())
-> (LangIdent -> LangLit -> ExecIO ())
-> LangIdent -> Expr -> ExecIO LangLit
assignment aFun aLit name e = execExpr e
>>= (\x -> case x of
x'@(LitLambda l) -> aFun name l >> returnVal x'
x' -> aLit name x' >> returnVal x')
execSingStmt :: SingStmt -> ExecIO LangLit
execSingStmt (StmtAssign name e) = assignment assignVarFunLocal assignVarLitLocal name e
execSingStmt (StmtAssignNonLocal name e) = assignment assignVarFunOuter assignVarLitOuter name e
execSingStmt (StmtAssignGlobal name e) = assignment assignVarFunGlobal assignVarLitGlobal name e
execSingStmt (StmtStruct x) = execLangStruct x
execSingStmt (StmtExpr x) = do
res <- execExpr x
setEnvSynRep (showSyn res)
returnVal res
execSingStmt (StmtComment _) = return LitNull
execSingStmt (StmtReturn x) = execReturn x
execSingStmt (StmtBreak x False) = execBreak x
execSingStmt (StmtBreak _ True) = throwContinue
execSingStmt (StmtRaise e) = raiseException e
execReturn :: Expr -> ExecIO LangLit
execReturn x = do
isGlob <- liftM (isOutermostScope . currentScope) getEnv
if isGlob
then throwExecError returnFromGlobalErr
else do
res <- execExpr x
case res of
LitLambda lam -> do
sc <- getScope
let newS = case lambdaScope lam of
Nothing -> sc
Just s -> modifyGlobalScope s (\sc' -> sc' { outerScope = Just sc })
throwReturn $ LitLambda lam { lambdaScope = Just newS }
y -> throwReturn y
execBreak :: Maybe Expr -> ExecIO LangLit
execBreak x = case x of
Nothing -> throwBreak Nothing
Just v -> execExpr v
>>= returnVal >>= (throwBreak . Just)
raiseException :: LangIdent -> ExecIO LangLit
raiseException e = do
env <- getEnv
let currE = currentException env
return (LitKeyword e)
case currE of
Nothing -> throwExecError $ userErr e
Just err -> if similarErr err
then throwAE err
else throwExecError $ userErr e
where
similarErr err = errToKeyword err == e
|| genErrKeyword err == e
|| LangIdent "error" == e
execLangStruct :: LangStruct -> ExecIO LangLit
execLangStruct (StructFor name e s)
= execStructFor name e s `catchBreak` maybe (getEnvValue >>= returnVal) returnVal
execLangStruct (StructWhile e s)
= execStructWhile e s `catchBreak` maybe (getEnvValue >>= returnVal) returnVal
execLangStruct (StructIf if' thn els)
= execStructIf if' thn els
execLangStruct (StructDefun name cs)
= assignVarFunLocal name cs *> return LitNull
execLangStruct (StructTryCatch b cs)
= execStructTryCatch b cs
execStructIf :: Expr -> Stmt -> Maybe Stmt -> ExecIO LangLit
execStructIf if' thn els = do
p <- execExpr if'
case p of
(LitBool True) -> execStmt thn
(LitBool False) -> case els of
Nothing -> return LitNull
Just s -> execStmt s
x -> throwExecError $ typeUnexpectedErr (typeOf x) LTBool
execStructFor :: LangIdent -> Expr -> Stmt -> ExecIO LangLit
execStructFor name e s = do
iterable <- execExpr e >>= fromIter
outScope <- liftM currentScope getEnv
res <- forM iterable (\v -> do
assignVarLitLocal name v
execStmt s `catchContinue` getEnvValue)
newS <- liftM currentScope getEnv
let newS' = deleteLitFromScope name newS
modifyScope (const $ mergeScope newS' outScope)
returnVal (LitList res)
execStructWhile :: Expr -> Stmt -> ExecIO LangLit
execStructWhile ex s = do
pos <- liftM envSourceRef getEnv
execStructFor (LangIdent "__WHILEVAL__")
(ExprLit
(LitRange (LitInt 1) Nothing Nothing))
(SingleStmt
(StmtStruct
(StructIf ex s (Just (SingleStmt (StmtBreak Nothing False) pos)))) pos)
execStructTryCatch :: Stmt -> [([LangIdent], Stmt)] -> ExecIO LangLit
execStructTryCatch b catchers = do
currEnv <- getEnv
execStmt b `catchAE` genHandle currEnv
where
genHandle env e = do
cscope <- getScope -- Preserve the scope!
updateEnv env { currentException = Just e, currentScope = cscope }
res <- checkCatch e catchers `catchBreak` breakTry
newEnv <- getEnv
updateEnv newEnv { currentException = Nothing }
return res
checkCatch e [] = throwError e
checkCatch e ((toCatch, ex):es) = if catches
then execStmt ex
else checkCatch e es
where catches = errToKeyword e `elem` toCatch || genErrKeyword e `elem` toCatch
|| LangIdent "error" `elem` toCatch
breakTry (Just (LitKeyword (LangIdent "try")))
= execStructTryCatch b catchers
breakTry e = throwBreak e
builtinArgs :: [Expr] -> ExecIO [LangLit]
builtinArgs xs = expandParams xs >>= mapM execExpr
callBuiltin :: LangIdent -> [Expr] -> ExecIO LangLit
callBuiltin (LangIdent "print") xs = builtinArgs xs >>= builtinPrint
callBuiltin (LangIdent "str") xs = builtinArgs xs >>= builtinStr
callBuiltin (LangIdent "index") xs = builtinArgs xs >>= builtinIndex
callBuiltin (LangIdent "length") xs = builtinArgs xs >>= builtinLength
callBuiltin (LangIdent "input") xs = builtinArgs xs >>= builtinInput
callBuiltin (LangIdent "eval") xs = builtinArgs xs >>= builtinEval
callBuiltin (LangIdent "asType") xs = builtinArgs xs >>= builtinAsType
callBuiltin (LangIdent "getArgs") xs = builtinArgs xs >>= builtinGetArgs
callBuiltin (LangIdent "isNull") xs = builtinArgs xs >>= builtinIsNull
callBuiltin (LangIdent "open") xs = builtinArgs xs >>= builtinOpen
callBuiltin (LangIdent "read") xs = builtinArgs xs >>= builtinRead
callBuiltin (LangIdent "write") xs = builtinArgs xs >>= builtinWrite
callBuiltin (LangIdent "close") xs = builtinArgs xs >>= builtinClose
callBuiltin (LangIdent "shell") xs = builtinArgs xs >>= builtinShell
callBuiltin (LangIdent "include") xs = builtinArgs xs >>= builtinInclude
callBuiltin (LangIdent "nonlocal") xs = builtinArgs xs >>= builtinNonLocal
callBuiltin (LangIdent "global") xs = builtinArgs xs >>= builtinGlobal
callBuiltin (LangIdent "local") xs = builtinArgs xs >>= builtinLocal
callBuiltin (LangIdent "round") xs = builtinArgs xs >>= builtinRound
callBuiltin (LangIdent x) _ = throwImplementationErr $ "callBuiltin - not a builtin function: " ++ x
builtinEval :: [LangLit] -> ExecIO LangLit
builtinEval xs = do
let r = evalParse st program
case r of
Left e -> throwExecError . syntaxErr $ e
Right res -> execStmt res
where st = argsToString xs
-- | Builtin @include@ function.
--
-- @include(file_names)@ will attempt to open, read and evaluate
-- each file in @file_names@ inplace. This is effectively the
-- same as having written the code contained within the files
-- at the point of call.
--
-- If any of @file_names@ are handles, then they will be read
-- fully and evaluated.
--
-- Filepaths are assumed to be relative, unless an absolute
-- path is provided.
builtinInclude :: [LangLit] -> ExecIO LangLit
builtinInclude xs = mapM_ includeFile xs >> return LitNull
where
includeFile h@(LitHandle _) = builtinRead [h] >>= builtinEval . (:[])
includeFile (LitStr f) = includeAngleFile f
includeFile x = throwExecError $ typeNotValidErr x
includeAngleFile f = do
fn <- getAngleFile f
case fn of
Nothing -> throwExecError . noSuchFileErr $ f
Just pth -> do
txt <- builtinRead [LitStr pth]
env <- getEnv
currFile <- liftM currentFile getEnv
currMain <- liftM runAsMain getEnv
currScope <- liftM currentScope getEnv
updateEnv env { currentFile = Just pth, runAsMain = False }
builtinEval [txt]
`catchAE` (\e -> do
newEnv <- getEnv
updateEnv env
throwAE e)
-- newEnv <- getEnv
recentScope <- liftM currentScope getEnv
updateEnv env { currentScope = mergeScope recentScope currScope }
-- updateEnv newEnv { currentFile = currFile, runAsMain = currMain }
return LitNull
-- builtinRead [f] >>= builtinEval . (:[])
-- | Builtin @nonlocal@ function.
--
-- @nonlocal(var)@ attempts to lookup the value of @var@ in any
-- of the parent scopes of the current scope. Fails with a
-- @nameError@ if @var@ is not defined.
--
-- @nonlocal(:fun, var)@ attempts to lookup the lambda value
-- of @var@ in a parent scope.
builtinNonLocal :: [LangLit] -> ExecIO LangLit
builtinNonLocal [LitKeyword (LangIdent "fun"), LitKeyword n]
= liftM LitLambda $ lookupVarFunOuter n
builtinNonLocal [LitKeyword n]
= lookupVarLitOuter n
builtinNonLocal _ = throwExecError . callBuiltinErr $ "nonlocal: invalid call signature"
-- | Builtin @global@ function.
--
-- @global(var)@ attempts to lookup the value of @var@ in the global
-- scope. Fails with a @nameError@ if @var@ is not defined.
--
-- @global(:fun, var)@ attempts to lookup the lambda value
-- of @var@ in the global scope.
builtinGlobal :: [LangLit] -> ExecIO LangLit
builtinGlobal [LitKeyword (LangIdent "fun"), LitKeyword n]
= liftM LitLambda $ lookupVarFunGlobal n
builtinGlobal [LitKeyword n]
= lookupVarLitGlobal n
builtinGlobal _ = throwExecError . callBuiltinErr $ "global: invalid call signature"
-- | Built-in @local@ function.
--
-- @local(var)@ attempts to resolve the variable represented by the keyword
-- @var@.
--
-- @local(:fun, var)@ attempts to resove the lambda value of @var@@
builtinLocal :: [LangLit] -> ExecIO LangLit
builtinLocal [LitKeyword (LangIdent "fun"), LitKeyword n]
= liftM LitLambda $ lookupVarFunLocal n
builtinLocal [LitKeyword n]
= lookupVarLitLocal n
builtinLocal _ = throwExecError . callBuiltinErr $ "local: invalid call signature"
getAngleFile :: FilePath -> ExecIO (Maybe FilePath)
getAngleFile fp = do
searchPath <- liftM angleLibPath getEnv
currFile <- liftM currentFile getEnv
maybeFound <- liftIO $ findFile (searchPath ++ [maybe "" takeDirectory currFile, ""]) fp
case maybeFound of
Nothing -> return Nothing
Just p -> liftM Just $ liftIO $ canonicalizePath p
validRangeLit :: LangLit -> Bool
validRangeLit (LitRange x Nothing Nothing)
= enumType x
validRangeLit (LitRange x (Just y) Nothing)
= enumType x && typeOf x == typeOf y
validRangeLit (LitRange x (Just y) (Just z))
= enumType x && all ((== typeOf x) . typeOf) [y, z]
validRangeLit (LitRange x Nothing (Just z))
= enumType x && typeOf x == typeOf z
validRangeLit _ = error "validRangeLit: Attempting to verify a non-range"
checkLitRange :: LangLit -> ExecIO ()
checkLitRange r@(LitRange x y z)
= unless (validRangeLit r)
(if enumType x
then throwExecError $ badRangeErr (typeOf x) (fmap typeOf y) (fmap typeOf z)
else throwExecError $ typeExpectConstrErr x (LangIdent "enum"))
checkLitRange _ = throwImplementationErr "checkLitRange: Attempting to check a non-range"