packages feed

spade-0.1.0.9: src/Interpreter/Lib/Misc.hs

module Interpreter.Lib.Misc where

import Control.Concurrent.STM (atomically, newTVarIO, writeTChan)
import Control.Concurrent.STM.TSem
import Control.Monad.IO.Class
import System.Directory
import System.Process
import System.FilePath
import qualified Data.Aeson as A
import qualified Data.Aeson.Key as A
import qualified Data.Aeson.KeyMap as A
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import Data.Coerce
import Data.Map as M
import Data.Proxy
import qualified Data.Scientific as S
import Data.Text as T
import Data.Text.Encoding
import Data.Text.IO as T
import Data.Vector as V
import qualified System.IO as S
import Text.Hex (decodeHex, encodeHex)
import Text.Printf (printf)
import Text.Read (readMaybe)
import Data.Typeable

import Common
import Compiler.AST.Program
import DiffRender.DiffRender
import Interpreter.Common
import Interpreter.Interpreter
import UI.Widgets.BorderBox
import UI.Widgets.Common hiding (getSelection)
import UI.Widgets.Spade.Layout
import UI.Widgets.Spade.Button
import UI.Widgets.Spade.TextContainer
import UI.Widgets.Spade.TextLabel
import UI.Widgets.Spade.RefLabel
import UI.Widgets.Spade.Selector
import UI.Widgets.Spade.Input
import Compiler.Parser

initCharScreen :: BuiltInFnWithDoc '[]
initCharScreen _ = do
  isTerminalParams <$> getInterpretM >>= \case
    Just (_, dm) ->  do
      csInitialize dm
      -- modifyDiffRender (\dfr -> dfr { dfDebug = True })
    Nothing      -> throwErr $ CustomRTE "Unknown terminal size"
  pure Nothing

charScreenGoto :: BuiltInFnWithDoc '[ '("x", Int), '("y", Int)]
charScreenGoto ((coerce -> x) :> (coerce -> y) :> _) = do
  charScreenGoto' x y
  pure Nothing

charGetDimensions :: BuiltInFnWithDoc '[]
charGetDimensions EmptyArgs = do
  isTerminalParams <$> getInterpretM >>= \case
    Just (_, dm) ->
      pure $ Just $ ObjectValue $ M.fromList [("width", NumberValue $ NumberInt $ fromIntegral $ diW dm), ("height", NumberValue $ NumberInt $ fromIntegral $ diH dm)]
    Nothing -> throwErr $ CustomRTE "Unknown terminal size"

charScreenGoto' :: Int -> Int -> InterpretM ()
charScreenGoto' x y = csSetCursorPosition x y

charScreenGet' :: InterpretM ScreenPos
charScreenGet' = do
  isDiffRender <$> getInterpretM >>= \case
    Just dm -> pure $ dfGetCursorPosition dm
    Nothing -> throwErr $ CustomRTE "Char screen not initialized"

charScreenGet :: BuiltInFnWithDoc '[]
charScreenGet EmptyArgs = do
  sp <- charScreenGet'
  pure $ Just $ ObjectValue $ M.fromList [("x", NumberValue $ NumberInt $ fromIntegral $ sX sp), ("y", NumberValue $ NumberInt $ fromIntegral $ sY sp)]

charScreenMove :: BuiltInFnWithDoc '[ '("x", Int), '("y", Int)]
charScreenMove ((coerce -> x) :> (coerce -> y) :> _) = charScreenMove' x y >> pure Nothing

charScreenMove' :: Int -> Int -> InterpretM ()
charScreenMove' x y = do
  isDiffRender <$> getInterpretM >>= \case
    Just dm ->  modifyInterpretM (\is -> is { isDiffRender = Just $ dfSetCursorPosition (+ x) (+ y) dm })
    Nothing -> throwErr $ CustomRTE "Char screen not initialized"

charScreenPrint :: BuiltInFnWithDoc '[ '("values", Variadic)]
charScreenPrint ((coerce -> Variadic vals) :> _) = charScreenPrint' (T.concat $ toStringVal <$> vals) >> pure Nothing

charScreenPrintLn :: BuiltInFnWithDoc '[ '("values", Variadic)]
charScreenPrintLn ((coerce -> Variadic vals) :> _) = do
  charScreenClearLine'
  charScreenPrint' (T.concat $ toStringVal <$> vals)
  charScreenMove' 0 1
  pure Nothing

