packages feed

spade-0.1.0.6: src/Interpreter/Common.hs

module Interpreter.Common where

import Control.Concurrent
import Control.Concurrent.STM.TBQueue
import Control.Concurrent.STM.TChan
import Control.Concurrent.STM.TMVar
import Control.Concurrent.STM.TSem
import Control.Exception
import Control.Monad.Catch (MonadCatch, MonadThrow, throwM)
import qualified Data.ByteString as BS
import Data.IORef
import Data.Kind (Type)
import Data.List.NonEmpty
import Data.Map as M
import Data.Maybe (isJust)
import Data.Proxy
import Data.Text as T
import Data.Text.IO as T
import qualified Data.Vector as V
import qualified Data.Vector.Storable as VS
import Data.Word
import Foreign.C.Types
import GHC.OverloadedLabels
import GHC.TypeLits
import SDL hiding (Keycode, Scancode, get)
import qualified SDL as SDL
import qualified SDL.Mixer as SDLM
import qualified System.IO as SIO
import Text.Hex (encodeHex)

import Common
import Compiler.AST.Program
import Compiler.Lexer
import DiffRender.DiffRender
import UI.Widgets.Common

audioSampleCount :: Int
audioSampleCount = 44100

type Sample = SDLM.Chunk

data BuiltinVal
  = BuiltinCallWithDoc SomeBuiltin
  | BuiltinCall BuiltInFn
  | BuiltinVal Value

instance Show BuiltinVal where
  show _ = "_builtin_"

newtype SDLKeyboardStateCallback = SDLKeyboardStateCallback (SDL.Scancode -> Bool)

data SDLValue
  = Renderer SDL.Renderer
  | Color (V4 Int)
  | Keycode SDL.Keycode
  | Scancode SDL.Scancode
  | KeyboardState SDLKeyboardStateCallback
  | SoundSample Sample

instance Show SDLValue where
  show (Keycode k)       = "(SDL_KEY)" <> show k
  show (Scancode k)      = "(SDL_SCANCODE)" <> show k
  show (KeyboardState _) = "(SDL_KEYBOARD_STATE)"
  show (Color _)         = "(SDL_COLOR)"
  show (Renderer _)      = "(SDL_RENDERER)"
  show (SoundSample _)   = "(SDL_SOUND)"

data Number
  = NumberInt IntType
  | NumberFractional FloatType
  deriving Show

negateValue :: Number -> Number
negateValue (NumberInt x)        = NumberInt $ negate x
negateValue (NumberFractional x) = NumberFractional $ negate x

numberBinaryFn :: (forall n. Num n => n -> n -> n) -> Number -> Number -> Number
numberBinaryFn fn n1 n2 = case (n1, n2) of
  (NumberInt x, NumberInt y) -> NumberInt $ fn x y
  (NumberFractional x, NumberFractional y) -> NumberFractional $ fn x y
  (NumberInt x, NumberFractional y) -> NumberFractional $ fn (realToFrac x) y
  (NumberFractional x, NumberInt y) -> NumberFractional $ fn x (realToFrac y)

numberBinaryFractionalFn :: (forall n. Fractional n => n -> n -> n) -> Number -> Number -> Number
numberBinaryFractionalFn fn n1 n2 = case (n1, n2) of
  (NumberInt x, NumberInt y) -> NumberFractional $ fn (realToFrac x) (realToFrac y)
  (NumberFractional x, NumberFractional y) -> NumberFractional $ fn x y
  (NumberInt x, NumberFractional y) -> NumberFractional $ fn (realToFrac x) y
  (NumberFractional x, NumberInt y) -> NumberFractional $ fn x (realToFrac y)

instance Eq Number where
  (NumberInt n1) == (NumberInt n2)               = n1 == n2
  (NumberFractional n1) == (NumberFractional n2) = n1 == n2
  (NumberFractional n1) == (NumberInt n2)        = n1 == (realToFrac n2)
  (NumberInt n1) == (NumberFractional n2)        = (realToFrac n1) == n2

instance Ord Number where
  compare (NumberInt x) (NumberInt y)               = compare x y
  compare (NumberFractional x) (NumberFractional y) = compare x y
  compare (NumberInt x) (NumberFractional y)        = compare (realToFrac x) y
  compare (NumberFractional x) (NumberInt y)        = compare x (realToFrac y)

