spade-0.1.0.0: src/Interpreter/Interpreter.hs
module Interpreter.Interpreter where
import Prelude hiding (map)
import Control.Concurrent.MVar
import Control.Exception (throw)
import Control.Monad
import Control.Monad.Catch (catch)
import Control.Monad.Loops (iterateWhile)
import Data.Coerce
import qualified Data.List.NonEmpty as NE
import Data.Map as M hiding (map)
import Data.Text as T hiding (index, map)
import qualified Data.Vector as V
import Common
import Compiler.AST.FunctionStatement
import Compiler.AST.Program
import Compiler.Lexer
import Control.Monad.State.Strict
import Interpreter.Common
lookupScope :: ScopeKey -> InterpretM Value
lookupScope key = ((lookupInTopScope key . isLocal) <$> get) >>= \case
Just v -> pure v
Nothing -> ((M.lookup key . isGlobalScope) <$> get) >>= \case
Just v -> pure v
Nothing ->
throwErr $ SymbolNotFound $ (pack $ show key)
lookupInTopScope :: ScopeKey -> [Scope] -> Maybe Value
lookupInTopScope _ [] = Nothing
lookupInTopScope key (h: _) = M.lookup key h
evaluateExpression :: ExpressionWithLoc -> InterpretM Value
evaluateExpression exp'@(ExpressionWithLoc _ loc) =
catch
(catch (executeDebugStepable exp') rteHandler) peHandler
where
rteHandler (r :: RuntimeError) = pure $ ErrorValue $ (hReadable r) <> " at " <> hReadable loc
peHandler (r :: ProgramError) = throwErr @(InterpretM Value) $ RuntimeErrorWithLoc (Right r) loc
evaluateExpression_ :: Expression -> InterpretM Value
evaluateExpression_ (EParan le) = evaluateExpression le
evaluateExpression_ (ENegated le) = evaluateExpression le >>= \case
NumberValue n -> pure $ NumberValue $ negateValue n
x -> throwErr $ UnexpectedType ("Number", x)
evaluateExpression_ (ELiteral le) = evaluateLiteralExpression le
evaluateExpression_ (EVar idf) = lookupScope (SkIdentifier idf) >>= \case
BuiltIn (BuiltinVal v) -> pure v
v -> pure v
evaluateExpression_ (ESubscripted subscript) = evaluateSubscriptedExpr subscript
evaluateExpression_ (EConditional boolEx ex1 ex2) = evaluateExpression boolEx >>= \case
BoolValue True -> evaluateExpression ex1
BoolValue False -> evaluateExpression ex2
x -> throwErr $ UnexpectedType ("Bool", x)
evaluateExpression_ (EOperator op e1 e2) = do
evaluateFn (FnOp op) [e1, e2] False >>= \case
Just v -> pure v
Nothing -> throwErr MissingProcedureReturn
evaluateExpression_ (ECall iden exprs isTail) = evaluateFn (FnName iden) exprs isTail >>= \case
Just v -> pure v
Nothing -> throwErr MissingProcedureReturn
evaluateExpression_ (EUnnamedFn args expr) = pure $ UnnamedFnValue $ UnNamedFn args expr
popScope :: InterpretM ()
popScope = do
(isLocal <$> get) >>= \case
(_:rst) -> do
modify (\is -> is { isLocal = rst })
_ -> throwErr EmptyScopeStack
data FnId
= FnOp Operator
| FnName Identifier
evaluateCallback :: Callback -> [Value] -> InterpretM (Maybe Value)
evaluateCallback (CallbackUnNamed un) args = Just <$> evaluateUnnamedFn un args
evaluateCallback (CallbackNamed idf) args =
evaluateProcedure (SkIdentifier idf) args
insertEmptyScope :: InterpretM ()
insertEmptyScope = modify $ mapLocal (\s -> mempty : s)
evaluateUnnamedFn :: UnNamedFn -> [Value] -> InterpretM Value
evaluateUnnamedFn (UnNamedFn Nothing expr) _ = evaluateExpression expr
evaluateUnnamedFn (UnNamedFn (Just (NE.toList -> argNames)) expr) argsVals = do
insertEmptyScope
zipWithM_ (\a1 a2 -> insertBinding a1 a2) (SkIdentifier <$> argNames) argsVals -- @TODO Check argument counts
r <- evaluateExpression expr
popScope
pure r
evaluateProcedure :: ScopeKey -> [Value] -> InterpretM (Maybe Value)
evaluateProcedure sk args = do
lookupScope sk >>= \case
UnnamedFnValue un -> Just <$> evaluateUnnamedFn un args
(ProcedureValue (FunctionDef _ argNames (NE.toList -> stms))) -> do
insertEmptyScope
zipWithM_ (\a1 a2 -> insertBinding a1 a2) (SkIdentifier <$> argNames) args -- @TODO Check argument counts
executeStatements stms >>= \case
ProcReturn False v -> do
popScope
pure $ Just v
ProcReturn True v -> do
-- Don't pop stack if the return was a tail call return
-- because the stack was popped before entering the
-- call.
pure $ Just v
ProcBreak -> do
popScope
pure Nothing
ProcContinue -> do
popScope
pure Nothing
(BuiltIn (BuiltinCall cb)) -> cb args
(BuiltIn (BuiltinCallWithDoc (SomeBuiltin cb))) -> cb (toArgs args)
a -> throwErr $ UnexpectedType ("Procedure", a)
evaluateFn :: FnId -> [ExpressionWithLoc] -> Bool -> InterpretM (Maybe Value)
evaluateFn fnId argsExps isTail = do
let sk = case fnId of
FnOp op -> SkOperator op
FnName idf -> SkIdentifier idf
args <- mapM (\x -> evaluateExpression x) argsExps
when isTail popScope
evaluateProcedure sk args
evaluateSubscriptedExpr :: SubscriptedExpression -> InterpretM Value
evaluateSubscriptedExpr (EArraySubscript expr indexExpr) = evaluateExpression expr >>= \case
ArrayValue v -> evaluateExpression indexExpr >>= \case
NumberValue (NumberInt i) -> do
let index :: Int = fromIntegral i
if index <= V.length v && index >= 0 then (pure $ v V.! (index - 1)) else (throwErr $ ListIndexOutOfBounds index)
a -> throwErr $ UnexpectedType ("Integer index", a)
ObjectValue mp -> evaluateExpression indexExpr >>= \case
StringValue key -> case M.lookup key mp of
Just v -> pure v
Nothing -> throwErr $ KeyNotFound (pack $ show key)
a -> throwErr $ UnexpectedType ("Property index", a)
a -> throwErr $ UnexpectedType ("Array/Map", a)
evaluateSubscriptedExpr (EPropertySubscript expr (unIdentifer -> key)) = evaluateExpression expr >>= \case
ObjectValue mp -> case M.lookup key mp of
Just v -> pure v
Nothing -> throwErr $ KeyNotFound (pack $ show key)
a -> throwErr $ UnexpectedType ("Map", a)
evaluateVar :: Subscript -> InterpretM Value
evaluateVar (NoSubscript idf) = lookupScope (SkIdentifier idf) >>= \case
BuiltIn (BuiltinVal v) -> pure $ v
v -> pure v
evaluateVar (SubscriptExpr sub expr) =
-- Arrays are indexed from 1, not 0.
evaluateExpression expr >>= \case
NumberValue (NumberInt int) ->
evaluateVar sub >>= \case
ArrayValue v -> do
let index :: Int = fromIntegral int
if index <= V.length v && index >= 0 then (pure $ v V.! (index - 1)) else (throwErr $ ListIndexOutOfBounds index)
a -> throwErr $ UnexpectedType ("Array/Object", a)
StringValue key -> lookupInMapVar sub key
a -> throwErr $ UnexpectedType ("String/Integer container key", a)
evaluateVar (PropertySubscript sub idf) = lookupInMapVar sub (unIdentifer idf)
lookupInMapVar :: Subscript -> Text -> InterpretM Value
lookupInMapVar sub key = evaluateVar sub >>= \case
ObjectValue mp -> case M.lookup key mp of
Just v -> pure v
Nothing -> throwErr $ KeyNotFound (pack $ show key)
a -> throwErr $ UnexpectedType ("Expecting Object Looking for key: " <> (T.pack $ show sub) <> ":" <> key, a)
evaluateLiteralExpression :: LiteralExpression -> InterpretM Value
evaluateLiteralExpression (LAtomic (LitString t)) = pure $ StringValue t
evaluateLiteralExpression (LAtomic (LitBytes t)) = pure $ BytesValue t
evaluateLiteralExpression (LAtomic (LitNumber n)) = pure $ NumberValue $ NumberInt n
evaluateLiteralExpression (LAtomic (LitFloat f)) = pure $ NumberValue $ NumberFractional (realToFrac f)
evaluateLiteralExpression (LAtomic (LitBool b)) = pure $ BoolValue b
evaluateLiteralExpression (LArray l) = do
v <- mapM (\x -> evaluateExpression x) l
pure $ ArrayValue (V.fromList v)
evaluateLiteralExpression (LObject l) = do
v <- mapM (\x -> evaluateExpression x) l
pure $ ObjectValue v
voidStm :: () -> InterpretM (Maybe Value)
voidStm _ = pure Nothing
executeStatements :: [FunctionStatementWithLoc] -> InterpretM ProcResult
executeStatements x = foldM (\a1 a2 -> fn a1 a2) ProcContinue x
where
fn :: ProcResult -> FunctionStatementWithLoc -> InterpretM ProcResult
fn (ProcReturn tc x') _ = pure $ ProcReturn tc x'
fn ProcBreak _ = pure ProcBreak
fn ProcContinue fs = executeStatement fs
modifyBinding :: Subscript -> Value -> InterpretM ()
modifyBinding (NoSubscript idf) val = insertBinding (SkIdentifier idf) val
modifyBinding (PropertySubscript sub (unIdentifer -> key)) val = do
evaluateVar sub >>= \case
ObjectValue v -> case M.lookup key v of
Just _ -> modifyBinding sub (ObjectValue $ M.insert key val v)
Nothing -> throwErr (KeyNotFound key)
a -> throwErr $ UnexpectedType ("Map", a)
modifyBinding (SubscriptExpr sub expr) val = do
evaluateVar sub >>= \case
ArrayValue v -> evaluateExpression expr >>= \case
NumberValue (NumberInt idx) -> do
let index :: Int = fromIntegral idx
if (index <= V.length v && index > 0)
then modifyBinding sub (ArrayValue $ V.update v (V.fromList [(index - 1, val)]))
else throwErr $ ListIndexOutOfBounds index
a -> throwErr $ UnexpectedType ("Integer Index", a)
ObjectValue v -> evaluateExpression expr >>= \case
StringValue key -> case M.lookup key v of
Just _ -> modifyBinding sub (ObjectValue $ M.insert key val v)
Nothing -> throwErr (KeyNotFound key)
a -> throwErr $ UnexpectedType ("String", a)
a -> throwErr $ UnexpectedType ("Map", a)
class ToSource a => DebugStepable a b | a -> b where
getLocation :: a -> Location
execute :: a -> InterpretM b
instance DebugStepable FunctionStatementWithLoc ProcResult where
getLocation (FunctionStatementWithLoc _ l) = l
execute (FunctionStatementWithLoc fs _) = executeStatement_ fs
instance DebugStepable ExpressionWithLoc Value where
getLocation (ExpressionWithLoc _ l) = l
execute (ExpressionWithLoc exp' _) = evaluateExpression_ exp'
executeStatement :: FunctionStatementWithLoc -> InterpretM ProcResult
executeStatement fs@(FunctionStatementWithLoc _ loc) =
catch
(catch (executeDebugStepable fs) rteHandler) peHandler
where
rteHandler (r :: RuntimeError) = case r of
CustomRTE msg -> throw (RuntimeErrorWithLoc (Left $ CustomRTE msg) loc)
_ -> throw (RuntimeErrorWithLoc (Left r) loc)
peHandler (r :: ProgramError) = throw (RuntimeErrorWithLoc (Right r) loc)
executeDebugStepable :: DebugStepable a b => a -> InterpretM b
executeDebugStepable dbs = do
isRunMode <$> get >>= \case
NormalMode -> do
execute dbs
DebugMode -> do
isStepMode <$> get >>= \case
Continue -> execute dbs
SingleStep -> do
isDebugOut <$> get >>= sendDebugOut
isDebugIn <$> get >>= (liftIO . takeMVar) >>= \case
Run -> do
modify (\is -> is { isRunMode = NormalMode })
execute dbs
StepIn -> execute dbs
AddWatch _ -> execute dbs
StepOver -> do
modify (\is -> is { isStepMode = Continue })
r <- execute dbs
modify (\is -> is { isStepMode = SingleStep })
pure r
where
sendDebugOut debugOut = do
currentScope <- isLocal <$> get >>= \case
[] -> isGlobalScope <$> get
(scope : _) -> pure scope
let
dd = DebugState currentScope (getLocation dbs) (Just $ toSource dbs)
liftIO $ putMVar debugOut $ DebugData dd
executeStatement_ :: FunctionStatement -> InterpretM ProcResult
executeStatement_ (FnComment _) = pure ProcContinue
executeStatement_ (Let sub exp') = do
sourceValue <- evaluateExpression exp'
modifyBinding sub sourceValue
pure ProcContinue
executeStatement_ (Call iden args) = do
_ <- evaluateFn (FnName iden) args False
pure ProcContinue
executeStatement_ (IfThen expr stms) = evaluateExpression expr >>= \case
BoolValue True -> executeStatements (NE.toList stms)
BoolValue _ -> pure ProcContinue
a -> throwErr $ UnexpectedType ("Bool", a)
executeStatement_ (If expr stms1 stms2) = evaluateExpression expr >>= \case
BoolValue b -> case b of
True -> executeStatements (NE.toList stms1)
False -> executeStatements (NE.toList stms2)
a -> throwErr $ UnexpectedType ("Bool", a)
executeStatement_ (MultiIf expr stms1 elseifs mstms2) = evaluateExpression expr >>= \case
BoolValue True -> executeStatements (NE.toList stms1)
BoolValue False -> foldM executeElseIf Nothing elseifs >>= \case
Just r -> pure r
Nothing -> case mstms2 of
Just stms2 -> executeStatements (NE.toList stms2)
Nothing -> pure ProcContinue
a -> throwErr $ UnexpectedType ("Bool", a)
where
executeElseIf a@(Just _) _ = pure a
executeElseIf Nothing (bexpr, stms) = evaluateExpression bexpr >>= \case
BoolValue True -> Just <$> executeStatements (NE.toList stms)
BoolValue False -> pure Nothing
a -> throwErr $ UnexpectedType ("Bool", a)
executeStatement_ (Return eloc@(ExpressionWithLoc { elExpression = ECall idf args _ })) =
-- TCO
evaluateExpression (eloc { elExpression = ECall idf args True }) >>= pure . ProcReturn True
executeStatement_ (Return expr) = evaluateExpression expr >>= pure . ProcReturn False
executeStatement_ Break = pure ProcBreak
executeStatement_ (Loop (NE.toList -> stms)) = iterateWhile (\case
ProcBreak -> False
ProcContinue -> True
ProcReturn _ _ -> False) (executeStatements stms)
executeStatement_ (While exprBool (NE.toList -> stms)) = do
r <- iterateWhile (\case
ProcBreak -> False
ProcContinue -> True
ProcReturn _ _ -> False)
(evaluateExpression exprBool >>= \case
BoolValue True -> executeStatements stms
BoolValue False -> pure ProcBreak
a -> throwErr $ UnexpectedType ("Bool", a))
case r of
ProcBreak -> pure ProcContinue
a -> pure a
executeStatement_ (For iden exprFrom exprTo (NE.toList -> stms)) = evaluateExpression exprFrom >>= \case
NumberValue (NumberInt start) -> evaluateExpression exprTo >>= \case
NumberValue (NumberInt end) -> do
let
fn :: ProcResult -> IntType -> InterpretM ProcResult
fn ProcContinue current = do
insertBinding (SkIdentifier iden) (NumberValue $ NumberInt $ current)
executeStatements stms
fn r _ = pure r
foldM (\a1 a2 -> fn a1 a2) ProcContinue [start .. end] >>= \case
ProcReturn tc v -> pure $ ProcReturn tc v
_ -> pure ProcContinue
a -> throwErr $ UnexpectedType ("Int", a)
a -> throwErr $ UnexpectedType ("Int", a)
executeStatement_ (ForEach iden expr (NE.toList -> stms)) = evaluateExpression expr >>= \case
ObjectValue map -> do
foldM (\a1 (k, v) -> fn a1 (ObjectValue $ M.fromList [("key", StringValue k), ("value", v)])) ProcContinue (M.assocs map) >>= \case
ProcReturn tc v -> pure $ ProcReturn tc v
_ -> pure ProcContinue
ArrayValue values -> do
V.foldM (\a1 a2 -> fn a1 a2) ProcContinue values >>= \case
ProcReturn tc v -> pure $ ProcReturn tc v
_ -> pure ProcContinue
a -> throwErr $ UnexpectedType ("Array/Object", a)
where
fn :: ProcResult -> Value -> InterpretM ProcResult
fn ProcContinue current = do
insertBinding (SkIdentifier iden) current
executeStatements stms
fn r _ = pure r
filter_ :: BuiltInFnWithDoc '[ '("list", V.Vector Value), '("callback", Callback)]
filter_ ((coerce -> v1) :> (coerce -> callback) :> _) =
(\x -> Just $ ArrayValue x) <$> V.filterM fn v1
where
fn v = evaluateCallback callback [v] >>= \case
Just (BoolValue x) -> pure x
_ -> throwErr $ CustomRTE "Callback returned a non-bool value"