charScreenPrint' :: Text -> InterpretM ()
charScreenPrint' txt = csPutText (Plain txt)

charScreenInputLine :: BuiltInFnWithDoc '[ '("prompt", Text)]
charScreenInputLine ((coerce -> prompt) :> _) = (Just . StringValue) <$> (charScreenInputLine' prompt)

charScreenInputLine' :: Text -> InterpretM Text
charScreenInputLine' prompt = do
  charScreenPrint' prompt
  charScreenDraw'
  charScreenMove' (T.length prompt) 0
  readCharScreenInput >>= \case
    Nothing -> do
      charScreenGoto' 0 -1
      charScreenClearLine'
      charScreenInputLine' prompt
    Just x -> pure x

charScreenClear :: BuiltInFnWithDoc '[]
charScreenClear _ = csClear >> pure Nothing

charScreenClearLine :: BuiltInFnWithDoc '[]
charScreenClearLine EmptyArgs = charScreenClearLine' >> pure Nothing

charScreenClearLine' :: InterpretM ()
charScreenClearLine' = csClearLine

charScreenDraw :: BuiltInFnWithDoc '[]
charScreenDraw _ = charScreenDraw' >> pure Nothing

charScreenDraw' :: InterpretM ()
charScreenDraw' = do
  tp <- isTerminalParams <$> getInterpretM
  isStdoutLock <$> getInterpretM >>= \case
    Just sem -> do
      liftIO $ atomically $ waitTSem sem
      csDraw tp
      liftIO $ atomically $ signalTSem sem
    Nothing -> csDraw tp

readCharScreenInput :: InterpretM (Maybe Text)
readCharScreenInput = readInput ""
  where
    readInput ti = do
      c <- interpreterInputChar
      case c of
        '\n' -> pure $ Just $ T.reverse $ T.pack ti
        '\ESC' -> pure Nothing
        '\BS' -> readInput ti
        _ -> do
          charScreenPrint' (T.singleton c)
          charScreenMove' 1 0
          charScreenDraw'
          readInput (c:ti)

builtInInitWIdgets :: BuiltInFnWithDoc '[]
builtInInitWIdgets EmptyArgs = do
  isWidgetState <$> getInterpretM >>= \case
    Nothing -> modifyInterpretM (\is -> is { isWidgetState = Just emptyWidgetState })
    Just _ -> pass
  pure Nothing

builtInLayoutWidget :: BuiltInFnWithDoc '[ '("orientation", Text), '("child_layout", [[Double]]), '("children", [SomeWidgetRef])]
builtInLayoutWidget ((coerce -> (orientation :: Text )) :> (coerce -> (cl :: [[Double]])) :> (coerce -> children) :> EmptyArgs) = do
  layout <- layoutWidget (if orientation == "h" then Horizontal else Vertical) cl children
  pure $ Just (WidgetValue (SomeWidgetRef layout))

builtInLayoutWidgetSimple :: BuiltInFnWithDoc '[ '("orientation", Text), '("children", [SomeWidgetRef]) ]
builtInLayoutWidgetSimple ((coerce -> (orientation :: Text )) :> (coerce -> children) :>  EmptyArgs) = do
  layout <- simpleLayoutWidget (if orientation == "h" then Horizontal else Vertical) children
  pure $ Just (WidgetValue (SomeWidgetRef layout))

builtInTextContainerWidget :: BuiltInFnWithDoc '[]
builtInTextContainerWidget EmptyArgs = do
  tc <- textContainer (ScreenPos 0 0) (Dimensions 20 5)
  pure $ Just (WidgetValue (SomeWidgetRef tc))

builtInInputWidget :: BuiltInFnWithDoc '[]
builtInInputWidget EmptyArgs = do
  iw <- input (Dimensions 60 10)
  pure $ Just (WidgetValue (SomeWidgetRef iw))

builtInSingleLineInputWidget :: BuiltInFnWithDoc '[]
builtInSingleLineInputWidget EmptyArgs = do
  iw <- input (Dimensions 60 1)
  pure $ Just (WidgetValue (SomeWidgetRef iw))

