use strict;
-use warnings FATAL => qw(all);
+use warnings FATAL => "recursion";
no if $] >= 5.018, warnings => "experimental::smartmatch";
+use feature qw(switch);
use File::Basename;
use lib dirname (__FILE__);
-use readline qw(mal_readline set_rl_mode);
-use feature qw(switch);
+
use Data::Dumper;
+use List::Util qw(pairs pairmap);
+use Scalar::Util qw(blessed);
-use types qw($nil $true $false _sequential_Q _symbol_Q _list_Q);
+use readline qw(mal_readline set_rl_mode);
+use types qw($nil $true $false _symbol_Q _list_Q);
use reader;
use printer;
use env;
-use core qw($core_ns);
+use core;
use interop qw(pl_to_mal);
# read
# eval
sub is_pair {
my ($x) = @_;
- return _sequential_Q($x) && scalar(@$x) > 0;
+ return $x->isa('Mal::Sequence') && @$x;
}
sub quasiquote {
my ($ast) = @_;
if (!is_pair($ast)) {
- return List->new([Symbol->new("quote"), $ast]);
+ return Mal::List->new([Mal::Symbol->new("quote"), $ast]);
} elsif (_symbol_Q($ast->[0]) && ${$ast->[0]} eq 'unquote') {
return $ast->[1];
} elsif (is_pair($ast->[0]) && _symbol_Q($ast->[0]->[0]) &&
${$ast->[0]->[0]} eq 'splice-unquote') {
- return List->new([Symbol->new("concat"),
+ return Mal::List->new([Mal::Symbol->new("concat"),
$ast->[0]->[1],
quasiquote($ast->rest())]);
} else {
- return List->new([Symbol->new("cons"),
+ return Mal::List->new([Mal::Symbol->new("cons"),
quasiquote($ast->[0]),
quasiquote($ast->rest())]);
}
_symbol_Q($ast->[0]) &&
$env->find($ast->[0])) {
my ($f) = $env->get($ast->[0]);
- if ((ref $f) =~ /^Function/) {
+ if ($f->isa('Mal::Function')) {
return $f->{ismacro};
}
}
sub macroexpand {
my ($ast, $env) = @_;
while (is_macro_call($ast, $env)) {
- my $mac = $env->get($ast->[0]);
- $ast = $mac->apply($ast->rest());
+ my @args = @$ast;
+ my $mac = $env->get(shift @args);
+ $ast = &$mac(@args);
}
return $ast;
}
sub eval_ast {
my($ast, $env) = @_;
- given (ref $ast) {
- when (/^Symbol/) {
- $env->get($ast);
- }
- when (/^List/) {
- my @lst = map {EVAL($_, $env)} @$ast;
- return List->new(\@lst);
- }
- when (/^Vector/) {
- my @lst = map {EVAL($_, $env)} @$ast;
- return Vector->new(\@lst);
- }
- when (/^HashMap/) {
- my $new_hm = {};
- foreach my $k (keys( %{ $ast->{val} })) {
- $new_hm->{$k} = EVAL($ast->get($k), $env);
- }
- return HashMap->new($new_hm);
- }
- default {
- return $ast;
- }
+ if ($ast->isa('Mal::Symbol')) {
+ return $env->get($ast);
+ } elsif ($ast->isa('Mal::Sequence')) {
+ return ref($ast)->new([ map { EVAL($_, $env) } @$ast ]);
+ } elsif ($ast->isa('Mal::HashMap')) {
+ return Mal::HashMap->new({ pairmap { $a => EVAL($b, $env) } %$ast });
+ } else {
+ return $ast;
}
}
if (! _list_Q($ast)) {
return eval_ast($ast, $env);
}
+ @$ast or return $ast;
# apply list
$ast = macroexpand($ast, $env);
my ($a0, $a1, $a2, $a3) = @$ast;
if (!$a0) { return $ast; }
- given ((ref $a0) =~ /^Symbol/ ? $$a0 : $a0) {
- when (/^def!$/) {
+ given ($a0->isa('Mal::Symbol') ? $$a0 : $a0) {
+ when ('def!') {
my $res = EVAL($a2, $env);
return $env->set($a1, $res);
}
- when (/^let\*$/) {
- my $let_env = Env->new($env);
- for(my $i=0; $i < scalar(@$a1); $i+=2) {
- $let_env->set($a1->[$i], EVAL($a1->[$i+1], $let_env));
+ when ('let*') {
+ my $let_env = Mal::Env->new($env);
+ foreach my $pair (pairs @$a1) {
+ my ($k, $v) = @$pair;
+ $let_env->set($k, EVAL($v, $let_env));
}
$ast = $a2;
$env = $let_env;
# Continue loop (TCO)
}
- when (/^quote$/) {
+ when ('quote') {
return $a1;
}
- when (/^quasiquote$/) {
+ when ('quasiquote') {
$ast = quasiquote($a1);
# Continue loop (TCO)
}
- when (/^defmacro!$/) {
- my $func = EVAL($a2, $env);
+ when ('defmacro!') {
+ my $func = EVAL($a2, $env)->clone;
$func->{ismacro} = 1;
return $env->set($a1, $func);
}
- when (/^macroexpand$/) {
+ when ('macroexpand') {
return macroexpand($a1, $env);
}
- when (/^try\*$/) {
- do {
- local $@;
- my $ret;
- eval {
- use autodie; # always "throw" errors
- $ret = EVAL($a1, $env);
- 1;
- } or do {
- my $err = $@;
- if ($a2 && ${$a2->[0]} eq "catch\*") {
- my $exc;
- if (ref $err) {
- $exc = $err;
- } else {
- $exc = String->new(substr $err, 0, -1);
- }
- return EVAL($a2->[2], Env->new($env,
- List->new([$a2->[1]]),
- List->new([$exc])));
- } else {
- die $err;
- }
- };
- return $ret;
- };
- }
- when (/^do$/) {
+ when ('try*') {
+ local $@;
+ my $ret = eval { EVAL($a1, $env) };
+ return $ret unless $@;
+ if ($a2 && ${$a2->[0]} eq 'catch*') {
+ my $exc;
+ if (defined(blessed $@) && $@->isa('Mal::Type')) {
+ $exc = $@;
+ } else {
+ chomp(my $msg = $@);
+ $exc = Mal::String->new($msg);
+ }
+ my $catch_env = Mal::Env->new($env, [$a2->[1]], [$exc]);
+ return EVAL($a2->[2], $catch_env)
+ } else {
+ die $@;
+ }
+ }
+ when ('do') {
eval_ast($ast->slice(1, $#$ast-1), $env);
$ast = $ast->[$#$ast];
# Continue loop (TCO)
}
- when (/^if$/) {
+ when ('if') {
my $cond = EVAL($a1, $env);
if ($cond eq $nil || $cond eq $false) {
$ast = $a3 ? $a3 : $nil;
}
# Continue loop (TCO)
}
- when (/^fn\*$/) {
- return Function->new(\&EVAL, $a2, $env, $a1);
+ when ('fn*') {
+ return Mal::Function->new(\&EVAL, $a2, $env, $a1);
}
default {
- my $el = eval_ast($ast, $env);
- my $f = $el->[0];
- if ((ref $f) =~ /^Function/) {
+ my @el = @{eval_ast($ast, $env)};
+ my $f = shift @el;
+ if ($f->isa('Mal::Function')) {
$ast = $f->{ast};
- $env = $f->gen_env($el->rest());
+ $env = $f->gen_env(\@el);
# Continue loop (TCO)
} else {
- return &{ $f }($el->rest());
+ return &$f(@el);
}
}
}
}
# repl
-my $repl_env = Env->new();
+my $repl_env = Mal::Env->new();
sub REP {
my $str = shift;
return PRINT(EVAL(READ($str), $repl_env));
}
# core.pl: defined using perl
-foreach my $n (%$core_ns) {
- $repl_env->set(Symbol->new($n), $core_ns->{$n});
+foreach my $n (keys %core::ns) {
+ $repl_env->set(Mal::Symbol->new($n), $core::ns{$n});
}
-$repl_env->set(Symbol->new('eval'), sub { EVAL($_[0]->[0], $repl_env); });
-my @_argv = map {String->new($_)} @ARGV[1..$#ARGV];
-$repl_env->set(Symbol->new('*ARGV*'), List->new(\@_argv));
+$repl_env->set(Mal::Symbol->new('eval'),
+ bless sub { EVAL($_[0], $repl_env); }, 'Mal::CoreFunction');
+my @_argv = map {Mal::String->new($_)} @ARGV[1..$#ARGV];
+$repl_env->set(Mal::Symbol->new('*ARGV*'), Mal::List->new(\@_argv));
# core.mal: defined using the language itself
-REP("(def! not (fn* (a) (if a false true)))");
-REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
-REP("(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)))))))");
+REP(q[(def! not (fn* (a) (if a false true)))]);
+REP(q[(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))]);
+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)))))))]);
-
-if (scalar(@ARGV) > 0 && $ARGV[0] eq "--raw") {
+if (@ARGV && $ARGV[0] eq "--raw") {
set_rl_mode("raw");
shift @ARGV;
}
-if (scalar(@ARGV) > 0) {
- REP("(load-file \"" . $ARGV[0] . "\")");
+if (@ARGV) {
+ REP(qq[(load-file "$ARGV[0]")]);
exit 0;
}
while (1) {
local $@;
my $ret;
eval {
- use autodie; # always "throw" errors
print(REP($line), "\n");
1;
} or do {
my $err = $@;
- given (ref $err) {
- when (/^BlankException/) {
- # ignore and continue
- }
- default {
- if (ref $err) {
- print "Error: ".printer::_pr_str($err)."\n";
- } else {
- chomp $err;
- print "Error: $err\n";
- }
- }
- }
+ if (defined(blessed $err) && $err->isa('Mal::BlankException')) {
+ # ignore and continue
+ } elsif (defined(blessed $err) && $err->isa('Mal::Type')) {
+ print "Error: ".printer::_pr_str($err)."\n";
+ } else {
+ chomp $err;
+ print "Error: $err\n";
+ }
};
};
}