go: update README. Backport Func usage.
[jackhill/mal.git] / perl / step7_quote.pl
CommitLineData
fd637e03
JM
1use strict;
2use warnings FATAL => qw(all);
f26bc011
JM
3use File::Basename;
4use lib dirname (__FILE__);
89bd4de1 5use readline qw(mal_readline);
fd637e03
JM
6use feature qw(switch);
7use Data::Dumper;
8
89bd4de1 9use types qw($nil $true $false _sequential_Q _symbol_Q _list_Q);
fd637e03
JM
10use reader;
11use printer;
12use env;
13use core qw($core_ns);
14
15# read
16sub READ {
17 my $str = shift;
18 return reader::read_str($str);
19}
20
21# eval
22sub is_pair {
23 my ($x) = @_;
89bd4de1 24 return _sequential_Q($x) && scalar(@{$x->{val}}) > 0;
fd637e03
JM
25}
26
27sub quasiquote {
28 my ($ast) = @_;
29 if (!is_pair($ast)) {
30 return List->new([Symbol->new("quote"), $ast]);
89bd4de1
JM
31 } elsif (_symbol_Q($ast->nth(0)) && ${$ast->nth(0)} eq 'unquote') {
32 return $ast->nth(1);
33 } elsif (is_pair($ast->nth(0)) && _symbol_Q($ast->nth(0)->nth(0)) &&
34 ${$ast->nth(0)->nth(0)} eq 'splice-unquote') {
fd637e03 35 return List->new([Symbol->new("concat"),
89bd4de1 36 $ast->nth(0)->nth(1),
fd637e03
JM
37 quasiquote($ast->rest())]);
38 } else {
39 return List->new([Symbol->new("cons"),
89bd4de1 40 quasiquote($ast->nth(0)),
fd637e03
JM
41 quasiquote($ast->rest())]);
42 }
43}
44
45sub eval_ast {
46 my($ast, $env) = @_;
47 given (ref $ast) {
48 when (/^Symbol/) {
49 $env->get($$ast);
50 }
51 when (/^List/) {
89bd4de1 52 my @lst = map {EVAL($_, $env)} @{$ast->{val}};
fd637e03
JM
53 return List->new(\@lst);
54 }
89bd4de1
JM
55 when (/^Vector/) {
56 my @lst = map {EVAL($_, $env)} @{$ast->{val}};
57 return Vector->new(\@lst);
58 }
59 when (/^HashMap/) {
60 my $new_hm = {};
61 foreach my $k (keys($ast->{val})) {
62 $new_hm->{$k} = EVAL($ast->get($k), $env);
63 }
64 return HashMap->new($new_hm);
65 }
fd637e03
JM
66 default {
67 return $ast;
68 }
69 }
70}
71
72sub EVAL {
73 my($ast, $env) = @_;
74
75 while (1) {
76
77 #print "EVAL: " . printer::_pr_str($ast) . "\n";
89bd4de1 78 if (! _list_Q($ast)) {
fd637e03
JM
79 return eval_ast($ast, $env);
80 }
81
82 # apply list
89bd4de1 83 my ($a0, $a1, $a2, $a3) = @{$ast->{val}};
fd637e03
JM
84 given ((ref $a0) =~ /^Symbol/ ? $$a0 : $a0) {
85 when (/^def!$/) {
86 my $res = EVAL($a2, $env);
87 return $env->set($$a1, $res);
88 }
89 when (/^let\*$/) {
90 my $let_env = Env->new($env);
89bd4de1
JM
91 for(my $i=0; $i < scalar(@{$a1->{val}}); $i+=2) {
92 $let_env->set(${$a1->nth($i)}, EVAL($a1->nth($i+1), $let_env));
fd637e03 93 }
89bd4de1
JM
94 $ast = $a2;
95 $env = $let_env;
6301e0b6 96 # Continue loop (TCO)
fd637e03
JM
97 }
98 when (/^quote$/) {
99 return $a1;
100 }
101 when (/^quasiquote$/) {
6301e0b6
JM
102 $ast = quasiquote($a1);
103 # Continue loop (TCO)
fd637e03
JM
104 }
105 when (/^do$/) {
89bd4de1
JM
106 eval_ast($ast->slice(1, $#{$ast->{val}}-1), $env);
107 $ast = $ast->nth($#{$ast->{val}});
6301e0b6 108 # Continue loop (TCO)
fd637e03
JM
109 }
110 when (/^if$/) {
111 my $cond = EVAL($a1, $env);
112 if ($cond eq $nil || $cond eq $false) {
113 $ast = $a3 ? $a3 : $nil;
114 } else {
115 $ast = $a2;
116 }
6301e0b6 117 # Continue loop (TCO)
fd637e03
JM
118 }
119 when (/^fn\*$/) {
120 return Function->new(\&EVAL, $a2, $env, $a1);
121 }
122 default {
123 my $el = eval_ast($ast, $env);
89bd4de1 124 my $f = $el->nth(0);
fd637e03
JM
125 if ((ref $f) =~ /^Function/) {
126 $ast = $f->{ast};
127 $env = $f->gen_env($el->rest());
6301e0b6 128 # Continue loop (TCO)
fd637e03
JM
129 } else {
130 return &{ $f }($el->rest());
131 }
132 }
133 }
134
135 } # TCO while loop
136}
137
138# print
139sub PRINT {
140 my $exp = shift;
141 return printer::_pr_str($exp);
142}
143
144# repl
145my $repl_env = Env->new();
146sub REP {
147 my $str = shift;
148 return PRINT(EVAL(READ($str), $repl_env));
149}
150
151# core.pl: defined using perl
152foreach my $n (%$core_ns) { $repl_env->set($n, $core_ns->{$n}); }
89bd4de1 153$repl_env->set('eval', sub { EVAL($_[0]->nth(0), $repl_env); });
fd637e03
JM
154my @_argv = map {String->new($_)} @ARGV[1..$#ARGV];
155$repl_env->set('*ARGV*', List->new(\@_argv));
156
157# core.mal: defined using the language itself
158REP("(def! not (fn* (a) (if a false true)))");
159REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
160
89bd4de1 161if (scalar(@ARGV) > 0) {
fd637e03
JM
162 REP("(load-file \"" . $ARGV[0] . "\")");
163 exit 0;
164}
165while (1) {
89bd4de1 166 my $line = mal_readline("user> ");
fd637e03 167 if (! defined $line) { last; }
89bd4de1
JM
168 do {
169 local $@;
170 my $ret;
171 eval {
172 use autodie; # always "throw" errors
173 print(REP($line), "\n");
174 1;
175 } or do {
176 my $err = $@;
177 given (ref $err) {
178 when (/^BlankException/) {
179 # ignore and continue
180 }
181 default {
182 chomp $err;
183 print "Error: $err\n";
184 }
185 }
186 };
187 };
fd637e03 188}