builtInTextLabelWidget :: BuiltInFnWithDoc '[ '("label", Text)]
builtInTextLabelWidget ((coerce -> label) :> EmptyArgs) = do
  tc <- textLabel (ScreenPos 0 0) (Dimensions 20 1) label
  pure $ Just (WidgetValue (SomeWidgetRef tc))

builtInTextRefLabelWidget :: BuiltInFnWithDoc '[ '("reflabel", MutableRef)]
builtInTextRefLabelWidget (((mref . coerce) -> label) :> EmptyArgs) = do
  tc <- textRefLabel (ScreenPos 0 0) (Dimensions 20 1) label
  pure $ Just (WidgetValue (SomeWidgetRef tc))

builtInSelectorWidget :: BuiltInFnWithDoc '[ '("label", Text), '("options", [(Value, Text)]), '("action", Maybe Callback)]
builtInSelectorWidget ((coerce -> (label :: Text)) :> (coerce -> options) :> (coerce -> cb) :>  EmptyArgs) = do
  tc <- selector label (ScreenPos 0 0) (Dimensions 20 1) options cb
  pure $ Just (WidgetValue (SomeWidgetRef tc))

builtInButtonWidget :: BuiltInFnWithDoc '[ '("label", Text), '("action", Maybe Callback)]
builtInButtonWidget ((coerce -> (label :: Text)) :>  (coerce -> (cb :: Maybe Callback)) :>  EmptyArgs) = do
  tc <- button (ScreenPos 0 0) (Dimensions (T.length label + 5) 3) label cb
  pure $ Just (WidgetValue (SomeWidgetRef tc))

builtInGetSelection :: BuiltInFnWithDoc '[ '("select", SomeWidgetRef) ]
builtInGetSelection ((coerce -> (SomeWidgetRef ref)) :>  EmptyArgs) =
  case cast ref of
    Just selectRef -> do
      getSelection selectRef  >>= \case
        Just x -> do
          pure $ Just x
        Nothing -> pure $ Just Void
    Nothing -> error "Selector widget is required"

builtInSetOptions :: BuiltInFnWithDoc '[ '("select", SomeWidgetRef), '("option", [(Value ,Text)]) ]
builtInSetOptions ((coerce -> (SomeWidgetRef ref)) :> (coerce -> (options :: [(Value, Text)])) :> EmptyArgs) = do
  case cast ref of
    Just selectRef -> setOptions selectRef options
    Nothing -> error "Selector widget is required"
  pure Nothing

builtInGetOptions :: BuiltInFnWithDoc '[ '("select", SomeWidgetRef) ]
builtInGetOptions ((coerce -> (SomeWidgetRef ref)) :> EmptyArgs) = do
  case cast ref of
    Just selectRef -> do
      options <- getOptions selectRef
      pure $ Just $ ArrayValue $ V.fromList ((ArrayValue . V.fromList . (\(a, b) -> [a, StringValue b])) <$> options)
    Nothing -> error "Selector widget is required"

builtInBorderBox :: BuiltInFnWithDoc '[ '("child", SomeWidgetRef) ]
builtInBorderBox ((coerce -> (c :: SomeWidgetRef)) :> EmptyArgs) = do
  bb <- borderBox (ScreenPos 0 0) (Dimensions 0 0) c
  pure $ Just (WidgetValue (SomeWidgetRef bb))

builtInAddWidget :: BuiltInFnWithDoc '[ '("widget", SomeWidgetRef), '("child", SomeWidgetRef)]
builtInAddWidget ((coerce -> (SomeWidgetRef parent)) :>  (coerce -> (SomeWidgetRef child)) :> EmptyArgs) = do
  withCapability (LayoutCap parent) $ do
    addWidget parent child
  pure Nothing

builtInAddWidgetAt :: BuiltInFnWithDoc '[ '("widget", SomeWidgetRef), '("stackorder", Int), '("child", SomeWidgetRef)]
builtInAddWidgetAt ((coerce -> (SomeWidgetRef parent)) :>  (coerce -> so) :> (coerce -> (SomeWidgetRef child)) :> EmptyArgs) = do
  withCapability (LayoutCap parent) $ do
    addWidget' parent so child
  pure Nothing

