2 use lib IO
::Path
.new
($?FILE
).dirname
;
10 return read_str
($str);
13 sub eval_ast
($ast, $env) {
15 when MalSymbol
{ $env.get
($ast.val
) || die X
::MalNotFound
.new
(name
=> $ast.val
) }
16 when MalList
{ MalList
([$ast.map({ eval($_, $env) })]) }
17 when MalVector
{ MalVector
([$ast.map({ eval($_, $env) })]) }
18 when MalHashMap
{ MalHashMap
($ast.kv
.map({ $^a
=> eval($^b
, $env) }).Hash
) }
19 default { $ast // $NIL }
24 my $acc = MalList
([]);
25 for |$ast.val
.reverse -> $elt {
26 if $elt ~~ MalList
&& $elt.elems
== 2 && $elt[0] ~~ MalSymbol
27 && $elt[0].val
eq 'splice-unquote'
29 $acc = MalList
([MalSymbol
('concat'), $elt[1], $acc]);
32 $acc = MalList
([MalSymbol
('cons'), quasiquote
($elt), $acc]);
38 sub quasiquote
($ast) {
41 if $ast.elems
== 2 && $ast[0] ~~ MalSymbol
&& $ast[0].val
eq 'unquote' {
47 when MalVector
{ MalList
([MalSymbol
('vec'), qqLoop
($ast)]) }
48 when MalSymbol
|MalHashMap
{ MalList
([MalSymbol
('quote'), $ast]) }
53 sub eval ($ast is copy
, $env is copy
) {
55 return eval_ast
($ast, $env) if $ast !~~ MalList
;
56 return $ast if !$ast.elems
;
58 my ($a0, $a1, $a2, $a3) = $ast.val
;
61 return $env.set
($a1.val
, eval($a2, $env));
64 my $new_env = MalEnv
.new
($env);
65 for |$a1.val
-> $key, $value {
66 $new_env.set
($key.val
, eval($value, $new_env));
72 eval_ast
(MalList
([$ast[1..*-2]]), $env);
76 if eval($a1, $env) ~~ MalNil
|MalFalse
{
77 return $NIL if $a3 ~~ $NIL;
85 my @binds = $a1 ??
$a1.map(*.val
) !! ();
87 eval($a2, MalEnv
.new
($env, @binds, @args));
89 return MalFunction
($a2, $env, @binds, &fn
);
91 when 'quote' { return $a1 }
92 when 'quasiquoteexpand' { return quasiquote
($a1) }
93 when 'quasiquote' { $ast = quasiquote
($a1) }
95 my ($func, @args) = eval_ast
($ast, $env).val
;
96 return $func.apply
(|@args) if $func !~~ MalFunction
;
98 $env = MalEnv
.new
($func.env
, $func.params
, @args);
105 return pr_str
($exp, True
);
108 my $repl_env = MalEnv
.new
;
111 return print(eval(read($str), $repl_env));
114 sub MAIN
($source_file?
, *@args) {
115 $repl_env.set
(.key
, .value
) for %core::ns
;
116 $repl_env.set
('eval', MalCode
({ eval($^a
, $repl_env) }));
117 $repl_env.set
('*ARGV*', MalList
([@args.map({ MalString
($_) })]));
118 rep
(q{(def! not (fn* (a) (if a false true)))});
119 rep
(q{(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))});
121 if ($source_file.defined) {
122 rep
("(load-file \"$source_file\")");
126 while (my $line = prompt
'user> ').defined {
129 when X
::MalException
{ .Str
.say }