Commit | Line | Data |
---|---|---|
16354bb4 JM |
1 | use strict; |
2 | use warnings FATAL => qw(all); | |
01c97316 | 3 | no if $] >= 5.018, warnings => "experimental::smartmatch"; |
f26bc011 JM |
4 | use File::Basename; |
5 | use lib dirname (__FILE__); | |
4425fecc | 6 | use List::Util qw(pairs); |
b8ee29b2 | 7 | use readline qw(mal_readline set_rl_mode); |
16354bb4 JM |
8 | use feature qw(switch); |
9 | use Data::Dumper; | |
10 | ||
216dd886 | 11 | use types qw($nil $true $false _sequential_Q _symbol_Q _list_Q _clone); |
16354bb4 JM |
12 | use reader; |
13 | use printer; | |
14 | use env; | |
e2defcb1 | 15 | use core; |
16354bb4 JM |
16 | use interop qw(pl_to_mal); |
17 | ||
18 | # read | |
19 | sub READ { | |
20 | my $str = shift; | |
21 | return reader::read_str($str); | |
22 | } | |
23 | ||
24 | # eval | |
25 | sub is_pair { | |
26 | my ($x) = @_; | |
e8fe22b0 | 27 | return _sequential_Q($x) && @$x; |
16354bb4 JM |
28 | } |
29 | ||
30 | sub quasiquote { | |
31 | my ($ast) = @_; | |
32 | if (!is_pair($ast)) { | |
33 | return List->new([Symbol->new("quote"), $ast]); | |
ea7a2d2f BH |
34 | } elsif (_symbol_Q($ast->[0]) && ${$ast->[0]} eq 'unquote') { |
35 | return $ast->[1]; | |
36 | } elsif (is_pair($ast->[0]) && _symbol_Q($ast->[0]->[0]) && | |
37 | ${$ast->[0]->[0]} eq 'splice-unquote') { | |
16354bb4 | 38 | return List->new([Symbol->new("concat"), |
ea7a2d2f | 39 | $ast->[0]->[1], |
16354bb4 JM |
40 | quasiquote($ast->rest())]); |
41 | } else { | |
42 | return List->new([Symbol->new("cons"), | |
ea7a2d2f | 43 | quasiquote($ast->[0]), |
16354bb4 JM |
44 | quasiquote($ast->rest())]); |
45 | } | |
46 | } | |
47 | ||
48 | sub is_macro_call { | |
49 | my ($ast, $env) = @_; | |
50 | if (_list_Q($ast) && | |
ea7a2d2f BH |
51 | _symbol_Q($ast->[0]) && |
52 | $env->find($ast->[0])) { | |
53 | my ($f) = $env->get($ast->[0]); | |
2634021c | 54 | if ($f->isa('Function')) { |
16354bb4 JM |
55 | return $f->{ismacro}; |
56 | } | |
57 | } | |
58 | return 0; | |
59 | } | |
60 | ||
61 | sub macroexpand { | |
62 | my ($ast, $env) = @_; | |
63 | while (is_macro_call($ast, $env)) { | |
f3e7214c BH |
64 | my @args = @$ast; |
65 | my $mac = $env->get(shift @args); | |
66 | $ast = &$mac(@args); | |
16354bb4 JM |
67 | } |
68 | return $ast; | |
69 | } | |
70 | ||
71 | ||
72 | sub eval_ast { | |
73 | my($ast, $env) = @_; | |
7b341cf0 BH |
74 | if ($ast->isa('Symbol')) { |
75 | return $env->get($ast); | |
76 | } elsif ($ast->isa('List')) { | |
77 | my @lst = map {EVAL($_, $env)} @$ast; | |
78 | return List->new(\@lst); | |
79 | } elsif ($ast->isa('Vector')) { | |
80 | my @lst = map {EVAL($_, $env)} @$ast; | |
81 | return Vector->new(\@lst); | |
82 | } elsif ($ast->isa('HashMap')) { | |
83 | my $new_hm = {}; | |
90865c5b | 84 | foreach my $k (keys %$ast) { |
7b341cf0 BH |
85 | $new_hm->{$k} = EVAL($ast->get($k), $env); |
86 | } | |
87 | return HashMap->new($new_hm); | |
88 | } else { | |
89 | return $ast; | |
16354bb4 JM |
90 | } |
91 | } | |
92 | ||
93 | sub EVAL { | |
94 | my($ast, $env) = @_; | |
95 | ||
96 | while (1) { | |
97 | ||
98 | #print "EVAL: " . printer::_pr_str($ast) . "\n"; | |
99 | if (! _list_Q($ast)) { | |
100 | return eval_ast($ast, $env); | |
101 | } | |
54ad3d9b | 102 | @$ast or return $ast; |
16354bb4 JM |
103 | |
104 | # apply list | |
105 | $ast = macroexpand($ast, $env); | |
b1165f91 DM |
106 | if (! _list_Q($ast)) { |
107 | return eval_ast($ast, $env); | |
108 | } | |
16354bb4 | 109 | |
e465d814 | 110 | my ($a0, $a1, $a2, $a3) = @$ast; |
e48d19b2 | 111 | if (!$a0) { return $ast; } |
7b341cf0 | 112 | given ($a0->isa('Symbol') ? $$a0 : $a0) { |
28e20df9 | 113 | when ('def!') { |
16354bb4 | 114 | my $res = EVAL($a2, $env); |
b8ee29b2 | 115 | return $env->set($a1, $res); |
16354bb4 | 116 | } |
28e20df9 | 117 | when ('let*') { |
16354bb4 | 118 | my $let_env = Env->new($env); |
4425fecc BH |
119 | foreach my $pair (pairs @$a1) { |
120 | my ($k, $v) = @$pair; | |
121 | $let_env->set($k, EVAL($v, $let_env)); | |
16354bb4 | 122 | } |
89bd4de1 JM |
123 | $ast = $a2; |
124 | $env = $let_env; | |
6301e0b6 | 125 | # Continue loop (TCO) |
16354bb4 | 126 | } |
28e20df9 | 127 | when ('quote') { |
16354bb4 JM |
128 | return $a1; |
129 | } | |
28e20df9 | 130 | when ('quasiquote') { |
6301e0b6 JM |
131 | $ast = quasiquote($a1); |
132 | # Continue loop (TCO) | |
16354bb4 | 133 | } |
28e20df9 | 134 | when ('defmacro!') { |
216dd886 | 135 | my $func = _clone(EVAL($a2, $env)); |
16354bb4 | 136 | $func->{ismacro} = 1; |
b8ee29b2 | 137 | return $env->set($a1, $func); |
16354bb4 | 138 | } |
28e20df9 | 139 | when ('macroexpand') { |
16354bb4 JM |
140 | return macroexpand($a1, $env); |
141 | } | |
28e20df9 | 142 | when ('pl*') { |
16354bb4 JM |
143 | return pl_to_mal(eval(${$a1})); |
144 | } | |
28e20df9 | 145 | when ('try*') { |
89bd4de1 JM |
146 | do { |
147 | local $@; | |
148 | my $ret; | |
149 | eval { | |
150 | use autodie; # always "throw" errors | |
151 | $ret = EVAL($a1, $env); | |
152 | 1; | |
153 | } or do { | |
154 | my $err = $@; | |
28e20df9 | 155 | if ($a2 && ${$a2->[0]} eq 'catch*') { |
89bd4de1 JM |
156 | my $exc; |
157 | if (ref $err) { | |
158 | $exc = $err; | |
159 | } else { | |
160 | $exc = String->new(substr $err, 0, -1); | |
161 | } | |
ea7a2d2f BH |
162 | return EVAL($a2->[2], Env->new($env, |
163 | List->new([$a2->[1]]), | |
89bd4de1 | 164 | List->new([$exc]))); |
16354bb4 | 165 | } else { |
89bd4de1 | 166 | die $err; |
16354bb4 | 167 | } |
89bd4de1 JM |
168 | }; |
169 | return $ret; | |
170 | }; | |
16354bb4 | 171 | } |
28e20df9 | 172 | when ('do') { |
e465d814 BH |
173 | eval_ast($ast->slice(1, $#$ast-1), $env); |
174 | $ast = $ast->[$#$ast]; | |
6301e0b6 | 175 | # Continue loop (TCO) |
16354bb4 | 176 | } |
28e20df9 | 177 | when ('if') { |
16354bb4 JM |
178 | my $cond = EVAL($a1, $env); |
179 | if ($cond eq $nil || $cond eq $false) { | |
180 | $ast = $a3 ? $a3 : $nil; | |
181 | } else { | |
182 | $ast = $a2; | |
183 | } | |
6301e0b6 | 184 | # Continue loop (TCO) |
16354bb4 | 185 | } |
28e20df9 | 186 | when ('fn*') { |
16354bb4 JM |
187 | return Function->new(\&EVAL, $a2, $env, $a1); |
188 | } | |
189 | default { | |
f3e7214c BH |
190 | my @el = @{eval_ast($ast, $env)}; |
191 | my $f = shift @el; | |
2634021c | 192 | if ($f->isa('Function')) { |
16354bb4 | 193 | $ast = $f->{ast}; |
f3e7214c | 194 | $env = $f->gen_env(\@el); |
6301e0b6 | 195 | # Continue loop (TCO) |
16354bb4 | 196 | } else { |
f3e7214c | 197 | return &$f(@el); |
16354bb4 JM |
198 | } |
199 | } | |
200 | } | |
201 | ||
202 | } # TCO while loop | |
203 | } | |
204 | ||
205 | ||
206 | sub PRINT { | |
207 | my $exp = shift; | |
208 | return printer::_pr_str($exp); | |
209 | } | |
210 | ||
211 | # repl | |
212 | my $repl_env = Env->new(); | |
213 | sub REP { | |
214 | my $str = shift; | |
215 | return PRINT(EVAL(READ($str), $repl_env)); | |
216 | } | |
217 | ||
218 | # core.pl: defined using perl | |
e2defcb1 BH |
219 | foreach my $n (keys %core::ns) { |
220 | $repl_env->set(Symbol->new($n), $core::ns{$n}); | |
b8ee29b2 | 221 | } |
b7ad769a | 222 | $repl_env->set(Symbol->new('eval'), |
78f0b085 | 223 | bless sub { EVAL($_[0], $repl_env); }, 'CoreFunction'); |
16354bb4 | 224 | my @_argv = map {String->new($_)} @ARGV[1..$#ARGV]; |
b8ee29b2 | 225 | $repl_env->set(Symbol->new('*ARGV*'), List->new(\@_argv)); |
16354bb4 JM |
226 | |
227 | # core.mal: defined using the language itself | |
4514e840 BH |
228 | REP(q[(def! *host-language* "perl")]); |
229 | REP(q[(def! not (fn* (a) (if a false true)))]); | |
230 | REP(q[(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))]); | |
231 | REP(q[(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)))))))]); | |
89bd4de1 | 232 | |
e8fe22b0 | 233 | if (@ARGV && $ARGV[0] eq "--raw") { |
b8ee29b2 JM |
234 | set_rl_mode("raw"); |
235 | shift @ARGV; | |
236 | } | |
e8fe22b0 | 237 | if (@ARGV) { |
4514e840 | 238 | REP(qq[(load-file "$ARGV[0]")]); |
16354bb4 JM |
239 | exit 0; |
240 | } | |
4514e840 | 241 | REP(q[(println (str "Mal [" *host-language* "]"))]); |
16354bb4 | 242 | while (1) { |
89bd4de1 | 243 | my $line = mal_readline("user> "); |
16354bb4 | 244 | if (! defined $line) { last; } |
89bd4de1 JM |
245 | do { |
246 | local $@; | |
247 | my $ret; | |
248 | eval { | |
249 | use autodie; # always "throw" errors | |
250 | print(REP($line), "\n"); | |
251 | 1; | |
252 | } or do { | |
253 | my $err = $@; | |
2634021c BH |
254 | if ($err->isa('BlankException')) { |
255 | # ignore and continue | |
256 | } else { | |
257 | if (ref $err) { | |
258 | print "Error: ".printer::_pr_str($err)."\n"; | |
259 | } else { | |
260 | chomp $err; | |
261 | print "Error: $err\n"; | |
262 | } | |
263 | } | |
89bd4de1 JM |
264 | }; |
265 | }; | |
16354bb4 | 266 | } |