builtInDrawWidget :: BuiltInFnWithDoc '[ '("widget", SomeWidgetRef) ]
builtInDrawWidget ((coerce -> (SomeWidgetRef w)) :> EmptyArgs) = do
  withCapability (DrawableCap w) $ draw w
  pure Nothing

builtInSetWidgetSize :: BuiltInFnWithDoc '[ '("widget", SomeWidgetRef), '("size_x", Int), '("size_y", Int) ]
builtInSetWidgetSize ((coerce -> (SomeWidgetRef w)) :> (coerce -> sx) :> (coerce -> sy) :> EmptyArgs) = do
  withCapability (MoveableCap w) $ resize w (\d -> d { diW = sx, diH = sy })
  pure Nothing

builtInSetFocus :: BuiltInFnWithDoc '[ '("widget", SomeWidgetRef), '("bool", Bool) ]
builtInSetFocus ((coerce -> (SomeWidgetRef w)) :> (coerce -> b) :> EmptyArgs) = do
  withCapability (FocusableCap w) $ setFocus w b
  pure Nothing

builtInSetTextWidgetContent :: BuiltInFnWithDoc '[ '("widget", SomeWidgetRef), '("content", Text)]
builtInSetTextWidgetContent ((coerce -> (SomeWidgetRef w)) :> (coerce -> (content :: Text)) :> EmptyArgs) = do
  withCapability (ContainerCap w (Proxy @Text)) $ setContent w content
  pure Nothing

builtInHandleInput :: BuiltInFnWithDoc '[ '("widget", SomeWidgetRef), '("keyevent", [KeyEvent]) ]
builtInHandleInput ((coerce -> (SomeWidgetRef w)) :> (coerce -> (ke :: [KeyEvent])) :> EmptyArgs) = do
  withCapability (KeyInputCap w) $ UI.Widgets.Common.mapM_ (handleInput w) ke
  pure Nothing

toStringValFmt :: (Text, Int,  [Int]) -> Value -> Text
toStringValFmt (s, _, p) v@(NumberValue (NumberInt _)) = amountFormat s p $ toStringVal v
toStringValFmt (s, f, p) (NumberValue (NumberFractional x)) = amountFormat s p $ T.pack $ printf ("%0." Prelude.++ show f Prelude.++ "f") x
toStringValFmt _ v = toStringVal v

builtinEncodeBytes :: BuiltInFnWithDoc '[ '("bytes", BS.ByteString)]
builtinEncodeBytes ((coerce -> bs) :> EmptyArgs) =
  pure $ Just $ StringValue $ encodeHex bs

builtinDecodeBytes :: BuiltInFnWithDoc '[ '("string", Text)]
builtinDecodeBytes ((coerce -> ebs) :> EmptyArgs) =
  case decodeHex ebs of
    Just bs -> pure $ Just $ BytesValue bs
    Nothing -> error "Input cannot be decoded as bytes"

printValLn :: BuiltInFnWithDoc '[ '("value", Variadic)]
printValLn ((coerce -> (Variadic vals)) :> EmptyArgs) = do
  outH <- isOutputHandle <$> getInterpretM
  tstrFn <- isDefaultPrintParams <$> getInterpretM >>= \case
    Just p  -> pure $ toStringValFmt p
    Nothing -> pure toStringVal
  liftIO $ do
    UI.Widgets.Common.mapM_ (T.hPutStr outH) (tstrFn <$> vals)
    T.hPutStrLn outH ""
    S.hFlush outH
  pure Nothing

printVal :: BuiltInFnWithDoc '[ '("value", Variadic)]
printVal ((coerce -> (Variadic vals)) :> EmptyArgs) = do
  outH <- isOutputHandle <$> getInterpretM
  tstrFn <- isDefaultPrintParams <$> getInterpretM >>= \case
    Just p  -> pure $ toStringValFmt p
    Nothing -> pure toStringVal
  liftIO $ do
    UI.Widgets.Common.mapM_ (T.hPutStr outH) (tstrFn <$> vals)
    S.hFlush outH
  pure Nothing

numberFromString :: BuiltInFnWithDoc '[ '("string", Text)]
numberFromString ((coerce -> (T.unpack -> str)) :> EmptyArgs) = case readMaybe @IntType str  of
  Just i -> pure $ Just $ NumberValue $ NumberInt i
  Nothing  -> case readMaybe @FloatType str of
    Just i -> pure $ Just $ NumberValue $ NumberFractional i
    Nothing -> pure $ throwErr $ CustomRTE "String cannot be converted to a number"

