perl: Replace _clone() with a ->clone method.
[jackhill/mal.git] / perl / step9_try.pl
index 3a2560a..2f1a175 100644 (file)
@@ -1,17 +1,20 @@
 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
@@ -23,22 +26,22 @@ sub 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())]);
     }
@@ -50,7 +53,7 @@ sub is_macro_call {
         _symbol_Q($ast->[0]) &&
         $env->find($ast->[0])) {
         my ($f) = $env->get($ast->[0]);
-        if ($f->isa('Function')) {
+        if ($f->isa('Mal::Function')) {
             return $f->{ismacro};
         }
     }
@@ -70,20 +73,12 @@ sub macroexpand {
 
 sub eval_ast {
     my($ast, $env) = @_;
-    if ($ast->isa('Symbol')) {
+    if ($ast->isa('Mal::Symbol')) {
        return $env->get($ast);
-    } elsif ($ast->isa('List')) {
-       my @lst = map {EVAL($_, $env)} @$ast;
-       return List->new(\@lst);
-    } elsif ($ast->isa('Vector')) {
-       my @lst = map {EVAL($_, $env)} @$ast;
-       return Vector->new(\@lst);
-    } elsif ($ast->isa('HashMap')) {
-       my $new_hm = {};
-       foreach my $k (keys %$ast) {
-           $new_hm->{$k} = EVAL($ast->get($k), $env);
-       }
-       return HashMap->new($new_hm);
+    } 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;
     }
@@ -108,15 +103,16 @@ sub EVAL {
 
     my ($a0, $a1, $a2, $a3) = @$ast;
     if (!$a0) { return $ast; }
-    given ($a0->isa('Symbol') ? $$a0 : $a0) {
+    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));
+            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;
@@ -130,7 +126,7 @@ sub EVAL {
             # Continue loop (TCO)
         }
         when ('defmacro!') {
-            my $func = EVAL($a2, $env);
+            my $func = EVAL($a2, $env)->clone;
             $func->{ismacro} = 1;
             return $env->set($a1, $func);
         }
@@ -138,31 +134,22 @@ sub EVAL {
             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;
-            };
+           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);
@@ -179,12 +166,12 @@ sub EVAL {
             # Continue loop (TCO)
         }
         when ('fn*') {
-            return Function->new(\&EVAL, $a2, $env, $a1);
+            return Mal::Function->new(\&EVAL, $a2, $env, $a1);
         }
         default {
             my @el = @{eval_ast($ast, $env)};
             my $f = shift @el;
-            if ($f->isa('Function')) {
+            if ($f->isa('Mal::Function')) {
                 $ast = $f->{ast};
                 $env = $f->gen_env(\@el);
                 # Continue loop (TCO)
@@ -204,32 +191,31 @@ sub PRINT {
 }
 
 # 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 (keys %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'),
-              bless sub { EVAL($_[0], $repl_env); }, 'CoreFunction');
-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(q[(def! not (fn* (a) (if a false true)))]);
-REP(q[(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))]);
+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) {
+if (@ARGV) {
     REP(qq[(load-file "$ARGV[0]")]);
     exit 0;
 }
@@ -240,20 +226,17 @@ while (1) {
         local $@;
         my $ret;
         eval {
-            use autodie; # always "throw" errors
             print(REP($line), "\n");
             1;
         } or do {
             my $err = $@;
-           if ($err->isa('BlankException')) {
+           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 {
-               if (ref $err) {
-                   print "Error: ".printer::_pr_str($err)."\n";
-               } else {
-                   chomp $err;
-                   print "Error: $err\n";
-               }
+               chomp $err;
+               print "Error: $err\n";
            }
         };
     };