Merge pull request #383 from asarhaddon/ada2tco-do
[jackhill/mal.git] / elm / step3_env.elm
CommitLineData
4cb2c1e4
JB
1port module Main exposing (..)
2
c792f15e
JB
3import IO exposing (..)
4import Json.Decode exposing (decodeValue)
4cb2c1e4 5import Platform exposing (programWithFlags)
74547df6 6import Types exposing (..)
4cb2c1e4
JB
7import Reader exposing (readString)
8import Printer exposing (printString)
9import Utils exposing (maybeToList, zip)
10import Dict exposing (Dict)
74547df6 11import Tuple exposing (mapFirst, mapSecond, second)
4cb2c1e4 12import Array
74547df6
JB
13import Env
14import Eval
4cb2c1e4
JB
15
16
4cb2c1e4
JB
17main : Program Flags Model Msg
18main =
19 programWithFlags
20 { init = init
21 , update = update
c792f15e 22 , subscriptions = \model -> input (decodeValue decodeIO >> Input)
4cb2c1e4
JB
23 }
24
25
26type alias Flags =
27 { args : List String
28 }
29
30
31type alias Model =
32 { args : List String
33 , env : Env
34 }
35
36
37type Msg
c792f15e 38 = Input (Result String IO)
4cb2c1e4
JB
39
40
41init : Flags -> ( Model, Cmd Msg )
42init { args } =
43 ( { args = args, env = initReplEnv }, readLine prompt )
44
45
46initReplEnv : Env
47initReplEnv =
48 let
74547df6
JB
49 makeFn =
50 CoreFunc >> MalFunction
51
4cb2c1e4
JB
52 binaryOp fn args =
53 case args of
54 [ MalInt x, MalInt y ] ->
74547df6 55 Eval.succeed <| MalInt (fn x y)
4cb2c1e4
JB
56
57 _ ->
74547df6 58 Eval.fail "unsupported arguments"
4cb2c1e4 59 in
74547df6
JB
60 Env.global
61 |> Env.set "+" (makeFn <| binaryOp (+))
62 |> Env.set "-" (makeFn <| binaryOp (-))
63 |> Env.set "*" (makeFn <| binaryOp (*))
64 |> Env.set "/" (makeFn <| binaryOp (//))
4cb2c1e4
JB
65
66
67update : Msg -> Model -> ( Model, Cmd Msg )
68update msg model =
69 case msg of
c792f15e 70 Input (Ok (LineRead (Just line))) ->
4cb2c1e4
JB
71 case rep model.env line of
72 Nothing ->
73 ( model, readLine prompt )
74
75 Just ( result, newEnv ) ->
c792f15e
JB
76 ( { model | env = newEnv }, writeLine (makeOutput result) )
77
78 Input (Ok LineWritten) ->
79 ( model, readLine prompt )
80
81 Input (Ok (LineRead Nothing)) ->
4cb2c1e4
JB
82 ( model, Cmd.none )
83
74547df6
JB
84 Input (Ok io) ->
85 Debug.crash "unexpected IO received: " io
86
c792f15e
JB
87 Input (Err msg) ->
88 Debug.crash msg ( model, Cmd.none )
89
4cb2c1e4 90
c792f15e 91makeOutput : Result String String -> String
4cb2c1e4 92makeOutput result =
c792f15e
JB
93 case result of
94 Ok str ->
95 str
4cb2c1e4 96
c792f15e 97 Err msg ->
dd7a4f55 98 "Error: " ++ msg
4cb2c1e4
JB
99
100
101prompt : String
102prompt =
103 "user> "
104
105
106{-| read can return three things:
107
108Ok (Just expr) -> parsed okay
109Ok Nothing -> empty string (only whitespace and/or comments)
110Err msg -> parse error
111
112-}
113read : String -> Result String (Maybe MalExpr)
114read =
115 readString
116
117
118eval : Env -> MalExpr -> ( Result String MalExpr, Env )
119eval env ast =
120 case ast of
121 MalList [] ->
122 ( Ok ast, env )
123
124 MalList ((MalSymbol "def!") :: args) ->
125 evalDef env args
126
127 MalList ((MalSymbol "let*") :: args) ->
128 evalLet env args
129
130 MalList list ->
131 case evalList env list [] of
132 ( Ok newList, newEnv ) ->
133 case newList of
134 [] ->
135 ( Err "can't happen", newEnv )
136
74547df6 137 (MalFunction (CoreFunc fn)) :: args ->
2110814e
JB
138 case Eval.runSimple (fn args) of
139 Ok res ->
74547df6
JB
140 ( Ok res, newEnv )
141
2110814e 142 Err msg ->
74547df6
JB
143 ( Err (print msg), newEnv )
144
4cb2c1e4 145 fn :: _ ->
74547df6 146 ( Err ((print fn) ++ " is not a function"), newEnv )
4cb2c1e4
JB
147
148 ( Err msg, newEnv ) ->
149 ( Err msg, newEnv )
150
151 _ ->
152 evalAst env ast
153
154
155evalAst : Env -> MalExpr -> ( Result String MalExpr, Env )
156evalAst env ast =
157 case ast of
158 MalSymbol sym ->
159 -- Lookup symbol in env and return value or raise error if not found.
160 case Env.get sym env of
161 Ok val ->
162 ( Ok val, env )
163
164 Err msg ->
165 ( Err msg, env )
166
167 MalList list ->
168 -- Return new list that is result of calling eval on each element of list.
169 evalList env list []
170 |> mapFirst (Result.map MalList)
171
172 MalVector vec ->
173 evalList env (Array.toList vec) []
174 |> mapFirst (Result.map (Array.fromList >> MalVector))
175
176 MalMap map ->
177 evalList env (Dict.values map) []
178 |> mapFirst
179 (Result.map
180 (zip (Dict.keys map)
181 >> Dict.fromList
182 >> MalMap
183 )
184 )
185
186 _ ->
187 ( Ok ast, env )
188
189
190evalList : Env -> List MalExpr -> List MalExpr -> ( Result String (List MalExpr), Env )
191evalList env list acc =
192 case list of
193 [] ->
194 ( Ok (List.reverse acc), env )
195
196 x :: rest ->
197 case eval env x of
198 ( Ok val, newEnv ) ->
199 evalList newEnv rest (val :: acc)
200
201 ( Err msg, newEnv ) ->
202 ( Err msg, newEnv )
203
204
205evalDef : Env -> List MalExpr -> ( Result String MalExpr, Env )
206evalDef env args =
207 case args of
208 [ MalSymbol name, uneValue ] ->
209 case eval env uneValue of
210 ( Ok value, newEnv ) ->
211 ( Ok value, Env.set name value newEnv )
212
213 err ->
214 err
215
216 _ ->
217 ( Err "def! expected two args: name and value", env )
218
219
220evalLet : Env -> List MalExpr -> ( Result String MalExpr, Env )
221evalLet env args =
222 let
223 evalBinds env binds =
224 case binds of
225 (MalSymbol name) :: expr :: rest ->
226 case eval env expr of
227 ( Ok value, newEnv ) ->
228 let
229 newEnv =
230 Env.set name value env
231 in
232 if List.isEmpty rest then
233 Ok newEnv
234 else
235 evalBinds newEnv rest
236
237 ( Err msg, _ ) ->
238 Err msg
239
240 _ ->
241 Err "let* expected an even number of binds (symbol expr ..)"
242
243 go binds body =
74547df6 244 case evalBinds (Env.push env) binds of
4cb2c1e4 245 Ok newEnv ->
74547df6
JB
246 eval newEnv body
247 |> mapSecond (\_ -> Env.pop newEnv)
4cb2c1e4
JB
248
249 Err msg ->
250 ( Err msg, env )
251 in
252 case args of
253 [ MalList binds, body ] ->
254 go binds body
255
256 [ MalVector bindsVec, body ] ->
257 go (Array.toList bindsVec) body
258
259 _ ->
260 ( Err "let* expected two args: binds and a body", env )
261
262
263{-| Try to map a list with a fn that can return a Err.
264
265Maps the list from left to right. As soon as a error
266occurs it will not process any more elements and return
267the error.
268
269-}
270tryMapList : (a -> Result e b) -> List a -> Result e (List b)
271tryMapList fn list =
272 let
273 go x =
274 Result.andThen
275 (\acc ->
276 case fn x of
277 Ok val ->
278 Ok (val :: acc)
279
280 Err msg ->
281 Err msg
282 )
283 in
284 List.foldl go (Ok []) list
285 |> Result.map List.reverse
286
287
288print : MalExpr -> String
289print =
74547df6 290 printString Env.global True
4cb2c1e4
JB
291
292
293{-| Read-Eval-Print. rep returns:
294
295Nothing -> if an empty string is read (ws/comments)
296Just ((Ok out), newEnv) -> input has been evaluated.
297Just ((Err msg), env) -> error parsing or evaluating.
298
299-}
300rep : Env -> String -> Maybe ( Result String String, Env )
301rep env input =
302 let
303 evalPrint =
304 eval env >> mapFirst (Result.map print)
305 in
306 case readString input of
307 Ok Nothing ->
308 Nothing
309
310 Err msg ->
311 Just ( Err msg, env )
312
313 Ok (Just ast) ->
314 Just (evalPrint ast)