spade-0.1.0.0: src/Interpreter/Lib/Misc.hs
module Interpreter.Lib.Misc where
import Control.Monad.IO.Class
import Control.Concurrent.STM
import qualified Data.Aeson as A
import qualified Data.ByteString as BS
import Data.Text.Encoding
import qualified Data.ByteString.Lazy as BSL
import Data.Coerce
import qualified Data.Scientific as S
import Data.Map as M
import Text.Hex (encodeHex)
import Data.HashMap.Strict as HM
import Data.Text as T
import Data.Text.IO as T
import Data.Time.Clock.System
import Data.Vector as V
import qualified System.IO as S
import Control.Monad.State.Strict as SM
import UI.Widgets.Common
import Interpreter.Common
printValLn :: BuiltInFnWithDoc '[ '("value", Variadic)]
printValLn ((coerce -> (Variadic vals)) :> EmptyArgs) = do
liftIO $ do
UI.Widgets.Common.mapM_ T.putStr (toStringVal <$> vals)
T.putStrLn ""
S.hFlush S.stdout
pure Nothing
printVal :: BuiltInFnWithDoc '[ '("value", Variadic)]
printVal ((coerce -> (Variadic vals)) :> EmptyArgs) = do
liftIO $ do
UI.Widgets.Common.mapM_ T.putStr (toStringVal <$> vals)
S.hFlush S.stdout
pure Nothing
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", Vector Value), '("item", Value)]
contains ((coerce -> v1) :> (coerce -> v2) :> _) = pure $ Just $ BoolValue $ V.foldl' fn False v1
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'
arrayTake :: BuiltInFnWithDoc ['("source_list", Vector Value), '("count", Int)]
arrayTake ((coerce -> v1) :> (coerce -> c) :> _) = pure $ Just $ ArrayValue (V.take c v1)
arrayDrop :: BuiltInFnWithDoc ['("source_list", Vector Value), '("count", Int)]
arrayDrop ((coerce -> v1) :> (coerce -> c) :> _) = pure $ Just $ ArrayValue (V.drop c v1)
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)
builtInWriteFile :: BuiltInFnWithDoc '[ '("filename", FilePath), '("data", BytesOrText)]
builtInWriteFile ((coerce -> filepath) :> (coerce -> bot) :> EmptyArgs) = liftIO $ case bot of
BTBytes bin -> do
BS.writeFile filepath bin
pure Nothing
BTText dat -> do
T.writeFile filepath dat
pure Nothing
builtInReadFile :: BuiltInFnWithDoc '[ '("filename", FilePath)]
builtInReadFile ((coerce -> filepath) :> _) = liftIO $ do
c <- BS.readFile filepath
pure $ Just $ BytesValue c
builtInReadTextFile :: BuiltInFnWithDoc '[ '("filename", FilePath)]
builtInReadTextFile ((coerce -> filepath) :> _) = do
c <- liftIO $ T.readFile filepath
pure $ Just $ StringValue 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", Value), '("alternate", Maybe Value)]
builtInTry ((coerce -> evaluation) :> (coerce -> malternate) :> _) = case (evaluation, malternate) of
(ErrorValue _, Just a) -> pure $ Just a
(e@(ErrorValue _), Nothing) -> pure $ Just e
(v, _) -> pure $ Just v
builtInTimestamp :: BuiltInFnWithDoc '[]
builtInTimestamp _ = do
st <- liftIO $ truncateSystemTimeLeapSecond <$> getSystemTime
pure $ Just $ NumberValue $ NumberInt $ ((fromIntegral $ systemSeconds st) * 1e9) + (fromIntegral $ systemNanoseconds st)
builtInJSONSerialize :: BuiltInFnWithDoc '[ '("value", Value)]
builtInJSONSerialize ((coerce -> (v :: Value)) :> _) =
(Just . BytesValue . BSL.toStrict . A.encode) <$> toAesonVal v
builtInInspect :: BuiltInFnWithDoc '[ '("value", Value)]
builtInInspect ((coerce -> (v :: Value)) :> _) = do
vText <- (decodeUtf8 . BSL.toStrict . A.encode) <$> toAesonVal v
liftIO $ do
T.putStr vText
S.hFlush S.stdout
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 -> pure $ Just $ ErrorValue ("JSON decoding failed with error:" <> (T.pack err))
builtInDecodeUTF8Bytes :: BuiltInFnWithDoc '[ '("bytes", BS.ByteString)]
builtInDecodeUTF8Bytes ((coerce -> b) :> _) = pure $ Just $ StringValue $ decodeUtf8 b
builtInEncodeUTF8Bytes :: BuiltInFnWithDoc '[ '("string", Text)]
builtInEncodeUTF8Bytes ((coerce -> b) :> _) = pure $ Just $ BytesValue $ encodeUtf8 b
builtInDebug :: BuiltInFnWithDoc '[]
builtInDebug _ = do
SM.modify (\is -> is { isRunMode = DebugMode, isStepMode = SingleStep })
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 $ HM.toList $ fromAesonVal <$> 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 (k, v)) $ M.toList b
pure $ A.Object $ HM.fromList vs
toAesonVal _ = throwErr $ CustomRTE "Unserializable value"
waitMillisec :: BuiltInFnWithDoc '[ '("timeinseconds", Number)]
waitMillisec ((coerce -> number) :> _) = (liftIO $ waitMillisec' number) >> pure Nothing
waitForKey :: BuiltInFnWithDoc '[]
waitForKey _ = do
inputChan <- isTerminalEventChan <$> get
(liftIO $ atomically $ readTChan inputChan) >>= \case
TerminalKey (KeyChar _ _ _ i) -> do
pure $ Just $ StringValue $ T.singleton i
_ -> pure $ Just $ StringValue ""
readChannel :: TChan TerminalEvent -> String -> IO Text
readChannel inputChan c = do
(liftIO $ atomically $ readTChan inputChan) >>= \case
TerminalKey (KeyCtrl _ _ _ Return) -> pure $ T.reverse $ T.pack c
TerminalKey (KeyChar _ _ _ i) -> do
S.putChar i
S.hFlush S.stdout
readChannel inputChan (i:c)
_ -> readChannel inputChan c
builtinInputLine :: BuiltInFnWithDoc '[ '("prompt", Text)]
builtinInputLine ((coerce -> prompt) :> _) = do
liftIO $ do
T.putStrLn prompt
S.hFlush S.stdout
inputChan <- isTerminalEventChan <$> get
(Just . StringValue) <$> (liftIO $ readChannel inputChan "")
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
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
v -> throwErr (UnexpectedType ("Array or Object", v))