data UnNamedFn = UnNamedFn (Maybe (NonEmpty Identifier)) Scope ExpressionWithLoc
  deriving Show

data ThreadInfo = ThreadInfo ThreadId (TMVar (Either SomeException Value))

instance Show ThreadInfo where
  show _ = "(ThreadInfo)"

newtype ChannelRef = ChannelRef (TChan Value)

instance Show ChannelRef where
  show _ = "(ConcurrencyChannel)"

newtype MutableRef = MutableRef (TMVar Value)

instance Show MutableRef where
  show _ = "(MutableRef)"

data Value
  = StringValue Text
  | NumberValue Number
  | BoolValue Bool
  | BytesValue BS.ByteString
  | ArrayValue (V.Vector Value)
  | ObjectValue (Map Text Value)
  | ProcedureValue FunctionDef
  | UnnamedFnValue UnNamedFn
  | BuiltIn BuiltinVal
  | SDLValue SDLValue
  | ThreadRef ThreadInfo
  | Channel ChannelRef
  | Ref MutableRef
  | ErrorValue Text
      -- ^ This should probably never be used directly, and should throw a CustomRTE instead
      --  so that it will be caught and rethrown with location information.
  | Void
  deriving Show

toStringVal :: Value -> Text
toStringVal = \case
  StringValue t                    -> t
  NumberValue (NumberInt i)        -> pack $ show i
  NumberValue (NumberFractional i) -> pack $ show i
  BoolValue True                   -> "true"
  BoolValue False                  -> "false"
  BytesValue b                     -> "0x" <> encodeHex b
  ArrayValue _                     -> "[array]"
  ObjectValue _                    -> "[object]"
  ProcedureValue _                 -> "(procedure)"
  ThreadRef _                      -> "(thread_ref)"
  Channel _                        -> "(concurrency_channel)"
  Ref _                            -> "(mutable_ref)"
  UnnamedFnValue _                 -> "(unnamed_function)"
  Void                             -> "(void)"
  BuiltIn _                        -> "(builtin)"
  s@(ErrorValue _)                 -> pack $ show s
  SDLValue s                       -> pack $ show s

instance Ord Value where
  compare (NumberValue x) (NumberValue y) = compare x y
  compare _ _                             = error "Cannot be compared"

data ProcResult
  = ProcReturn Bool Value -- Bool indicates tail call
  | ProcBreak
  | ProcContinue

instance Eq Value where
 (StringValue t) == (StringValue v) = t == v
 (NumberValue t) == (NumberValue v)       = t == v
 (BoolValue t) == (BoolValue v)     = t == v
 (BytesValue t) == (BytesValue v)     = t == v
 (ArrayValue t) == (ArrayValue v)   = t == v
 (ObjectValue t) == (ObjectValue v) = t == v
 (SDLValue (Keycode kc1)) == (SDLValue (Keycode kc2)) = kc1 == kc2
 (ProcedureValue _) == _            = error "cannot be compared"
 _ == (ProcedureValue _)            = error "cannot be compared"
 (BuiltIn _) == _                   = error "cannot be compared"
 _ == (BuiltIn _)                   = error "cannot be compared"
 Void == _                          = error "cannot be compared"
 _ == Void                          = error "cannot be compared"
 _ == _                             = False

data ScopeKey
  = SkIdentifier Identifier
  | SkOperator Operator
  deriving (Ord, Eq)

instance Show ScopeKey where
  show (SkIdentifier i) = unpack $ unIdentifer i
  show (SkOperator i)   = show i

type Scope = Map ScopeKey Value

data RunMode
  = NormalMode
  | DebugMode DebugEnv
  deriving Show

data DebugIn
  = StepIn
  | Run
  | Start -- Start stepping through the program
  | StartStep -- Start stepping through the program
  | Stop -- Exit the stepping thread
  deriving (Show, Eq)

data StepMode
  = SingleStep
  | Continue
  deriving Show

