packages feed

spade-0.1.0.9: src/Interpreter/Interpreter.hs

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 ()