Commit | Line | Data |
---|---|---|
fe50dd7a JB |
1 | port module Main exposing (..) |
2 | ||
3 | import Array | |
4 | import Dict exposing (Dict) | |
5 | import IO exposing (..) | |
6 | import Json.Decode exposing (decodeValue) | |
7 | import Platform exposing (programWithFlags) | |
8 | import Types exposing (..) | |
9 | import Reader exposing (readString) | |
10 | import Printer exposing (printString) | |
16fbc20a | 11 | import Utils exposing (maybeToList, zip, last, justValues, makeCall) |
fe50dd7a JB |
12 | import Env |
13 | import Core | |
14 | import Eval | |
15 | ||
16 | ||
17 | main : Program Flags Model Msg | |
18 | main = | |
19 | programWithFlags | |
20 | { init = init | |
21 | , update = update | |
22 | , subscriptions = | |
23 | \model -> input (decodeValue decodeIO >> Input) | |
24 | } | |
25 | ||
26 | ||
27 | type alias Args = | |
28 | List String | |
29 | ||
30 | ||
31 | type alias Flags = | |
32 | { args : Args | |
33 | } | |
34 | ||
35 | ||
36 | type 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 | ||
44 | init : Flags -> ( Model, Cmd Msg ) | |
45 | init { 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 |
66 | malInit : List String |
67 | malInit = | |
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 |
78 | update : Msg -> Model -> ( Model, Cmd Msg ) |
79 | update 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 | ||
132 | runInit : Args -> Env -> Eval MalExpr -> ( Model, Cmd Msg ) | |
133 | runInit 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 | ||
156 | runScript : String -> List String -> Env -> ( Model, Cmd Msg ) | |
157 | runScript 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 | ||
174 | runScriptLoop : Env -> Eval MalExpr -> ( Model, Cmd Msg ) | |
175 | runScriptLoop 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 | ||
187 | run : Env -> Eval MalExpr -> ( Model, Cmd Msg ) | |
188 | run 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 | ||
200 | prompt : String | |
201 | prompt = | |
202 | "user> " | |
203 | ||
204 | ||
205 | {-| read can return three things: | |
206 | ||
207 | Ok (Just expr) -> parsed okay | |
208 | Ok Nothing -> empty string (only whitespace and/or comments) | |
209 | Err msg -> parse error | |
210 | ||
211 | -} | |
212 | read : String -> Result String (Maybe MalExpr) | |
213 | read = | |
214 | readString | |
215 | ||
216 | ||
217 | debug : String -> (Env -> a) -> Eval b -> Eval b | |
218 | debug msg f e = | |
219 | Eval.withEnv | |
220 | (\env -> | |
221 | Env.debug env msg (f env) | |
222 | |> always e | |
223 | ) | |
224 | ||
225 | ||
226 | eval : MalExpr -> Eval MalExpr | |
227 | eval 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 | ||
245 | malEval : List MalExpr -> Eval MalExpr | |
246 | malEval args = | |
247 | case args of | |
248 | [ expr ] -> | |
b346116e | 249 | Eval.inGlobal (eval expr) |
fe50dd7a JB |
250 | |
251 | _ -> | |
252 | Eval.fail "unsupported arguments" | |
253 | ||
254 | ||
255 | evalApply : ApplyRec -> Eval MalExpr | |
256 | evalApply { 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 | ||
266 | evalNoApply : MalExpr -> Eval MalExpr | |
267 | evalNoApply 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 | ||
332 | evalAst : MalExpr -> Eval MalExpr | |
333 | evalAst 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 | ||
360 | evalList : List MalExpr -> Eval (List MalExpr) | |
361 | evalList 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 | ||
378 | evalDef : List MalExpr -> Eval MalExpr | |
379 | evalDef 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 | ||
393 | evalLet : List MalExpr -> Eval MalExpr | |
394 | evalLet 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 | ||
432 | evalDo : List MalExpr -> Eval MalExpr | |
433 | evalDo 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 | ||
443 | evalIf : List MalExpr -> Eval MalExpr | |
444 | evalIf 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 | ||
473 | evalFn : List MalExpr -> Eval MalExpr | |
474 | evalFn 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 |
582 | evalQuote : List MalExpr -> Eval MalExpr |
583 | evalQuote args = | |
584 | case args of | |
585 | [ expr ] -> | |
586 | Eval.succeed expr | |
587 | ||
588 | _ -> | |
589 | Eval.fail "unsupported arguments" | |
590 | ||
591 | ||
592 | evalQuasiQuote : MalExpr -> MalExpr | |
593 | evalQuasiQuote 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 |
618 | print : Env -> MalExpr -> String |
619 | print env = | |
620 | printString env True | |
621 | ||
622 | ||
c9c948de JB |
623 | printError : Env -> MalExpr -> String |
624 | printError env expr = | |
dd7a4f55 | 625 | "Error: " ++ (printString env False expr) |
c9c948de JB |
626 | |
627 | ||
fe50dd7a JB |
628 | {-| Read-Eval-Print. |
629 | ||
630 | Doesn't actually run the Eval but returns the monad. | |
631 | ||
632 | -} | |
633 | rep : String -> Maybe (Eval MalExpr) | |
634 | rep 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 |