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