DISABLE FDs (REMOVE ME).
[jackhill/mal.git] / jq / step5_tco.jq
CommitLineData
086a79dc
A
1include "reader";
2include "printer";
3include "utils";
086a79dc
A
4include "core";
5
6def read_line:
7 . as $in
8 | label $top
e9cb5f03 9 | _readline;
086a79dc
A
10
11def READ:
12 read_str | read_form | .value;
13
413107d1
A
14# Environment Functions
15
16def childEnv(binds; exprs):
17 {
18 parent: .,
19 fallback: null,
20 environment: [binds, exprs] | transpose | (
21 . as $dot | reduce .[] as $item (
22 { value: [], seen: false, name: null, idx: 0 };
23 if $item[1] != null then
24 if .seen then
25 {
26 value: (.value[1:-1] + (.value|last[1].value += [$item[1]])),
27 seen: true,
28 name: .name
29 }
30 else
31 if $item[0] == "&" then
32 $dot[.idx+1][0] as $name | {
33 value: (.value + [[$name, {kind:"list", value: [$item[1]]}]]),
34 seen: true,
35 name: $name
36 }
37 else
38 {
39 value: (.value + [$item]),
40 seen: false,
41 name: null
42 }
43 end
44 end | (.idx |= .idx + 1)
45 else
46 if $item[0] == "&" then
47 $dot[.idx+1][0] as $name | {
48 value: (.value + [[$name, {kind:"list", value: []}]]),
49 seen: true,
50 name: $name
51 }
52 else . end
53 end
54 )
55 ) | .value | map({(.[0]): .[1]}) | add
56 };
57
58def pureChildEnv:
59 {
60 parent: .,
61 environment: {},
62 fallback: null
63 };
64
65def rootEnv:
66 {
67 parent: null,
68 fallback: null,
69 environment: {}
70 };
71
72def inform_function(name):
73 (.names += [name]) | (.names |= unique);
74
75def inform_function_multi(names):
76 . as $dot | reduce names[] as $name(
77 $dot;
78 inform_function($name)
79 );
80
81def env_multiset(keys; value):
82 (if value.kind == "function" then # multiset not allowed on atoms
83 value | inform_function_multi(keys)
84 else
85 value
86 end) as $value | {
87 parent: .parent,
88 environment: (
89 .environment + (reduce keys[] as $key(.environment; .[$key] |= value))
90 ),
91 fallback: .fallback
92 };
93
94def env_multiset(env; keys; value):
95 env | env_multiset(keys; value);
96
97def env_set($key; $value):
98 (if $value.kind == "function" or $value.kind == "atom" then
99 # inform the function/atom of its names
100 ($value |
101 if $value.kind == "atom" then
102 # check if the one we have is newer
103 env_req(env; key) as $ours |
104 if $ours.last_modified > $value.last_modified then
105 $ours
106 else
107 # update modification timestamp
108 $value | .last_modified |= now
109 end
110 else
111 .
112 end) | inform_function($key)
113 else
114 $value
115 end) as $value | {
116 parent: .parent,
117 environment: (.environment + (.environment | .[$key] |= $value)), # merge together, as .environment[key] |= value does not work
118 fallback: .fallback
119 };
120
121def env_dump_keys:
122 def _dump1:
123 .environment // {} | keys;
124 if . == null then [] else
125 if .parent == null then
126 (
127 _dump1 +
128 (.fallback | env_dump_keys)
129 )
130 else
131 (
132 _dump1 +
133 (.parent | env_dump_keys) +
134 (.fallback | env_dump_keys)
135 )
136 end | unique
137 end;
138
139def env_find(env):
140 if env.environment[.] == null then
141 if env.parent then
142 env_find(env.parent) // if env.fallback then env_find(env.fallback) else null end
143 else
144 null
145 end
146 else
147 env
148 end;
149
150def env_get(env):
151 . as $key | $key | env_find(env).environment[$key] as $value |
152 if $value == null then
153 jqmal_error("'\($key)' not found")
154 else
155 if $value.kind == "atom" then
156 $value.identity as $id |
157 $key | env_find(env.parent).environment[$key] as $possibly_newer |
158 if $possibly_newer.identity == $id and $possibly_newer.last_modified > $value.last_modified then
159 $possibly_newer
160 else
161 $value
162 end
163 else
164 $value
165 end
166 end;
167
168def env_get(env; key):
169 key | env_get(env);
170
171def env_req(env; key):
172 key as $key | key | env_find(env).environment[$key] as $value |
173 if $value == null then
174 null
175 else
176 if $value.kind == "atom" then
177 $value.identity as $id |
178 $key | env_find(env.parent).environment[$key] as $possibly_newer |
179 if $possibly_newer.identity == $id and $possibly_newer.last_modified > $value.last_modified then
180 $possibly_newer
181 else
182 $value
183 end
184 else
185 $value
186 end
187 end;
188
189def env_set(env; $key; $value):
190 (if $value.kind == "function" or $value.kind == "atom" then
191 # inform the function/atom of its names
192 $value | (.names += [$key]) | (.names |= unique) |
193 if $value.kind == "atom" then
194 # check if the one we have is newer
195 env_req(env; $key) as $ours |
196 if $ours.last_modified > $value.last_modified then
197 $ours
198 else
199 # update modification timestamp
200 $value | .last_modified |= now
201 end
202 else
203 .
204 end
205 else
206 $value
207 end) as $value | {
208 parent: env.parent,
209 environment: ((env.environment // jqmal_error("Environment empty in \(env | keys)")) + (env.environment | .[$key] |= $value)), # merge together, as env.environment[key] |= value does not work
210 fallback: env.fallback
211 };
212
213def env_setfallback(env; fallback):
214 {
215 parent: env.parent,
216 fallback: fallback,
217 environment: env.environment
218 };
219
220def addEnv(env):
221 {
222 expr: .,
223 env: env
224 };
225
226def addToEnv(env; name; expr):
227 {
228 expr: expr,
229 env: env_set(env; name; expr)
230 };
231
232
233def wrapEnv(atoms):
234 {
235 replEnv: .,
236 currentEnv: .,
237 atoms: atoms,
238 isReplEnv: true
239 };
240
241def wrapEnv(replEnv; atoms):
242 {
243 replEnv: replEnv,
244 currentEnv: .,
245 atoms: atoms, # id -> value
246 isReplEnv: (replEnv == .) # should we allow separate copies?
247 };
248
249def unwrapReplEnv:
250 .replEnv;
251
252def unwrapCurrentEnv:
253 .currentEnv;
254
255def env_set6(env; key; value):
256 if env.isReplEnv then
257 env_set(env.currentEnv; key; value) | wrapEnv(env.atoms)
258 else
259 env_set(env.currentEnv; key; value) | wrapEnv(env.replEnv; env.atoms)
260 end;
261
262def env_set_(env; key; value):
263 if env.currentEnv != null then
264 env_set6(env; key; value)
265 else
266 env_set(env; key; value)
267 end;
268
269def addToEnv6(envexp; name):
270 envexp.expr as $value
271 | envexp.env as $rawEnv
272 | (if $rawEnv.isReplEnv then
273 env_set_($rawEnv.currentEnv; name; $value) | wrapEnv($rawEnv.atoms)
274 else
275 env_set_($rawEnv.currentEnv; name; $value) | wrapEnv($rawEnv.replEnv; $rawEnv.atoms)
276 end) as $newEnv
277 | {
278 expr: $value,
279 env: $newEnv
280 };
281
282def addToEnv(envexp; name):
283 if envexp.env.replEnv != null then
284 addToEnv6(envexp; name)
285 else {
286 expr: envexp.expr,
287 env: env_set_(envexp.env; name; envexp.expr)
288 } end;
289
290def _env_remove_references(refs):
291 if . != null then
292 {
293 environment: (.environment | to_entries | map(select(.key as $key | refs | contains([$key]) | not)) | from_entries),
294 parent: (.parent | _env_remove_references(refs)),
295 fallback: (.fallback | _env_remove_references(refs))
296 }
297 else . end;
298
299def env_remove_references(refs):
300 . as $env
301 | if has("replEnv") then
302 .currentEnv |= _env_remove_references(refs)
303 else
304 _env_remove_references(refs)
305 end;
306
307# Evaluation
308
309def arg_check(args):
310 if .inputs < 0 then
311 if (abs(.inputs) - 1) > (args | length) then
312 jqmal_error("Invalid number of arguments (expected at least \(abs(.inputs) - 1), got \(args|length))")
313 else
314 .
315 end
316 else if .inputs != (args|length) then
317 jqmal_error("Invalid number of arguments (expected \(.inputs), got \(args|length))")
318 else
319 .
320 end end;
321
322def addFrees(newEnv; frees):
323 . as $env
324 | reduce frees[] as $free (
325 $env;
326 . as $dot
327 | env_req(newEnv; $free) as $lookup
328 | if $lookup != null then
329 env_set_(.; $free; $lookup)
330 else
331 .
332 end)
333 | . as $env
334 | $env;
335
336def interpret(arguments; env; _eval):
337 (if $DEBUG then _debug("INTERP: \(. | pr_str(env))") else . end) |
338 (select(.kind == "fn") |
339 arg_check(arguments) |
340 (core_interp(arguments; env) | addEnv(env))
341 ) //
342 (select(.kind == "function") as $fn |
343 # todo: arg_check
344 (.body | pr_str(env)) as $src |
345 # _debug("INTERP " + $src) |
346 # _debug("FREES " + ($fn.free_referencess | tostring)) |
347 env_setfallback((.env | addFrees(env; $fn.free_referencess)); env) | childEnv($fn.binds; arguments) as $fnEnv |
348 # tell it about its surroundings
349 (reduce $fn.free_referencess[] as $name (
350 $fnEnv;
351 . as $env | try env_set(
352 .;
353 $name;
354 $name | env_get(env) | . as $xvalue
355 | if $xvalue.kind == "function" then
356 setpath(["free_referencess"]; $fn.free_referencess)
357 else
358 $xvalue
359 end
360 ) catch $env)) as $fnEnv |
361 # tell it about itself
362 env_multiset($fnEnv; $fn.names; $fn) as $fnEnv |
363 {
364 env: env_multiset($fnEnv; $fn.names; $fn),
365 expr: $fn.body
366 }
367 | . as $dot
368 # | _debug("FNEXEC " + (.expr | pr_str) + " " + (env_req($dot.env; $fn.binds[0]) | pr_str))
369 | _eval
370 | . as $envexp
371 |
372 {
373 expr: .expr,
374 env: env
375 }
376 # | . as $dot
377 # | _debug("FNPOST " + (.expr | pr_str) + " " + (env_req($dot.expr.env; $fn.binds[0]) | pr_str))
378 # | _debug("INTERP " + $src + " = " + (.expr|pr_str))
379 ) //
380 jqmal_error("Unsupported function kind \(.kind)");
381
a451ec51
A
382def recurseflip(x; y):
383 recurse(y; x);
384
385def TCOWrap(env; retenv; continue):
386 {
387 ast: .,
388 env: env,
389 ret_env: retenv,
390 finish: (continue | not),
391 cont: true # set inside
392 };
393
086a79dc
A
394def EVAL(env):
395 def _eval_here:
396 .env as $env | .expr | EVAL($env);
397
398 def hmap_with_env:
399 .env as $env | .list as $list |
400 if $list|length == 0 then
401 empty
402 else
403 $list[0] as $elem |
404 $list[1:] as $rest |
405 $elem[1] | EVAL($env) as $resv |
406 { value: [$elem[0], $resv.expr], env: env },
407 ({env: $resv.env, list: $rest} | hmap_with_env)
408 end;
409 def map_with_env:
410 .env as $env | .list as $list |
411 if $list|length == 0 then
412 empty
413 else
414 $list[0] as $elem |
415 $list[1:] as $rest |
416 $elem | EVAL($env) as $resv |
417 { value: $resv.expr, env: env },
418 ({env: $resv.env, list: $rest} | map_with_env)
419 end;
a451ec51
A
420 . as $ast
421 | { env: env, ast: ., cont: true, finish: false, ret_env: null }
422 | [ recurseflip(.cont;
423 .env as $_menv
424 | if .finish then
425 .cont |= false
086a79dc 426 else
a451ec51
A
427 (.ret_env//.env) as $_retenv
428 | .ret_env as $_orig_retenv
429 | .ast
430 |
431 (select(.kind == "list") |
432 if .value | length == 0 then
433 . | TCOWrap($_menv; $_orig_retenv; false)
434 else
435 (
436 (
437 .value | select(.[0].value == "def!") as $value |
438 ($value[2] | EVAL($_menv)) as $evval |
439 addToEnv($evval; $value[1].value) as $val |
440 $val.expr | TCOWrap($val.env; $_orig_retenv; false)
441 ) //
442 (
443 .value | select(.[0].value == "let*") as $value |
444 ($_menv | pureChildEnv) as $subenv |
445 (reduce ($value[1].value | nwise(2)) as $xvalue (
446 $subenv;
447 . as $env | $xvalue[1] | EVAL($env) as $expenv |
448 env_set($expenv.env; $xvalue[0].value; $expenv.expr))) as $env
449 | $value[2] | TCOWrap($env; $_retenv; true)
450 ) //
451 (
452 .value | select(.[0].value == "do") as $value |
453 (reduce ($value[1:][]) as $xvalue (
454 { env: $_menv, expr: {kind:"nil"} };
455 .env as $env | $xvalue | EVAL($env)
456 )) | . as $ex | .expr | TCOWrap($ex.env; $_orig_retenv; false)
457 ) //
458 (
459 .value | select(.[0].value == "if") as $value |
460 $value[1] | EVAL(env) as $condenv |
461 (if (["false", "nil"] | contains([$condenv.expr.kind])) then
462 ($value[3] // {kind:"nil"})
463 else
464 $value[2]
465 end) | TCOWrap($condenv.env; $_orig_retenv; true)
466 ) //
467 (
468 .value | select(.[0].value == "fn*") as $value |
a451ec51
A
469 # (fn* args body)
470 $value[1].value | map(.value) as $binds | {
471 kind: "function",
472 binds: $binds,
473 env: $_menv,
474 body: $value[2],
597522fa 475 names: [], # we can't do that circular reference thing
a451ec51
A
476 free_referencess: $value[2] | find_free_references($_menv | env_dump_keys + $binds) # for dynamically scoped variables
477 } | TCOWrap($_menv; $_orig_retenv; false)
478 ) //
479 (
480 reduce .value[] as $elem (
481 [];
482 . as $dot | $elem | EVAL($_menv) as $eval_env |
483 ($dot + [$eval_env.expr])
484 ) | . as $expr | first |
485 interpret($expr[1:]; $_menv; _eval_here) as $exprenv |
486 $exprenv.expr | TCOWrap($exprenv.env; $_orig_retenv; false)
487 ) //
488 TCOWrap($_menv; $_orig_retenv; false)
489 )
490 end
491 ) //
492 (select(.kind == "vector") |
493 if .value|length == 0 then
494 {
495 kind: "vector",
496 value: []
497 } | TCOWrap($_menv; $_orig_retenv; false)
498 else
499 [ { env: $_menv, list: .value } | map_with_env ] as $res |
500 {
501 kind: "vector",
502 value: $res | map(.value)
503 } | TCOWrap($res | last.env; $_orig_retenv; false)
504 end
505 ) //
506 (select(.kind == "hashmap") |
507 [ { env: $_menv, list: .value | to_entries } | hmap_with_env ] as $res |
508 {
509 kind: "hashmap",
510 value: $res | map(.value) | from_entries
511 } | TCOWrap($res | last.env; $_orig_retenv; false)
512 ) //
513 (select(.kind == "function") |
514 . | TCOWrap($_menv; $_orig_retenv; false) # return this unchanged, since it can only be applied to
515 ) //
516 (select(.kind == "symbol") |
517 .value | env_get($_menv) | TCOWrap($_menv; null; false)
518 ) // TCOWrap($_menv; $_orig_retenv; false)
086a79dc 519 end
a451ec51
A
520 ) ]
521 | last as $result
522 | ($result.ret_env // $result.env) as $env
523 | $result.ast
524 | addEnv($env);
086a79dc
A
525
526def PRINT:
527 pr_str;
528
529def rep(env):
530 READ | EVAL(env) as $expenv |
531 if $expenv.expr != null then
532 $expenv.expr | PRINT
533 else
534 null
535 end | addEnv($expenv.env);
536
537def repl_(env):
538 ("user> " | _print) |
539 (read_line | rep(env));
540
541# we don't have no indirect functions, so we'll have to interpret the old way
542def replEnv:
543 {
544 parent: null,
545 environment: ({
546 "+": {
547 kind: "fn", # native function
548 inputs: 2,
549 function: "number_add"
550 },
551 "-": {
552 kind: "fn", # native function
553 inputs: 2,
554 function: "number_sub"
555 },
556 "*": {
557 kind: "fn", # native function
558 inputs: 2,
559 function: "number_mul"
560 },
561 "/": {
562 kind: "fn", # native function
563 inputs: 2,
564 function: "number_div"
565 },
e9cb5f03 566 } + core_identify),
e9cb5f03 567 fallback: null
086a79dc
A
568 };
569
570def repl(env):
571 def xrepl:
572 (.env as $env | try repl_($env) catch addEnv($env)) as $expenv |
573 {
574 value: $expenv.expr,
575 stop: false,
576 env: ($expenv.env // .env)
577 } | ., xrepl;
83b974c5 578 {stop: false, env: env} | xrepl | if .value then (.value | _display) else empty end;
086a79dc
A
579
580repl(
581 "(def! not (fn* (a) (if a false true)))" | rep(replEnv) | .env
597522fa 582)