Elm: step5 - TCO the theory
[jackhill/mal.git] / elm / step4_if_fn_do.elm
CommitLineData
c792f15e
JB
1port module Main exposing (..)
2
3import Array
4import Dict exposing (Dict)
5import IO exposing (..)
6import Json.Decode exposing (decodeValue)
7import Platform exposing (programWithFlags)
8import Types exposing (..)
9import Reader exposing (readString)
10import Printer exposing (printString)
86fcd61d 11import Utils exposing (maybeToList, zip, last, justValues)
c792f15e
JB
12import Env
13import Core
14import Eval
15
16
17main : Program Flags Model Msg
18main =
19 programWithFlags
20 { init = init
21 , update = update
22 , subscriptions =
23 \model -> input (decodeValue decodeIO >> Input)
24 }
25
26
27type alias Flags =
28 { args : List String
29 }
30
31
86fcd61d
JB
32type Model
33 = InitIO Env (IO -> Eval MalExpr)
34 | InitError String
35 | ReplActive Env
36 | ReplIO Env (IO -> Eval MalExpr)
c792f15e
JB
37
38
39init : Flags -> ( Model, Cmd Msg )
40init { args } =
86fcd61d
JB
41 let
42 initEnv =
43 Core.ns
44
45 evalMalInit =
46 Core.malInit
47 |> List.map rep
48 |> justValues
49 |> List.foldl
50 (\b a -> a |> Eval.andThen (\_ -> b))
51 (Eval.succeed MalNil)
52 in
53 runInit initEnv evalMalInit
c792f15e
JB
54
55
56update : Msg -> Model -> ( Model, Cmd Msg )
57update msg model =
86fcd61d
JB
58 case model of
59 InitError _ ->
60 -- ignore all
61 ( model, Cmd.none )
c792f15e 62
86fcd61d 63 InitIO env cont ->
c792f15e
JB
64 case msg of
65 Input (Ok io) ->
86fcd61d 66 runInit env (cont io)
c792f15e
JB
67
68 Input (Err msg) ->
0bac0757 69 Debug.crash msg
c792f15e 70
86fcd61d
JB
71 ReplActive env ->
72 case msg of
73 Input (Ok (LineRead (Just line))) ->
74 case rep line of
75 Just expr ->
76 run env expr
c792f15e 77
86fcd61d
JB
78 Nothing ->
79 ( model, readLine prompt )
c792f15e 80
86fcd61d
JB
81 Input (Ok LineWritten) ->
82 ( model, readLine prompt )
c792f15e 83
86fcd61d
JB
84 Input (Ok (LineRead Nothing)) ->
85 -- Ctrl+D = The End.
86 ( model, Cmd.none )
c792f15e 87
0bac0757
JB
88 Input (Ok io) ->
89 Debug.crash "unexpected IO received: " io
90
86fcd61d 91 Input (Err msg) ->
0bac0757 92 Debug.crash msg
c792f15e 93
86fcd61d
JB
94 ReplIO env cont ->
95 case msg of
96 Input (Ok io) ->
97 run env (cont io)
c792f15e 98
86fcd61d
JB
99 Input (Err msg) ->
100 Debug.crash msg ( model, Cmd.none )
c792f15e 101
c792f15e 102
86fcd61d
JB
103runInit : Env -> Eval MalExpr -> ( Model, Cmd Msg )
104runInit env expr =
105 case Eval.run env expr of
106 ( env, EvalOk expr ) ->
107 -- Init went okay, start REPL.
108 ( ReplActive env, readLine prompt )
c792f15e 109
86fcd61d
JB
110 ( env, EvalErr msg ) ->
111 -- Init failed, don't start REPL.
112 ( InitError msg, writeLine ("ERR:" ++ msg) )
c792f15e 113
86fcd61d
JB
114 ( env, EvalIO cmd cont ) ->
115 -- IO in init.
116 ( InitIO env cont, cmd )
c792f15e 117
86fcd61d
JB
118
119run : Env -> Eval MalExpr -> ( Model, Cmd Msg )
120run env expr =
121 case Eval.run env expr of
122 ( env, EvalOk expr ) ->
123 ( ReplActive env, writeLine (print expr) )
124
125 ( env, EvalErr msg ) ->
126 ( ReplActive env, writeLine ("ERR:" ++ msg) )
127
128 ( env, EvalIO cmd cont ) ->
129 ( ReplIO env cont, cmd )
c792f15e
JB
130
131
132prompt : String
133prompt =
134 "user> "
135
136
137{-| read can return three things:
138
139Ok (Just expr) -> parsed okay
140Ok Nothing -> empty string (only whitespace and/or comments)
141Err msg -> parse error
142
143-}
144read : String -> Result String (Maybe MalExpr)
145read =
146 readString
147
148
149eval : MalExpr -> Eval MalExpr
150eval ast =
0bac0757
JB
151 Debug.log "eval " (printString True ast)
152 |> (\_ ->
153 case ast of
154 MalList [] ->
155 Eval.succeed ast
c792f15e 156
0bac0757
JB
157 MalList ((MalSymbol "def!") :: args) ->
158 evalDef args
c792f15e 159
0bac0757
JB
160 MalList ((MalSymbol "let*") :: args) ->
161 evalLet args
c792f15e 162
0bac0757
JB
163 MalList ((MalSymbol "do") :: args) ->
164 evalDo args
c792f15e 165
0bac0757
JB
166 MalList ((MalSymbol "if") :: args) ->
167 evalIf args
c792f15e 168
0bac0757
JB
169 MalList ((MalSymbol "fn*") :: args) ->
170 evalFn args
c792f15e 171
0bac0757
JB
172 MalList list ->
173 evalList list
174 |> Eval.andThen
175 (\newList ->
176 case newList of
177 [] ->
178 Eval.fail "can't happen"
c792f15e 179
0bac0757
JB
180 (MalFunction (CoreFunc fn)) :: args ->
181 fn args
c792f15e 182
0bac0757
JB
183 (MalFunction (UserFunc { fn })) :: args ->
184 fn args
c792f15e 185
0bac0757
JB
186 fn :: _ ->
187 Eval.fail ((printString True fn) ++ " is not a function")
188 )
189
190 _ ->
191 evalAst ast
192 )
c792f15e
JB
193
194
195evalAst : MalExpr -> Eval MalExpr
196evalAst ast =
197 case ast of
198 MalSymbol sym ->
199 -- Lookup symbol in env and return value or raise error if not found.
86fcd61d
JB
200 Eval.withEnv
201 (\env ->
202 case Env.get sym env of
c792f15e
JB
203 Ok val ->
204 Eval.succeed val
205
206 Err msg ->
207 Eval.fail msg
208 )
209
210 MalList list ->
211 -- Return new list that is result of calling eval on each element of list.
212 evalList list
213 |> Eval.map MalList
214
215 MalVector vec ->
216 evalList (Array.toList vec)
217 |> Eval.map (Array.fromList >> MalVector)
218
219 MalMap map ->
220 evalList (Dict.values map)
221 |> Eval.map
222 (zip (Dict.keys map)
223 >> Dict.fromList
224 >> MalMap
225 )
226
227 _ ->
228 Eval.succeed ast
229
230
231evalList : List MalExpr -> Eval (List MalExpr)
232evalList list =
233 let
234 go list acc =
235 case list of
236 [] ->
237 Eval.succeed (List.reverse acc)
238
239 x :: rest ->
240 eval x
241 |> Eval.andThen
242 (\val ->
243 go rest (val :: acc)
244 )
245 in
246 go list []
247
248
249evalDef : List MalExpr -> Eval MalExpr
250evalDef args =
251 case args of
252 [ MalSymbol name, uneValue ] ->
253 eval uneValue
254 |> Eval.andThen
255 (\value ->
86fcd61d 256 Eval.modifyEnv (Env.set name value)
c792f15e
JB
257 |> Eval.andThen (\_ -> Eval.succeed value)
258 )
259
260 _ ->
261 Eval.fail "def! expected two args: name and value"
262
263
264evalLet : List MalExpr -> Eval MalExpr
265evalLet args =
266 let
267 evalBinds binds =
268 case binds of
269 (MalSymbol name) :: expr :: rest ->
270 eval expr
271 |> Eval.andThen
272 (\value ->
86fcd61d 273 Eval.modifyEnv (Env.set name value)
c792f15e
JB
274 |> Eval.andThen
275 (\_ ->
276 if List.isEmpty rest then
277 Eval.succeed ()
278 else
279 evalBinds rest
280 )
281 )
282
283 _ ->
284 Eval.fail "let* expected an even number of binds (symbol expr ..)"
285
286 go binds body =
86fcd61d 287 Eval.modifyEnv Env.push
c792f15e
JB
288 |> Eval.andThen (\_ -> evalBinds binds)
289 |> Eval.andThen (\_ -> eval body)
86fcd61d
JB
290 |> Eval.andThen
291 (\res ->
0bac0757
JB
292 Eval.modifyEnv Env.pop
293 |> Eval.map (\_ -> res)
86fcd61d 294 )
c792f15e
JB
295 in
296 case args of
297 [ MalList binds, body ] ->
298 go binds body
299
300 [ MalVector bindsVec, body ] ->
301 go (Array.toList bindsVec) body
302
303 _ ->
304 Eval.fail "let* expected two args: binds and a body"
305
306
307evalDo : List MalExpr -> Eval MalExpr
308evalDo args =
309 let
310 returnLast list =
311 case last list of
312 Just value ->
313 Eval.succeed value
314
315 Nothing ->
316 Eval.fail "do expected at least one arg"
317 in
318 evalList args
319 |> Eval.andThen returnLast
320
321
322evalIf : List MalExpr -> Eval MalExpr
323evalIf args =
324 let
325 isThruthy expr =
326 expr /= MalNil && expr /= (MalBool False)
327
328 go condition trueExpr falseExpr =
329 eval condition
330 |> Eval.map isThruthy
331 |> Eval.andThen
332 (\cond ->
333 eval
334 (if cond then
335 trueExpr
336 else
337 falseExpr
338 )
339 )
340 in
341 case args of
342 [ condition, trueExpr ] ->
343 go condition trueExpr MalNil
344
345 [ condition, trueExpr, falseExpr ] ->
346 go condition trueExpr falseExpr
347
348 _ ->
349 Eval.fail "if expected at least two args"
350
351
352evalFn : List MalExpr -> Eval MalExpr
353evalFn args =
354 let
86fcd61d
JB
355 {- Extract symbols from the binds list and verify their uniqueness -}
356 extractSymbols acc list =
c792f15e
JB
357 case list of
358 [] ->
359 Ok (List.reverse acc)
360
361 (MalSymbol name) :: rest ->
86fcd61d
JB
362 if List.member name acc then
363 Err "all binds must have unique names"
364 else
365 extractSymbols (name :: acc) rest
c792f15e
JB
366
367 _ ->
368 Err "all binds in fn* must be a symbol"
369
86fcd61d
JB
370 parseBinds list =
371 case List.reverse list of
372 var :: "&" :: rest ->
373 Ok <| bindVarArgs (List.reverse rest) var
c792f15e 374
86fcd61d
JB
375 _ ->
376 if List.member "&" list then
377 Err "varargs separator '&' is used incorrectly"
378 else
379 Ok <| bindArgs list
380
381 extractAndParse =
382 extractSymbols [] >> Result.andThen parseBinds
383
384 bindArgs binds args =
385 let
386 numBinds =
387 List.length binds
388 in
389 if List.length args /= numBinds then
390 Err <|
391 "function expected "
392 ++ (toString numBinds)
393 ++ " arguments"
394 else
395 Ok <| zip binds args
396
397 bindVarArgs binds var args =
398 let
399 minArgs =
400 List.length binds
401
402 varArgs =
403 MalList (List.drop minArgs args)
404 in
405 if List.length args < minArgs then
406 Err <|
407 "function expected at least "
408 ++ (toString minArgs)
409 ++ " arguments"
410 else
411 Ok <| zip binds args ++ [ ( var, varArgs ) ]
412
0bac0757
JB
413 makeFn frameId binder body =
414 MalFunction <|
415 UserFunc
416 { frameId = frameId
417 , fn =
418 \args ->
419 case binder args of
420 Ok bound ->
421 Eval.enter frameId bound (eval body)
86fcd61d 422
0bac0757
JB
423 Err msg ->
424 Eval.fail msg
425 }
86fcd61d
JB
426
427 go bindsList body =
428 case extractAndParse bindsList of
429 Ok binder ->
430 Eval.modifyEnv Env.ref
431 -- reference the current frame.
432 |> Eval.andThen
433 (\_ ->
434 Eval.withEnv
435 (\env ->
436 Eval.succeed
0bac0757 437 (makeFn env.currentFrameId binder body)
86fcd61d
JB
438 )
439 )
c792f15e 440
86fcd61d
JB
441 Err msg ->
442 Eval.fail msg
c792f15e
JB
443 in
444 case args of
445 [ MalList bindsList, body ] ->
86fcd61d 446 go bindsList body
c792f15e 447
86fcd61d
JB
448 [ MalVector bindsVec, body ] ->
449 go (Array.toList bindsVec) body
c792f15e
JB
450
451 _ ->
452 Eval.fail "fn* expected two args: binds list and body"
453
454
455print : MalExpr -> String
456print =
457 printString True
458
459
460{-| Read-Eval-Print.
461
462Doesn't actually run the Eval but returns the monad.
463
464-}
465rep : String -> Maybe (Eval MalExpr)
466rep input =
467 case readString input of
468 Ok Nothing ->
469 Nothing
470
471 Err msg ->
472 Just (Eval.fail msg)
473
474 Ok (Just ast) ->
475 eval ast |> Just