multiplication :: BuiltInFn
multiplication (NumberValue v1: NumberValue v2 : []) = pure $ Just $ NumberValue $ numberBinaryFn (*) v1 v2
multiplication a = throwBadArgs a "number"

addition :: BuiltInFn
addition (NumberValue v1: NumberValue v2 : []) = pure $ Just $ NumberValue $ numberBinaryFn (+) v1 v2
addition (ArrayValue i1 : ArrayValue i2 : []) = pure $ Just $ ArrayValue $ i1 V.++ i2
addition a = throwBadArgs a "number/list"

substraction :: BuiltInFn
substraction (NumberValue v1: NumberValue v2 : []) = pure $ Just $ NumberValue $ numberBinaryFn (-) v1 v2
substraction a = throwBadArgs a "number"

division :: BuiltInFn
division (NumberValue _: NumberValue (NumberInt 0) : []) = throwErr $ CustomRTE "Divison by zero!"
division (NumberValue _: NumberValue (NumberFractional 0.0) : []) = throwErr $ CustomRTE "Divison by zero!"
division (NumberValue v1: NumberValue v2 : []) = pure $ Just $ NumberValue $ numberBinaryFractionalFn (/) v1 v2
division a = throwBadArgs a "number"

comparison :: (Value -> Value -> Bool) -> BuiltInFn
comparison fn (v1: v2 : []) = pure $ Just $ BoolValue (fn v1 v2)
comparison _ a              = throwBadArgs a "values"

boolean :: (Bool -> Bool -> Bool) -> BuiltInFn
boolean fn (BoolValue v1 : BoolValue v2 : []) = pure $ Just $ BoolValue (fn v1 v2)
boolean _ a              = throwBadArgs a "bools"

