Elm step A
[jackhill/mal.git] / elm / step9_try.elm
CommitLineData
16fbc20a
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, makeCall)
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 Args =
28 List String
29
30
31type alias Flags =
32 { args : Args
33 }
34
35
36type Model
37 = InitIO Args Env (IO -> Eval MalExpr)
38 | ScriptIO Env (IO -> Eval MalExpr)
39 | ReplActive Env
40 | ReplIO Env (IO -> Eval MalExpr)
41 | Stopped
42
43
44init : Flags -> ( Model, Cmd Msg )
45init { args } =
46 let
47 makeFn =
48 CoreFunc >> MalFunction
49
50 initEnv =
51 Core.ns
52 |> Env.set "eval" (makeFn malEval)
53 |> Env.set "*ARGV*" (MalList (args |> List.map MalString))
54
55 evalMalInit =
56 malInit
57 |> List.map rep
58 |> justValues
59 |> List.foldl
60 (\b a -> a |> Eval.andThen (\_ -> b))
61 (Eval.succeed MalNil)
62 in
63 runInit args initEnv evalMalInit
64
65
66malInit : List String
67malInit =
68 [ """(def! not
69 (fn* (a)
70 (if a false true)))"""
71 , """(def! load-file
72 (fn* (f)
73 (eval (read-string
74 (str "(do " (slurp f) ")")))))"""
75 , """(defmacro! cond
76 (fn* (& xs)
77 (if (> (count xs) 0)
78 (list 'if (first xs)
79 (if (> (count xs) 1)
80 (nth xs 1)
81 (throw "odd number of forms to cond"))
82 (cons 'cond (rest (rest xs)))))))"""
83 , """(defmacro! or
84 (fn* (& xs)
85 (if (empty? xs)
86 nil
87 (if (= 1 (count xs))
88 (first xs)
89 `(let* (or_FIXME ~(first xs))
90 (if or_FIXME or_FIXME (or ~@(rest xs))))))))"""
91 ]
92
93
94update : Msg -> Model -> ( Model, Cmd Msg )
95update msg model =
96 case model of
97 Stopped ->
98 ( model, Cmd.none )
99
100 InitIO args env cont ->
101 case msg of
102 Input (Ok io) ->
103 runInit args env (cont io)
104
105 Input (Err msg) ->
106 Debug.crash msg
107
108 ScriptIO env cont ->
109 case msg of
110 Input (Ok io) ->
111 runScriptLoop env (cont io)
112
113 Input (Err msg) ->
114 Debug.crash msg
115
116 ReplActive env ->
117 case msg of
118 Input (Ok (LineRead (Just line))) ->
119 case rep line of
120 Just expr ->
121 run env expr
122
123 Nothing ->
124 ( model, readLine prompt )
125
126 Input (Ok LineWritten) ->
127 ( model, readLine prompt )
128
129 Input (Ok (LineRead Nothing)) ->
130 -- Ctrl+D = The End.
131 ( model, Cmd.none )
132
133 Input (Ok io) ->
134 Debug.crash "unexpected IO received: " io
135
136 Input (Err msg) ->
137 Debug.crash msg
138
139 ReplIO env cont ->
140 case msg of
141 Input (Ok io) ->
142 run env (cont io)
143
144 Input (Err msg) ->
145 Debug.crash msg ( model, Cmd.none )
146
147
148runInit : Args -> Env -> Eval MalExpr -> ( Model, Cmd Msg )
149runInit args env expr =
150 case Eval.run env expr of
151 ( env, EvalOk expr ) ->
152 -- Init went okay.
153 case args of
154 -- If we got no args: start REPL.
155 [] ->
156 ( ReplActive env, readLine prompt )
157
158 -- Run the script in the first argument.
159 -- Put the rest of the arguments as *ARGV*.
160 filename :: argv ->
161 runScript filename argv env
162
163 ( env, EvalErr msg ) ->
164 -- Init failed, don't start REPL.
c9c948de 165 ( Stopped, writeLine (printError env msg) )
16fbc20a
JB
166
167 ( env, EvalIO cmd cont ) ->
168 -- IO in init.
169 ( InitIO args env cont, cmd )
170
171
172runScript : String -> List String -> Env -> ( Model, Cmd Msg )
173runScript filename argv env =
174 let
175 malArgv =
176 MalList (List.map MalString argv)
177
178 newEnv =
179 env |> Env.set "*ARGV*" malArgv
180
181 program =
182 MalList
183 [ MalSymbol "load-file"
184 , MalString filename
185 ]
186 in
187 runScriptLoop newEnv (eval program)
188
189
190runScriptLoop : Env -> Eval MalExpr -> ( Model, Cmd Msg )
191runScriptLoop env expr =
192 case Eval.run env expr of
193 ( env, EvalOk expr ) ->
194 ( Stopped, Cmd.none )
195
196 ( env, EvalErr msg ) ->
c9c948de 197 ( Stopped, writeLine (printError env msg) )
16fbc20a
JB
198
199 ( env, EvalIO cmd cont ) ->
200 ( ScriptIO env cont, cmd )
201
202
203run : Env -> Eval MalExpr -> ( Model, Cmd Msg )
204run env expr =
205 case Eval.run env expr of
206 ( env, EvalOk expr ) ->
207 ( ReplActive env, writeLine (print env expr) )
208
209 ( env, EvalErr msg ) ->
c9c948de 210 ( ReplActive env, writeLine (printError env msg) )
16fbc20a
JB
211
212 ( env, EvalIO cmd cont ) ->
213 ( ReplIO env cont, cmd )
214
215
216prompt : String
217prompt =
218 "user> "
219
220
221{-| read can return three things:
222
223Ok (Just expr) -> parsed okay
224Ok Nothing -> empty string (only whitespace and/or comments)
225Err msg -> parse error
226
227-}
228read : String -> Result String (Maybe MalExpr)
229read =
230 readString
231
232
233debug : String -> (Env -> a) -> Eval b -> Eval b
234debug msg f e =
235 Eval.withEnv
236 (\env ->
237 Env.debug env msg (f env)
238 |> always e
239 )
240
241
242eval : MalExpr -> Eval MalExpr
243eval ast =
244 let
245 apply expr env =
246 case expr of
247 MalApply app ->
248 Left
249 (debug "evalApply"
250 (\env -> printString env True expr)
251 (evalApply app)
252 )
253
254 _ ->
255 Right expr
256 in
257 evalNoApply ast
258 |> Eval.andThen (Eval.runLoop apply)
259
260
261malEval : List MalExpr -> Eval MalExpr
262malEval args =
263 case args of
264 [ expr ] ->
265 Eval.withEnv
266 (\env ->
267 Eval.modifyEnv (Env.jump Env.globalFrameId)
268 |> Eval.andThen (\_ -> eval expr)
269 |> Eval.andThen
270 (\res ->
271 Eval.modifyEnv (Env.jump env.currentFrameId)
272 |> Eval.andThen (\_ -> Eval.succeed res)
273 )
274 )
275
276 _ ->
277 Eval.fail "unsupported arguments"
278
279
280evalApply : ApplyRec -> Eval MalExpr
281evalApply { frameId, bound, body } =
282 Eval.withEnv
283 (\env ->
284 Eval.modifyEnv (Env.enter frameId bound)
285 |> Eval.andThen (\_ -> evalNoApply body)
286 |> Eval.ignore (Eval.modifyEnv (Env.leave env.currentFrameId))
287 )
288
289
290evalNoApply : MalExpr -> Eval MalExpr
291evalNoApply ast =
292 let
293 go ast =
294 case ast of
295 MalList [] ->
296 Eval.succeed ast
297
298 MalList ((MalSymbol "def!") :: args) ->
299 evalDef args
300
301 MalList ((MalSymbol "let*") :: args) ->
302 evalLet args
303
304 MalList ((MalSymbol "do") :: args) ->
305 evalDo args
306
307 MalList ((MalSymbol "if") :: args) ->
308 evalIf args
309
310 MalList ((MalSymbol "fn*") :: args) ->
311 evalFn args
312
313 MalList ((MalSymbol "quote") :: args) ->
314 evalQuote args
315
316 MalList ((MalSymbol "quasiquote") :: args) ->
317 case args of
318 [ expr ] ->
319 -- TCO.
320 evalNoApply (evalQuasiQuote expr)
321
322 _ ->
323 Eval.fail "unsupported arguments"
324
325 MalList ((MalSymbol "defmacro!") :: args) ->
326 evalDefMacro args
327
328 MalList ((MalSymbol "macroexpand") :: args) ->
329 case args of
330 [ expr ] ->
331 macroexpand expr
332
333 _ ->
334 Eval.fail "unsupported arguments"
335
336 MalList ((MalSymbol "try*") :: args) ->
337 evalTry args
338
339 MalList list ->
340 evalList list
341 |> Eval.andThen
342 (\newList ->
343 case newList of
344 [] ->
345 Eval.fail "can't happen"
346
347 (MalFunction (CoreFunc fn)) :: args ->
348 fn args
349
350 (MalFunction (UserFunc { lazyFn })) :: args ->
351 lazyFn args
352
353 fn :: _ ->
354 Eval.withEnv
355 (\env ->
356 Eval.fail ((printString env True fn) ++ " is not a function")
357 )
358 )
359
360 _ ->
361 evalAst ast
362 in
363 debug "evalNoApply"
364 (\env -> printString env True ast)
365 (macroexpand ast |> Eval.andThen go)
366
367
368evalAst : MalExpr -> Eval MalExpr
369evalAst ast =
370 case ast of
371 MalSymbol sym ->
372 -- Lookup symbol in env and return value or raise error if not found.
373 Eval.withEnv (Env.get sym >> Eval.fromResult)
374
375 MalList list ->
376 -- Return new list that is result of calling eval on each element of list.
377 evalList list
378 |> Eval.map MalList
379
380 MalVector vec ->
381 evalList (Array.toList vec)
382 |> Eval.map (Array.fromList >> MalVector)
383
384 MalMap map ->
385 evalList (Dict.values map)
386 |> Eval.map
387 (zip (Dict.keys map)
388 >> Dict.fromList
389 >> MalMap
390 )
391
392 _ ->
393 Eval.succeed ast
394
395
396evalList : List MalExpr -> Eval (List MalExpr)
397evalList list =
398 let
399 go list acc =
400 case list of
401 [] ->
402 Eval.succeed (List.reverse acc)
403
404 x :: rest ->
405 eval x
406 |> Eval.andThen
407 (\val ->
408 go rest (val :: acc)
409 )
410 in
411 go list []
412
413
414evalDef : List MalExpr -> Eval MalExpr
415evalDef args =
416 case args of
417 [ MalSymbol name, uneValue ] ->
418 eval uneValue
419 |> Eval.andThen
420 (\value ->
421 Eval.modifyEnv (Env.set name value)
422 |> Eval.andThen (\_ -> Eval.succeed value)
423 )
424
425 _ ->
426 Eval.fail "def! expected two args: name and value"
427
428
429evalDefMacro : List MalExpr -> Eval MalExpr
430evalDefMacro args =
431 case args of
432 [ MalSymbol name, uneValue ] ->
433 eval uneValue
434 |> Eval.andThen
435 (\value ->
436 case value of
437 MalFunction (UserFunc fn) ->
438 let
439 macroFn =
440 MalFunction (UserFunc { fn | isMacro = True })
441 in
442 Eval.modifyEnv (Env.set name macroFn)
443 |> Eval.andThen (\_ -> Eval.succeed macroFn)
444
445 _ ->
446 Eval.fail "defmacro! is only supported on a user function"
447 )
448
449 _ ->
450 Eval.fail "defmacro! expected two args: name and value"
451
452
453evalLet : List MalExpr -> Eval MalExpr
454evalLet args =
455 let
456 evalBinds binds =
457 case binds of
458 (MalSymbol name) :: expr :: rest ->
459 eval expr
460 |> Eval.andThen
461 (\value ->
462 Eval.modifyEnv (Env.set name value)
463 |> Eval.andThen
464 (\_ ->
465 if List.isEmpty rest then
466 Eval.succeed ()
467 else
468 evalBinds rest
469 )
470 )
471
472 _ ->
473 Eval.fail "let* expected an even number of binds (symbol expr ..)"
474
475 go binds body =
476 Eval.modifyEnv Env.push
477 |> Eval.andThen (\_ -> evalBinds binds)
478 |> Eval.andThen (\_ -> evalNoApply body)
479 |> Eval.ignore (Eval.modifyEnv Env.pop)
480 in
481 case args of
482 [ MalList binds, body ] ->
483 go binds body
484
485 [ MalVector bindsVec, body ] ->
486 go (Array.toList bindsVec) body
487
488 _ ->
489 Eval.fail "let* expected two args: binds and a body"
490
491
492evalDo : List MalExpr -> Eval MalExpr
493evalDo args =
494 case List.reverse args of
495 last :: rest ->
496 evalList (List.reverse rest)
497 |> Eval.andThen (\_ -> evalNoApply last)
498
499 [] ->
500 Eval.fail "do expected at least one arg"
501
502
503evalIf : List MalExpr -> Eval MalExpr
504evalIf args =
505 let
506 isThruthy expr =
507 expr /= MalNil && expr /= (MalBool False)
508
509 go condition trueExpr falseExpr =
510 eval condition
511 |> Eval.map isThruthy
512 |> Eval.andThen
513 (\cond ->
514 evalNoApply
515 (if cond then
516 trueExpr
517 else
518 falseExpr
519 )
520 )
521 in
522 case args of
523 [ condition, trueExpr ] ->
524 go condition trueExpr MalNil
525
526 [ condition, trueExpr, falseExpr ] ->
527 go condition trueExpr falseExpr
528
529 _ ->
530 Eval.fail "if expected at least two args"
531
532
533evalFn : List MalExpr -> Eval MalExpr
534evalFn args =
535 let
536 {- Extract symbols from the binds list and verify their uniqueness -}
537 extractSymbols acc list =
538 case list of
539 [] ->
540 Ok (List.reverse acc)
541
542 (MalSymbol name) :: rest ->
543 if List.member name acc then
544 Err "all binds must have unique names"
545 else
546 extractSymbols (name :: acc) rest
547
548 _ ->
549 Err "all binds in fn* must be a symbol"
550
551 parseBinds list =
552 case List.reverse list of
553 var :: "&" :: rest ->
554 Ok <| bindVarArgs (List.reverse rest) var
555
556 _ ->
557 if List.member "&" list then
558 Err "varargs separator '&' is used incorrectly"
559 else
560 Ok <| bindArgs list
561
562 extractAndParse =
563 extractSymbols [] >> Result.andThen parseBinds
564
565 bindArgs binds args =
566 let
567 numBinds =
568 List.length binds
569 in
570 if List.length args /= numBinds then
571 Err <|
572 "function expected "
573 ++ (toString numBinds)
574 ++ " arguments"
575 else
576 Ok <| zip binds args
577
578 bindVarArgs binds var args =
579 let
580 minArgs =
581 List.length binds
582
583 varArgs =
584 MalList (List.drop minArgs args)
585 in
586 if List.length args < minArgs then
587 Err <|
588 "function expected at least "
589 ++ (toString minArgs)
590 ++ " arguments"
591 else
592 Ok <| zip binds args ++ [ ( var, varArgs ) ]
593
594 makeFn frameId binder body =
595 MalFunction <|
596 let
597 lazyFn =
598 binder
599 >> Eval.fromResult
600 >> Eval.map
601 (\bound ->
602 -- TODO : choice Env.enter prematurely?
603 -- I think it is needed by the garbage collect..
604 MalApply
605 { frameId = frameId
606 , bound = bound
607 , body = body
608 }
609 )
610 in
611 UserFunc
612 { frameId = frameId
613 , lazyFn = lazyFn
614 , eagerFn = lazyFn >> Eval.andThen eval
615 , isMacro = False
c9c948de 616 , meta = Nothing
16fbc20a
JB
617 }
618
619 go bindsList body =
620 extractAndParse bindsList
621 |> Eval.fromResult
622 -- reference the current frame.
623 |> Eval.ignore (Eval.modifyEnv Env.ref)
624 |> Eval.andThen
625 (\binder ->
626 Eval.withEnv
627 (\env ->
628 Eval.succeed
629 (makeFn env.currentFrameId binder body)
630 )
631 )
632 in
633 case args of
634 [ MalList bindsList, body ] ->
635 go bindsList body
636
637 [ MalVector bindsVec, body ] ->
638 go (Array.toList bindsVec) body
639
640 _ ->
641 Eval.fail "fn* expected two args: binds list and body"
642
643
644evalQuote : List MalExpr -> Eval MalExpr
645evalQuote args =
646 case args of
647 [ expr ] ->
648 Eval.succeed expr
649
650 _ ->
651 Eval.fail "unsupported arguments"
652
653
654evalQuasiQuote : MalExpr -> MalExpr
655evalQuasiQuote expr =
656 let
657 apply list empty =
658 case list of
659 [ MalSymbol "unquote", ast ] ->
660 ast
661
662 (MalList [ MalSymbol "splice-unquote", ast ]) :: rest ->
663 makeCall "concat"
664 [ ast
665 , evalQuasiQuote (MalList rest)
666 ]
667
668 ast :: rest ->
669 makeCall "cons"
670 [ evalQuasiQuote ast
671 , evalQuasiQuote (MalList rest)
672 ]
673
674 _ ->
675 makeCall "quote" [ empty ]
676 in
677 case expr of
678 MalList list ->
679 apply list (MalList [])
680
681 MalVector vec ->
682 apply (Array.toList vec) (MalVector Array.empty)
683
684 ast ->
685 makeCall "quote" [ ast ]
686
687
688macroexpand : MalExpr -> Eval MalExpr
689macroexpand expr =
690 let
691 expand expr env =
692 case expr of
693 MalList ((MalSymbol name) :: args) ->
694 case Env.get name env of
695 Ok (MalFunction (UserFunc fn)) ->
696 if fn.isMacro then
697 Left <| fn.eagerFn args
698 else
699 Right expr
700
701 _ ->
702 Right expr
703
704 _ ->
705 Right expr
706 in
707 Eval.runLoop expand expr
708
709
710evalTry : List MalExpr -> Eval MalExpr
711evalTry args =
712 case args of
713 [ body, MalList [ MalSymbol "catch*", MalSymbol sym, handler ] ] ->
714 eval body
715 |> Eval.catchError
c9c948de 716 (\ex ->
16fbc20a
JB
717 Eval.modifyEnv Env.push
718 |> Eval.andThen
719 (\_ ->
c9c948de 720 Eval.modifyEnv (Env.set sym ex)
16fbc20a
JB
721 )
722 |> Eval.andThen (\_ -> evalNoApply handler)
723 |> Eval.ignore (Eval.modifyEnv Env.pop)
724 )
725
726 _ ->
727 Eval.fail "try* expected a body a catch block"
728
729
730print : Env -> MalExpr -> String
731print env =
732 printString env True
733
734
c9c948de
JB
735printError : Env -> MalExpr -> String
736printError env expr =
737 "ERR:" ++ (printString env False expr)
738
739
16fbc20a
JB
740{-| Read-Eval-Print.
741
742Doesn't actually run the Eval but returns the monad.
743
744-}
745rep : String -> Maybe (Eval MalExpr)
746rep input =
747 case readString input of
748 Ok Nothing ->
749 Nothing
750
751 Err msg ->
752 Just (Eval.fail msg)
753
754 Ok (Just ast) ->
755 eval ast |> Just