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 return so
$ast ~~ MalList
|MalVector
&& $ast.elems
;
27 sub quasiquote
($ast) {
29 return MalList
([MalSymbol
('quote'), $ast]);
31 elsif $ast[0] ~~ MalSymbol
&& $ast[0].val
eq 'unquote' {
34 elsif is_pair
($ast[0]) && $ast[0][0] ~~ MalSymbol
&& $ast[0][0].val
eq 'splice-unquote' {
35 return MalList
([MalSymbol
('concat'), $ast[0][1], quasiquote
(MalList
([$ast[1..*]]))]);
38 return MalList
([MalSymbol
('cons'), quasiquote
($ast[0]), quasiquote
(MalList
([$ast[1..*]]))]);
42 sub eval ($ast is copy
, $env is copy
) {
44 return eval_ast
($ast, $env) if $ast !~~ MalList
;
45 return $ast if !$ast.elems
;
47 my ($a0, $a1, $a2, $a3) = $ast.val
;
50 return $env.set
($a1.val
, eval($a2, $env));
53 my $new_env = MalEnv
.new
($env);
54 for |$a1.val
-> $key, $value {
55 $new_env.set
($key.val
, eval($value, $new_env));
61 eval_ast
(MalList
([$ast[1..*-2]]), $env);
65 if eval($a1, $env) ~~ MalNil
|MalFalse
{
66 return $NIL if $a3 ~~ $NIL;
74 my @binds = $a1 ??
$a1.map(*.val
) !! ();
76 eval($a2, MalEnv
.new
($env, @binds, @args));
78 return MalFunction
($a2, $env, @binds, &fn
);
80 when 'quote' { return $a1 }
81 when 'quasiquote' { $ast = quasiquote
($a1) }
83 my ($func, @args) = eval_ast
($ast, $env).val
;
84 return $func.apply
(|@args) if $func !~~ MalFunction
;
86 $env = MalEnv
.new
($func.env
, $func.params
, @args);
93 return pr_str
($exp, True
);
96 my $repl_env = MalEnv
.new
;
99 return print(eval(read($str), $repl_env));
102 sub MAIN
($source_file?
, *@args) {
103 $repl_env.set
(.key
, .value
) for %core::ns
;
104 $repl_env.set
('eval', MalCode
({ eval($^a
, $repl_env) }));
105 $repl_env.set
('*ARGV*', MalList
([@args.map({ MalString
($_) })]));
106 rep
(q{(def! not (fn* (a) (if a false true)))});
107 rep
(q{(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))});
109 if ($source_file.defined) {
110 rep
("(load-file \"$source_file\")");
114 while (my $line = prompt
'user> ').defined {
117 when X
::MalException
{ .Str
.say }