diff --git a/impls/perl/step0_repl.pl b/impls/perl/step0_repl.pl index 77936339a7..2947178df3 100644 --- a/impls/perl/step0_repl.pl +++ b/impls/perl/step0_repl.pl @@ -1,9 +1,13 @@ +#!/usr/bin/perl + use strict; use warnings; -use File::Basename; -use lib dirname (__FILE__); +use File::Basename 'dirname'; +use lib dirname(__FILE__); + +use English '-no_match_vars'; -use readline qw(mal_readline set_rl_mode); +use Readline qw(mal_readline set_rl_mode); # read sub READ { @@ -13,7 +17,7 @@ sub READ { # eval sub EVAL { - my($ast, $env) = @_; + my ($ast) = @_; return $ast; } @@ -26,14 +30,15 @@ sub PRINT { # repl sub REP { my $str = shift; - return PRINT(EVAL(READ($str), {})); + return PRINT( EVAL( READ($str) ) ); } -if (scalar(@ARGV) > 0 && $ARGV[0] eq "--raw") { - set_rl_mode("raw"); +# Command line arguments +if ( $ARGV[0] eq '--raw' ) { + set_rl_mode('raw'); + shift @ARGV; } -while (1) { - my $line = mal_readline("user> "); - if (! defined $line) { last; } - print(REP($line), "\n"); + +while ( defined( my $line = mal_readline('user> ') ) ) { + print REP($line), "\n" or die $ERRNO; } diff --git a/impls/perl/step1_read_print.pl b/impls/perl/step1_read_print.pl index 9717d7e51c..43f2d82fcf 100644 --- a/impls/perl/step1_read_print.pl +++ b/impls/perl/step1_read_print.pl @@ -1,58 +1,52 @@ +#!/usr/bin/perl + use strict; use warnings; -use File::Basename; -use lib dirname (__FILE__); +use File::Basename 'dirname'; +use lib dirname(__FILE__); -use Scalar::Util qw(blessed); +use English '-no_match_vars'; -use readline qw(mal_readline set_rl_mode); -use reader; -use printer; +use Readline qw(mal_readline set_rl_mode); +use Reader qw(read_str); +use Printer qw(pr_str); # read sub READ { my $str = shift; - return reader::read_str($str); + return read_str($str); } # eval sub EVAL { - my($ast, $env) = @_; + my ($ast) = @_; return $ast; } # print sub PRINT { my $exp = shift; - return printer::_pr_str($exp); + return pr_str($exp); } # repl sub REP { my $str = shift; - return PRINT(EVAL(READ($str), {})); + return PRINT( EVAL( READ($str) ) ); } -if (@ARGV && $ARGV[0] eq "--raw") { - set_rl_mode("raw"); +# Command line arguments +if ( $ARGV[0] eq '--raw' ) { + set_rl_mode('raw'); + shift @ARGV; } -while (1) { - my $line = mal_readline("user> "); - if (! defined $line) { last; } - do { - local $@; - my $ret; - eval { - print(REP($line), "\n"); - 1; - } or do { - my $err = $@; - if (defined(blessed $err) && $err->isa('Mal::BlankException')) { - # ignore and continue - } else { - chomp $err; - print "Error: $err\n"; - } - }; + +while ( defined( my $line = mal_readline('user> ') ) ) { + eval { + print REP($line), "\n" or die $ERRNO; + 1; + } or do { + my $err = $EVAL_ERROR; + print 'Error: ', $err or die $ERRNO; }; } diff --git a/impls/perl/step2_eval.pl b/impls/perl/step2_eval.pl index 3ef9ce44c8..23bccdc69f 100644 --- a/impls/perl/step2_eval.pl +++ b/impls/perl/step2_eval.pl @@ -1,86 +1,79 @@ +#!/usr/bin/perl + use strict; use warnings; -use File::Basename; -use lib dirname (__FILE__); +use File::Basename 'dirname'; +use lib dirname(__FILE__); -use Data::Dumper; +use English '-no_match_vars'; use List::Util qw(pairmap); -use Scalar::Util qw(blessed); -use readline qw(mal_readline set_rl_mode); -use types; -use reader; -use printer; +use Readline qw(mal_readline set_rl_mode); +use Types qw(); +use Reader qw(read_str); +use Printer qw(pr_str); # read sub READ { my $str = shift; - return reader::read_str($str); + return read_str($str); } # eval sub EVAL { - my($ast, $env) = @_; - #print "EVAL: " . printer::_pr_str($ast) . "\n"; - - if ($ast->isa('Mal::Symbol')) { - return $env->{$$ast} // die "'$$ast' not found\n"; - } elsif ($ast->isa('Mal::Vector')) { - return ref($ast)->new([ map { EVAL($_, $env) } @$ast ]); - } elsif ($ast->isa('Mal::HashMap')) { - return Mal::HashMap->new({ pairmap { $a => EVAL($b, $env) } %$ast }); - } elsif (! $ast->isa('Mal::List')) { - return $ast; - } + my ( $ast, $env ) = @_; - # apply list + #print 'EVAL: ', pr_str($ast), "\n" or die $ERRNO; - unless (@$ast) { return $ast; } - my ($a0) = @$ast; - my $f = EVAL($a0, $env); - my (undef, @args) = @$ast; - return &$f(map { EVAL($_, $env) } @args); + if ( $ast->isa('Mal::Symbol') ) { + return $env->{ ${$ast} } // die "'${$ast}' not found\n"; + } + if ( $ast->isa('Mal::Vector') ) { + return Mal::Vector->new( [ map { EVAL( $_, $env ) } @{$ast} ] ); + } + if ( $ast->isa('Mal::HashMap') ) { + return Mal::HashMap->new( + { pairmap { $a => EVAL( $b, $env ) } %{$ast} } ); + } + if ( $ast->isa('Mal::List') and @{$ast} ) { + my ( $a0, @args ) = @{$ast}; + my $f = EVAL( $a0, $env ); + return $f->( map { EVAL( $_, $env ) } @args ); + } + return $ast; } # print sub PRINT { my $exp = shift; - return printer::_pr_str($exp); + return pr_str($exp); } # repl my $repl_env = { - '+' => sub { Mal::Integer->new(${$_[0]} + ${$_[1]}) }, - '-' => sub { Mal::Integer->new(${$_[0]} - ${$_[1]}) }, - '*' => sub { Mal::Integer->new(${$_[0]} * ${$_[1]}) }, - '/' => sub { Mal::Integer->new(${$_[0]} / ${$_[1]}) }, + q{+} => sub { Mal::Integer->new( ${ $_[0] } + ${ $_[1] } ) }, + q{-} => sub { Mal::Integer->new( ${ $_[0] } - ${ $_[1] } ) }, + q{*} => sub { Mal::Integer->new( ${ $_[0] } * ${ $_[1] } ) }, + q{/} => sub { Mal::Integer->new( ${ $_[0] } / ${ $_[1] } ) }, }; sub REP { my $str = shift; - return PRINT(EVAL(READ($str), $repl_env)); + return PRINT( EVAL( READ($str), $repl_env ) ); } -if (@ARGV && $ARGV[0] eq "--raw") { - set_rl_mode("raw"); +# Command line arguments +if ( $ARGV[0] eq '--raw' ) { + set_rl_mode('raw'); + shift @ARGV; } -while (1) { - my $line = mal_readline("user> "); - if (! defined $line) { last; } - do { - local $@; - my $ret; - eval { - print(REP($line), "\n"); - 1; - } or do { - my $err = $@; - if (defined(blessed $err) && $err->isa('Mal::BlankException')) { - # ignore and continue - } else { - chomp $err; - print "Error: $err\n"; - } - }; + +while ( defined( my $line = mal_readline('user> ') ) ) { + eval { + print REP($line), "\n" or die $ERRNO; + 1; + } or do { + my $err = $EVAL_ERROR; + print 'Error: ', $err or die $ERRNO; }; } diff --git a/impls/perl/step3_env.pl b/impls/perl/step3_env.pl index 5e6baa1a16..9b09031d8a 100644 --- a/impls/perl/step3_env.pl +++ b/impls/perl/step3_env.pl @@ -1,115 +1,109 @@ +#!/usr/bin/perl + use strict; use warnings; -no if $] >= 5.018, warnings => "experimental::smartmatch"; -use feature qw(switch); -use File::Basename; -use lib dirname (__FILE__); +use File::Basename 'dirname'; +use lib dirname(__FILE__); -use Data::Dumper; +use English '-no_match_vars'; use List::Util qw(pairs pairmap); -use Scalar::Util qw(blessed); -use readline qw(mal_readline set_rl_mode); -use types qw($nil $false); -use reader; -use printer; -use env; +use Readline qw(mal_readline set_rl_mode); +use Types qw(nil false); +use Reader qw(read_str); +use Printer qw(pr_str); +use Env; # read sub READ { my $str = shift; - return reader::read_str($str); + return read_str($str); } # eval + +my %special_forms = ( + 'def!' => \&special_def, + 'let*' => \&special_let, +); + sub EVAL { - my($ast, $env) = @_; + my ( $ast, $env ) = @_; my $dbgeval = $env->get('DEBUG-EVAL'); - if ($dbgeval and $dbgeval ne $nil and $dbgeval ne $false) { - print "EVAL: " . printer::_pr_str($ast) . "\n"; + if ( $dbgeval + and not $dbgeval->isa('Mal::Nil') + and not $dbgeval->isa('Mal::False') ) + { + print 'EVAL: ', pr_str($ast), "\n" or die $ERRNO; } - if ($ast->isa('Mal::Symbol')) { - my $val = $env->get($$ast); - die "'$$ast' not found\n" unless $val; - return $val; - } elsif ($ast->isa('Mal::Vector')) { - return ref($ast)->new([ map { EVAL($_, $env) } @$ast ]); - } elsif ($ast->isa('Mal::HashMap')) { - return Mal::HashMap->new({ pairmap { $a => EVAL($b, $env) } %$ast }); - } elsif (! $ast->isa('Mal::List')) { - return $ast; + if ( $ast->isa('Mal::Symbol') ) { + return $env->get( ${$ast} ) // die "'${$ast}' not found\n"; + } + if ( $ast->isa('Mal::Vector') ) { + return Mal::Vector->new( [ map { EVAL( $_, $env ) } @{$ast} ] ); + } + if ( $ast->isa('Mal::HashMap') ) { + return Mal::HashMap->new( + { pairmap { $a => EVAL( $b, $env ) } %{$ast} } ); + } + if ( $ast->isa('Mal::List') and @{$ast} ) { + my ( $a0, @args ) = @{$ast}; + if ( $a0->isa('Mal::Symbol') and my $sf = $special_forms{ ${$a0} } ) { + return $sf->( $env, @args ); + } + my $f = EVAL( $a0, $env ); + return $f->( map { EVAL( $_, $env ) } @args ); } + return $ast; +} - # apply list +sub special_def { + my ( $env, $sym, $val ) = @_; + return $env->set( ${$sym}, EVAL( $val, $env ) ); +} - unless (@$ast) { return $ast; } - my ($a0) = @$ast; - given ($a0->isa('Mal::Symbol') ? $$a0 : $a0) { - when ('def!') { - my (undef, $sym, $val) = @$ast; - return $env->set($$sym, EVAL($val, $env)); - } - when ('let*') { - my (undef, $bindings, $body) = @$ast; - my $let_env = Mal::Env->new($env); - foreach my $pair (pairs @$bindings) { - my ($k, $v) = @$pair; - $let_env->set($$k, EVAL($v, $let_env)); - } - return EVAL($body, $let_env); - } - default { - my $f = EVAL($a0, $env); - my (undef, @args) = @$ast; - return &$f(map { EVAL($_, $env) } @args); - } +sub special_let { + my ( $env, $bindings, $body ) = @_; + my $let_env = Env->new($env); + foreach my $pair ( pairs @{$bindings} ) { + my ( $k, $v ) = @{$pair}; + $let_env->set( ${$k}, EVAL( $v, $let_env ) ); } + return EVAL( $body, $let_env ); } # print sub PRINT { my $exp = shift; - return printer::_pr_str($exp); + return pr_str($exp); } # repl -my $repl_env = Mal::Env->new(); +my $repl_env = Env->new(); +$repl_env->set( q{+}, sub { Mal::Integer->new( ${ $_[0] } + ${ $_[1] } ) } ); +$repl_env->set( q{-}, sub { Mal::Integer->new( ${ $_[0] } - ${ $_[1] } ) } ); +$repl_env->set( q{*}, sub { Mal::Integer->new( ${ $_[0] } * ${ $_[1] } ) } ); +$repl_env->set( q{/}, sub { Mal::Integer->new( ${ $_[0] } / ${ $_[1] } ) } ); + sub REP { my $str = shift; - return PRINT(EVAL(READ($str), $repl_env)); + return PRINT( EVAL( READ($str), $repl_env ) ); } -$repl_env->set('+', - sub { Mal::Integer->new(${$_[0]} + ${$_[1]}) } ); -$repl_env->set('-', - sub { Mal::Integer->new(${$_[0]} - ${$_[1]}) } ); -$repl_env->set('*', - sub { Mal::Integer->new(${$_[0]} * ${$_[1]}) } ); -$repl_env->set('/', - sub { Mal::Integer->new(${$_[0]} / ${$_[1]}) } ); - -if (@ARGV && $ARGV[0] eq "--raw") { - set_rl_mode("raw"); +# Command line arguments +if ( $ARGV[0] eq '--raw' ) { + set_rl_mode('raw'); + shift @ARGV; } -while (1) { - my $line = mal_readline("user> "); - if (! defined $line) { last; } - do { - local $@; - my $ret; - eval { - print(REP($line), "\n"); - 1; - } or do { - my $err = $@; - if (defined(blessed $err) && $err->isa('Mal::BlankException')) { - # ignore and continue - } else { - chomp $err; - print "Error: $err\n"; - } - }; + +while ( defined( my $line = mal_readline('user> ') ) ) { + eval { + print REP($line), "\n" or die $ERRNO; + 1; + } or do { + my $err = $EVAL_ERROR; + print 'Error: ', $err or die $ERRNO; }; } diff --git a/impls/perl/step4_if_fn_do.pl b/impls/perl/step4_if_fn_do.pl index 3958796d27..df4b825302 100644 --- a/impls/perl/step4_if_fn_do.pl +++ b/impls/perl/step4_if_fn_do.pl @@ -1,137 +1,148 @@ +#!/usr/bin/perl + use strict; use warnings; -no if $] >= 5.018, warnings => "experimental::smartmatch"; -use feature qw(switch); -use File::Basename; -use lib dirname (__FILE__); +use File::Basename 'dirname'; +use lib dirname(__FILE__); -use Data::Dumper; +use English '-no_match_vars'; use List::Util qw(pairs pairmap); -use Scalar::Util qw(blessed); -use readline qw(mal_readline set_rl_mode); -use types qw($nil $true $false); -use reader; -use printer; -use env; -use core; +use Readline qw(mal_readline set_rl_mode); +use Types qw(nil false); +use Reader qw(read_str); +use Printer qw(pr_str); +use Env; +use Core qw(%NS); # read sub READ { my $str = shift; - return reader::read_str($str); + return read_str($str); } # eval + +my %special_forms = ( + 'def!' => \&special_def, + 'let*' => \&special_let, + + 'do' => \&special_do, + 'if' => \&special_if, + 'fn*' => \&special_fn, +); + sub EVAL { - my($ast, $env) = @_; + my ( $ast, $env ) = @_; my $dbgeval = $env->get('DEBUG-EVAL'); - if ($dbgeval and $dbgeval ne $nil and $dbgeval ne $false) { - print "EVAL: " . printer::_pr_str($ast) . "\n"; + if ( $dbgeval + and not $dbgeval->isa('Mal::Nil') + and not $dbgeval->isa('Mal::False') ) + { + print 'EVAL: ', pr_str($ast), "\n" or die $ERRNO; } - if ($ast->isa('Mal::Symbol')) { - my $val = $env->get($$ast); - die "'$$ast' not found\n" unless $val; - return $val; - } elsif ($ast->isa('Mal::Vector')) { - return ref($ast)->new([ map { EVAL($_, $env) } @$ast ]); - } elsif ($ast->isa('Mal::HashMap')) { - return Mal::HashMap->new({ pairmap { $a => EVAL($b, $env) } %$ast }); - } elsif (! $ast->isa('Mal::List')) { - return $ast; + if ( $ast->isa('Mal::Symbol') ) { + return $env->get( ${$ast} ) // die "'${$ast}' not found\n"; + } + if ( $ast->isa('Mal::Vector') ) { + return Mal::Vector->new( [ map { EVAL( $_, $env ) } @{$ast} ] ); + } + if ( $ast->isa('Mal::HashMap') ) { + return Mal::HashMap->new( + { pairmap { $a => EVAL( $b, $env ) } %{$ast} } ); + } + if ( $ast->isa('Mal::List') and @{$ast} ) { + my ( $a0, @args ) = @{$ast}; + if ( $a0->isa('Mal::Symbol') and my $sf = $special_forms{ ${$a0} } ) { + return $sf->( $env, @args ); + } + my $f = EVAL( $a0, $env ); + return $f->( map { EVAL( $_, $env ) } @args ); } + return $ast; +} - # apply list +sub special_def { + my ( $env, $sym, $val ) = @_; + return $env->set( ${$sym}, EVAL( $val, $env ) ); +} - unless (@$ast) { return $ast; } - my ($a0) = @$ast; - given ($a0->isa('Mal::Symbol') ? $$a0 : $a0) { - when ('def!') { - my (undef, $sym, $val) = @$ast; - return $env->set($$sym, EVAL($val, $env)); - } - when ('let*') { - my (undef, $bindings, $body) = @$ast; - my $let_env = Mal::Env->new($env); - foreach my $pair (pairs @$bindings) { - my ($k, $v) = @$pair; - $let_env->set($$k, EVAL($v, $let_env)); - } - return EVAL($body, $let_env); - } - when ('do') { - my (undef, @todo) = @$ast; - my $last = pop @todo; - map { EVAL($_, $env) } @todo; - return EVAL($last, $env); - } - when ('if') { - my (undef, $if, $then, $else) = @$ast; - my $cond = EVAL($if, $env); - if ($cond eq $nil || $cond eq $false) { - return $else ? EVAL($else, $env) : $nil; - } else { - return EVAL($then, $env); - } - } - when ('fn*') { - my (undef, $params, $body) = @$ast; - return Mal::Function->new(sub { - #print "running fn*\n"; - return EVAL($body, Mal::Env->new($env, $params, \@_)); - }); - } - default { - my $f = EVAL($a0, $env); - my (undef, @args) = @$ast; - return &$f(map { EVAL($_, $env) } @args); - } +sub special_let { + my ( $env, $bindings, $body ) = @_; + my $let_env = Env->new($env); + foreach my $pair ( pairs @{$bindings} ) { + my ( $k, $v ) = @{$pair}; + $let_env->set( ${$k}, EVAL( $v, $let_env ) ); + } + return EVAL( $body, $let_env ); +} + +sub special_do { + my ( $env, @todo ) = @_; + my $final = pop @todo; + for (@todo) { + EVAL( $_, $env ); + } + return EVAL( $final, $env ); +} + +sub special_if { + my ( $env, $if, $then, $else ) = @_; + my $cond = EVAL( $if, $env ); + if ( not $cond->isa('Mal::Nil') and not $cond->isa('Mal::False') ) { + return EVAL( $then, $env ); + } + if ($else) { + return EVAL( $else, $env ); } + return nil; +} + +sub special_fn { + my ( $env, $params, $body ) = @_; + return Mal::Function->new( + sub { + return EVAL( $body, Env->new( $env, $params, \@_ ) ); + } + ); } # print sub PRINT { my $exp = shift; - return printer::_pr_str($exp); + return pr_str($exp); } # repl -my $repl_env = Mal::Env->new(); +my $repl_env = Env->new(); + sub REP { my $str = shift; - return PRINT(EVAL(READ($str), $repl_env)); + return PRINT( EVAL( READ($str), $repl_env ) ); +} + +# Command line arguments +if ( $ARGV[0] eq '--raw' ) { + set_rl_mode('raw'); + shift @ARGV; } # core.pl: defined using perl -foreach my $n (keys %core::ns) { - $repl_env->set($n, $core::ns{$n}); +while ( my ( $k, $v ) = each %NS ) { + $repl_env->set( $k, Mal::Function->new($v) ); } # core.mal: defined using the language itself REP(q[(def! not (fn* (a) (if a false true)))]); -if (@ARGV && $ARGV[0] eq "--raw") { - set_rl_mode("raw"); -} -while (1) { - my $line = mal_readline("user> "); - if (! defined $line) { last; } - do { - local $@; - my $ret; - eval { - print(REP($line), "\n"); - 1; - } or do { - my $err = $@; - if (defined(blessed $err) && $err->isa('Mal::BlankException')) { - # ignore and continue - } else { - chomp $err; - print "Error: $err\n"; - } - }; +while ( defined( my $line = mal_readline('user> ') ) ) { + eval { + print REP($line), "\n" or die $ERRNO; + 1; + } or do { + my $err = $EVAL_ERROR; + print 'Error: ', $err or die $ERRNO; }; } diff --git a/impls/perl/step5_tco.pl b/impls/perl/step5_tco.pl index c7fd80e3e9..7d2460de36 100644 --- a/impls/perl/step5_tco.pl +++ b/impls/perl/step5_tco.pl @@ -1,142 +1,158 @@ +#!/usr/bin/perl + use strict; -use warnings FATAL => "recursion"; -no if $] >= 5.018, warnings => "experimental::smartmatch"; -use feature qw(switch); -use File::Basename; -use lib dirname (__FILE__); +use warnings FATAL => 'recursion'; +use File::Basename 'dirname'; +use lib dirname(__FILE__); -use Data::Dumper; +use English '-no_match_vars'; use List::Util qw(pairs pairmap); -use Scalar::Util qw(blessed); -use readline qw(mal_readline set_rl_mode); -use types qw($nil $true $false); -use reader; -use printer; -use env; -use core; +use Readline qw(mal_readline set_rl_mode); +use Types qw(nil false); +use Reader qw(read_str); +use Printer qw(pr_str); +use Env; +use Core qw(%NS); + +# False positives because of TCO. +## no critic (Subroutines::RequireArgUnpacking) # read sub READ { my $str = shift; - return reader::read_str($str); + return read_str($str); } # eval + +my %special_forms = ( + 'def!' => \&special_def, + 'let*' => \&special_let, + + 'do' => \&special_do, + 'if' => \&special_if, + 'fn*' => \&special_fn, +); + sub EVAL { - my($ast, $env) = @_; + my ( $ast, $env ) = @_; my $dbgeval = $env->get('DEBUG-EVAL'); - if ($dbgeval and $dbgeval ne $nil and $dbgeval ne $false) { - print "EVAL: " . printer::_pr_str($ast) . "\n"; + if ( $dbgeval + and not $dbgeval->isa('Mal::Nil') + and not $dbgeval->isa('Mal::False') ) + { + print 'EVAL: ', pr_str($ast), "\n" or die $ERRNO; } - if ($ast->isa('Mal::Symbol')) { - my $val = $env->get($$ast); - die "'$$ast' not found\n" unless $val; - return $val; - } elsif ($ast->isa('Mal::Vector')) { - return ref($ast)->new([ map { EVAL($_, $env) } @$ast ]); - } elsif ($ast->isa('Mal::HashMap')) { - return Mal::HashMap->new({ pairmap { $a => EVAL($b, $env) } %$ast }); - } elsif (! $ast->isa('Mal::List')) { - return $ast; + if ( $ast->isa('Mal::Symbol') ) { + return $env->get( ${$ast} ) // die "'${$ast}' not found\n"; + } + if ( $ast->isa('Mal::Vector') ) { + return Mal::Vector->new( [ map { EVAL( $_, $env ) } @{$ast} ] ); + } + if ( $ast->isa('Mal::HashMap') ) { + return Mal::HashMap->new( + { pairmap { $a => EVAL( $b, $env ) } %{$ast} } ); + } + if ( $ast->isa('Mal::List') and @{$ast} ) { + my ( $a0, @args ) = @{$ast}; + if ( $a0->isa('Mal::Symbol') and my $sf = $special_forms{ ${$a0} } ) { + @_ = ( $env, @args ); + goto &{$sf}; + } + my $f = EVAL( $a0, $env ); + @_ = map { EVAL( $_, $env ) } @args; + goto &{$f}; } + return $ast; +} - # apply list +sub special_def { + my ( $env, $sym, $val ) = @_; + return $env->set( ${$sym}, EVAL( $val, $env ) ); +} - unless (@$ast) { return $ast; } - my ($a0) = @$ast; - given ($a0->isa('Mal::Symbol') ? $$a0 : $a0) { - when ('def!') { - my (undef, $sym, $val) = @$ast; - return $env->set($$sym, EVAL($val, $env)); - } - when ('let*') { - my (undef, $bindings, $body) = @$ast; - my $let_env = Mal::Env->new($env); - foreach my $pair (pairs @$bindings) { - my ($k, $v) = @$pair; - $let_env->set($$k, EVAL($v, $let_env)); - } - @_ = ($body, $let_env); - goto &EVAL; - } - when ('do') { - my (undef, @todo) = @$ast; - my $last = pop @todo; - map { EVAL($_, $env) } @todo; - @_ = ($last, $env); +sub special_let { + my ( $env, $bindings, $body ) = @_; + my $let_env = Env->new($env); + foreach my $pair ( pairs @{$bindings} ) { + my ( $k, $v ) = @{$pair}; + $let_env->set( ${$k}, EVAL( $v, $let_env ) ); + } + @_ = ( $body, $let_env ); + goto &EVAL; +} + +sub special_do { + my ( $env, @todo ) = @_; + my $final = pop @todo; + for (@todo) { + EVAL( $_, $env ); + } + @_ = ( $final, $env ); + goto &EVAL; +} + +sub special_if { + my ( $env, $if, $then, $else ) = @_; + my $cond = EVAL( $if, $env ); + if ( not $cond->isa('Mal::Nil') and not $cond->isa('Mal::False') ) { + @_ = ( $then, $env ); + goto &EVAL; + } + if ($else) { + @_ = ( $else, $env ); + goto &EVAL; + } + return nil; +} + +sub special_fn { + my ( $env, $params, $body ) = @_; + return Mal::Function->new( + sub { + @_ = ( $body, Env->new( $env, $params, \@_ ) ); goto &EVAL; } - when ('if') { - my (undef, $if, $then, $else) = @$ast; - my $cond = EVAL($if, $env); - if ($cond eq $nil || $cond eq $false) { - @_ = ($else // $nil, $env); - } else { - @_ = ($then, $env); - } - goto &EVAL; - } - when ('fn*') { - my (undef, $params, $body) = @$ast; - return Mal::Function->new(sub { - #print "running fn*\n"; - @_ = ($body, Mal::Env->new($env, $params, \@_)); - goto &EVAL; - }); - } - default { - my $f = EVAL($a0, $env); - my (undef, @args) = @$ast; - @_ = map { EVAL($_, $env) } @args; - goto &$f; - } - } + ); } # print sub PRINT { my $exp = shift; - return printer::_pr_str($exp); + return pr_str($exp); } # repl -my $repl_env = Mal::Env->new(); +my $repl_env = Env->new(); + sub REP { my $str = shift; - return PRINT(EVAL(READ($str), $repl_env)); + return PRINT( EVAL( READ($str), $repl_env ) ); +} + +# Command line arguments +if ( $ARGV[0] eq '--raw' ) { + set_rl_mode('raw'); + shift @ARGV; } # core.pl: defined using perl -foreach my $n (keys %core::ns) { - $repl_env->set($n, $core::ns{$n}); +while ( my ( $k, $v ) = each %NS ) { + $repl_env->set( $k, Mal::Function->new($v) ); } # core.mal: defined using the language itself REP(q[(def! not (fn* (a) (if a false true)))]); -if (@ARGV && $ARGV[0] eq "--raw") { - set_rl_mode("raw"); -} -while (1) { - my $line = mal_readline("user> "); - if (! defined $line) { last; } - do { - local $@; - my $ret; - eval { - print(REP($line), "\n"); - 1; - } or do { - my $err = $@; - if (defined(blessed $err) && $err->isa('Mal::BlankException')) { - # ignore and continue - } else { - chomp $err; - print "Error: $err\n"; - } - }; +while ( defined( my $line = mal_readline('user> ') ) ) { + eval { + print REP($line), "\n" or die $ERRNO; + 1; + } or do { + my $err = $EVAL_ERROR; + print 'Error: ', $err or die $ERRNO; }; } diff --git a/impls/perl/step6_file.pl b/impls/perl/step6_file.pl index 46d12a809b..e0eca83ec5 100644 --- a/impls/perl/step6_file.pl +++ b/impls/perl/step6_file.pl @@ -1,152 +1,170 @@ +#!/usr/bin/perl + use strict; -use warnings FATAL => "recursion"; -no if $] >= 5.018, warnings => "experimental::smartmatch"; -use feature qw(switch); -use File::Basename; -use lib dirname (__FILE__); +use warnings FATAL => 'recursion'; +use File::Basename 'dirname'; +use lib dirname(__FILE__); -use Data::Dumper; +use English '-no_match_vars'; use List::Util qw(pairs pairmap); -use Scalar::Util qw(blessed); -use readline qw(mal_readline set_rl_mode); -use types qw($nil $true $false); -use reader; -use printer; -use env; -use core; +use Readline qw(mal_readline set_rl_mode); +use Types qw(nil false); +use Reader qw(read_str); +use Printer qw(pr_str); +use Env; +use Core qw(%NS); + +# False positives because of TCO. +## no critic (Subroutines::RequireArgUnpacking) # read sub READ { my $str = shift; - return reader::read_str($str); + return read_str($str); } # eval + +my %special_forms = ( + 'def!' => \&special_def, + 'let*' => \&special_let, + + 'do' => \&special_do, + 'if' => \&special_if, + 'fn*' => \&special_fn, +); + sub EVAL { - my($ast, $env) = @_; + my ( $ast, $env ) = @_; my $dbgeval = $env->get('DEBUG-EVAL'); - if ($dbgeval and $dbgeval ne $nil and $dbgeval ne $false) { - print "EVAL: " . printer::_pr_str($ast) . "\n"; + if ( $dbgeval + and not $dbgeval->isa('Mal::Nil') + and not $dbgeval->isa('Mal::False') ) + { + print 'EVAL: ', pr_str($ast), "\n" or die $ERRNO; } - if ($ast->isa('Mal::Symbol')) { - my $val = $env->get($$ast); - die "'$$ast' not found\n" unless $val; - return $val; - } elsif ($ast->isa('Mal::Vector')) { - return ref($ast)->new([ map { EVAL($_, $env) } @$ast ]); - } elsif ($ast->isa('Mal::HashMap')) { - return Mal::HashMap->new({ pairmap { $a => EVAL($b, $env) } %$ast }); - } elsif (! $ast->isa('Mal::List')) { - return $ast; + if ( $ast->isa('Mal::Symbol') ) { + return $env->get( ${$ast} ) // die "'${$ast}' not found\n"; + } + if ( $ast->isa('Mal::Vector') ) { + return Mal::Vector->new( [ map { EVAL( $_, $env ) } @{$ast} ] ); + } + if ( $ast->isa('Mal::HashMap') ) { + return Mal::HashMap->new( + { pairmap { $a => EVAL( $b, $env ) } %{$ast} } ); + } + if ( $ast->isa('Mal::List') and @{$ast} ) { + my ( $a0, @args ) = @{$ast}; + if ( $a0->isa('Mal::Symbol') and my $sf = $special_forms{ ${$a0} } ) { + @_ = ( $env, @args ); + goto &{$sf}; + } + my $f = EVAL( $a0, $env ); + @_ = map { EVAL( $_, $env ) } @args; + goto &{$f}; } + return $ast; +} - # apply list +sub special_def { + my ( $env, $sym, $val ) = @_; + return $env->set( ${$sym}, EVAL( $val, $env ) ); +} - unless (@$ast) { return $ast; } - my ($a0) = @$ast; - given ($a0->isa('Mal::Symbol') ? $$a0 : $a0) { - when ('def!') { - my (undef, $sym, $val) = @$ast; - return $env->set($$sym, EVAL($val, $env)); - } - when ('let*') { - my (undef, $bindings, $body) = @$ast; - my $let_env = Mal::Env->new($env); - foreach my $pair (pairs @$bindings) { - my ($k, $v) = @$pair; - $let_env->set($$k, EVAL($v, $let_env)); - } - @_ = ($body, $let_env); - goto &EVAL; - } - when ('do') { - my (undef, @todo) = @$ast; - my $last = pop @todo; - map { EVAL($_, $env) } @todo; - @_ = ($last, $env); +sub special_let { + my ( $env, $bindings, $body ) = @_; + my $let_env = Env->new($env); + foreach my $pair ( pairs @{$bindings} ) { + my ( $k, $v ) = @{$pair}; + $let_env->set( ${$k}, EVAL( $v, $let_env ) ); + } + @_ = ( $body, $let_env ); + goto &EVAL; +} + +sub special_do { + my ( $env, @todo ) = @_; + my $final = pop @todo; + for (@todo) { + EVAL( $_, $env ); + } + @_ = ( $final, $env ); + goto &EVAL; +} + +sub special_if { + my ( $env, $if, $then, $else ) = @_; + my $cond = EVAL( $if, $env ); + if ( not $cond->isa('Mal::Nil') and not $cond->isa('Mal::False') ) { + @_ = ( $then, $env ); + goto &EVAL; + } + if ($else) { + @_ = ( $else, $env ); + goto &EVAL; + } + return nil; +} + +sub special_fn { + my ( $env, $params, $body ) = @_; + return Mal::Function->new( + sub { + @_ = ( $body, Env->new( $env, $params, \@_ ) ); goto &EVAL; } - when ('if') { - my (undef, $if, $then, $else) = @$ast; - my $cond = EVAL($if, $env); - if ($cond eq $nil || $cond eq $false) { - @_ = ($else // $nil, $env); - } else { - @_ = ($then, $env); - } - goto &EVAL; - } - when ('fn*') { - my (undef, $params, $body) = @$ast; - return Mal::Function->new(sub { - #print "running fn*\n"; - @_ = ($body, Mal::Env->new($env, $params, \@_)); - goto &EVAL; - }); - } - default { - my $f = EVAL($a0, $env); - my (undef, @args) = @$ast; - @_ = map { EVAL($_, $env) } @args; - goto &$f; - } - } + ); } # print sub PRINT { my $exp = shift; - return printer::_pr_str($exp); + return pr_str($exp); } # repl -my $repl_env = Mal::Env->new(); +my $repl_env = Env->new(); + sub REP { my $str = shift; - return PRINT(EVAL(READ($str), $repl_env)); + return PRINT( EVAL( READ($str), $repl_env ) ); +} + +# Command line arguments +if ( $ARGV[0] eq '--raw' ) { + set_rl_mode('raw'); + shift @ARGV; } +my $script_file = shift @ARGV; # core.pl: defined using perl -foreach my $n (keys %core::ns) { - $repl_env->set($n, $core::ns{$n}); +while ( my ( $k, $v ) = each %NS ) { + $repl_env->set( $k, Mal::Function->new($v) ); } -$repl_env->set('eval', - Mal::Function->new(sub { EVAL($_[0], $repl_env) })); -my @_argv = map {Mal::String->new($_)} @ARGV[1..$#ARGV]; -$repl_env->set('*ARGV*', Mal::List->new(\@_argv)); +$repl_env->set( 'eval', + Mal::Function->new( sub { EVAL( $_[0], $repl_env ) } ) ); +$repl_env->set( '*ARGV*', + Mal::List->new( [ map { Mal::String->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) "\nnil)")))))]); +REP(<<'EOF'); +(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)"))))) +EOF -if (@ARGV && $ARGV[0] eq "--raw") { - set_rl_mode("raw"); - shift @ARGV; -} -if (@ARGV) { - REP(qq[(load-file "$ARGV[0]")]); +if ( defined $script_file ) { + REP(qq[(load-file "$script_file")]); exit 0; } -while (1) { - my $line = mal_readline("user> "); - if (! defined $line) { last; } - do { - local $@; - my $ret; - eval { - print(REP($line), "\n"); - 1; - } or do { - my $err = $@; - if (defined(blessed $err) && $err->isa('Mal::BlankException')) { - # ignore and continue - } else { - chomp $err; - print "Error: $err\n"; - } - }; +while ( defined( my $line = mal_readline('user> ') ) ) { + eval { + print REP($line), "\n" or die $ERRNO; + 1; + } or do { + my $err = $EVAL_ERROR; + print 'Error: ', $err or die $ERRNO; }; } diff --git a/impls/perl/step7_quote.pl b/impls/perl/step7_quote.pl index 2e52f77bbb..ceee9c894f 100644 --- a/impls/perl/step7_quote.pl +++ b/impls/perl/step7_quote.pl @@ -1,190 +1,223 @@ +#!/usr/bin/perl + use strict; -use warnings FATAL => "recursion"; -no if $] >= 5.018, warnings => "experimental::smartmatch"; -use feature qw(switch); -use File::Basename; -use lib dirname (__FILE__); +use warnings FATAL => 'recursion'; +use File::Basename 'dirname'; +use lib dirname(__FILE__); -use Data::Dumper; +use English '-no_match_vars'; use List::Util qw(pairs pairmap); -use Scalar::Util qw(blessed); -use readline qw(mal_readline set_rl_mode); -use types qw($nil $true $false); -use reader; -use printer; -use env; -use core; +use Readline qw(mal_readline set_rl_mode); +use Types qw(nil false); +use Reader qw(read_str); +use Printer qw(pr_str); +use Env; +use Core qw(%NS); + +# False positives because of TCO. +## no critic (Subroutines::RequireArgUnpacking) # read sub READ { my $str = shift; - return reader::read_str($str); + return read_str($str); } # eval sub starts_with { - my ($ast, $sym) = @_; - return @$ast && $ast->[0]->isa('Mal::Symbol') && ${$ast->[0]} eq $sym; + my ( $ast, $sym ) = @_; + return @{$ast} && $ast->[0]->isa('Mal::Symbol') && ${ $ast->[0] } eq $sym; } + sub quasiquote_loop { my ($ast) = @_; - my $res = Mal::List->new([]); - foreach my $elt (reverse @$ast) { - if ($elt->isa('Mal::List') and starts_with($elt, 'splice-unquote')) { - $res = Mal::List->new([Mal::Symbol->new('concat'), $elt->[1], $res]); - } else { - $res = Mal::List->new([Mal::Symbol->new('cons'), quasiquote($elt), $res]); + my $res = Mal::List->new( [] ); + foreach my $elt ( reverse @{$ast} ) { + if ( $elt->isa('Mal::List') and starts_with( $elt, 'splice-unquote' ) ) + { + $res = + Mal::List->new( [ Mal::Symbol->new('concat'), $elt->[1], $res ] ); + } + else { + $res = Mal::List->new( + [ Mal::Symbol->new('cons'), quasiquote($elt), $res ] ); } } return $res; } + sub quasiquote { my ($ast) = @_; - if ($ast->isa('Mal::Vector')) { - return Mal::List->new([Mal::Symbol->new('vec'), quasiquote_loop($ast)]); - } elsif ($ast->isa('Mal::HashMap') or $ast->isa('Mal::Symbol')) { - return Mal::List->new([Mal::Symbol->new("quote"), $ast]); - } elsif (!$ast->isa('Mal::List')) { - return $ast; - } elsif (starts_with($ast, 'unquote')) { - return $ast->[1]; - } else { + if ( $ast->isa('Mal::Vector') ) { + return Mal::List->new( + [ Mal::Symbol->new('vec'), quasiquote_loop($ast) ] ); + } + if ( $ast->isa('Mal::HashMap') or $ast->isa('Mal::Symbol') ) { + return Mal::List->new( [ Mal::Symbol->new('quote'), $ast ] ); + } + if ( $ast->isa('Mal::List') ) { + if ( starts_with( $ast, 'unquote' ) ) { + return $ast->[1]; + } return quasiquote_loop($ast); } + return $ast; } +my %special_forms = ( + 'def!' => \&special_def, + 'let*' => \&special_let, + + 'do' => \&special_do, + 'if' => \&special_if, + 'fn*' => \&special_fn, + + 'quasiquote' => \&special_quasiquote, + 'quote' => \&special_quote, +); + sub EVAL { - my($ast, $env) = @_; + my ( $ast, $env ) = @_; my $dbgeval = $env->get('DEBUG-EVAL'); - if ($dbgeval and $dbgeval ne $nil and $dbgeval ne $false) { - print "EVAL: " . printer::_pr_str($ast) . "\n"; + if ( $dbgeval + and not $dbgeval->isa('Mal::Nil') + and not $dbgeval->isa('Mal::False') ) + { + print 'EVAL: ', pr_str($ast), "\n" or die $ERRNO; } - if ($ast->isa('Mal::Symbol')) { - my $val = $env->get($$ast); - die "'$$ast' not found\n" unless $val; - return $val; - } elsif ($ast->isa('Mal::Vector')) { - return ref($ast)->new([ map { EVAL($_, $env) } @$ast ]); - } elsif ($ast->isa('Mal::HashMap')) { - return Mal::HashMap->new({ pairmap { $a => EVAL($b, $env) } %$ast }); - } elsif (! $ast->isa('Mal::List')) { - return $ast; + if ( $ast->isa('Mal::Symbol') ) { + return $env->get( ${$ast} ) // die "'${$ast}' not found\n"; } + if ( $ast->isa('Mal::Vector') ) { + return Mal::Vector->new( [ map { EVAL( $_, $env ) } @{$ast} ] ); + } + if ( $ast->isa('Mal::HashMap') ) { + return Mal::HashMap->new( + { pairmap { $a => EVAL( $b, $env ) } %{$ast} } ); + } + if ( $ast->isa('Mal::List') and @{$ast} ) { + my ( $a0, @args ) = @{$ast}; + if ( $a0->isa('Mal::Symbol') and my $sf = $special_forms{ ${$a0} } ) { + @_ = ( $env, @args ); + goto &{$sf}; + } + my $f = EVAL( $a0, $env ); + @_ = map { EVAL( $_, $env ) } @args; + goto &{$f}; + } + return $ast; +} - # apply list +sub special_def { + my ( $env, $sym, $val ) = @_; + return $env->set( ${$sym}, EVAL( $val, $env ) ); +} - unless (@$ast) { return $ast; } - my ($a0) = @$ast; - given ($a0->isa('Mal::Symbol') ? $$a0 : $a0) { - when ('def!') { - my (undef, $sym, $val) = @$ast; - return $env->set($$sym, EVAL($val, $env)); - } - when ('let*') { - my (undef, $bindings, $body) = @$ast; - my $let_env = Mal::Env->new($env); - foreach my $pair (pairs @$bindings) { - my ($k, $v) = @$pair; - $let_env->set($$k, EVAL($v, $let_env)); - } - @_ = ($body, $let_env); - goto &EVAL; - } - when ('quote') { - return $ast->[1]; - } - when ('quasiquote') { - @_ = (quasiquote($ast->[1]), $env); - goto &EVAL; - } - when ('do') { - my (undef, @todo) = @$ast; - my $last = pop @todo; - map { EVAL($_, $env) } @todo; - @_ = ($last, $env); +sub special_let { + my ( $env, $bindings, $body ) = @_; + my $let_env = Env->new($env); + foreach my $pair ( pairs @{$bindings} ) { + my ( $k, $v ) = @{$pair}; + $let_env->set( ${$k}, EVAL( $v, $let_env ) ); + } + @_ = ( $body, $let_env ); + goto &EVAL; +} + +sub special_quote { + my ( $env, $quoted ) = @_; + return $quoted; +} + +sub special_quasiquote { + my ( $env, $quoted ) = @_; + @_ = ( quasiquote($quoted), $env ); + goto &EVAL; +} + +sub special_do { + my ( $env, @todo ) = @_; + my $final = pop @todo; + for (@todo) { + EVAL( $_, $env ); + } + @_ = ( $final, $env ); + goto &EVAL; +} + +sub special_if { + my ( $env, $if, $then, $else ) = @_; + my $cond = EVAL( $if, $env ); + if ( not $cond->isa('Mal::Nil') and not $cond->isa('Mal::False') ) { + @_ = ( $then, $env ); + goto &EVAL; + } + if ($else) { + @_ = ( $else, $env ); + goto &EVAL; + } + return nil; +} + +sub special_fn { + my ( $env, $params, $body ) = @_; + return Mal::Function->new( + sub { + @_ = ( $body, Env->new( $env, $params, \@_ ) ); goto &EVAL; } - when ('if') { - my (undef, $if, $then, $else) = @$ast; - my $cond = EVAL($if, $env); - if ($cond eq $nil || $cond eq $false) { - @_ = ($else // $nil, $env); - } else { - @_ = ($then, $env); - } - goto &EVAL; - } - when ('fn*') { - my (undef, $params, $body) = @$ast; - return Mal::Function->new(sub { - #print "running fn*\n"; - @_ = ($body, Mal::Env->new($env, $params, \@_)); - goto &EVAL; - }); - } - default { - my $f = EVAL($a0, $env); - my (undef, @args) = @$ast; - @_ = map { EVAL($_, $env) } @args; - goto &$f; - } - } + ); } # print sub PRINT { my $exp = shift; - return printer::_pr_str($exp); + return pr_str($exp); } # repl -my $repl_env = Mal::Env->new(); +my $repl_env = Env->new(); + sub REP { my $str = shift; - return PRINT(EVAL(READ($str), $repl_env)); + return PRINT( EVAL( READ($str), $repl_env ) ); } +# Command line arguments +if ( $ARGV[0] eq '--raw' ) { + set_rl_mode('raw'); + shift @ARGV; +} +my $script_file = shift @ARGV; + # core.pl: defined using perl -foreach my $n (keys %core::ns) { - $repl_env->set($n, $core::ns{$n}); +while ( my ( $k, $v ) = each %NS ) { + $repl_env->set( $k, Mal::Function->new($v) ); } -$repl_env->set('eval', - Mal::Function->new(sub { EVAL($_[0], $repl_env) })); -my @_argv = map {Mal::String->new($_)} @ARGV[1..$#ARGV]; -$repl_env->set('*ARGV*', Mal::List->new(\@_argv)); +$repl_env->set( 'eval', + Mal::Function->new( sub { EVAL( $_[0], $repl_env ) } ) ); +$repl_env->set( '*ARGV*', + Mal::List->new( [ map { Mal::String->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) "\nnil)")))))]); +REP(<<'EOF'); +(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)"))))) +EOF -if (@ARGV && $ARGV[0] eq "--raw") { - set_rl_mode("raw"); - shift @ARGV; -} -if (@ARGV) { - REP(qq[(load-file "$ARGV[0]")]); +if ( defined $script_file ) { + REP(qq[(load-file "$script_file")]); exit 0; } -while (1) { - my $line = mal_readline("user> "); - if (! defined $line) { last; } - do { - local $@; - my $ret; - eval { - print(REP($line), "\n"); - 1; - } or do { - my $err = $@; - if (defined(blessed $err) && $err->isa('Mal::BlankException')) { - # ignore and continue - } else { - chomp $err; - print "Error: $err\n"; - } - }; +while ( defined( my $line = mal_readline('user> ') ) ) { + eval { + print REP($line), "\n" or die $ERRNO; + 1; + } or do { + my $err = $EVAL_ERROR; + print 'Error: ', $err or die $ERRNO; }; } diff --git a/impls/perl/step8_macros.pl b/impls/perl/step8_macros.pl index 6301e9879a..aee1bb8274 100644 --- a/impls/perl/step8_macros.pl +++ b/impls/perl/step8_macros.pl @@ -1,199 +1,239 @@ +#!/usr/bin/perl + use strict; -use warnings FATAL => "recursion"; -no if $] >= 5.018, warnings => "experimental::smartmatch"; -use feature qw(switch); -use File::Basename; -use lib dirname (__FILE__); +use warnings FATAL => 'recursion'; +use File::Basename 'dirname'; +use lib dirname(__FILE__); -use Data::Dumper; +use English '-no_match_vars'; use List::Util qw(pairs pairmap); -use Scalar::Util qw(blessed); -use readline qw(mal_readline set_rl_mode); -use types qw($nil $true $false); -use reader; -use printer; -use env; -use core; +use Readline qw(mal_readline set_rl_mode); +use Types qw(nil false); +use Reader qw(read_str); +use Printer qw(pr_str); +use Env; +use Core qw(%NS); + +# False positives because of TCO. +## no critic (Subroutines::RequireArgUnpacking) # read sub READ { my $str = shift; - return reader::read_str($str); + return read_str($str); } # eval sub starts_with { - my ($ast, $sym) = @_; - return @$ast && $ast->[0]->isa('Mal::Symbol') && ${$ast->[0]} eq $sym; + my ( $ast, $sym ) = @_; + return @{$ast} && $ast->[0]->isa('Mal::Symbol') && ${ $ast->[0] } eq $sym; } + sub quasiquote_loop { my ($ast) = @_; - my $res = Mal::List->new([]); - foreach my $elt (reverse @$ast) { - if ($elt->isa('Mal::List') and starts_with($elt, 'splice-unquote')) { - $res = Mal::List->new([Mal::Symbol->new('concat'), $elt->[1], $res]); - } else { - $res = Mal::List->new([Mal::Symbol->new('cons'), quasiquote($elt), $res]); + my $res = Mal::List->new( [] ); + foreach my $elt ( reverse @{$ast} ) { + if ( $elt->isa('Mal::List') and starts_with( $elt, 'splice-unquote' ) ) + { + $res = + Mal::List->new( [ Mal::Symbol->new('concat'), $elt->[1], $res ] ); + } + else { + $res = Mal::List->new( + [ Mal::Symbol->new('cons'), quasiquote($elt), $res ] ); } } return $res; } + sub quasiquote { my ($ast) = @_; - if ($ast->isa('Mal::Vector')) { - return Mal::List->new([Mal::Symbol->new('vec'), quasiquote_loop($ast)]); - } elsif ($ast->isa('Mal::HashMap') or $ast->isa('Mal::Symbol')) { - return Mal::List->new([Mal::Symbol->new("quote"), $ast]); - } elsif (!$ast->isa('Mal::List')) { - return $ast; - } elsif (starts_with($ast, 'unquote')) { - return $ast->[1]; - } else { + if ( $ast->isa('Mal::Vector') ) { + return Mal::List->new( + [ Mal::Symbol->new('vec'), quasiquote_loop($ast) ] ); + } + if ( $ast->isa('Mal::HashMap') or $ast->isa('Mal::Symbol') ) { + return Mal::List->new( [ Mal::Symbol->new('quote'), $ast ] ); + } + if ( $ast->isa('Mal::List') ) { + if ( starts_with( $ast, 'unquote' ) ) { + return $ast->[1]; + } return quasiquote_loop($ast); } + return $ast; } +my %special_forms = ( + 'def!' => \&special_def, + 'let*' => \&special_let, + + 'do' => \&special_do, + 'if' => \&special_if, + 'fn*' => \&special_fn, + + 'quasiquote' => \&special_quasiquote, + 'quote' => \&special_quote, + + 'defmacro!' => \&special_defmacro, +); + sub EVAL { - my($ast, $env) = @_; + my ( $ast, $env ) = @_; my $dbgeval = $env->get('DEBUG-EVAL'); - if ($dbgeval and $dbgeval ne $nil and $dbgeval ne $false) { - print "EVAL: " . printer::_pr_str($ast) . "\n"; + if ( $dbgeval + and not $dbgeval->isa('Mal::Nil') + and not $dbgeval->isa('Mal::False') ) + { + print 'EVAL: ', pr_str($ast), "\n" or die $ERRNO; } - if ($ast->isa('Mal::Symbol')) { - my $val = $env->get($$ast); - die "'$$ast' not found\n" unless $val; - return $val; - } elsif ($ast->isa('Mal::Vector')) { - return ref($ast)->new([ map { EVAL($_, $env) } @$ast ]); - } elsif ($ast->isa('Mal::HashMap')) { - return Mal::HashMap->new({ pairmap { $a => EVAL($b, $env) } %$ast }); - } elsif (! $ast->isa('Mal::List')) { - return $ast; + if ( $ast->isa('Mal::Symbol') ) { + return $env->get( ${$ast} ) // die "'${$ast}' not found\n"; } - - # apply list - - unless (@$ast) { return $ast; } - my ($a0) = @$ast; - given ($a0->isa('Mal::Symbol') ? $$a0 : $a0) { - when ('def!') { - my (undef, $sym, $val) = @$ast; - return $env->set($$sym, EVAL($val, $env)); - } - when ('let*') { - my (undef, $bindings, $body) = @$ast; - my $let_env = Mal::Env->new($env); - foreach my $pair (pairs @$bindings) { - my ($k, $v) = @$pair; - $let_env->set($$k, EVAL($v, $let_env)); - } - @_ = ($body, $let_env); - goto &EVAL; - } - when ('quote') { - return $ast->[1]; - } - when ('quasiquote') { - @_ = (quasiquote($ast->[1]), $env); - goto &EVAL; - } - when ('defmacro!') { - my (undef, $sym, $val) = @$ast; - return $env->set($$sym, Mal::Macro->new(EVAL($val, $env)->clone)); + if ( $ast->isa('Mal::Vector') ) { + return Mal::Vector->new( [ map { EVAL( $_, $env ) } @{$ast} ] ); + } + if ( $ast->isa('Mal::HashMap') ) { + return Mal::HashMap->new( + { pairmap { $a => EVAL( $b, $env ) } %{$ast} } ); + } + if ( $ast->isa('Mal::List') and @{$ast} ) { + my ( $a0, @args ) = @{$ast}; + if ( $a0->isa('Mal::Symbol') and my $sf = $special_forms{ ${$a0} } ) { + @_ = ( $env, @args ); + goto &{$sf}; } - when ('do') { - my (undef, @todo) = @$ast; - my $last = pop @todo; - map { EVAL($_, $env) } @todo; - @_ = ($last, $env); + my $f = EVAL( $a0, $env ); + if ( $f->isa('Mal::Macro') ) { + @_ = ( $f->(@args), $env ); goto &EVAL; } - when ('if') { - my (undef, $if, $then, $else) = @$ast; - my $cond = EVAL($if, $env); - if ($cond eq $nil || $cond eq $false) { - @_ = ($else // $nil, $env); - } else { - @_ = ($then, $env); - } - goto &EVAL; - } - when ('fn*') { - my (undef, $params, $body) = @$ast; - return Mal::Function->new(sub { - #print "running fn*\n"; - @_ = ($body, Mal::Env->new($env, $params, \@_)); - goto &EVAL; - }); - } - default { - my $f = EVAL($a0, $env); - my (undef, @args) = @$ast; - if ($f->isa('Mal::Macro')) { - @_ = (&$f(@args), $env); - goto &EVAL; - } - @_ = map { EVAL($_, $env) } @args; - goto &$f; - } + @_ = map { EVAL( $_, $env ) } @args; + goto &{$f}; } + return $ast; +} + +sub special_def { + my ( $env, $sym, $val ) = @_; + return $env->set( ${$sym}, EVAL( $val, $env ) ); +} + +sub special_let { + my ( $env, $bindings, $body ) = @_; + my $let_env = Env->new($env); + foreach my $pair ( pairs @{$bindings} ) { + my ( $k, $v ) = @{$pair}; + $let_env->set( ${$k}, EVAL( $v, $let_env ) ); + } + @_ = ( $body, $let_env ); + goto &EVAL; +} + +sub special_quote { + my ( $env, $quoted ) = @_; + return $quoted; +} + +sub special_quasiquote { + my ( $env, $quoted ) = @_; + @_ = ( quasiquote($quoted), $env ); + goto &EVAL; +} + +sub special_defmacro { + my ( $env, $sym, $val ) = @_; + return $env->set( ${$sym}, Mal::Macro->new( EVAL( $val, $env )->clone ) ); +} + +sub special_do { + my ( $env, @todo ) = @_; + my $final = pop @todo; + for (@todo) { + EVAL( $_, $env ); + } + @_ = ( $final, $env ); + goto &EVAL; +} + +sub special_if { + my ( $env, $if, $then, $else ) = @_; + my $cond = EVAL( $if, $env ); + if ( not $cond->isa('Mal::Nil') and not $cond->isa('Mal::False') ) { + @_ = ( $then, $env ); + goto &EVAL; + } + if ($else) { + @_ = ( $else, $env ); + goto &EVAL; + } + return nil; +} + +sub special_fn { + my ( $env, $params, $body ) = @_; + return Mal::Function->new( + sub { + @_ = ( $body, Env->new( $env, $params, \@_ ) ); + goto &EVAL; + } + ); } # print sub PRINT { my $exp = shift; - return printer::_pr_str($exp); + return pr_str($exp); } # repl -my $repl_env = Mal::Env->new(); +my $repl_env = Env->new(); + sub REP { my $str = shift; - return PRINT(EVAL(READ($str), $repl_env)); + return PRINT( EVAL( READ($str), $repl_env ) ); +} + +# Command line arguments +if ( $ARGV[0] eq '--raw' ) { + set_rl_mode('raw'); + shift @ARGV; } +my $script_file = shift @ARGV; # core.pl: defined using perl -foreach my $n (keys %core::ns) { - $repl_env->set($n, $core::ns{$n}); +while ( my ( $k, $v ) = each %NS ) { + $repl_env->set( $k, Mal::Function->new($v) ); } -$repl_env->set('eval', - Mal::Function->new(sub { EVAL($_[0], $repl_env) })); -my @_argv = map {Mal::String->new($_)} @ARGV[1..$#ARGV]; -$repl_env->set('*ARGV*', Mal::List->new(\@_argv)); +$repl_env->set( 'eval', + Mal::Function->new( sub { EVAL( $_[0], $repl_env ) } ) ); +$repl_env->set( '*ARGV*', + Mal::List->new( [ map { Mal::String->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) "\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)))))))]); +REP(<<'EOF'); +(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)"))))) +EOF +REP(<<'EOF'); +(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))))))) +EOF -if (@ARGV && $ARGV[0] eq "--raw") { - set_rl_mode("raw"); - shift @ARGV; -} -if (@ARGV) { - REP(qq[(load-file "$ARGV[0]")]); +if ( defined $script_file ) { + REP(qq[(load-file "$script_file")]); exit 0; } -while (1) { - my $line = mal_readline("user> "); - if (! defined $line) { last; } - do { - local $@; - my $ret; - eval { - print(REP($line), "\n"); - 1; - } or do { - my $err = $@; - if (defined(blessed $err) && $err->isa('Mal::BlankException')) { - # ignore and continue - } else { - chomp $err; - print "Error: $err\n"; - } - }; +while ( defined( my $line = mal_readline('user> ') ) ) { + eval { + print REP($line), "\n" or die $ERRNO; + 1; + } or do { + my $err = $EVAL_ERROR; + print 'Error: ', $err or die $ERRNO; }; } diff --git a/impls/perl/step9_try.pl b/impls/perl/step9_try.pl index 37d7eda227..3612fba99d 100644 --- a/impls/perl/step9_try.pl +++ b/impls/perl/step9_try.pl @@ -1,222 +1,265 @@ +#!/usr/bin/perl + use strict; -use warnings FATAL => "recursion"; -no if $] >= 5.018, warnings => "experimental::smartmatch"; -use feature qw(switch); -use File::Basename; -use lib dirname (__FILE__); - -use Data::Dumper; -use List::Util qw(pairs pairmap); +use warnings FATAL => 'recursion'; +use File::Basename 'dirname'; +use lib dirname(__FILE__); + +use English '-no_match_vars'; +use List::Util qw(pairs pairmap); use Scalar::Util qw(blessed); -use readline qw(mal_readline set_rl_mode); -use types qw($nil $true $false); -use reader; -use printer; -use env; -use core; +use Readline qw(mal_readline set_rl_mode); +use Types qw(nil false); +use Reader qw(read_str); +use Printer qw(pr_str); +use Env; +use Core qw(%NS); + +# False positives because of TCO. +## no critic (Subroutines::RequireArgUnpacking) # read sub READ { my $str = shift; - return reader::read_str($str); + return read_str($str); } # eval sub starts_with { - my ($ast, $sym) = @_; - return @$ast && $ast->[0]->isa('Mal::Symbol') && ${$ast->[0]} eq $sym; + my ( $ast, $sym ) = @_; + return @{$ast} && $ast->[0]->isa('Mal::Symbol') && ${ $ast->[0] } eq $sym; } + sub quasiquote_loop { my ($ast) = @_; - my $res = Mal::List->new([]); - foreach my $elt (reverse @$ast) { - if ($elt->isa('Mal::List') and starts_with($elt, 'splice-unquote')) { - $res = Mal::List->new([Mal::Symbol->new('concat'), $elt->[1], $res]); - } else { - $res = Mal::List->new([Mal::Symbol->new('cons'), quasiquote($elt), $res]); + my $res = Mal::List->new( [] ); + foreach my $elt ( reverse @{$ast} ) { + if ( $elt->isa('Mal::List') and starts_with( $elt, 'splice-unquote' ) ) + { + $res = + Mal::List->new( [ Mal::Symbol->new('concat'), $elt->[1], $res ] ); + } + else { + $res = Mal::List->new( + [ Mal::Symbol->new('cons'), quasiquote($elt), $res ] ); } } return $res; } + sub quasiquote { my ($ast) = @_; - if ($ast->isa('Mal::Vector')) { - return Mal::List->new([Mal::Symbol->new('vec'), quasiquote_loop($ast)]); - } elsif ($ast->isa('Mal::HashMap') or $ast->isa('Mal::Symbol')) { - return Mal::List->new([Mal::Symbol->new("quote"), $ast]); - } elsif (!$ast->isa('Mal::List')) { - return $ast; - } elsif (starts_with($ast, 'unquote')) { - return $ast->[1]; - } else { + if ( $ast->isa('Mal::Vector') ) { + return Mal::List->new( + [ Mal::Symbol->new('vec'), quasiquote_loop($ast) ] ); + } + if ( $ast->isa('Mal::HashMap') or $ast->isa('Mal::Symbol') ) { + return Mal::List->new( [ Mal::Symbol->new('quote'), $ast ] ); + } + if ( $ast->isa('Mal::List') ) { + if ( starts_with( $ast, 'unquote' ) ) { + return $ast->[1]; + } return quasiquote_loop($ast); } + return $ast; } +my %special_forms = ( + 'def!' => \&special_def, + 'let*' => \&special_let, + + 'do' => \&special_do, + 'if' => \&special_if, + 'fn*' => \&special_fn, + + 'quasiquote' => \&special_quasiquote, + 'quote' => \&special_quote, + + 'defmacro!' => \&special_defmacro, + + 'try*' => \&special_try, +); + sub EVAL { - my($ast, $env) = @_; + my ( $ast, $env ) = @_; my $dbgeval = $env->get('DEBUG-EVAL'); - if ($dbgeval and $dbgeval ne $nil and $dbgeval ne $false) { - print "EVAL: " . printer::_pr_str($ast) . "\n"; + if ( $dbgeval + and not $dbgeval->isa('Mal::Nil') + and not $dbgeval->isa('Mal::False') ) + { + print 'EVAL: ', pr_str($ast), "\n" or die $ERRNO; } - if ($ast->isa('Mal::Symbol')) { - my $val = $env->get($$ast); - die "'$$ast' not found\n" unless $val; - return $val; - } elsif ($ast->isa('Mal::Vector')) { - return ref($ast)->new([ map { EVAL($_, $env) } @$ast ]); - } elsif ($ast->isa('Mal::HashMap')) { - return Mal::HashMap->new({ pairmap { $a => EVAL($b, $env) } %$ast }); - } elsif (! $ast->isa('Mal::List')) { - return $ast; + if ( $ast->isa('Mal::Symbol') ) { + return $env->get( ${$ast} ) // die "'${$ast}' not found\n"; } - - # apply list - - unless (@$ast) { return $ast; } - my ($a0) = @$ast; - given ($a0->isa('Mal::Symbol') ? $$a0 : $a0) { - when ('def!') { - my (undef, $sym, $val) = @$ast; - return $env->set($$sym, EVAL($val, $env)); - } - when ('let*') { - my (undef, $bindings, $body) = @$ast; - my $let_env = Mal::Env->new($env); - foreach my $pair (pairs @$bindings) { - my ($k, $v) = @$pair; - $let_env->set($$k, EVAL($v, $let_env)); - } - @_ = ($body, $let_env); - goto &EVAL; - } - when ('quote') { - return $ast->[1]; - } - when ('quasiquote') { - @_ = (quasiquote($ast->[1]), $env); - goto &EVAL; - } - when ('defmacro!') { - my (undef, $sym, $val) = @$ast; - return $env->set($$sym, Mal::Macro->new(EVAL($val, $env)->clone)); - } - when ('try*') { - my (undef, $try, $catch) = @$ast; - local $@; - my $ret = eval { EVAL($try, $env) }; - return $ret unless $@; - if ($catch && ${$catch->[0]} eq 'catch*') { - my (undef, $binding, $body) = @$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, [$binding], [$exc]); - @_ = ($body, $catch_env); - goto &EVAL; - } else { - die $@; - } + if ( $ast->isa('Mal::Vector') ) { + return Mal::Vector->new( [ map { EVAL( $_, $env ) } @{$ast} ] ); + } + if ( $ast->isa('Mal::HashMap') ) { + return Mal::HashMap->new( + { pairmap { $a => EVAL( $b, $env ) } %{$ast} } ); + } + if ( $ast->isa('Mal::List') and @{$ast} ) { + my ( $a0, @args ) = @{$ast}; + if ( $a0->isa('Mal::Symbol') and my $sf = $special_forms{ ${$a0} } ) { + @_ = ( $env, @args ); + goto &{$sf}; } - when ('do') { - my (undef, @todo) = @$ast; - my $last = pop @todo; - map { EVAL($_, $env) } @todo; - @_ = ($last, $env); + my $f = EVAL( $a0, $env ); + if ( $f->isa('Mal::Macro') ) { + @_ = ( $f->(@args), $env ); goto &EVAL; } - when ('if') { - my (undef, $if, $then, $else) = @$ast; - my $cond = EVAL($if, $env); - if ($cond eq $nil || $cond eq $false) { - @_ = ($else // $nil, $env); - } else { - @_ = ($then, $env); - } - goto &EVAL; - } - when ('fn*') { - my (undef, $params, $body) = @$ast; - return Mal::Function->new(sub { - #print "running fn*\n"; - @_ = ($body, Mal::Env->new($env, $params, \@_)); - goto &EVAL; - }); + @_ = map { EVAL( $_, $env ) } @args; + goto &{$f}; + } + return $ast; +} + +sub special_def { + my ( $env, $sym, $val ) = @_; + return $env->set( ${$sym}, EVAL( $val, $env ) ); +} + +sub special_let { + my ( $env, $bindings, $body ) = @_; + my $let_env = Env->new($env); + foreach my $pair ( pairs @{$bindings} ) { + my ( $k, $v ) = @{$pair}; + $let_env->set( ${$k}, EVAL( $v, $let_env ) ); + } + @_ = ( $body, $let_env ); + goto &EVAL; +} + +sub special_quote { + my ( $env, $quoted ) = @_; + return $quoted; +} + +sub special_quasiquote { + my ( $env, $quoted ) = @_; + @_ = ( quasiquote($quoted), $env ); + goto &EVAL; +} + +sub special_defmacro { + my ( $env, $sym, $val ) = @_; + return $env->set( ${$sym}, Mal::Macro->new( EVAL( $val, $env )->clone ) ); +} + +sub special_try { + my ( $env, $try, $catch ) = @_; + if ($catch) { + my ( undef, $binding, $body ) = @{$catch}; + if ( my $ret = eval { EVAL( $try, $env ) } ) { + return $ret; } - default { - my $f = EVAL($a0, $env); - my (undef, @args) = @$ast; - if ($f->isa('Mal::Macro')) { - @_ = (&$f(@args), $env); - goto &EVAL; - } - @_ = map { EVAL($_, $env) } @args; - goto &$f; + my $exc = $EVAL_ERROR; + if ( not blessed($exc) or not $exc->isa('Mal::Type') ) { + chomp $exc; + $exc = Mal::String->new($exc); } + my $catch_env = Env->new( $env, [$binding], [$exc] ); + @_ = ( $body, $catch_env ); + goto &EVAL; } + @_ = ( $try, $env ); + goto &EVAL; +} + +sub special_do { + my ( $env, @todo ) = @_; + my $final = pop @todo; + for (@todo) { + EVAL( $_, $env ); + } + @_ = ( $final, $env ); + goto &EVAL; +} + +sub special_if { + my ( $env, $if, $then, $else ) = @_; + my $cond = EVAL( $if, $env ); + if ( not $cond->isa('Mal::Nil') and not $cond->isa('Mal::False') ) { + @_ = ( $then, $env ); + goto &EVAL; + } + if ($else) { + @_ = ( $else, $env ); + goto &EVAL; + } + return nil; +} + +sub special_fn { + my ( $env, $params, $body ) = @_; + return Mal::Function->new( + sub { + @_ = ( $body, Env->new( $env, $params, \@_ ) ); + goto &EVAL; + } + ); } # print sub PRINT { my $exp = shift; - return printer::_pr_str($exp); + return pr_str($exp); } # repl -my $repl_env = Mal::Env->new(); +my $repl_env = Env->new(); + sub REP { my $str = shift; - return PRINT(EVAL(READ($str), $repl_env)); + return PRINT( EVAL( READ($str), $repl_env ) ); +} + +# Command line arguments +if ( $ARGV[0] eq '--raw' ) { + set_rl_mode('raw'); + shift @ARGV; } +my $script_file = shift @ARGV; # core.pl: defined using perl -foreach my $n (keys %core::ns) { - $repl_env->set($n, $core::ns{$n}); +while ( my ( $k, $v ) = each %NS ) { + $repl_env->set( $k, Mal::Function->new($v) ); } -$repl_env->set('eval', - Mal::Function->new(sub { EVAL($_[0], $repl_env) })); -my @_argv = map {Mal::String->new($_)} @ARGV[1..$#ARGV]; -$repl_env->set('*ARGV*', Mal::List->new(\@_argv)); +$repl_env->set( 'eval', + Mal::Function->new( sub { EVAL( $_[0], $repl_env ) } ) ); +$repl_env->set( '*ARGV*', + Mal::List->new( [ map { Mal::String->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) "\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)))))))]); +REP(<<'EOF'); +(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)"))))) +EOF +REP(<<'EOF'); +(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))))))) +EOF -if (@ARGV && $ARGV[0] eq "--raw") { - set_rl_mode("raw"); - shift @ARGV; -} -if (@ARGV) { - REP(qq[(load-file "$ARGV[0]")]); +if ( defined $script_file ) { + REP(qq[(load-file "$script_file")]); exit 0; } -while (1) { - my $line = mal_readline("user> "); - if (! defined $line) { last; } - do { - local $@; - my $ret; - eval { - print(REP($line), "\n"); - 1; - } or do { - my $err = $@; - 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"; - } - }; +while ( defined( my $line = mal_readline('user> ') ) ) { + eval { + print REP($line), "\n" or die $ERRNO; + 1; + } or do { + my $err = $EVAL_ERROR; + if ( defined blessed($err) and $err->isa('Mal::Type') ) { + $err = pr_str($err) . "\n"; + } + print 'Error: ', $err or die $ERRNO; }; }