1
1
mirror of https://github.com/kanaka/mal.git synced 2024-09-19 17:47:53 +03:00

perl: backport all recent style changes from stepA to previous steps

This commit is contained in:
Nicolas Boulenguez 2023-04-18 19:42:58 +02:00 committed by Joel Martin
parent 4e02e6231e
commit 87b0b23970
10 changed files with 1096 additions and 949 deletions

View File

@ -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 readline qw(mal_readline set_rl_mode);
use English '-no_match_vars';
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;
}

View File

@ -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;
};
}

View File

@ -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";
my ( $ast, $env ) = @_;
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;
#print 'EVAL: ', pr_str($ast), "\n" or die $ERRNO;
if ( $ast->isa('Mal::Symbol') ) {
return $env->{ ${$ast} } // die "'${$ast}' not found\n";
}
# apply list
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::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;
};
}

View File

@ -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";
}
# 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));
}
return EVAL($body, $let_env);
}
default {
my $f = EVAL($a0, $env);
my (undef, @args) = @$ast;
return &$f(map { EVAL($_, $env) } @args);
}
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;
}
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 ) );
}
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;
};
}

View File

@ -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";
}
# 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));
}
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);
}
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;
}
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 ) );
}
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;
};
}

View File

@ -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";
}
# 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));
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 ('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);
my $f = EVAL( $a0, $env );
@_ = 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_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;
};
}

View File

@ -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";
}
# 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));
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 ('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);
my $f = EVAL( $a0, $env );
@_ = 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_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 ) );
}
# core.pl: defined using perl
foreach my $n (keys %core::ns) {
$repl_env->set($n, $core::ns{$n});
# Command line arguments
if ( $ARGV[0] eq '--raw' ) {
set_rl_mode('raw');
shift @ARGV;
}
$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));
my $script_file = shift @ARGV;
# core.pl: defined using perl
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 ) } ) );
$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;
};
}

View File

@ -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 {
return quasiquote_loop($ast);
if ( $ast->isa('Mal::Vector') ) {
return Mal::List->new(
[ Mal::Symbol->new('vec'), quasiquote_loop($ast) ] );
}
}
sub EVAL {
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 ( $ast->isa('Mal::HashMap') or $ast->isa('Mal::Symbol') ) {
return Mal::List->new( [ Mal::Symbol->new('quote'), $ast ] );
}
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;
}
# 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') {
if ( $ast->isa('Mal::List') ) {
if ( starts_with( $ast, 'unquote' ) ) {
return $ast->[1];
}
when ('quasiquote') {
@_ = (quasiquote($ast->[1]), $env);
goto &EVAL;
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 $dbgeval = $env->get('DEBUG-EVAL');
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') ) {
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};
}
when ('do') {
my (undef, @todo) = @$ast;
my $last = pop @todo;
map { EVAL($_, $env) } @todo;
@_ = ($last, $env);
my $f = EVAL( $a0, $env );
@_ = 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_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 ) );
}
# core.pl: defined using perl
foreach my $n (keys %core::ns) {
$repl_env->set($n, $core::ns{$n});
# Command line arguments
if ( $ARGV[0] eq '--raw' ) {
set_rl_mode('raw');
shift @ARGV;
}
$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));
my $script_file = shift @ARGV;
# core.pl: defined using perl
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 ) } ) );
$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;
};
}

View File

@ -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 {
return quasiquote_loop($ast);
if ( $ast->isa('Mal::Vector') ) {
return Mal::List->new(
[ Mal::Symbol->new('vec'), quasiquote_loop($ast) ] );
}
}
sub EVAL {
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 ( $ast->isa('Mal::HashMap') or $ast->isa('Mal::Symbol') ) {
return Mal::List->new( [ Mal::Symbol->new('quote'), $ast ] );
}
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;
}
# 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') {
if ( $ast->isa('Mal::List') ) {
if ( starts_with( $ast, 'unquote' ) ) {
return $ast->[1];
}
when ('quasiquote') {
@_ = (quasiquote($ast->[1]), $env);
goto &EVAL;
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 $dbgeval = $env->get('DEBUG-EVAL');
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') ) {
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};
}
when ('defmacro!') {
my (undef, $sym, $val) = @$ast;
return $env->set($$sym, Mal::Macro->new(EVAL($val, $env)->clone));
}
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 ) );
}
# core.pl: defined using perl
foreach my $n (keys %core::ns) {
$repl_env->set($n, $core::ns{$n});
# Command line arguments
if ( $ARGV[0] eq '--raw' ) {
set_rl_mode('raw');
shift @ARGV;
}
$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));
my $script_file = shift @ARGV;
# core.pl: defined using perl
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 ) } ) );
$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;
};
}

View File

@ -1,222 +1,265 @@
use strict;
use warnings FATAL => "recursion";
no if $] >= 5.018, warnings => "experimental::smartmatch";
use feature qw(switch);
use File::Basename;
use lib dirname (__FILE__);
#!/usr/bin/perl
use Data::Dumper;
use List::Util qw(pairs pairmap);
use strict;
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 {
return quasiquote_loop($ast);
if ( $ast->isa('Mal::Vector') ) {
return Mal::List->new(
[ Mal::Symbol->new('vec'), quasiquote_loop($ast) ] );
}
}
sub EVAL {
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 ( $ast->isa('Mal::HashMap') or $ast->isa('Mal::Symbol') ) {
return Mal::List->new( [ Mal::Symbol->new('quote'), $ast ] );
}
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;
}
# 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') {
if ( $ast->isa('Mal::List') ) {
if ( starts_with( $ast, 'unquote' ) ) {
return $ast->[1];
}
when ('quasiquote') {
@_ = (quasiquote($ast->[1]), $env);
goto &EVAL;
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 $dbgeval = $env->get('DEBUG-EVAL');
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') ) {
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};
}
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 $@;
}
}
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_try {
my ( $env, $try, $catch ) = @_;
if ($catch) {
my ( undef, $binding, $body ) = @{$catch};
if ( my $ret = eval { EVAL( $try, $env ) } ) {
return $ret;
}
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 ) );
}
# core.pl: defined using perl
foreach my $n (keys %core::ns) {
$repl_env->set($n, $core::ns{$n});
# Command line arguments
if ( $ARGV[0] eq '--raw' ) {
set_rl_mode('raw');
shift @ARGV;
}
$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));
my $script_file = shift @ARGV;
# core.pl: defined using perl
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 ) } ) );
$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;
};
}