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