2017-06-05 13:23:33 +03:00
|
|
|
port module Main exposing (..)
|
|
|
|
|
2017-06-08 20:19:27 +03:00
|
|
|
import IO exposing (..)
|
|
|
|
import Json.Decode exposing (decodeValue)
|
2017-06-05 13:23:33 +03:00
|
|
|
import Platform exposing (programWithFlags)
|
|
|
|
import Types exposing (MalExpr(..))
|
|
|
|
import Reader exposing (readString)
|
|
|
|
import Printer exposing (printString)
|
|
|
|
import Utils exposing (maybeToList, zip)
|
|
|
|
import Dict exposing (Dict)
|
|
|
|
import Tuple exposing (mapFirst)
|
|
|
|
import Array
|
|
|
|
|
|
|
|
|
|
|
|
main : Program Flags Model Msg
|
|
|
|
main =
|
|
|
|
programWithFlags
|
|
|
|
{ init = init
|
|
|
|
, update = update
|
2017-06-08 20:19:27 +03:00
|
|
|
, subscriptions =
|
|
|
|
\model -> input (decodeValue decodeIO >> Input)
|
2017-06-05 13:23:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type alias Flags =
|
|
|
|
{ args : List String
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type alias ReplEnv =
|
|
|
|
Dict String MalExpr
|
|
|
|
|
|
|
|
|
|
|
|
type alias Model =
|
|
|
|
{ args : List String
|
|
|
|
, env : ReplEnv
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type Msg
|
2017-06-08 20:19:27 +03:00
|
|
|
= Input (Result String IO)
|
2017-06-05 13:23:33 +03:00
|
|
|
|
|
|
|
|
|
|
|
init : Flags -> ( Model, Cmd Msg )
|
|
|
|
init { args } =
|
|
|
|
( { args = args, env = initReplEnv }, readLine prompt )
|
|
|
|
|
|
|
|
|
|
|
|
initReplEnv : ReplEnv
|
|
|
|
initReplEnv =
|
|
|
|
let
|
|
|
|
binaryOp fn args =
|
|
|
|
case args of
|
|
|
|
[ MalInt x, MalInt y ] ->
|
|
|
|
Ok <| MalInt (fn x y)
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
Err "unsupported arguments"
|
|
|
|
in
|
|
|
|
Dict.fromList
|
|
|
|
[ ( "+", MalFunction <| binaryOp (+) )
|
|
|
|
, ( "-", MalFunction <| binaryOp (-) )
|
|
|
|
, ( "*", MalFunction <| binaryOp (*) )
|
|
|
|
, ( "/", MalFunction <| binaryOp (//) )
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
update : Msg -> Model -> ( Model, Cmd Msg )
|
|
|
|
update msg model =
|
|
|
|
case msg of
|
2017-06-08 20:19:27 +03:00
|
|
|
Input (Ok (LineRead (Just line))) ->
|
2017-06-05 13:23:33 +03:00
|
|
|
case rep model.env line of
|
|
|
|
Nothing ->
|
|
|
|
( model, readLine prompt )
|
|
|
|
|
|
|
|
Just ( result, newEnv ) ->
|
2017-06-08 20:19:27 +03:00
|
|
|
( { model | env = newEnv }, writeLine (makeOutput result) )
|
|
|
|
|
|
|
|
Input (Ok LineWritten) ->
|
|
|
|
( model, readLine prompt )
|
|
|
|
|
|
|
|
Input (Ok (LineRead Nothing)) ->
|
2017-06-05 13:23:33 +03:00
|
|
|
( model, Cmd.none )
|
|
|
|
|
2017-06-08 20:19:27 +03:00
|
|
|
Input (Err msg) ->
|
|
|
|
Debug.crash msg ( model, Cmd.none )
|
|
|
|
|
2017-06-05 13:23:33 +03:00
|
|
|
|
2017-06-08 20:19:27 +03:00
|
|
|
makeOutput : Result String String -> String
|
2017-06-05 13:23:33 +03:00
|
|
|
makeOutput result =
|
2017-06-08 20:19:27 +03:00
|
|
|
case result of
|
|
|
|
Ok str ->
|
|
|
|
str
|
2017-06-05 13:23:33 +03:00
|
|
|
|
2017-06-08 20:19:27 +03:00
|
|
|
Err msg ->
|
|
|
|
"ERR:" ++ msg
|
2017-06-05 13:23:33 +03:00
|
|
|
|
|
|
|
|
|
|
|
prompt : String
|
|
|
|
prompt =
|
|
|
|
"user> "
|
|
|
|
|
|
|
|
|
|
|
|
{-| read can return three things:
|
|
|
|
|
|
|
|
Ok (Just expr) -> parsed okay
|
|
|
|
Ok Nothing -> empty string (only whitespace and/or comments)
|
|
|
|
Err msg -> parse error
|
|
|
|
|
|
|
|
-}
|
|
|
|
read : String -> Result String (Maybe MalExpr)
|
|
|
|
read =
|
|
|
|
readString
|
|
|
|
|
|
|
|
|
|
|
|
eval : ReplEnv -> MalExpr -> ( Result String MalExpr, ReplEnv )
|
|
|
|
eval env ast =
|
|
|
|
case ast of
|
|
|
|
MalList [] ->
|
|
|
|
( Ok ast, env )
|
|
|
|
|
|
|
|
MalList list ->
|
|
|
|
case evalList env list [] of
|
|
|
|
( Ok newList, newEnv ) ->
|
|
|
|
case newList of
|
|
|
|
[] ->
|
|
|
|
( Err "can't happen", newEnv )
|
|
|
|
|
|
|
|
(MalFunction fn) :: args ->
|
|
|
|
( fn args, newEnv )
|
|
|
|
|
|
|
|
fn :: _ ->
|
|
|
|
( Err ((printString True fn) ++ " is not a function"), newEnv )
|
|
|
|
|
|
|
|
( Err msg, newEnv ) ->
|
|
|
|
( Err msg, newEnv )
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
evalAst env ast
|
|
|
|
|
|
|
|
|
|
|
|
evalAst : ReplEnv -> MalExpr -> ( Result String MalExpr, ReplEnv )
|
|
|
|
evalAst env ast =
|
|
|
|
case ast of
|
|
|
|
MalSymbol sym ->
|
|
|
|
-- Lookup symbol in env and return value or raise error if not found.
|
|
|
|
case Dict.get sym env of
|
|
|
|
Just val ->
|
|
|
|
( Ok val, env )
|
|
|
|
|
|
|
|
Nothing ->
|
|
|
|
( Err "symbol not found", env )
|
|
|
|
|
|
|
|
MalList list ->
|
|
|
|
-- Return new list that is result of calling eval on each element of list.
|
|
|
|
evalList env list []
|
|
|
|
|> mapFirst (Result.map MalList)
|
|
|
|
|
|
|
|
MalVector vec ->
|
|
|
|
evalList env (Array.toList vec) []
|
|
|
|
|> mapFirst (Result.map (Array.fromList >> MalVector))
|
|
|
|
|
|
|
|
MalMap map ->
|
|
|
|
evalList env (Dict.values map) []
|
|
|
|
|> mapFirst
|
|
|
|
(Result.map
|
|
|
|
(zip (Dict.keys map)
|
|
|
|
>> Dict.fromList
|
|
|
|
>> MalMap
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
( Ok ast, env )
|
|
|
|
|
|
|
|
|
|
|
|
evalList : ReplEnv -> List MalExpr -> List MalExpr -> ( Result String (List MalExpr), ReplEnv )
|
|
|
|
evalList env list acc =
|
|
|
|
case list of
|
|
|
|
[] ->
|
|
|
|
( Ok (List.reverse acc), env )
|
|
|
|
|
|
|
|
x :: rest ->
|
|
|
|
case eval env x of
|
|
|
|
( Ok val, newEnv ) ->
|
|
|
|
evalList newEnv rest (val :: acc)
|
|
|
|
|
|
|
|
( Err msg, newEnv ) ->
|
|
|
|
( Err msg, newEnv )
|
|
|
|
|
|
|
|
|
|
|
|
{-| Try to map a list with a fn that can return a Err.
|
|
|
|
|
|
|
|
Maps the list from left to right. As soon as a error
|
|
|
|
occurs it will not process any more elements and return
|
|
|
|
the error.
|
|
|
|
|
|
|
|
-}
|
|
|
|
tryMapList : (a -> Result e b) -> List a -> Result e (List b)
|
|
|
|
tryMapList fn list =
|
|
|
|
let
|
|
|
|
go x =
|
|
|
|
Result.andThen
|
|
|
|
(\acc ->
|
|
|
|
case fn x of
|
|
|
|
Ok val ->
|
|
|
|
Ok (val :: acc)
|
|
|
|
|
|
|
|
Err msg ->
|
|
|
|
Err msg
|
|
|
|
)
|
|
|
|
in
|
|
|
|
List.foldl go (Ok []) list
|
|
|
|
|> Result.map List.reverse
|
|
|
|
|
|
|
|
|
|
|
|
print : MalExpr -> String
|
|
|
|
print =
|
|
|
|
printString True
|
|
|
|
|
|
|
|
|
|
|
|
{-| Read-Eval-Print. rep returns:
|
|
|
|
|
|
|
|
Nothing -> if an empty string is read (ws/comments)
|
|
|
|
Just ((Ok out), newEnv) -> input has been evaluated.
|
|
|
|
Just ((Err msg), env) -> error parsing or evaluating.
|
|
|
|
|
|
|
|
-}
|
|
|
|
rep : ReplEnv -> String -> Maybe ( Result String String, ReplEnv )
|
|
|
|
rep env input =
|
|
|
|
let
|
|
|
|
evalPrint =
|
|
|
|
eval env >> mapFirst (Result.map print)
|
|
|
|
in
|
|
|
|
case readString input of
|
|
|
|
Ok Nothing ->
|
|
|
|
Nothing
|
|
|
|
|
|
|
|
Err msg ->
|
|
|
|
Just ( Err msg, env )
|
|
|
|
|
|
|
|
Ok (Just ast) ->
|
|
|
|
Just (evalPrint ast)
|