1
1
mirror of https://github.com/kanaka/mal.git synced 2024-10-27 06:40:14 +03:00
mal/impls/jq/step8_macros.jq

256 lines
11 KiB
Plaintext
Raw Normal View History

2020-01-07 23:26:31 +03:00
include "reader";
include "printer";
include "utils";
include "interp";
include "env";
include "core";
def READ:
read_form;
2020-01-07 23:26:31 +03:00
def recurseflip(x; y):
recurse(y; x);
def TCOWrap(env; retenv; continue):
{
ast: .,
env: env,
ret_env: (if retenv != null then (retenv | setpath(["atoms"]; env.atoms)) else retenv end),
2020-01-07 23:26:31 +03:00
finish: (continue | not),
cont: true # set inside
};
def quasiquote:
# If input is ('name, arg), return arg, else nothing.
def _starts_with(name):
select(.kind == "list")
| .value
| select(length == 2)
| select(.[0] | .kind == "symbol" and .value == name)
| .[1];
# Right-folding function. The current element is provided as input.
def qq_loop(acc):
(_starts_with("splice-unquote")
| {kind:"list", value:[{kind:"symbol", value:"concat"}, ., acc]})
// {kind:"list", value:[{kind:"symbol", value:"cons"}, quasiquote, acc]};
# Adapt parameters for jq foldr.
def qq_foldr:
.value
| reverse
| reduce .[] as $elt ({kind:"list", value:[]};
. as $acc | $elt | qq_loop($acc));
_starts_with("unquote")
// (
select(.kind == "list")
| qq_foldr
) // (
select(.kind == "vector")
| {kind:"list", value: [{kind:"symbol", value:"vec"}, qq_foldr]}
) // (
select(.kind == "hashmap" or .kind == "symbol")
| {kind:"list", value:[{kind:"symbol", value:"quote"}, .]}
) // .;
2020-01-07 23:26:31 +03:00
def set_macro_function:
if .kind != "function" then
jqmal_error("expected a function to be defined by defmacro!")
else
.is_macro |= true
end;
def EVAL(env):
def _eval_here:
.env as $env | .expr | EVAL($env);
. as $ast
| { env: env, ast: ., cont: true, finish: false, ret_env: null }
| [ recurseflip(.cont;
.env as $_menv
| if .finish then
.cont |= false
else
(.ret_env//.env) as $_retenv
| .ret_env as $_orig_retenv
| .ast
| . as $init
| $_menv | unwrapCurrentEnv as $currentEnv # unwrap env "package"
| $_menv | unwrapReplEnv as $replEnv # -
| $init
|
if "DEBUG-EVAL" | env_req($currentEnv) |
. != null and .kind != "false" and .kind != "nil"
then
("EVAL: \(pr_str(env))" | _display | empty), .
end
|
2020-01-07 23:26:31 +03:00
(select(.kind == "list") |
.value | select(length != 0) as $value |
2020-01-07 23:26:31 +03:00
(
(
select(.[0].value == "def!") |
2020-01-07 23:26:31 +03:00
($value[2] | EVAL($_menv)) as $evval |
addToEnv($evval; $value[1].value) as $val |
$val.expr | TCOWrap($val.env; $_orig_retenv; false)
) //
(
select(.[0].value == "defmacro!") |
2020-01-07 23:26:31 +03:00
($value[2] | EVAL($_menv) | (.expr |= set_macro_function)) as $evval |
addToEnv($evval; $value[1].value) as $val |
$val.expr | TCOWrap($val.env; $_orig_retenv; false)
) //
(
select(.[0].value == "let*") |
($currentEnv | pureChildEnv | wrapEnv($replEnv; $_menv.atoms)) as $_menv |
2020-01-07 23:26:31 +03:00
(reduce ($value[1].value | nwise(2)) as $xvalue (
$_menv;
2020-01-07 23:26:31 +03:00
. as $env | $xvalue[1] | EVAL($env) as $expenv |
env_set_($expenv.env; $xvalue[0].value; $expenv.expr))) as $env
| $value[2] | TCOWrap($env; $_retenv; true)
) //
(
select(.[0].value == "do") |
2020-01-07 23:26:31 +03:00
(reduce ($value[1:][]) as $xvalue (
{ env: $_menv, expr: {kind:"nil"} };
.env as $env | $xvalue | EVAL($env)
)) | . as $ex | .expr | TCOWrap($ex.env; $_orig_retenv; false)
) //
(
select(.[0].value == "if") |
2020-01-07 23:26:31 +03:00
$value[1] | EVAL($_menv) as $condenv |
(if (["false", "nil"] | contains([$condenv.expr.kind])) then
($value[3] // {kind:"nil"})
else
$value[2]
end) | TCOWrap($condenv.env; $_orig_retenv; true)
) //
(
select(.[0].value == "fn*") |
2020-01-07 23:26:31 +03:00
# (fn* args body)
$value[1].value | map(.value) as $binds |
($value[2] | find_free_references($currentEnv | env_dump_keys + $binds)) as $free_referencess | {
kind: "function",
binds: $binds,
env: ($_menv | env_remove_references($free_referencess)),
body: $value[2],
names: [], # we can't do that circular reference thing
free_referencess: $free_referencess, # for dynamically scoped variables
is_macro: false
} | TCOWrap($_menv; $_orig_retenv; false)
2020-01-07 23:26:31 +03:00
) //
(
select(.[0].value == "quote") |
2020-01-07 23:26:31 +03:00
$value[1] | TCOWrap($_menv; $_orig_retenv; false)
) //
(
select(.[0].value == "quasiquote") |
2020-01-07 23:26:31 +03:00
$value[1] | quasiquote | TCOWrap($_menv; $_orig_retenv; true)
) //
(
(
.[0] | EVAL($_menv) |
(.env | setpath(["atoms"]; $_menv.atoms)) as $_menv |
.expr
) as $fn |
if $fn.kind == "function" and $fn.is_macro then
$fn | interpret($value[1:]; $_menv; _eval_here) as $exprenv |
$exprenv.expr | TCOWrap($exprenv.env; $_orig_retenv; true)
else
$value[1:] |
(reduce .[] as $elem (
{env: $_menv, val: []};
# debug(".val: \(.val) elem=\($elem)") |
. as $dot | $elem | EVAL($dot.env) as $eval_env |
($dot.env | setpath(["atoms"]; $eval_env.env.atoms)) as $_menv |
{env: $_menv, val: ($dot.val + [$eval_env.expr])}
# | debug(".val: \(.val)")
)) as $expr |
# debug("fn.kind: \($fn.kind)", "expr: \($expr)") |
$fn |
interpret($expr.val; $expr.env; _eval_here) as $exprenv |
2020-01-07 23:26:31 +03:00
$exprenv.expr | TCOWrap($exprenv.env; $_orig_retenv; false)
end
2020-01-07 23:26:31 +03:00
) //
TCOWrap($_menv; $_orig_retenv; false)
)
) //
(
select(.kind == "vector") |
.value |
reduce .[] as $x ({expr:[], env:$_menv};
. as $acc |
$x | EVAL($acc.env) |
.expr |= $acc.expr + [.]
) |
.env as $e |
{kind:"vector", value:.expr} |
TCOWrap($e; $_orig_retenv; false)
2020-01-07 23:26:31 +03:00
) //
(
select(.kind == "hashmap") |
.value | to_entries |
reduce .[] as $x ({expr:[], env:$_menv};
. as $acc |
$x.value.value | EVAL($acc.env) |
.expr |= (. as $e | $acc.expr + [$x | .value.value |= $e])
) |
.env as $e |
{kind:"hashmap", value:.expr|from_entries} |
TCOWrap($e; $_orig_retenv; false)
) //
(
select(.kind == "function") |
. | TCOWrap($_menv; $_orig_retenv; false) # return this unchanged, since it can only be applied to
) //
(
select(.kind == "symbol") |
.value | env_get($currentEnv) | TCOWrap($_menv; $_orig_retenv; false)
) //
TCOWrap($_menv; $_orig_retenv; false)
2020-01-07 23:26:31 +03:00
end
) ]
| last as $result
| ($result.ret_env // $result.env) as $env
| $result.ast
| addEnv($env);
def PRINT(env):
pr_str(env);
2020-01-07 23:26:31 +03:00
def repl:
# Infinite generator, interrupted by an exception or ./run.
. as $env | "user> " | __readline |
try (
READ | EVAL($env) | .env as $env |
(.expr | PRINT($env)), ($env | repl)
) catch if is_jqmal_error then
., ($env | repl)
else
halt_error
end;
2020-01-07 23:26:31 +03:00
def eval_ign(expr):
. as $env | expr | READ | EVAL($env) | .env;
2020-01-07 23:26:31 +03:00
# The main program starts here.
2020-01-07 23:26:31 +03:00
{
parent: null,
environment: core_identify,
fallback: null
}
| wrapEnv({})
2020-01-07 23:26:31 +03:00
| eval_ign("(def! not (fn* (a) (if a false true)))")
| eval_ign("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))))")
| 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)))))))")
| env_set_(.; "*ARGV*"; {kind:"list", value:[$ARGS.positional[1:] | .[] | {kind:"string", value:.}]})
|
2020-01-07 23:26:31 +03:00
if $ARGS.positional|length > 0 then
eval_ign("(load-file \($ARGS.positional[0] | tojson))") |
empty
2020-01-07 23:26:31 +03:00
else
repl
end