Elm step A
[jackhill/mal.git] / elm / step8_macros.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 debug "evalNoApply"
293 (\env -> printString env True ast)
294 (macroexpand ast
295 |> Eval.andThen
296 (\ast ->
297 case ast of
298 MalList [] ->
299 Eval.succeed ast
300
301 MalList ((MalSymbol "def!") :: args) ->
302 evalDef args
303
304 MalList ((MalSymbol "let*") :: args) ->
305 evalLet args
306
307 MalList ((MalSymbol "do") :: args) ->
308 evalDo args
309
310 MalList ((MalSymbol "if") :: args) ->
311 evalIf args
312
313 MalList ((MalSymbol "fn*") :: args) ->
314 evalFn args
315
316 MalList ((MalSymbol "quote") :: args) ->
317 evalQuote args
318
319 MalList ((MalSymbol "quasiquote") :: args) ->
320 case args of
321 [ expr ] ->
322 -- TCO.
323 evalNoApply (evalQuasiQuote expr)
324
325 _ ->
326 Eval.fail "unsupported arguments"
327
328 MalList ((MalSymbol "defmacro!") :: args) ->
329 evalDefMacro args
330
331 MalList ((MalSymbol "macroexpand") :: args) ->
332 case args of
333 [ expr ] ->
334 macroexpand expr
335
336 _ ->
337 Eval.fail "unsupported arguments"
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 )
363 )
364
365
366evalAst : MalExpr -> Eval MalExpr
367evalAst ast =
368 case ast of
369 MalSymbol sym ->
370 -- Lookup symbol in env and return value or raise error if not found.
371 Eval.withEnv (Env.get sym >> Eval.fromResult)
372
373 MalList list ->
374 -- Return new list that is result of calling eval on each element of list.
375 evalList list
376 |> Eval.map MalList
377
378 MalVector vec ->
379 evalList (Array.toList vec)
380 |> Eval.map (Array.fromList >> MalVector)
381
382 MalMap map ->
383 evalList (Dict.values map)
384 |> Eval.map
385 (zip (Dict.keys map)
386 >> Dict.fromList
387 >> MalMap
388 )
389
390 _ ->
391 Eval.succeed ast
392
393
394evalList : List MalExpr -> Eval (List MalExpr)
395evalList list =
396 let
397 go list acc =
398 case list of
399 [] ->
400 Eval.succeed (List.reverse acc)
401
402 x :: rest ->
403 eval x
404 |> Eval.andThen
405 (\val ->
406 go rest (val :: acc)
407 )
408 in
409 go list []
410
411
412evalDef : List MalExpr -> Eval MalExpr
413evalDef args =
414 case args of
415 [ MalSymbol name, uneValue ] ->
416 eval uneValue
417 |> Eval.andThen
418 (\value ->
419 Eval.modifyEnv (Env.set name value)
420 |> Eval.andThen (\_ -> Eval.succeed value)
421 )
422
423 _ ->
424 Eval.fail "def! expected two args: name and value"
425
426
427evalDefMacro : List MalExpr -> Eval MalExpr
428evalDefMacro args =
429 case args of
430 [ MalSymbol name, uneValue ] ->
431 eval uneValue
432 |> Eval.andThen
433 (\value ->
434 case value of
435 MalFunction (UserFunc fn) ->
436 let
437 macroFn =
438 MalFunction (UserFunc { fn | isMacro = True })
439 in
440 Eval.modifyEnv (Env.set name macroFn)
441 |> Eval.andThen (\_ -> Eval.succeed macroFn)
442
443 _ ->
444 Eval.fail "defmacro! is only supported on a user function"
445 )
446
447 _ ->
448 Eval.fail "defmacro! expected two args: name and value"
449
450
451evalLet : List MalExpr -> Eval MalExpr
452evalLet args =
453 let
454 evalBinds binds =
455 case binds of
456 (MalSymbol name) :: expr :: rest ->
457 eval expr
458 |> Eval.andThen
459 (\value ->
460 Eval.modifyEnv (Env.set name value)
461 |> Eval.andThen
462 (\_ ->
463 if List.isEmpty rest then
464 Eval.succeed ()
465 else
466 evalBinds rest
467 )
468 )
469
470 _ ->
471 Eval.fail "let* expected an even number of binds (symbol expr ..)"
472
473 go binds body =
474 Eval.modifyEnv Env.push
475 |> Eval.andThen (\_ -> evalBinds binds)
476 |> Eval.andThen (\_ -> evalNoApply body)
477 |> Eval.ignore (Eval.modifyEnv Env.pop)
478 in
479 case args of
480 [ MalList binds, body ] ->
481 go binds body
482
483 [ MalVector bindsVec, body ] ->
484 go (Array.toList bindsVec) body
485
486 _ ->
487 Eval.fail "let* expected two args: binds and a body"
488
489
490evalDo : List MalExpr -> Eval MalExpr
491evalDo args =
492 case List.reverse args of
493 last :: rest ->
494 evalList (List.reverse rest)
495 |> Eval.andThen (\_ -> evalNoApply last)
496
497 [] ->
498 Eval.fail "do expected at least one arg"
499
500
501evalIf : List MalExpr -> Eval MalExpr
502evalIf args =
503 let
504 isThruthy expr =
505 expr /= MalNil && expr /= (MalBool False)
506
507 go condition trueExpr falseExpr =
508 eval condition
509 |> Eval.map isThruthy
510 |> Eval.andThen
511 (\cond ->
512 evalNoApply
513 (if cond then
514 trueExpr
515 else
516 falseExpr
517 )
518 )
519 in
520 case args of
521 [ condition, trueExpr ] ->
522 go condition trueExpr MalNil
523
524 [ condition, trueExpr, falseExpr ] ->
525 go condition trueExpr falseExpr
526
527 _ ->
528 Eval.fail "if expected at least two args"
529
530
531evalFn : List MalExpr -> Eval MalExpr
532evalFn args =
533 let
534 {- Extract symbols from the binds list and verify their uniqueness -}
535 extractSymbols acc list =
536 case list of
537 [] ->
538 Ok (List.reverse acc)
539
540 (MalSymbol name) :: rest ->
541 if List.member name acc then
542 Err "all binds must have unique names"
543 else
544 extractSymbols (name :: acc) rest
545
546 _ ->
547 Err "all binds in fn* must be a symbol"
548
549 parseBinds list =
550 case List.reverse list of
551 var :: "&" :: rest ->
552 Ok <| bindVarArgs (List.reverse rest) var
553
554 _ ->
555 if List.member "&" list then
556 Err "varargs separator '&' is used incorrectly"
557 else
558 Ok <| bindArgs list
559
560 extractAndParse =
561 extractSymbols [] >> Result.andThen parseBinds
562
563 bindArgs binds args =
564 let
565 numBinds =
566 List.length binds
567 in
568 if List.length args /= numBinds then
569 Err <|
570 "function expected "
571 ++ (toString numBinds)
572 ++ " arguments"
573 else
574 Ok <| zip binds args
575
576 bindVarArgs binds var args =
577 let
578 minArgs =
579 List.length binds
580
581 varArgs =
582 MalList (List.drop minArgs args)
583 in
584 if List.length args < minArgs then
585 Err <|
586 "function expected at least "
587 ++ (toString minArgs)
588 ++ " arguments"
589 else
590 Ok <| zip binds args ++ [ ( var, varArgs ) ]
591
592 makeFn frameId binder body =
593 MalFunction <|
594 let
595 lazyFn =
596 binder
597 >> Eval.fromResult
598 >> Eval.map
599 (\bound ->
600 -- TODO : choice Env.enter prematurely?
601 -- I think it is needed by the garbage collect..
602 MalApply
603 { frameId = frameId
604 , bound = bound
605 , body = body
606 }
607 )
608 in
609 UserFunc
610 { frameId = frameId
611 , lazyFn = lazyFn
612 , eagerFn = lazyFn >> Eval.andThen eval
613 , isMacro = False
c9c948de 614 , meta = Nothing
16fbc20a
JB
615 }
616
617 go bindsList body =
618 extractAndParse bindsList
619 |> Eval.fromResult
620 -- reference the current frame.
621 |> Eval.ignore (Eval.modifyEnv Env.ref)
622 |> Eval.andThen
623 (\binder ->
624 Eval.withEnv
625 (\env ->
626 Eval.succeed
627 (makeFn env.currentFrameId binder body)
628 )
629 )
630 in
631 case args of
632 [ MalList bindsList, body ] ->
633 go bindsList body
634
635 [ MalVector bindsVec, body ] ->
636 go (Array.toList bindsVec) body
637
638 _ ->
639 Eval.fail "fn* expected two args: binds list and body"
640
641
642evalQuote : List MalExpr -> Eval MalExpr
643evalQuote args =
644 case args of
645 [ expr ] ->
646 Eval.succeed expr
647
648 _ ->
649 Eval.fail "unsupported arguments"
650
651
652evalQuasiQuote : MalExpr -> MalExpr
653evalQuasiQuote expr =
654 let
655 apply list empty =
656 case list of
657 [ MalSymbol "unquote", ast ] ->
658 ast
659
660 (MalList [ MalSymbol "splice-unquote", ast ]) :: rest ->
661 makeCall "concat"
662 [ ast
663 , evalQuasiQuote (MalList rest)
664 ]
665
666 ast :: rest ->
667 makeCall "cons"
668 [ evalQuasiQuote ast
669 , evalQuasiQuote (MalList rest)
670 ]
671
672 _ ->
673 makeCall "quote" [ empty ]
674 in
675 case expr of
676 MalList list ->
677 apply list (MalList [])
678
679 MalVector vec ->
680 apply (Array.toList vec) (MalVector Array.empty)
681
682 ast ->
683 makeCall "quote" [ ast ]
684
685
686macroexpand : MalExpr -> Eval MalExpr
687macroexpand expr =
688 let
689 expand expr env =
690 case expr of
691 MalList ((MalSymbol name) :: args) ->
692 case Env.get name env of
693 Ok (MalFunction (UserFunc fn)) ->
694 if fn.isMacro then
695 Left <| fn.eagerFn args
696 else
697 Right expr
698
699 _ ->
700 Right expr
701
702 _ ->
703 Right expr
704 in
705 Eval.runLoop expand expr
706
707
708print : Env -> MalExpr -> String
709print env =
710 printString env True
711
712
c9c948de
JB
713printError : Env -> MalExpr -> String
714printError env expr =
715 "ERR:" ++ (printString env False expr)
716
717
16fbc20a
JB
718{-| Read-Eval-Print.
719
720Doesn't actually run the Eval but returns the monad.
721
722-}
723rep : String -> Maybe (Eval MalExpr)
724rep input =
725 case readString input of
726 Ok Nothing ->
727 Nothing
728
729 Err msg ->
730 Just (Eval.fail msg)
731
732 Ok (Just ast) ->
733 eval ast |> Just