perl: Replace _clone() with a ->clone method.
[jackhill/mal.git] / perl / stepA_mal.pl
CommitLineData
16354bb4 1use strict;
60cb3f03 2use warnings FATAL => "recursion";
01c97316 3no if $] >= 5.018, warnings => "experimental::smartmatch";
29702ab6 4use feature qw(switch);
f26bc011
JM
5use File::Basename;
6use lib dirname (__FILE__);
29702ab6
BH
7
8use Data::Dumper;
7a17c605 9use List::Util qw(pairs pairmap);
378c04f2 10use Scalar::Util qw(blessed);
16354bb4 11
29702ab6 12use readline qw(mal_readline set_rl_mode);
ddc11cf2 13use types qw($nil $true $false _symbol_Q _list_Q);
16354bb4
JM
14use reader;
15use printer;
16use env;
e2defcb1 17use core;
16354bb4
JM
18
19# read
20sub READ {
21 my $str = shift;
22 return reader::read_str($str);
23}
24
25# eval
26sub is_pair {
27 my ($x) = @_;
a71beb7e 28 return $x->isa('Mal::Sequence') && @$x;
16354bb4
JM
29}
30
31sub 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
49sub 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
62sub 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
73sub 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
86sub 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# print
187sub PRINT {
188 my $exp = shift;
189 return printer::_pr_str($exp);
190}
191
192# repl
6708078b 193my $repl_env = Mal::Env->new();
16354bb4
JM
194sub REP {
195 my $str = shift;
196 return PRINT(EVAL(READ($str), $repl_env));
197}
198
199# core.pl: defined using perl
e2defcb1 200foreach 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');
205my @_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
209REP(q[(def! *host-language* "perl")]);
210REP(q[(def! not (fn* (a) (if a false true)))]);
e6d41de4 211REP(q[(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))]);
4514e840 212REP(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 214if (@ARGV && $ARGV[0] eq "--raw") {
b8ee29b2
JM
215 set_rl_mode("raw");
216 shift @ARGV;
217}
e8fe22b0 218if (@ARGV) {
4514e840 219 REP(qq[(load-file "$ARGV[0]")]);
16354bb4
JM
220 exit 0;
221}
4514e840 222REP(q[(println (str "Mal [" *host-language* "]"))]);
16354bb4 223while (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}