module Interpreter.Interpreter where import Prelude hiding (map) import Control.Monad.IO.Unlift import Control.Monad.Catch (try) import Control.Concurrent import Control.Concurrent.STM as STM import Control.Exception (throw, IOException) 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.Text as T (index) import qualified Data.Vector as V import qualified Data.ByteString as BS import System.Posix.Directory as POSIX import System.FilePath import System.Directory import Common import Compiler.AST.FunctionStatement import Compiler.AST.Program import Compiler.Lexer import Interpreter.Common lookupScope :: ScopeKey -> InterpretM Value lookupScope key = ((lookupInTopScope key . isLocal) <$> getInterpretM) >>= \case Just v -> pure v Nothing -> ((lookupInTopScope key . isModuleScope) <$> getInterpretM) >>= \case Just v -> pure v Nothing -> do ((M.lookup key . isGlobalScope) <$> getInterpretM) >>= \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 -> pure Void evaluateExpression_ (ECall (ExpressionWithLoc (EVar iden) _) exprs isTail) = evaluateFn (FnName iden) exprs isTail >>= \case Just v -> pure v Nothing -> pure Void evaluateExpression_ (ECall ex exprs isTail) = evaluateFn (FnExpr ex) exprs isTail >>= \case Just v -> pure v Nothing -> pure Void evaluateExpression_ (EUnnamedFn args expr) = do isLocal <$> getInterpretM >>= \case [] -> pure $ UnnamedFnValue $ UnNamedFn args mempty expr (h: _) -> pure $ UnnamedFnValue $ UnNamedFn args h expr popScope :: InterpretM () popScope = do (isLocal <$> getInterpretM) >>= \case (_:rst) -> do modifyInterpretM (\is -> is { isLocal = rst }) _ -> throwErr EmptyScopeStack data FnId = FnOp Operator | FnName Identifier | FnExpr ExpressionWithLoc 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 = insertScope mempty insertScope :: Scope -> InterpretM () insertScope scope = modifyInterpretM $ mapLocal (\s -> scope : s) pushModuleScope :: Scope -> InterpretM () pushModuleScope scope = modifyInterpretM $ (\s -> s {isModuleScope = scope : (isModuleScope s)} ) popModuleScope :: InterpretM Scope popModuleScope = (isModuleScope <$> getInterpretM) >>= \case [] -> throwErr EmptyModuleScopeStack (scp : rst) -> do modifyInterpretM $ (\s -> s {isModuleScope = rst} ) pure scp evaluateUnnamedFn :: UnNamedFn -> [Value] -> InterpretM Value evaluateUnnamedFn (UnNamedFn [] scope expr) _ = do insertScope scope x <- evaluateExpression expr popScope pure x evaluateUnnamedFn (UnNamedFn argNames scope expr) argsVals = do insertScope scope zipWithM_ (\a1 a2 -> insertBinding False a1 a2) (SkIdentifier <$> argNames) argsVals -- @TODO Check argument counts r <- evaluateExpression expr popScope pure r evaluateProcedure_ :: Value -> [Value] -> InterpretM (Maybe Value) evaluateProcedure_ fnVal args = case fnVal of ModuleValue fp (ScopeRef scopeRef) (Just fnId) -> do scope <- liftIO $ readTVarIO scopeRef pushModuleScope scope lookupScope (SkIdentifier fnId) >>= \case ProcedureValue fndef -> do insertEmptyScope oldFp <- isCurrentModulePath <$> getInterpretM modifyInterpretM (\is -> is { isCurrentModulePath = Just fp }) r <- case fndef of FunctionDef False _ _ _ -> runProcedure fndef FunctionDef True _ _ _ -> withStateClone $ runProcedure fndef finalModuleScope <- popModuleScope modifyInterpretM (\is -> is { isCurrentModulePath = oldFp }) liftIO $ atomically $ writeTVar scopeRef finalModuleScope pure r _ -> throwErr $ SymbolNotFound (pack $ show fnId) UnnamedFnValue un -> Just <$> evaluateUnnamedFn un args ProcedureValue fndef -> do insertEmptyScope runProcedure fndef (BuiltIn (BuiltinCall cb)) -> cb args (BuiltIn (BuiltinCallWithDoc (SomeBuiltin cb))) -> cb (toArgs args) a -> throwErr $ UnexpectedType ("Procedure", a) where runProcedure :: FunctionDef -> InterpretM (Maybe Value) runProcedure = \case FunctionDef True a b c -> do inChan <- liftIO newTChanIO outChan <- liftIO newTChanIO let gChans = GeneratorChannels inChan outChan void $ withRunInIO $ \runInIO -> forkIO $ runInIO (do modifyInterpretM (\is -> is { isGeneratorChannels = Just gChans }) void $ runProcedure $ FunctionDef False a b c liftIO $ atomically $ writeTChan outChan Nothing ) pure $ Just $ GeneratorValue gChans FunctionDef False _ argNames (NE.toList -> stms) -> do zipWithM_ (\a1 a2 -> insertBinding False 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 evaluateProcedure :: ScopeKey -> [Value] -> InterpretM (Maybe Value) evaluateProcedure sk args = lookupScope sk >>= (\x -> evaluateProcedure_ x args) evaluateFn :: FnId -> [ExpressionWithLoc] -> Bool -> InterpretM (Maybe Value) evaluateFn fnId argsExps isTail = do args <- mapM (\x -> evaluateExpression x) argsExps fnVal <- case fnId of FnOp op -> lookupScope (SkOperator op) FnName op -> lookupScope (SkIdentifier op) FnExpr expr -> evaluateExpression expr if isTail then do popScope evaluateProcedure_ fnVal args else evaluateProcedure_ fnVal args evaluateSubscriptedExpr :: SubscriptedExpression -> InterpretM Value evaluateSubscriptedExpr (EArraySubscript expr indexExpr) = evaluateExpression expr >>= \case StringValue v -> evaluateExpression indexExpr >>= \case NumberValue (NumberInt i) -> do let index :: Int = fromIntegral i if index <= T.length v && index >= 0 then pure $ StringValue $ T.singleton (T.index v (index - 1)) else (throwErr $ IndexOutOfBounds index) a -> throwErr $ UnexpectedType ("Integer index", a) BytesValue v -> evaluateExpression indexExpr >>= \case NumberValue (NumberInt i) -> do let index :: Int = fromIntegral i case BS.indexMaybe v (index - 1) of Just w -> pure $ NumberValue $ NumberInt $ fromIntegral w Nothing -> (throwErr $ IndexOutOfBounds index) a -> throwErr $ UnexpectedType ("Integer index", a) 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 $ IndexOutOfBounds 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) ModuleValue fp scope Nothing -> pure $ ModuleValue fp scope $ Just $ Identifier 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 $ IndexOutOfBounds 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 :: Bool -> Subscript -> Value -> InterpretM () modifyBinding isGlobal (NoSubscript idf) val = insertBinding isGlobal (SkIdentifier idf) val modifyBinding isGlobal (PropertySubscript sub (unIdentifer -> key)) val = do evaluateVar sub >>= \case ObjectValue v -> case M.lookup key v of Just _ -> modifyBinding isGlobal sub (ObjectValue $ M.insert key val v) Nothing -> throwErr (KeyNotFound key) a -> throwErr $ UnexpectedType ("Map", a) modifyBinding isGlobal (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 isGlobal sub (ArrayValue $ V.update v (V.fromList [(index - 1, val)])) else throwErr $ IndexOutOfBounds index a -> throwErr $ UnexpectedType ("Integer Index", a) BytesValue v -> evaluateExpression expr >>= \case NumberValue (NumberInt idx) -> do let index :: Int = fromIntegral idx if (index <= BS.length v && index > 0) then let prefix = BS.take (index - 1) v suffix = BS.drop index v wv = fromValue val in modifyBinding isGlobal sub (BytesValue $ prefix <> BS.cons wv suffix) else throwErr $ IndexOutOfBounds index a -> throwErr $ UnexpectedType ("Integer Index", a) StringValue v -> evaluateExpression expr >>= \case NumberValue (NumberInt idx) -> do let index :: Int = fromIntegral idx if (index <= T.length v && index > 0) then let prefix = T.take (index - 1) v suffix = T.drop index v wv = fromValue val in modifyBinding isGlobal sub (StringValue $ prefix <> T.cons wv suffix) else throwErr $ IndexOutOfBounds index a -> throwErr $ UnexpectedType ("Integer Index", a) ObjectValue v -> evaluateExpression expr >>= \case StringValue key -> case M.lookup key v of Just _ -> modifyBinding isGlobal 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 :: Show a => DebugStepable a b => a -> InterpretM b executeDebugStepable dbs = do isRunMode <$> getInterpretM >>= \case NormalMode -> do execute dbs DebugMode debugEnv@(DebugEnv { deInQueue = isDebugIn, deOutQueue = isDebugOut, deStepMode = stepMode }) -> do stepMode' <- case stepMode of Continue -> (liftIO $ atomically $ tryReadTBQueue isDebugIn) >>= \case Just StartStep -> pure SingleStep -- Only StartStep will trigger a break to step debugging here. _ -> pure Continue SingleStep -> pure SingleStep case stepMode' of Continue -> execute dbs SingleStep -> do -- Send location of current instruction, and wait for command. sendDebugOut isDebugOut (liftIO $ atomically $ readTBQueue isDebugIn) >>= \case Run -> do modifyInterpretM (\is -> is { isRunMode = DebugMode $ debugEnv { deStepMode = Continue } }) execute dbs StepIn -> do modifyInterpretM (\is -> is { isRunMode = DebugMode (DebugEnv SingleStep isDebugIn isDebugOut) }) execute dbs _ -> error "Unexpected debug command" where sendDebugOut debugOut = do is <- getInterpretM let currentScope = case isLocal is of [] -> isGlobalScope is (scope : _) -> scope let dd = DebugState currentScope (getLocation dbs) (Just $ trimAndElipsis $ toSource dbs) (isThreadName is) liftIO $ atomically $ writeTBQueue debugOut $ DebugData dd trimAndElipsis (T.replace "\n" " " -> t) = if T.length t > 30 then T.take 30 t <> "..." else t executeStatement_ :: FunctionStatement -> InterpretM ProcResult executeStatement_ (FnComment _) = pure ProcContinue executeStatement_ (Let sub isGlobal exp') = do sourceValue <- evaluateExpression exp' modifyBinding isGlobal sub sourceValue pure ProcContinue executeStatement_ (Call expr) = do _ <- evaluateExpression expr 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_ (Yield expr) = evaluateExpression expr >>= \v -> isGeneratorChannels <$> getInterpretM >>= \case Nothing -> error "Generator channel unavailable unexpectedly!" Just (GeneratorChannels inChan outChan) -> liftIO $ do atomically $ writeTChan outChan $ Just v atomically $ readTChan inChan pure $ ProcContinue executeStatement_ Break = pure ProcBreak executeStatement_ Pass = pure ProcContinue executeStatement_ (Loop (NE.toList -> stms)) = do r <- iterateWhile (\case ProcBreak -> False ProcContinue -> True ProcReturn _ _ -> False) (executeStatements stms) case r of ProcBreak -> pure ProcContinue a -> pure a 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) mStepExpr) = do fromVal <- evaluateExpression exprFrom >>= \case NumberValue n -> pure n a -> throwErr $ UnexpectedType ("Int/Fractional", a) toVal <- evaluateExpression exprTo >>= \case NumberValue n -> pure n a -> throwErr $ UnexpectedType ("Int/Fractional", a) stepValue <- case mStepExpr of Just stepExpr -> evaluateExpression stepExpr >>= \case NumberValue n -> pure n a -> throwErr $ UnexpectedType ("Int/Fractional", a) Nothing -> pure $ NumberInt 1 let fn :: ProcResult -> Number -> InterpretM ProcResult fn ProcContinue current = do insertBinding False (SkIdentifier iden) (NumberValue current) executeStatements stms fn r _ = pure r foldM (\a1 a2 -> fn a1 a2) ProcContinue (Prelude.takeWhile (<= toVal) $ iterate ((numberBinaryFn (+)) stepValue) fromVal) >>= \case ProcReturn tc v -> pure $ ProcReturn tc v _ -> pure ProcContinue executeStatement_ (ForEach iden expr (NE.toList -> stms)) = evaluateExpression expr >>= \case GeneratorValue genChans -> let go (ProcReturn tc v) = pure $ ProcReturn tc v go r = generatorNext genChans >>= \case Just v -> fn r v >>= go Nothing -> pure r in go ProcContinue 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 DirectoryStack dhref -> do let go lr = do (liftIO $ readDirectoryStack dhref) >>= \case EmptyItem -> pure ProcContinue fi -> fn lr (mkObjectFromFileItem fi) >>= go go ProcContinue a -> throwErr $ UnexpectedType ("Array/Object", a) where fn :: ProcResult -> Value -> InterpretM ProcResult fn ProcContinue current = do insertBinding False (SkIdentifier iden) current executeStatements stms fn r _ = pure r generatorNext :: GeneratorChannels -> InterpretM (Maybe Value) generatorNext (GeneratorChannels inChan outChan) = do v <- liftIO $ do atomically $ writeTChan inChan () atomically $ readTChan outChan case v of Just v' -> pure $ Just v' Nothing -> do liftIO $ atomically $ unGetTChan outChan Nothing pure Nothing data FileEntry = FileItem FilePath | DirItem FilePath | SymlinkItem FilePath | ErrorItem FilePath Text | EmptyItem mkObjectFromFileItem :: FileEntry -> Value mkObjectFromFileItem EmptyItem = error "Impossible!" mkObjectFromFileItem (FileItem t) = ObjectValue $ M.fromList [("type", StringValue "file"), ("path", StringValue $ T.pack t)] mkObjectFromFileItem (DirItem t) = ObjectValue $ M.fromList [("type", StringValue "dir"), ("path", StringValue $ T.pack t)] mkObjectFromFileItem (SymlinkItem t) = ObjectValue $ M.fromList [("type", StringValue "symlink"), ("path", StringValue $ T.pack t)] mkObjectFromFileItem (ErrorItem t e) = ObjectValue $ M.fromList [("type", StringValue "error"), ("path", StringValue $ T.pack t), ("message", StringValue e)] readDirectoryStack :: DirHandleRef -> IO FileEntry readDirectoryStack a@(DirHandleRef recursive ref) = do readTVarIO ref >>= \case [] -> pure EmptyItem (DirStreamInfo (AbsoluteFilePath afp) mh: _) -> do eh <- case mh of Just h -> pure $ Right h Nothing -> do try @_ @IOException (POSIX.openDirStream afp) >>= \case Right h -> do atomically $ modifyTVar ref (\case [] -> error "Impossible!" (_:c) -> (DirStreamInfo (AbsoluteFilePath afp) (Just h) : c)) pure $ Right h Left err -> do atomically $ modifyTVar ref (\case [] -> error "Impossible!" (_:c) -> c) pure $ Left (T.pack $ show err) case eh of Left h -> pure $ ErrorItem afp h Right h -> do POSIX.readDirStream h >>= \case "" -> do -- pop top most path if it has run out of files. POSIX.closeDirStream h atomically $ modifyTVar ref (\case [] -> error "Impossible!" (_:rs) -> rs) readDirectoryStack a "." -> readDirectoryStack a ".." -> readDirectoryStack a fp -> do -- check if this is a dir, if yes, push it on top of stack, but -- only if recursion is enabled. -- then return its path. let fp' = afp fp pathIsSymbolicLink fp' >>= \case True -> pure (SymlinkItem fp') False -> if recursive then do doesDirectoryExist fp' >>= \case True -> do atomically $ modifyTVar ref (\c -> (DirStreamInfo (AbsoluteFilePath fp') Nothing : c)) pure (DirItem fp') _ -> pure (FileItem fp') else do doesDirectoryExist fp' >>= \case True -> pure (DirItem fp') _ -> pure (FileItem fp') 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" interpretPassOne :: Program -> InterpretM () interpretPassOne x = mapM_ (\a -> fn a) x where fn :: ProgramStatement -> InterpretM () fn (FunctionDefStatement fdef@(FunctionDef _ name _ _)) = modifyInterpretM $ mapGlobalScope $ \s -> insert (SkIdentifier name) (ProcedureValue fdef) s fn _ = pure () interpretPassTwo :: Program -> InterpretM () interpretPassTwo x = mapM_ (\a -> fn a) x where fn :: ProgramStatement -> InterpretM () fn (FunctionDefStatement (FunctionDef _ _ _ _)) = pure () fn (NakedStatement fs) = void $ executeStatement fs fn (TopLevelComment _) = pure ()