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