Change quasiquote algorithm
[jackhill/mal.git] / impls / elm / step7_quote.elm
CommitLineData
fe50dd7a
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)
16fbc20a 11import Utils exposing (maybeToList, zip, last, justValues, makeCall)
fe50dd7a
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 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 =
16fbc20a 56 malInit
fe50dd7a
JB
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
16fbc20a
JB
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
e6d41de4 74 (str "(do " (slurp f) "\nnil)")))))"""
16fbc20a
JB
75 ]
76
77
fe50dd7a
JB
78update : Msg -> Model -> ( Model, Cmd Msg )
79update msg model =
80 case model of
81 Stopped ->
82 ( model, Cmd.none )
83
84 InitIO args env cont ->
85 case msg of
86 Input (Ok io) ->
87 runInit args env (cont io)
88
89 Input (Err msg) ->
90 Debug.crash msg
91
92 ScriptIO env cont ->
93 case msg of
94 Input (Ok io) ->
95 runScriptLoop env (cont io)
96
97 Input (Err msg) ->
98 Debug.crash msg
99
100 ReplActive env ->
101 case msg of
102 Input (Ok (LineRead (Just line))) ->
103 case rep line of
104 Just expr ->
105 run env expr
106
107 Nothing ->
108 ( model, readLine prompt )
109
110 Input (Ok LineWritten) ->
111 ( model, readLine prompt )
112
113 Input (Ok (LineRead Nothing)) ->
114 -- Ctrl+D = The End.
115 ( model, Cmd.none )
116
117 Input (Ok io) ->
118 Debug.crash "unexpected IO received: " io
119
120 Input (Err msg) ->
121 Debug.crash msg
122
123 ReplIO env cont ->
124 case msg of
125 Input (Ok io) ->
126 run env (cont io)
127
128 Input (Err msg) ->
129 Debug.crash msg ( model, Cmd.none )
130
131
132runInit : Args -> Env -> Eval MalExpr -> ( Model, Cmd Msg )
133runInit args env expr =
134 case Eval.run env expr of
135 ( env, EvalOk expr ) ->
136 -- Init went okay.
137 case args of
138 -- If we got no args: start REPL.
139 [] ->
140 ( ReplActive env, readLine prompt )
141
142 -- Run the script in the first argument.
143 -- Put the rest of the arguments as *ARGV*.
144 filename :: argv ->
145 runScript filename argv env
146
147 ( env, EvalErr msg ) ->
148 -- Init failed, don't start REPL.
c9c948de 149 ( Stopped, writeLine (printError env msg) )
fe50dd7a
JB
150
151 ( env, EvalIO cmd cont ) ->
152 -- IO in init.
153 ( InitIO args env cont, cmd )
154
155
156runScript : String -> List String -> Env -> ( Model, Cmd Msg )
157runScript filename argv env =
158 let
159 malArgv =
160 MalList (List.map MalString argv)
161
162 newEnv =
163 env |> Env.set "*ARGV*" malArgv
164
165 program =
166 MalList
167 [ MalSymbol "load-file"
168 , MalString filename
169 ]
170 in
171 runScriptLoop newEnv (eval program)
172
173
174runScriptLoop : Env -> Eval MalExpr -> ( Model, Cmd Msg )
175runScriptLoop env expr =
176 case Eval.run env expr of
177 ( env, EvalOk expr ) ->
178 ( Stopped, Cmd.none )
179
180 ( env, EvalErr msg ) ->
c9c948de 181 ( Stopped, writeLine (printError env msg) )
fe50dd7a
JB
182
183 ( env, EvalIO cmd cont ) ->
184 ( ScriptIO env cont, cmd )
185
186
187run : Env -> Eval MalExpr -> ( Model, Cmd Msg )
188run env expr =
189 case Eval.run env expr of
190 ( env, EvalOk expr ) ->
191 ( ReplActive env, writeLine (print env expr) )
192
193 ( env, EvalErr msg ) ->
c9c948de 194 ( ReplActive env, writeLine (printError env msg) )
fe50dd7a
JB
195
196 ( env, EvalIO cmd cont ) ->
197 ( ReplIO env cont, cmd )
198
199
200prompt : String
201prompt =
202 "user> "
203
204
205{-| read can return three things:
206
207Ok (Just expr) -> parsed okay
208Ok Nothing -> empty string (only whitespace and/or comments)
209Err msg -> parse error
210
211-}
212read : String -> Result String (Maybe MalExpr)
213read =
214 readString
215
216
217debug : String -> (Env -> a) -> Eval b -> Eval b
218debug msg f e =
219 Eval.withEnv
220 (\env ->
221 Env.debug env msg (f env)
222 |> always e
223 )
224
225
226eval : MalExpr -> Eval MalExpr
227eval ast =
228 let
16fbc20a 229 apply expr env =
fe50dd7a
JB
230 case expr of
231 MalApply app ->
232 Left
233 (debug "evalApply"
234 (\env -> printString env True expr)
235 (evalApply app)
236 )
237
238 _ ->
239 Right expr
240 in
241 evalNoApply ast
242 |> Eval.andThen (Eval.runLoop apply)
243
244
245malEval : List MalExpr -> Eval MalExpr
246malEval args =
247 case args of
248 [ expr ] ->
b346116e 249 Eval.inGlobal (eval expr)
fe50dd7a
JB
250
251 _ ->
252 Eval.fail "unsupported arguments"
253
254
255evalApply : ApplyRec -> Eval MalExpr
256evalApply { frameId, bound, body } =
257 Eval.withEnv
258 (\env ->
259 Eval.modifyEnv (Env.enter frameId bound)
260 |> Eval.andThen (\_ -> evalNoApply body)
4c696bfb 261 |> Eval.finally Env.leave
b346116e 262 |> Eval.gcPass
fe50dd7a
JB
263 )
264
265
266evalNoApply : MalExpr -> Eval MalExpr
267evalNoApply ast =
268 debug "evalNoApply"
269 (\env -> printString env True ast)
270 (case ast of
271 MalList [] ->
272 Eval.succeed ast
273
274 MalList ((MalSymbol "def!") :: args) ->
275 evalDef args
276
277 MalList ((MalSymbol "let*") :: args) ->
278 evalLet args
279
280 MalList ((MalSymbol "do") :: args) ->
281 evalDo args
282
283 MalList ((MalSymbol "if") :: args) ->
284 evalIf args
285
286 MalList ((MalSymbol "fn*") :: args) ->
287 evalFn args
288
16fbc20a
JB
289 MalList ((MalSymbol "quote") :: args) ->
290 evalQuote args
291
fbfe6784
NB
292 MalList [MalSymbol "quasiquoteexpand", expr] ->
293 Eval.succeed <| evalQuasiQuote expr
294 MalList (MalSymbol "quasiquoteexpand" :: _) ->
295 Eval.fail "quasiquoteexpand: arg count"
296
16fbc20a
JB
297 MalList ((MalSymbol "quasiquote") :: args) ->
298 case args of
299 [ expr ] ->
300 -- TCO.
301 evalNoApply (evalQuasiQuote expr)
302
303 _ ->
304 Eval.fail "unsupported arguments"
305
fe50dd7a
JB
306 MalList list ->
307 evalList list
308 |> Eval.andThen
309 (\newList ->
310 case newList of
311 [] ->
312 Eval.fail "can't happen"
313
314 (MalFunction (CoreFunc fn)) :: args ->
315 fn args
316
317 (MalFunction (UserFunc { lazyFn })) :: args ->
318 lazyFn args
319
320 fn :: _ ->
321 Eval.withEnv
322 (\env ->
323 Eval.fail ((printString env True fn) ++ " is not a function")
324 )
325 )
326
327 _ ->
328 evalAst ast
329 )
330
331
332evalAst : MalExpr -> Eval MalExpr
333evalAst ast =
334 case ast of
335 MalSymbol sym ->
336 -- Lookup symbol in env and return value or raise error if not found.
337 Eval.withEnv (Env.get sym >> Eval.fromResult)
338
339 MalList list ->
340 -- Return new list that is result of calling eval on each element of list.
341 evalList list
342 |> Eval.map MalList
343
344 MalVector vec ->
345 evalList (Array.toList vec)
346 |> Eval.map (Array.fromList >> MalVector)
347
348 MalMap map ->
349 evalList (Dict.values map)
350 |> Eval.map
351 (zip (Dict.keys map)
352 >> Dict.fromList
353 >> MalMap
354 )
355
356 _ ->
357 Eval.succeed ast
358
359
360evalList : List MalExpr -> Eval (List MalExpr)
361evalList list =
362 let
363 go list acc =
364 case list of
365 [] ->
366 Eval.succeed (List.reverse acc)
367
368 x :: rest ->
369 eval x
370 |> Eval.andThen
371 (\val ->
372 go rest (val :: acc)
373 )
374 in
375 go list []
376
377
378evalDef : List MalExpr -> Eval MalExpr
379evalDef args =
380 case args of
381 [ MalSymbol name, uneValue ] ->
382 eval uneValue
383 |> Eval.andThen
384 (\value ->
071ce8a8 385 Eval.modifyEnv (Env.set name value)
fe50dd7a
JB
386 |> Eval.andThen (\_ -> Eval.succeed value)
387 )
388
389 _ ->
390 Eval.fail "def! expected two args: name and value"
391
392
393evalLet : List MalExpr -> Eval MalExpr
394evalLet args =
395 let
396 evalBinds binds =
397 case binds of
398 (MalSymbol name) :: expr :: rest ->
399 eval expr
400 |> Eval.andThen
401 (\value ->
402 Eval.modifyEnv (Env.set name value)
403 |> Eval.andThen
404 (\_ ->
405 if List.isEmpty rest then
406 Eval.succeed ()
407 else
408 evalBinds rest
409 )
410 )
411
412 _ ->
413 Eval.fail "let* expected an even number of binds (symbol expr ..)"
414
415 go binds body =
416 Eval.modifyEnv Env.push
417 |> Eval.andThen (\_ -> evalBinds binds)
418 |> Eval.andThen (\_ -> evalNoApply body)
cfb42291 419 |> Eval.finally Env.pop
fe50dd7a
JB
420 in
421 case args of
422 [ MalList binds, body ] ->
423 go binds body
424
425 [ MalVector bindsVec, body ] ->
426 go (Array.toList bindsVec) body
427
428 _ ->
429 Eval.fail "let* expected two args: binds and a body"
430
431
432evalDo : List MalExpr -> Eval MalExpr
433evalDo args =
434 case List.reverse args of
435 last :: rest ->
436 evalList (List.reverse rest)
437 |> Eval.andThen (\_ -> evalNoApply last)
438
439 [] ->
440 Eval.fail "do expected at least one arg"
441
442
443evalIf : List MalExpr -> Eval MalExpr
444evalIf args =
445 let
446 isThruthy expr =
447 expr /= MalNil && expr /= (MalBool False)
448
449 go condition trueExpr falseExpr =
450 eval condition
451 |> Eval.map isThruthy
452 |> Eval.andThen
453 (\cond ->
454 evalNoApply
455 (if cond then
456 trueExpr
457 else
458 falseExpr
459 )
460 )
461 in
462 case args of
463 [ condition, trueExpr ] ->
464 go condition trueExpr MalNil
465
466 [ condition, trueExpr, falseExpr ] ->
467 go condition trueExpr falseExpr
468
469 _ ->
470 Eval.fail "if expected at least two args"
471
472
473evalFn : List MalExpr -> Eval MalExpr
474evalFn args =
475 let
476 {- Extract symbols from the binds list and verify their uniqueness -}
477 extractSymbols acc list =
478 case list of
479 [] ->
480 Ok (List.reverse acc)
481
482 (MalSymbol name) :: rest ->
483 if List.member name acc then
484 Err "all binds must have unique names"
485 else
486 extractSymbols (name :: acc) rest
487
488 _ ->
489 Err "all binds in fn* must be a symbol"
490
491 parseBinds list =
492 case List.reverse list of
493 var :: "&" :: rest ->
494 Ok <| bindVarArgs (List.reverse rest) var
495
496 _ ->
497 if List.member "&" list then
498 Err "varargs separator '&' is used incorrectly"
499 else
500 Ok <| bindArgs list
501
502 extractAndParse =
503 extractSymbols [] >> Result.andThen parseBinds
504
505 bindArgs binds args =
506 let
507 numBinds =
508 List.length binds
509 in
510 if List.length args /= numBinds then
511 Err <|
512 "function expected "
513 ++ (toString numBinds)
514 ++ " arguments"
515 else
516 Ok <| zip binds args
517
518 bindVarArgs binds var args =
519 let
520 minArgs =
521 List.length binds
522
523 varArgs =
524 MalList (List.drop minArgs args)
525 in
526 if List.length args < minArgs then
527 Err <|
528 "function expected at least "
529 ++ (toString minArgs)
530 ++ " arguments"
531 else
532 Ok <| zip binds args ++ [ ( var, varArgs ) ]
533
534 makeFn frameId binder body =
535 MalFunction <|
536 let
537 lazyFn =
538 binder
539 >> Eval.fromResult
540 >> Eval.map
541 (\bound ->
fe50dd7a
JB
542 MalApply
543 { frameId = frameId
544 , bound = bound
545 , body = body
546 }
547 )
548 in
549 UserFunc
550 { frameId = frameId
551 , lazyFn = lazyFn
b346116e 552 , eagerFn = lazyFn >> Eval.andThen eval
16fbc20a 553 , isMacro = False
c9c948de 554 , meta = Nothing
fe50dd7a
JB
555 }
556
557 go bindsList body =
558 extractAndParse bindsList
559 |> Eval.fromResult
49672689
JB
560 -- reference the current frame.
561 |> Eval.ignore (Eval.modifyEnv Env.ref)
fe50dd7a
JB
562 |> Eval.andThen
563 (\binder ->
49672689
JB
564 Eval.withEnv
565 (\env ->
566 Eval.succeed
567 (makeFn env.currentFrameId binder body)
568 )
fe50dd7a
JB
569 )
570 in
571 case args of
572 [ MalList bindsList, body ] ->
573 go bindsList body
574
575 [ MalVector bindsVec, body ] ->
576 go (Array.toList bindsVec) body
577
578 _ ->
579 Eval.fail "fn* expected two args: binds list and body"
580
581
16fbc20a
JB
582evalQuote : List MalExpr -> Eval MalExpr
583evalQuote args =
584 case args of
585 [ expr ] ->
586 Eval.succeed expr
587
588 _ ->
589 Eval.fail "unsupported arguments"
590
591
592evalQuasiQuote : MalExpr -> MalExpr
593evalQuasiQuote expr =
594 let
fbfe6784
NB
595 qq_loop : MalExpr -> MalExpr -> MalExpr
596 qq_loop elt acc =
597 case elt of
598 (MalList [MalSymbol "splice-unquote", form]) ->
599 MalList <| [MalSymbol "concat", form, acc ]
16fbc20a 600 _ ->
fbfe6784 601 MalList <| [MalSymbol "cons", evalQuasiQuote elt, acc ]
16fbc20a
JB
602 in
603 case expr of
fbfe6784
NB
604 (MalList [MalSymbol "unquote", form]) ->
605 form
606 (MalList xs) ->
607 List.foldr qq_loop (MalList []) xs
608 (MalVector xs) ->
609 MalList <| (\x -> [MalSymbol "vec", x]) <| Array.foldr qq_loop (MalList []) xs
610 (MalSymbol _) ->
611 MalList <| [MalSymbol "quote", expr]
612 (MalMap _) ->
613 MalList <| [MalSymbol "quote", expr]
614 _ ->
615 expr
16fbc20a
JB
616
617
fe50dd7a
JB
618print : Env -> MalExpr -> String
619print env =
620 printString env True
621
622
c9c948de
JB
623printError : Env -> MalExpr -> String
624printError env expr =
dd7a4f55 625 "Error: " ++ (printString env False expr)
c9c948de
JB
626
627
fe50dd7a
JB
628{-| Read-Eval-Print.
629
630Doesn't actually run the Eval but returns the monad.
631
632-}
633rep : String -> Maybe (Eval MalExpr)
634rep input =
635 case readString input of
636 Ok Nothing ->
637 Nothing
638
639 Err msg ->
640 Just (Eval.fail msg)
641
642 Ok (Just ast) ->
643 eval ast |> Just