Change quasiquote algorithm
[jackhill/mal.git] / impls / jq / stepA_mal.jq
CommitLineData
b103f95e
A
1include "reader";
2include "printer";
3include "utils";
4include "interp";
5include "env";
6include "core";
7
8def read_line:
9 . as $in
10 | label $top
11 | _readline;
12
13def READ:
14 read_str | read_form | .value;
15
16def recurseflip(x; y):
17 recurse(y; x);
18
19def TCOWrap(env; retenv; continue):
20 {
21 ast: .,
22 env: env,
fed3ca50 23 ret_env: (if retenv != null then (retenv | setpath(["atoms"]; env.atoms)) else retenv end),
b103f95e
A
24 finish: (continue | not),
25 cont: true # set inside
26 };
27
28def _symbol(name):
29 {
30 kind: "symbol",
31 value: name
32 };
33
34def _symbol_v(name):
35 if .kind == "symbol" then
36 .value == name
37 else
38 false
39 end;
40
41def quasiquote:
fbfe6784
NB
42
43 # If input is ('name, arg), return arg, else nothing.
44 def _starts_with(name):
45 select(.kind == "list")
46 | .value
47 | select(length == 2)
48 | select(.[0] | _symbol_v(name))
49 | .[1];
50
51 # Right-folding function. The current element is provided as input.
52 def qq_loop(acc):
53 ((_starts_with("splice-unquote") | [_symbol("concat"), ., acc])
54 // [_symbol("cons"), quasiquote, acc])
55 | {kind:"list", value:.};
56
57 # Adapt parameters for jq foldr.
58 def qq_foldr:
59 .value
60 | reverse
61 | reduce .[] as $elt ({kind:"list", value:[]};
62 . as $acc | $elt | qq_loop($acc));
63
64 _starts_with("unquote")
65 // (
66 select(.kind == "list")
67 | qq_foldr
68 ) // (
69 select(.kind == "vector")
70 | {kind:"list", value:[_symbol("vec"), qq_foldr]}
71 ) // (
72 select(.kind == "hashmap" or .kind == "symbol")
73 | {kind:"list", value:[_symbol("quote"), .]}
74 ) // .;
b103f95e
A
75
76def set_macro_function:
77 if .kind != "function" then
78 jqmal_error("expected a function to be defined by defmacro!")
79 else
80 .is_macro |= true
81 end;
82
83def is_macro_call(env):
84 if .kind != "list" then
85 false
86 else
87 if (.value|first.kind == "symbol") then
88 env_req(env; .value|first.value)
89 | if .kind != "function" then
90 false
91 else
92 .is_macro
93 end
94 else
95 false
96 end
97 end;
98
99def EVAL(env):
100 def _eval_here:
101 .env as $env | .expr | EVAL($env);
102
103 def _interpret($_menv):
104 reduce .value[] as $elem (
fed3ca50
A
105 {env: $_menv, val: []};
106 . as $dot | $elem | EVAL($dot.env) as $eval_env |
107 ($dot.env | setpath(["atoms"]; $eval_env.env.atoms)) as $_menv |
108 {env: $_menv, val: ($dot.val + [$eval_env.expr])}
109 ) | . as $expr | $expr.val | first |
110 interpret($expr.val[1:]; $expr.env; _eval_here);
b103f95e
A
111
112 def macroexpand(env):
832abfbd
A
113 . as $dot |
114 $dot |
b103f95e 115 [ while(is_macro_call(env | unwrapCurrentEnv);
832abfbd
A
116 . as $dot
117 | ($dot.value[0] | EVAL(env).expr) as $fn
118 | $dot.value[1:] as $args
119 | $fn
120 | interpret($args; env; _eval_here).expr) // . ]
121 | last
b103f95e 122 | if is_macro_call(env | unwrapCurrentEnv) then
832abfbd
A
123 . as $dot
124 | ($dot.value[0] | EVAL(env).expr) as $fn
125 | $dot.value[1:] as $args
126 | $fn
127 | interpret($args; env; _eval_here).expr
b103f95e
A
128 else
129 .
832abfbd
A
130 end
131 ;
b103f95e
A
132
133 def hmap_with_env:
134 .env as $env | .list as $list |
135 if $list|length == 0 then
136 empty
137 else
138 $list[0] as $elem |
139 $list[1:] as $rest |
140 $elem.value.value | EVAL($env) as $resv |
141 {
142 value: {
143 key: $elem.key,
144 value: { kkind: $elem.value.kkind, value: $resv.expr }
145 },
146 env: env
147 },
148 ({env: $resv.env, list: $rest} | hmap_with_env)
149 end;
150 def map_with_env:
151 .env as $env | .list as $list |
152 if $list|length == 0 then
153 empty
154 else
155 $list[0] as $elem |
156 $list[1:] as $rest |
157 $elem | EVAL($env) as $resv |
158 { value: $resv.expr, env: env },
159 ({env: $resv.env, list: $rest} | map_with_env)
160 end;
161 def eval_ast(env):
162 (select(.kind == "vector") |
163 if .value|length == 0 then
164 {
165 kind: "vector",
166 value: []
167 }
168 else
169 [ { env: env, list: .value } | map_with_env ] as $res |
170 {
171 kind: "vector",
172 value: $res | map(.value)
173 }
174 end
175 ) //
176 (select(.kind == "hashmap") |
177 [ { env: env, list: (.value | to_entries) } | hmap_with_env ] as $res |
178 {
179 kind: "hashmap",
180 value: $res | map(.value) | from_entries
181 }
182 ) //
183 (select(.kind == "function") |
184 .# return this unchanged, since it can only be applied to
185 ) //
186 (select(.kind == "symbol") |
187 .value | env_get(env | unwrapCurrentEnv)
188 ) // .;
189
190 . as $ast
191 | { env: env, ast: ., cont: true, finish: false, ret_env: null }
192 | [ recurseflip(.cont;
193 .env as $_menv
194 | (if $DEBUG then _debug("EVAL: \($ast | pr_str($_menv))") else . end)
fed3ca50 195 | (if $DEBUG then _debug("ATOMS: \($_menv.atoms)") else . end)
b103f95e
A
196 | if .finish then
197 .cont |= false
198 else
199 (.ret_env//.env) as $_retenv
200 | .ret_env as $_orig_retenv
201 | .ast
202 | . as $init
203 | $_menv | unwrapCurrentEnv as $currentEnv # unwrap env "package"
204 | $_menv | unwrapReplEnv as $replEnv # -
205 | $init
206 |
207 (select(.kind == "list") |
208 macroexpand($_menv) |
209 if .kind != "list" then
210 eval_ast($_menv) | TCOWrap($_menv; $_orig_retenv; false)
211 else
212 if .value | length == 0 then
213 . | TCOWrap($_menv; $_orig_retenv; false)
214 else
215 (
fed3ca50
A
216 (
217 .value | select(.[0].value == "atoms??") as $value |
218 $_menv.atoms | keys | map(wrap("string")) | wrap("list") | TCOWrap($_menv; $_orig_retenv; false)
219 ) //
b103f95e
A
220 (
221 .value | select(.[0].value == "def!") as $value |
222 ($value[2] | EVAL($_menv)) as $evval |
223 addToEnv($evval; $value[1].value) as $val |
224 $val.expr | TCOWrap($val.env; $_orig_retenv; false)
225 ) //
226 (
227 .value | select(.[0].value == "defmacro!") as $value |
228 ($value[2] | EVAL($_menv) | (.expr |= set_macro_function)) as $evval |
229 addToEnv($evval; $value[1].value) as $val |
230 $val.expr | TCOWrap($val.env; $_orig_retenv; false)
231 ) //
232 (
233 .value | select(.[0].value == "let*") as $value |
2ce3c78e 234 ($currentEnv | pureChildEnv | wrapEnv($replEnv; $_menv.atoms)) as $_menv |
b103f95e 235 (reduce ($value[1].value | nwise(2)) as $xvalue (
fed3ca50 236 $_menv;
b103f95e
A
237 . as $env | $xvalue[1] | EVAL($env) as $expenv |
238 env_set_($expenv.env; $xvalue[0].value; $expenv.expr))) as $env
239 | $value[2] | TCOWrap($env; $_retenv; true)
240 ) //
241 (
242 .value | select(.[0].value == "do") as $value |
243 (reduce ($value[1:][]) as $xvalue (
244 { env: $_menv, expr: {kind:"nil"} };
245 .env as $env | $xvalue | EVAL($env)
246 )) | . as $ex | .expr | TCOWrap($ex.env; $_orig_retenv; false)
247 ) //
248 (
249 .value | select(.[0].value == "try*") as $value |
250 try (
251 $value[1] | EVAL($_menv) as $exp | $exp.expr | TCOWrap($exp.env; $_orig_retenv; false)
252 ) catch ( . as $exc |
253 if $value[2] then
254 if ($value[2].value[0] | _symbol_v("catch*")) then
255 (if ($exc | is_jqmal_error) then
256 $exc[19:] as $ex |
257 try (
258 $ex
259 | fromjson
260 ) catch (
261 $ex |
262 wrap("string")
263 )
264 else
265 $exc|wrap("string")
266 end) as $exc |
fed3ca50 267 $value[2].value[2] | EVAL($currentEnv | childEnv([$value[2].value[1].value]; [$exc]) | wrapEnv($replEnv; $_menv.atoms)) as $ex |
b103f95e
A
268 $ex.expr | TCOWrap($ex.env; $_retenv; false)
269 else
270 error($exc)
271 end
272 else
273 error($exc)
274 end
275 )
276 ) //
277 (
278 .value | select(.[0].value == "if") as $value |
279 $value[1] | EVAL($_menv) as $condenv |
280 (if (["false", "nil"] | contains([$condenv.expr.kind])) then
281 ($value[3] // {kind:"nil"})
282 else
283 $value[2]
284 end) | TCOWrap($condenv.env; $_orig_retenv; true)
285 ) //
286 (
287 .value | select(.[0].value == "fn*") as $value |
b103f95e
A
288 # (fn* args body)
289 $value[1].value | map(.value) as $binds |
290 ($value[2] | find_free_references($currentEnv | env_dump_keys + $binds)) as $free_referencess | {
291 kind: "function",
292 binds: $binds,
6b0d8c8d 293 env: ($_menv | env_remove_references($free_referencess)),
b103f95e 294 body: $value[2],
597522fa 295 names: [], # we can't do that circular reference thing
b103f95e
A
296 free_referencess: $free_referencess, # for dynamically scoped variables
297 is_macro: false
298 } | TCOWrap($_menv; $_orig_retenv; false)
299 ) //
300 (
301 .value | select(.[0].value == "quote") as $value |
302 $value[1] | TCOWrap($_menv; $_orig_retenv; false)
303 ) //
fbfe6784
NB
304 (
305 .value | select(.[0].value == "quasiquoteexpand")
306 | .[1] | quasiquote | TCOWrap($_menv; $_orig_retenv; false)
307 ) //
b103f95e
A
308 (
309 .value | select(.[0].value == "quasiquote") as $value |
310 $value[1] | quasiquote | TCOWrap($_menv; $_orig_retenv; true)
311 ) //
312 (
313 .value | select(.[0].value == "macroexpand") as $value |
832abfbd 314 $value[1] | macroexpand($_menv) | TCOWrap($_menv; $_orig_retenv; false)
b103f95e
A
315 ) //
316 (
317 . as $dot | _interpret($_menv) as $exprenv |
318 $exprenv.expr | TCOWrap($exprenv.env; $_orig_retenv; false)
319 ) //
320 TCOWrap($_menv; $_orig_retenv; false)
321 )
322 end
323 end
324 ) //
325 (eval_ast($_menv) | TCOWrap($_menv; $_orig_retenv; false))
326 end
832abfbd 327 | (if $DEBUG then _debug("POSTEVAL: \($ast | pr_str($_menv)) = \(.ast | pr_str($_menv))") else . end)
b103f95e
A
328 ) ]
329 | last as $result
330 | ($result.ret_env // $result.env) as $env
331 | $result.ast
332 | addEnv($env);
333
334def PRINT(env):
335 pr_str(env);
336
337def rep(env):
338 READ | EVAL(env) as $expenv |
339 if $expenv.expr != null then
340 $expenv.expr | PRINT($expenv.env)
341 else
342 null
343 end | addEnv($expenv.env);
344
345def repl_(env):
346 ("user> " | _print) |
347 (read_line | rep(env));
348
349# we don't have no indirect functions, so we'll have to interpret the old way
350def replEnv:
351 {
352 parent: null,
353 environment: ({
354 "+": {
355 kind: "fn", # native function
356 inputs: 2,
357 function: "number_add"
358 },
359 "-": {
360 kind: "fn", # native function
361 inputs: 2,
362 function: "number_sub"
363 },
364 "*": {
365 kind: "fn", # native function
366 inputs: 2,
367 function: "number_mul"
368 },
369 "/": {
370 kind: "fn", # native function
371 inputs: 2,
372 function: "number_div"
373 },
374 "eval": {
375 kind: "fn",
376 inputs: 1,
377 function: "eval"
378 }
379 } + core_identify),
b103f95e
A
380 fallback: null
381 };
382
383def repl(env):
384 def xrepl:
385 (.env as $env | try repl_($env) catch addEnv($env)) as $expenv |
386 {
387 value: $expenv.expr,
388 stop: false,
389 env: ($expenv.env // .env)
390 } | ., xrepl;
83b974c5 391 {stop: false, env: env} | xrepl | if .value then (.value | _display) else empty end;
b103f95e
A
392
393def eval_ign(expr):
394 . as $env | expr | rep($env) | .env;
395
396def eval_val(expr):
397 . as $env | expr | rep($env) | .expr;
398
399def getEnv:
400 replEnv
fed3ca50 401 | wrapEnv({})
b103f95e
A
402 | eval_ign("(def! *host-language* \"jq\")")
403 | eval_ign("(def! not (fn* (a) (if a false true)))")
404 | eval_ign("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))))")
405 | eval_ign("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))")
406 ;
407
408def main:
409 if $ARGS.positional|length > 0 then
410 try (
411 getEnv as $env |
412 env_set_($env; "*ARGV*"; $ARGS.positional[1:] | map(wrap("string")) | wrap("list")) |
83b974c5
A
413 eval_val("(load-file \($ARGS.positional[0] | tojson))") |
414 ""
b103f95e
A
415 ) catch (
416 _print
417 )
418 else
419 repl( getEnv as $env | env_set_($env; "*ARGV*"; [] | wrap("list")) )
420 end;
421
597522fa 422[ main ] | _halt