data DebugState = DebugState
  { dsScope            :: Scope
  , dsLocation         :: Location
  , dsCurrenEvaluation :: Maybe Text
  , dsThreadName       :: Text
  } deriving (Show, Eq)

data DebugOut
  = Finished Bool
  | Errored Text
  | DebugData DebugState
  deriving (Show, Eq)

data DebugEnv = DebugEnv
  { deStepMode :: StepMode
  , deInQueue  :: TBQueue DebugIn
  , deOutQueue :: TBQueue DebugOut
  }

instance Show DebugEnv where
  show _ = "{DebugEnv}"

data InterpreterState = InterpreterState
  { isLocal           :: [Scope]
  , isRunMode         :: RunMode
  , isGlobalScope     :: Scope
  , isDefaultRenderer :: Maybe SDL.Renderer
  , isAccelerated     :: Maybe Bool
  , isDefaultWindow   :: Maybe SDL.Window
  , isSDLWindows      :: IORef [SDL.Window] -- We need these to cleanup any SDL windows after an exception.
  , isInputHandle     :: SIO.Handle
  , isOutputHandle    :: SIO.Handle
  , isThreadName      :: Text
  , isDiffRender      :: Maybe DiffRender
  , isWidgetState     :: Maybe WidgetState
  , isTerminalParams  :: Maybe (ScreenPos, Dimensions)
  , isStdoutLock      :: Maybe TSem
  , isDefaultPrintParams :: Maybe (Text, Int, [Int])
  -- ^ This lock is required to sync stdout writing when the program
  -- is run from within the IDE and both IDE code and the interepreted
  -- program wants to write to stdout concurrently.
  }

instance Show InterpreterState where
  show InterpreterState {..} = show (isJust isDefaultWindow)

dummyIS :: InterpreterState
dummyIS = InterpreterState
  { isLocal = mempty
  , isRunMode = NormalMode
  , isGlobalScope = mempty
  , isDefaultRenderer = Nothing
  , isAccelerated = Nothing
  , isDefaultWindow = Nothing
  , isSDLWindows = error "Unavailable"
  , isInputHandle = SIO.stdin
  , isOutputHandle = SIO.stdout
  , isThreadName = "MAIN"
  , isDiffRender = Nothing
  , isTerminalParams = Nothing
  , isStdoutLock = Nothing
  , isWidgetState = Nothing
  , isDefaultPrintParams = Nothing
  }

emptyIs :: IORef [SDL.Window] -> InterpreterState
emptyIs sdlWindowsRef = InterpreterState
  { isLocal = mempty
  , isRunMode = NormalMode
  , isGlobalScope = mempty
  , isDefaultRenderer = Nothing
  , isAccelerated = Nothing
  , isDefaultWindow = Nothing
  , isSDLWindows = sdlWindowsRef
  , isInputHandle = SIO.stdin
  , isOutputHandle = SIO.stdout
  , isThreadName = "MAIN"
  , isDiffRender = Nothing
  , isTerminalParams = Nothing
  , isStdoutLock = Nothing
  , isWidgetState = Nothing
  , isDefaultPrintParams = Just (".", 2, [3, 2, 2, 2, 2, 2, 2])
  }

interpreterOutput :: Text -> InterpretM ()
interpreterOutput c = isOutputHandle <$> get >>= (liftIO . flip T.hPutStr c)

interpreterOutputFlush :: InterpretM ()
interpreterOutputFlush = isOutputHandle <$> get >>= (liftIO . SIO.hFlush)

interpreterInputChar :: InterpretM Char
interpreterInputChar = isInputHandle <$> get >>= (liftIO . SIO.hGetChar)

interpreterInput :: InterpretM Text
interpreterInput = isInputHandle <$> get >>= (liftIO . T.hGetContents)

readInterpreterInputLine :: InterpretM Text
readInterpreterInputLine = readInput ""
  where
    readInput ti = do
      c <- interpreterInputChar
      case c of
        '\n' -> pure $ T.reverse $ T.pack ti
        '\ESC' -> do
            interpreterOutput "\nInput cancelled, please try again: "
            interpreterOutputFlush
            readInput ""
        '\BS' -> readInput ti
        _ -> do
          interpreterOutput (T.singleton c)
          interpreterOutputFlush
          readInput (c:ti)