not' :: BuiltInFnWithDoc '[ '("bool", Bool)]
not' ((coerce -> v1) :> _) = pure $ Just $ BoolValue (not v1)

contains :: BuiltInFnWithDoc '[ '("list", TextOrList), '("item", Value)]
contains ((coerce -> v1) :> (coerce -> v2) :> EmptyArgs) = case v1 of
  TCText t -> case v2 of
    StringValue v -> pure $ Just $ BoolValue $ T.isInfixOf v t
    v             -> throwBadArgs [v] "string"
  TCList lst -> pure $ Just $ BoolValue $ V.foldl' fn False lst
    where
      fn :: Bool -> Value -> Bool
      fn True _  = True
      fn False v = v2 == v

haskey :: BuiltInFnWithDoc '[ '("dictionary",  M.Map Text Value), '("key", Text)]
haskey ((coerce -> (map' :: M.Map Text Value)) :> (coerce -> key) :> _) = pure $ Just $ BoolValue $ M.member key map'

getkey :: BuiltInFnWithDoc '[ '("dictionary",  M.Map Text Value), '("key", Text)]
getkey ((coerce -> (map' :: M.Map Text Value)) :> (coerce -> key) :> _) = case M.lookup key map' of
  Just v ->  pure $ Just v
  Nothing -> pure $ Just $ ErrorValue $ "Key '" <> key <> "' not found in dictionary"

addkey :: BuiltInFnWithDoc '[ '("dictionary",  M.Map Text Value), '("key", Text), '("value", Value)]
addkey ((coerce -> (map' :: M.Map Text Value)) :> (coerce -> key) :> (coerce -> val) :> EmptyArgs) = pure $ Just $ ObjectValue $ M.insert key val map'

builtInRepeat :: BuiltInFnWithDoc ['("count", Int), '("source", Value)]
builtInRepeat ((coerce -> c) :> (coerce -> vl) :>  EmptyArgs) = pure $ Just $ ArrayValue $ V.fromList $ Prelude.take c $ repeat vl

builtInTake :: BuiltInFnWithDoc ['("count", Int), '("source", TextOrList)]
builtInTake ((coerce -> c) :> (coerce -> vl) :>  EmptyArgs) = case vl of
  TCText t -> pure $ Just $ StringValue $ T.take c t
  TCList l -> pure $ Just $ ArrayValue (V.take c l)

builtInDrop :: BuiltInFnWithDoc ['("count", Int), '("source", TextOrList)]
builtInDrop ((coerce -> c) :> (coerce -> vl) :> EmptyArgs) = case vl of
  TCText t -> pure $ Just $ StringValue $ T.drop c t
  TCList l -> pure $ Just $ ArrayValue (V.drop c l)

builtInArrayInsertLeft :: BuiltInFnWithDoc ['("item", Value), '("initial_list", Vector Value)]
builtInArrayInsertLeft ((coerce -> c) :> (coerce -> v1) :> _) = pure $ Just $ ArrayValue (V.cons c v1)

builtInArrayInsertRight :: BuiltInFnWithDoc ['("initial_list", Vector Value), '("item", Value)]
builtInArrayInsertRight ((coerce -> v1) :> (coerce -> c) :> _) = pure $ Just $ ArrayValue (V.snoc v1 c)

builtInHead :: BuiltInFnWithDoc '[ '("source_list", Vector Value)]
builtInHead ((coerce -> v1) :> _) = case V.uncons v1 of
  Just (x, _) -> pure $ Just x
  Nothing     -> throwErr $ CustomRTE "Empty list found for 'head' call"

builtInTry :: BuiltInFnWithDoc '[ '("evaluation", EitherError Value), '("alternate", Value)]
builtInTry ((coerce -> evaluation) :>  (coerce -> alternate) :> _) = case evaluation of
  EitherError (Left _)  -> pure $ Just alternate
  EitherError (Right v) -> pure $ Just v

builtInTimestamp :: BuiltInFnWithDoc '[]
builtInTimestamp _ = do
  st <- liftIO getSystemTimestamp
  pure $ Just $ NumberValue $ NumberInt st

serializeJSON :: Value -> InterpretM BS.ByteString
serializeJSON v =
  (BSL.toStrict . A.encode) <$> toAesonVal v

builtInJSONSerialize :: BuiltInFnWithDoc '[ '("value", Value)]
builtInJSONSerialize ((coerce -> (v :: Value)) :> _) =
  (Just . BytesValue ) <$> serializeJSON v

builtInInspect :: BuiltInFnWithDoc '[ '("value", Value)]
builtInInspect ((coerce -> (v :: Value)) :> _) = do
  vText <- decodeUtf8 <$> serializeJSON v
  interpreterOutput vText
  interpreterOutputFlush
  pure Nothing

builtInJSONParse :: BuiltInFnWithDoc '[ '("value", Value)]
builtInJSONParse v  = let
  bytes = case v of
    ((coerce -> (StringValue b)) :> _) -> encodeUtf8 b
    ((coerce -> (BytesValue b)) :> _) -> b
    ((coerce -> (a :: Value)) :> _) -> throwErr $ BadArguments ("String/Bytes", T.pack $ show a)
  in case A.eitherDecodeStrict bytes of
    Right val -> pure $ Just $ fromAesonVal val
    Left err -> throwErr $ CustomRTE ("JSON decoding failed with error:" <> (T.pack err))

builtInError :: BuiltInFnWithDoc '[ '("message", Text)]
builtInError ((coerce -> v) :> EmptyArgs) =
  pure $ Just $ ErrorValue v

builtInIsError :: BuiltInFnWithDoc '[ '("value", Value)]
builtInIsError ((coerce -> v) :> _) = case v of
  ErrorValue _ -> pure $ Just $ BoolValue True
  _ -> pure $ Just $ BoolValue False

builtInDebug :: BuiltInFnWithDoc '[]
builtInDebug _ = do
  modifyInterpretM (\is -> case isRunMode is of
    DebugMode debugEnv -> is { isRunMode = DebugMode (debugEnv { deStepMode = SingleStep }) }
    _ -> is
    )
  pure Nothing

fromAesonVal :: A.Value -> Value
fromAesonVal (A.String s) = StringValue s
fromAesonVal (A.Number s) = NumberValue $ if S.isInteger s then NumberInt (round s) else NumberFractional (realToFrac s)
fromAesonVal (A.Bool b) = BoolValue b
fromAesonVal (A.Array b) = ArrayValue (fromAesonVal <$> b)
fromAesonVal (A.Object b) = ObjectValue (M.fromList $ (\(a, b') -> (A.toText a, fromAesonVal b')) <$> (A.toList b))
fromAesonVal A.Null = Void

toAesonVal :: Value -> InterpretM A.Value
toAesonVal Void = pure A.Null
toAesonVal (StringValue s) = pure $ A.String s
toAesonVal (NumberValue (NumberInt n)) = pure $ A.Number $ fromIntegral n
toAesonVal (NumberValue (NumberFractional n)) = pure $ A.Number $ realToFrac n
toAesonVal (BoolValue b) = pure $ A.Bool b
toAesonVal (ArrayValue b) = do
  vs <- V.mapM (\x -> toAesonVal x) b
  pure $ A.Array vs
toAesonVal (ObjectValue b) = do
  vs <- Prelude.mapM (\(k, x) -> do v <- toAesonVal x; pure (A.fromText k, v)) $ M.toList b
  pure $ A.Object $ A.fromList vs
toAesonVal _ = throwErr UnserializeableValue

waitMillisec :: BuiltInFnWithDoc '[ '("timeinseconds", Number)]
waitMillisec ((coerce -> number) :> _) = (liftIO $ waitMillisec' number) >> pure Nothing

waitForKey :: BuiltInFnWithDoc '[]
waitForKey _ = do
  inputHandle <- isInputHandle <$> getInterpretM
  c <- liftIO $ readKey_ inputHandle
  pure $ Just $ ArrayValue $ V.fromList (EventValue <$> strToKeyEvent c)

builtinInputLine :: BuiltInFnWithDoc '[ '("prompt", Text)]
builtinInputLine ((coerce -> prompt) :> _) = do
  interpreterOutput prompt
  interpreterOutputFlush
  (Just . StringValue) <$> readInterpreterInputLine

builtinImport :: BuiltInFnWithDoc '[ '("filepath", FilePath)]
builtinImport ((coerce -> filepath) :> _) =
  Just <$> importModule filepath

builtinExec :: BuiltInFnWithDoc '[ '("filepath", FilePath), '("args", [String])]
builtinExec ((coerce -> filepath) :> (coerce -> (args :: [String])) :> _) = do
  liftIO $ callProcess filepath args
  pure Nothing

builtinCreateDirectory :: BuiltInFnWithDoc '[ '("filepath", FilePath), '("createParents", Bool)]
builtinCreateDirectory ((coerce -> filePath) :> (coerce -> (createParents :: Bool)) :> _) = do
  liftIO $ createDirectoryIfMissing createParents filePath
  pure Nothing

valueSize :: BuiltInFnWithDoc '[ '("list_or_map", Value)]
valueSize ((coerce -> v1) :> _) = case v1 of
  ArrayValue v  -> pure $ Just $ NumberValue $ NumberInt $ fromIntegral $ V.length v
  ObjectValue m -> pure $ Just $ NumberValue $ NumberInt $ fromIntegral $ M.size m
  StringValue t -> pure $ Just $ NumberValue $ NumberInt $ fromIntegral $ T.length t
  BytesValue t -> pure $ Just $ NumberValue $ NumberInt $ fromIntegral $ BS.length t
  v             -> throwErr (UnexpectedType ("Array/Object/Bytes/String", v))

builtinConsoleLog :: BuiltInFnWithDoc '[ '("msg", Text)]
builtinConsoleLog ((coerce -> msg) :> _) = do
  consoleLog msg
  pure Nothing

consoleLog :: Text -> InterpretM ()
consoleLog msg = do
  isLogChannel <$> getInterpretM >>= \case
    Just chan ->  liftIO $ atomically $ writeTChan chan msg
    Nothing -> pass

importModule :: FilePath -> InterpretM Value
importModule filePath = do
  fullPath <- isCurrentModulePath <$> getInterpretM >>= \case
    Just p -> liftIO $ canonicalizePath $ p </> filePath
    Nothing -> error "Import path not available"
  content <- do
    liftIO $ T.readFile fullPath
  program <- liftIO $ compile @Program content
  pushModuleScope mempty
  modifyInterpretM (\is -> is { isCurrentModulePath = Just $ takeDirectory fullPath })
  interpretPassOne program
  interpretPassTwo program
  moduleScope <- popModuleScope
  scopeTVar <- liftIO $ newTVarIO moduleScope
  pure $ ModuleValue filePath (ScopeRef scopeTVar) Nothing