mapLocal :: ([Scope] -> [Scope]) -> (InterpreterState -> InterpreterState)
mapLocal fn = \is -> is { isLocal = fn $ isLocal is }

mapGlobalScope :: (Scope -> Scope) -> (InterpreterState -> InterpreterState)
mapGlobalScope fn = \is -> is { isGlobalScope = fn $ isGlobalScope is }

type InterpretM a = forall m. (MonadCatch m, MonadIO m) => StateT InterpreterState m a

runInterpretM :: (MonadCatch m, MonadIO m) => InterpreterState -> InterpretM a -> m (a, InterpreterState)
runInterpretM istate act  = flip runStateT (istate) act

instance {-# OVERLAPPING #-} (MonadCatch m, MonadIO m) => MonadIO (StateT InterpreterState m) where
  liftIO act = lift $ liftIO @m (wrapSomeException IOError act)

type BuiltInFnWithDoc s = NamedArgs s -> InterpretM (Maybe Value)
type BuiltInFn = [Value] -> InterpretM (Maybe Value)

data Callback
  = CallbackUnNamed UnNamedFn
  | CallbackNamed Identifier

instance FromValue (M.Map Text Value) where
  fromValue (ObjectValue t) = t
  fromValue a               = throwErr $ UnexpectedType ("object", a)
  typeName = "object"

instance FromValue ThreadInfo where
  fromValue (ThreadRef t) = t
  fromValue a             = throwErr $ UnexpectedType ("thread_ref", a)
  typeName = "thread_ref"

instance FromValue ChannelRef where
  fromValue (Channel t) = t
  fromValue a           = throwErr $ UnexpectedType ("concurrency_channel", a)
  typeName = "concurrency_channel"

instance FromValue MutableRef where
  fromValue (Ref t) = t
  fromValue a       = throwErr $ UnexpectedType ("mutable_reference", a)
  typeName = "mutable_reference"

instance FromValue BytesOrText where
  fromValue (StringValue t) = BTText t
  fromValue (BytesValue t)  = BTBytes t
  fromValue a               = throwErr $ UnexpectedType ("filepath", a)
  typeName = "bytes_or_text"

instance FromValue TextOrList where
  fromValue (StringValue t) = TCText t
  fromValue (ArrayValue t)  = TCList t
  fromValue a               = throwErr $ UnexpectedType ("text_or_list", a)
  typeName = "text_or_list"

instance {-# OVERLAPPING #-} FromValue FilePath where
  fromValue (StringValue t) = T.unpack t
  fromValue a               = throwErr $ UnexpectedType ("filepath", a)
  typeName = "filepath"

instance FromValue Sample where
  fromValue (SDLValue (SoundSample s)) = s
  fromValue a = throwErr $ UnexpectedType ("soundsample", a)
  typeName = "soundsample"

instance FromValue Text where
  fromValue (StringValue t) = t
  fromValue a               = throwErr $ UnexpectedType ("string", a)
  typeName = "string"

instance FromValue BS.ByteString where
  fromValue (BytesValue b) = b
  fromValue a              = throwErr $ UnexpectedType ("bytes", a)
  typeName = "bytes"

instance FromValue SDL.Scancode where
  fromValue (SDLValue (Scancode sc)) = sc
  fromValue a                        = throwErr $ UnexpectedType ("scancode", a)
  typeName = "scancode"

instance FromValue SDLKeyboardStateCallback where
  fromValue (SDLValue (KeyboardState cb)) = cb
  fromValue a = throwErr $ UnexpectedType ("callback", a)
  typeName = "keyboardstate"

instance FromValue Callback where
  fromValue (UnnamedFnValue un) = CallbackUnNamed un
  fromValue (ProcedureValue (FunctionDef idf _ _)) = CallbackNamed idf
  fromValue a = throwErr $ UnexpectedType ("callback", a)
  typeName = "callback"

class FromValue a where
  fromValue :: Value -> a
  fromError :: Text -> a
  fromError err = throwErr $ CustomRTE err
  typeName :: Text

instance FromValue SDL.Keycode where
  fromValue (SDLValue (Keycode keycode)) = keycode
  fromValue a = throwErr $ UnexpectedType ("keycode", a)
  typeName = "keycode"

instance FromValue Int where
  fromValue (NumberValue (NumberInt i)) = fromIntegral i
  fromValue (NumberValue (NumberFractional i)) = round i
  fromValue a            = throwErr $ UnexpectedType ("number", a)
  typeName = "integer"

instance FromValue SDLM.Channel where
  fromValue = fromIntegral . fromValue @Int
  typeName = "channel"

instance FromValue Bool where
  fromValue (BoolValue b) = b
  fromValue a             = throwErr $ UnexpectedType ("boolean", a)
  typeName = "boolean"

instance FromValue Integer where
  fromValue (NumberValue (NumberInt i)) = i
  fromValue (NumberValue (NumberFractional i)) = round i
  fromValue a            = throwErr $ UnexpectedType ("number", a)
  typeName = "integer"

instance FromValue Double where
  fromValue (NumberValue (NumberInt i)) = realToFrac i
  fromValue (NumberValue (NumberFractional i)) = i
  fromValue a            = throwErr $ UnexpectedType ("fractional", a)
  typeName = "fractional"

instance FromValue a => FromValue (V.Vector a) where
  fromValue (ArrayValue v) = V.map fromValue v
  fromValue a              = throwErr $ UnexpectedType ("number", a)
  typeName = "list of " <> (typeName @a)

instance FromValue a => FromValue [a] where
  fromValue (ArrayValue v) = V.toList $ V.map fromValue v
  fromValue a              = throwErr $ UnexpectedType ("number", a)
  typeName = "list of " <> (typeName @a)

instance FromValue Number where
  fromValue (NumberValue i) = i
  fromValue a               = throwErr $ UnexpectedType ("number", a)
  typeName = "number"

instance FromValue CInt where
  fromValue (NumberValue (NumberInt i)) = fromIntegral i
  fromValue (NumberValue (NumberFractional i)) = fromIntegral $ round i
  fromValue a            = throwErr $ UnexpectedType ("number", a)
  typeName = "number"

instance FromValue Value where
  fromValue = id
  typeName = "any"

mkPoint :: CInt -> CInt -> SDL.Point SDL.V2 CInt
mkPoint x y = SDL.P (SDL.V2 x y)

instance FromValue (VS.Vector (SDL.Point V2 CInt)) where
  fromValue (ArrayValue v) = VS.fromList $ V.toList $ fn <$> v
    where
      fn :: Value -> SDL.Point V2 CInt
      fn (ArrayValue (V.toList -> [v1, v2])) = mkPoint (fromValue v1) (fromValue v2)
      fn a = throwErr $ UnexpectedType ("list(2)", a)
  fromValue a = throwErr $ UnexpectedType ("list", a)
  typeName = "list"

instance FromValue (Number, Number) where
  typeName = "tuple"
  fromValue (ArrayValue (V.toList -> ((NumberValue idx0) : (NumberValue idx1) : _))) = (idx0, idx1)
  fromValue a = throwErr $ UnexpectedType ("list with two values", a)

instance FromValue Word8 where
  fromValue = \case
    (NumberValue (NumberInt i)) -> fromInt i
    (NumberValue (NumberFractional i)) -> fromInt (round i)
    a -> throwErr $ UnexpectedType ("number", a)
    where
      fromInt i = if (fromIntegral i) <= (maxBound @Word8) then fromIntegral i else throwErr $ CustomRTE "Number too large to be converted to a byte"
  typeName = "number"

newtype Variadic = Variadic [Value]

instance FromValue Variadic where
  fromValue v = Variadic [v]
  typeName = "variadic argument"

newtype NamedArg (s :: Symbol) a = NamedArg a

instance KnownSymbol s => IsLabel s (a -> NamedArg s a) where
  fromLabel = NamedArg

data NamedArgs (s :: [(Symbol, Type)]) where
  EmptyArgs :: NamedArgs '[]
  (:>) :: (KnownSymbol s, FromValue a) => NamedArg s a -> NamedArgs r -> NamedArgs ('(s, a) ': r)
infixr 8 :>

class KnownArgs a where
  toArgDoc :: [(Text, Text)]
  toArgs :: [Value] -> NamedArgs a

instance {-# OVERLAPPING #-} KnownSymbol n => KnownArgs '[ '(n, Variadic)] where
  toArgDoc = [("variadic", "Value")]
  toArgs x = NamedArg (Variadic x) :> EmptyArgs

instance KnownArgs '[] where
  toArgDoc = []
  toArgs [] = EmptyArgs
  toArgs _  = error "Unexpected arguments"

instance (KnownSymbol n, FromValue t, KnownArgs s) => KnownArgs (('(n, t) ': s)) where
  toArgDoc = (pack $ symbolVal (Proxy @n), typeName @t) : toArgDoc @s
  toArgs []        = throwErr  $ BadArguments ((pack $ symbolVal (Proxy @n)) <> " of type " <> typeName @t, "No argument")
  toArgs (ErrorValue err : rst) = (NamedArg $ fromError @t err) :> (toArgs @s rst)  -- throwErr $ CustomRTE err
  toArgs (v : rst) = (NamedArg $ fromValue v) :> (toArgs @s rst)

data EitherError a = EitherError (Either Text a)

instance FromValue a => FromValue (EitherError a) where
  fromValue v = EitherError (Right $ fromValue v)
  fromError err = EitherError (Left err)
  typeName = (typeName @a) <> "(optional)"

instance FromValue a => FromValue (Maybe a) where
  fromValue v = Just $ fromValue v
  typeName = (typeName @a) <> "(optional)"

instance {-# OVERLAPPING #-} (KnownSymbol n, FromValue t, KnownArgs s) => KnownArgs (('(n, Maybe t) ': s)) where
  toArgDoc = (pack $ symbolVal (Proxy @n), typeName @(Maybe t)) : toArgDoc @s
  toArgs []        = (NamedArg (Nothing :: Maybe t)) :> (toArgs @s [])
  toArgs (v : rst) = (NamedArg $ fromValue v) :> (toArgs @s rst)

data SomeBuiltin where
  SomeBuiltin :: KnownArgs s => (NamedArgs s -> InterpretM (Maybe Value)) -> SomeBuiltin

extractDoc :: SomeBuiltin -> [(Text, Text)]
extractDoc (SomeBuiltin (_ :: NamedArgs s -> InterpretM (Maybe Value))) = toArgDoc @s

wrapSomeException :: (Text -> RuntimeError) -> IO a -> IO a
wrapSomeException fn act =
  flip catch (\(e :: SomeException) -> case fromException @AsyncException e of
    Just _ -> throwM e
    _ -> case fromException @IOException e of
      Just _  -> throwM $ fn $ T.pack $ displayException e
      Nothing -> throwM e) act

instance HReadable (Either RuntimeError ProgramError) where
  hReadable (Right x) = hReadable x
  hReadable (Left x)  = hReadable x

data RuntimeErrorWithLoc =  RuntimeErrorWithLoc (Either RuntimeError ProgramError) Location

instance HReadable RuntimeErrorWithLoc where
  hReadable (RuntimeErrorWithLoc msg loc) = "Runtime error: " <> (hReadable msg) <> ", at: " <> (hReadable loc)

instance Show RuntimeErrorWithLoc where
  show = T.unpack . hReadable

instance Exception RuntimeErrorWithLoc where

data ProgramError
  = SymbolNotFound Text
  | UnexpectedType (Text, Value)
  | MissingProcedureReturn
  | EmptyScopeStack
  | BadArguments (Text, Text)

data RuntimeError
  = IOError Text
  | SDLError Text
  | ListIndexOutOfBounds Int
  | KeyNotFound Text
  | UnserializeableValue
  | CustomRTE Text

instance Show RuntimeError where
  show = T.unpack . hReadable

instance Show ProgramError where
  show = T.unpack . hReadable

instance HReadable ProgramError where
  hReadable = \case
    SymbolNotFound s -> "Unknown symbol reference: '" <> s <>"'"
    UnexpectedType (e, f) -> case f of
      ErrorValue t -> t
      _ -> "Unexpected type, expected " <> e <> ", but got: " <> (T.pack $ show f)
    MissingProcedureReturn -> "Function or callback call did not return a value as expected"
    EmptyScopeStack -> "Stack was unexpectedly empty"
    BadArguments (e, f) -> "Unexpected argument count or type, expected: " <> e <> ", but got: " <> f

instance HReadable RuntimeError where
  hReadable = \case
    IOError t -> "Input/Output error: " <> t
    SDLError t -> "SDL error: " <> t
    ListIndexOutOfBounds t -> "Out of bound access of a list at index: " <> (T.pack $ show t)
    KeyNotFound t -> "Non-existing key access in dictionary at key: " <> t
    CustomRTE t -> "Run time error: " <> t
    UnserializeableValue -> "UnserializeableValue"

throwBadArgs :: [Value] -> Text -> InterpretM (Maybe Value)
throwBadArgs v' m = throwBadArgs' v' [] m
  where
    throwBadArgs' :: [Value] -> [Value] -> Text -> InterpretM (Maybe Value)
    throwBadArgs' (v@(ErrorValue _): _) _ _ = pure $ Just v
    throwBadArgs' (v : rst) bargs msg  = throwBadArgs' rst (v:bargs) msg
    throwBadArgs' [] bargs msg = throwErr $ BadArguments (msg, T.pack $ show $ Prelude.reverse bargs)

data BytesOrText
  = BTBytes BS.ByteString
  | BTText Text
  deriving Show

data TextOrList
  = TCList (V.Vector Value)
  | TCText Text
  deriving Show

instance Exception RuntimeError where
  displayException e = T.unpack $ hReadable e

instance Exception ProgramError where
  displayException e = T.unpack $ hReadable e

class InterpreterException a where
  throwErr :: (Exception e) => e -> a

instance InterpreterException a where
  throwErr r = throw r

instance {-# OVERLAPPING #-} InterpreterException ([] a) where
  throwErr r = throw r

instance {-# OVERLAPPING #-} MonadThrow m => InterpreterException (StateT InterpreterState m a) where
  throwErr = throwM

waitMillisec' :: Number -> IO ()
waitMillisec' number = case number of
  NumberFractional v -> do
    threadDelay (round $ v * 1000000)
  NumberInt v -> do
    threadDelay (fromInteger $ v * 1000000)

insertBuiltInVal :: ScopeKey -> Value -> InterpretM ()
insertBuiltInVal sk val = modify $ mapGlobalScope fn
  where
    fn :: Scope -> Scope
    fn s = M.insert sk (BuiltIn $ BuiltinVal val) s

insertBuiltIn :: ScopeKey -> BuiltInFn -> InterpretM ()
insertBuiltIn sk val = modify $ mapGlobalScope fn
  where
    fn :: Scope -> Scope
    fn s = M.insert sk (BuiltIn $ BuiltinCall val) s

insertBuiltInWithDoc :: ScopeKey -> SomeBuiltin -> InterpretM ()
insertBuiltInWithDoc sk val = modify $ mapGlobalScope fn
  where
    fn :: Scope -> Scope
    fn s = M.insert sk (BuiltIn $ BuiltinCallWithDoc val) s

insertBinding :: ScopeKey -> Value -> InterpretM ()
insertBinding sk val = modify fn
  where
    fn :: InterpreterState -> InterpreterState
    fn is@(InterpreterState { isGlobalScope, isLocal }) = case isLocal of
      []        -> is { isGlobalScope = M.insert sk val isGlobalScope }
      (s : rst) -> is { isLocal = (M.insert sk val s) : rst }

-- | Separates the given text using the decimal separator
-- and inserts commas as specivied by the positions list
-- into the non-fractional part.
amountFormat :: Text -> [Int] -> Text -> Text
amountFormat sep pos v = let
  (n, f) = T.breakOn sep v
  in (putCommas pos n) <> f
  where
    putCommas :: [Int] -> Text -> Text
    putCommas pos' v' = T.reverse $ T.intercalate "," (splitPos pos' (T.reverse v'))

    splitPos :: [Int] -> Text -> [Text]
    splitPos [] "" = []
    splitPos [] v' = [v']
    splitPos _ "" = []
    splitPos (p: rs) v' = (T.take p v') : (splitPos rs (T.drop p v'))