2021-04-01 22:44:06 +02:00
|
|
|
package Minilisp;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
use Data::Dumper;
|
|
|
|
|
|
|
|
use constant {
|
|
|
|
LPAREN => 1,
|
|
|
|
RPAREN => 2,
|
|
|
|
IDENT => 3,
|
|
|
|
STRING => 4,
|
|
|
|
NUMBER => 5,
|
2021-04-02 20:52:26 +02:00
|
|
|
KEYWORD => 6,
|
2021-04-03 18:09:20 +02:00
|
|
|
LIST => 7,
|
2021-04-01 22:44:06 +02:00
|
|
|
};
|
|
|
|
|
2021-04-05 00:06:49 +02:00
|
|
|
use constant {
|
|
|
|
LISP_TRUE => 1,
|
|
|
|
LISP_FALSE => undef,
|
|
|
|
};
|
|
|
|
|
|
|
|
sub to_lisp_bool {
|
|
|
|
return shift ? LISP_TRUE : LISP_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub from_lisp_bool {
|
|
|
|
return defined(shift);
|
|
|
|
}
|
|
|
|
|
2021-04-01 22:44:06 +02:00
|
|
|
sub tokenize {
|
|
|
|
my $str = shift;
|
|
|
|
my @tokens;
|
|
|
|
|
|
|
|
$str =~ s/^\s+//;
|
|
|
|
$str =~ s/\s+$//;
|
|
|
|
|
|
|
|
while ($str)
|
|
|
|
{
|
2021-04-02 16:05:22 +02:00
|
|
|
if ($str =~ s/^;.*\n//)
|
|
|
|
{
|
|
|
|
# Comment. do nothing
|
|
|
|
}
|
|
|
|
elsif ($str =~ s/^\(//)
|
2021-04-01 22:44:06 +02:00
|
|
|
{
|
|
|
|
push @tokens, { type => LPAREN };
|
|
|
|
}
|
|
|
|
elsif($str =~ s/^\)//)
|
|
|
|
{
|
|
|
|
push @tokens, { type => RPAREN };
|
|
|
|
}
|
2021-04-03 18:09:20 +02:00
|
|
|
elsif($str =~ s/^'\(//) # short notation for lists
|
|
|
|
{
|
|
|
|
push @tokens, { type => LIST }, { type => LPAREN };
|
|
|
|
}
|
2021-04-02 20:52:26 +02:00
|
|
|
elsif($str =~ s/^'([^\s()"]+)//)
|
|
|
|
{
|
|
|
|
push @tokens, {
|
|
|
|
type => KEYWORD,
|
|
|
|
value => $1,
|
|
|
|
};
|
|
|
|
}
|
2021-04-01 22:44:06 +02:00
|
|
|
elsif($str =~ s/^"(([^"\\]|\\.)+)"//)
|
|
|
|
{
|
|
|
|
my $value = $1;
|
|
|
|
|
|
|
|
my %special_chars = (
|
|
|
|
'"' => '"',
|
|
|
|
"n" => "\n",
|
|
|
|
"t" => "\t",
|
|
|
|
"\\" => "\\",
|
|
|
|
);
|
|
|
|
|
|
|
|
$value =~ s/\\(.)/$special_chars{$1}/eg;
|
|
|
|
|
|
|
|
push @tokens, {
|
|
|
|
type => STRING,
|
|
|
|
value => $value,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
elsif($str =~ s/^([^\s()"]+)//)
|
|
|
|
{
|
|
|
|
my $ident = $1;
|
2021-04-05 03:50:33 +02:00
|
|
|
if($ident eq ".")
|
2021-04-01 22:44:06 +02:00
|
|
|
{
|
2021-04-05 03:50:33 +02:00
|
|
|
die "short cons not supported"
|
|
|
|
}
|
|
|
|
elsif($ident =~ /^-?([0-9]+|[0-9]*\.[0-9]*)$/)
|
|
|
|
{
|
|
|
|
if($ident =~ s/^-//)
|
|
|
|
{
|
|
|
|
$ident = 0 - $ident;
|
|
|
|
}
|
|
|
|
|
2021-04-01 22:44:06 +02:00
|
|
|
push @tokens, {
|
|
|
|
type => NUMBER,
|
|
|
|
value => 0+ $ident,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
push @tokens, {
|
|
|
|
type => IDENT,
|
|
|
|
value => $ident,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
die "Unknown token: $str";
|
|
|
|
}
|
|
|
|
|
|
|
|
$str =~ s/^\s+//;
|
|
|
|
$str =~ s/\s+$//;
|
|
|
|
}
|
|
|
|
return @tokens;
|
|
|
|
}
|
|
|
|
|
2021-04-04 22:15:04 +02:00
|
|
|
sub ctx_create {
|
|
|
|
my $base = shift || undef;
|
|
|
|
my $vars = shift || {};
|
|
|
|
|
|
|
|
return {
|
|
|
|
base => $base,
|
|
|
|
vars => $vars
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub ctx_get {
|
|
|
|
my $ctx = shift;
|
|
|
|
my $identifier = shift;
|
|
|
|
|
|
|
|
while (defined $ctx && !exists($ctx->{vars}->{$identifier}))
|
|
|
|
{
|
|
|
|
$ctx = $ctx->{base};
|
|
|
|
}
|
|
|
|
|
2021-04-05 03:18:53 +02:00
|
|
|
die "Identifier $identifier is not defined" unless defined $ctx;
|
2021-04-04 22:15:04 +02:00
|
|
|
|
|
|
|
return $ctx->{vars}->{$identifier};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub ctx_set {
|
|
|
|
my $ctx = shift;
|
|
|
|
my $identifier = shift;
|
|
|
|
my $value = shift;
|
|
|
|
|
|
|
|
$ctx->{vars}->{$identifier} = $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub ctx_assign {
|
|
|
|
my $ctx = shift;
|
|
|
|
my $identifier = shift;
|
|
|
|
my $value = shift;
|
|
|
|
|
|
|
|
while (defined $ctx && !exists($ctx->{vars}->{$identifier}))
|
|
|
|
{
|
|
|
|
$ctx = $ctx->{base};
|
|
|
|
}
|
|
|
|
|
|
|
|
die "Variable $identifier does not defined" unless defined $ctx;
|
|
|
|
|
|
|
|
$ctx->{vars}->{$identifier} = $value;
|
|
|
|
}
|
|
|
|
|
2021-04-02 20:01:12 +02:00
|
|
|
sub lisp_format {
|
|
|
|
my $e = shift;
|
|
|
|
if (ref($e) eq "ARRAY")
|
|
|
|
{
|
|
|
|
return "(" . join(" ", map { lisp_format($_) } @$e) . ")";
|
|
|
|
}
|
2021-04-02 20:52:26 +02:00
|
|
|
elsif (ref($e) eq "KEYWORD")
|
|
|
|
{
|
|
|
|
return "'" . $e->{value};
|
|
|
|
}
|
2021-04-02 20:01:12 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
return "$e";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-02 20:52:57 +02:00
|
|
|
sub lisp_equal {
|
|
|
|
my ($a, $b) = @_;
|
|
|
|
if ( ref($a) eq "ARRAY" && ref($b) eq "ARRAY" )
|
|
|
|
{
|
2021-04-05 00:06:49 +02:00
|
|
|
return LISP_FALSE unless scalar(@$a) == scalar(@$b);
|
2021-04-02 20:52:57 +02:00
|
|
|
|
|
|
|
for(my $i = 0; $i < @$a; $i++)
|
|
|
|
{
|
2021-04-05 00:06:49 +02:00
|
|
|
return LISP_FALSE unless lisp_equal($a->[$i], $b->[$i]);
|
2021-04-02 20:52:57 +02:00
|
|
|
}
|
|
|
|
|
2021-04-05 00:06:49 +02:00
|
|
|
return LISP_TRUE;
|
2021-04-02 20:52:57 +02:00
|
|
|
}
|
|
|
|
elsif ( ref($a) eq "KEYWORD" && ref($b) eq "KEYWORD" )
|
|
|
|
{
|
2021-04-05 00:06:49 +02:00
|
|
|
return to_lisp_bool($a->{value} eq $b->{value});
|
2021-04-02 20:52:57 +02:00
|
|
|
}
|
|
|
|
elsif ( ref($a) eq "" && ref($b) eq "" )
|
|
|
|
{
|
2021-04-05 00:06:49 +02:00
|
|
|
return to_lisp_bool($a == $b);
|
2021-04-02 20:52:57 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-04-05 00:06:49 +02:00
|
|
|
return LISP_FALSE;
|
2021-04-02 20:52:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-01 22:44:06 +02:00
|
|
|
my %stdctx = (
|
2021-04-02 01:02:19 +02:00
|
|
|
'+' => sub {
|
2021-04-01 22:44:06 +02:00
|
|
|
my $sum = 0;
|
|
|
|
$sum += $_ foreach (@_);
|
|
|
|
return $sum;
|
|
|
|
},
|
2021-04-02 01:02:19 +02:00
|
|
|
'-' => sub {
|
2021-04-01 22:44:06 +02:00
|
|
|
my $sum = shift;
|
|
|
|
$sum -= $_ foreach (@_);
|
|
|
|
return $sum;
|
|
|
|
},
|
2021-04-02 01:02:19 +02:00
|
|
|
'*' => sub {
|
|
|
|
my $prod = 1;
|
|
|
|
$prod *= $_ foreach(@_);
|
|
|
|
return $prod;
|
|
|
|
},
|
|
|
|
'/' => sub {
|
|
|
|
my $quot = shift;
|
|
|
|
$quot /= $_ foreach(@_);
|
|
|
|
return $quot;
|
|
|
|
},
|
|
|
|
'write-line' => sub {
|
2021-04-01 22:44:06 +02:00
|
|
|
my $e = shift;
|
2021-04-02 20:01:12 +02:00
|
|
|
print lisp_format($e) . "\n";
|
|
|
|
return undef;
|
|
|
|
},
|
|
|
|
'write' => sub {
|
|
|
|
my $e = shift;
|
|
|
|
print lisp_format($e);
|
2021-04-01 22:44:06 +02:00
|
|
|
return undef;
|
|
|
|
},
|
2021-04-02 01:12:22 +02:00
|
|
|
'null' => sub { my ($a) = @_; return ! defined $a; },
|
2021-04-05 00:06:49 +02:00
|
|
|
'evenp' => sub { my ($a) = @_; return to_lisp_bool($a % 2 == 0); },
|
|
|
|
'oddp' => sub { my ($a) = @_; return to_lisp_bool($a % 2 != 0); },
|
|
|
|
'zerop' => sub { my ($a) = @_; return to_lisp_bool($a == 0); },
|
|
|
|
'eq' => sub { my ($a, $b) = @_; return to_lisp_bool($a == $b); },
|
|
|
|
'ne' => sub { my ($a, $b) = @_; return to_lisp_bool($a != $b); },
|
2021-04-02 03:35:59 +02:00
|
|
|
|
|
|
|
# Logical operators
|
2021-04-05 00:06:49 +02:00
|
|
|
'not' => sub { my ($a) = @_; return to_lisp_bool(!from_lisp_bool($a)); },
|
2021-04-02 03:10:24 +02:00
|
|
|
|
|
|
|
# Numeric comparison
|
2021-04-05 00:06:49 +02:00
|
|
|
'=' => sub { my ($a, $b) = @_; return to_lisp_bool($a == $b); },
|
|
|
|
'/=' => sub { my ($a, $b) = @_; return to_lisp_bool($a != $b); },
|
|
|
|
'>' => sub { my ($a,$b) = @_; return to_lisp_bool($a > $b); },
|
|
|
|
'<' => sub { my ($a,$b) = @_; return to_lisp_bool($a < $b); },
|
|
|
|
'>=' => sub { my ($a,$b) = @_; return to_lisp_bool($a >= $b); },
|
|
|
|
'<=' => sub { my ($a,$b) = @_; return to_lisp_bool($a <= $b); },
|
2021-04-02 03:36:26 +02:00
|
|
|
'max' => sub {
|
|
|
|
die "max: At least 2 parameters" unless scalar(@_) >= 2;
|
|
|
|
my $v = shift;
|
|
|
|
foreach (@_)
|
|
|
|
{
|
|
|
|
$v = $_ if $v < $_;
|
|
|
|
}
|
|
|
|
return $v;
|
|
|
|
},
|
|
|
|
'min' => sub {
|
|
|
|
die "min: At least 2 parameters" unless scalar(@_) >= 2;
|
|
|
|
my $v = shift;
|
|
|
|
foreach (@_)
|
|
|
|
{
|
|
|
|
$v = $_ if $v > $_;
|
|
|
|
}
|
|
|
|
return $v;
|
|
|
|
},
|
2021-04-05 03:50:48 +02:00
|
|
|
'mod' => sub {
|
|
|
|
my ($number, $divisor) = @_;
|
|
|
|
$number += $divisor while ( $number < 0 );
|
|
|
|
$number -= $divisor while ( $number >= $divisor );
|
|
|
|
|
|
|
|
return $number;
|
|
|
|
},
|
2021-04-02 03:10:24 +02:00
|
|
|
|
|
|
|
# String comparison
|
2021-04-05 00:06:49 +02:00
|
|
|
'string=' => sub { my ($a, $b) = @_; return to_lisp_bool($a eq $b); },
|
|
|
|
'string/=' => sub { my ($a, $b) = @_; return to_lisp_bool($a ne $b); },
|
|
|
|
'string<' => sub { my ($a, $b) = @_; return to_lisp_bool($a lt $b); },
|
|
|
|
'string>' => sub { my ($a, $b) = @_; return to_lisp_bool($a gt $b); },
|
|
|
|
'string<=' => sub { my ($a, $b) = @_; return to_lisp_bool(!($a gt $b)); },
|
|
|
|
'string>=' => sub { my ($a, $b) = @_; return to_lisp_bool(!($a lt $b)); },
|
|
|
|
'string-equal' => sub { my ($a, $b) = @_; return to_lisp_bool( lc($a) eq lc($b)); },
|
|
|
|
'string-not-equal' => sub { my ($a, $b) = @_; return to_lisp_bool(lc($a) ne lc($b)); },
|
|
|
|
'string-lessp' => sub { my ($a, $b) = @_; return to_lisp_bool(lc($a) lt lc($b)); },
|
|
|
|
'string-greaterp' => sub { my ($a, $b) = @_; return to_lisp_bool(lc($a) gt lc($b)); },
|
|
|
|
'string-not-greaterp' => sub { my ($a, $b) = @_; return to_lisp_bool(!(lc($a) gt lc($b))); },
|
|
|
|
'string-not-lessp' => sub { my ($a, $b) = @_; return to_lisp_bool(!(lc($a) lt lc($b))); },
|
2021-04-02 03:10:24 +02:00
|
|
|
|
2021-04-03 18:47:45 +02:00
|
|
|
# string operations
|
|
|
|
'string-upcase' => sub { return uc(shift); },
|
|
|
|
'string-downcase' => sub { return lc(shift); },
|
|
|
|
'string-capitalize' => sub {
|
|
|
|
my $str = shift;
|
|
|
|
return $str =~ s/(\S+)/ucfirst($1)/erg;
|
|
|
|
},
|
|
|
|
|
2021-04-02 03:36:39 +02:00
|
|
|
# Bitwise operations
|
|
|
|
'logand' => sub {
|
|
|
|
my $v = -1;
|
|
|
|
$v &= $_ foreach (@_);
|
|
|
|
return $v;
|
|
|
|
},
|
|
|
|
'logior' => sub {
|
|
|
|
my $v = 0;
|
|
|
|
$v |= $_ foreach (@_);
|
|
|
|
return $v;
|
|
|
|
},
|
|
|
|
'logxor' => sub {
|
|
|
|
my $v = 0;
|
|
|
|
$v ^= $_ foreach (@_);
|
|
|
|
return $v;
|
|
|
|
},
|
|
|
|
'lognor' => sub {
|
|
|
|
my $v = -1;
|
|
|
|
$v |= $_ foreach (@_);
|
|
|
|
return ~$v;
|
|
|
|
},
|
|
|
|
'logeqv' => sub {
|
|
|
|
my $v = 0;
|
|
|
|
$v ^= $_ foreach (@_);
|
|
|
|
return ~$v;
|
|
|
|
},
|
|
|
|
|
2021-04-02 20:01:12 +02:00
|
|
|
# Lists
|
|
|
|
'list' => sub { return [ @_ ]; },
|
|
|
|
'first' => sub { return (shift)->[0]; },
|
|
|
|
'second' => sub { return (shift)->[1]; },
|
|
|
|
'nth' => sub { my ($idx,$list) = @_; return $list->[$idx]; },
|
2021-04-02 20:18:42 +02:00
|
|
|
'map' => sub {
|
|
|
|
my ($cb,$list) = @_;
|
|
|
|
die "map: First parameter must be a function" unless ref($cb) eq "CODE";
|
|
|
|
die "map: Second parameter must be a list" unless ref($list) eq "ARRAY";
|
|
|
|
|
|
|
|
return [ map { $cb->($_) } @$list ];
|
|
|
|
},
|
|
|
|
'reduce' => sub {
|
|
|
|
my ($cb,$list) = @_;
|
|
|
|
die "map: First parameter must be a function" unless ref($cb) eq "CODE";
|
|
|
|
die "map: Second parameter must be a list" unless ref($list) eq "ARRAY";
|
|
|
|
|
|
|
|
my @copy = ( @$list );
|
|
|
|
my $v = shift @copy;
|
|
|
|
$v = $cb->($v,$_) foreach (@copy);
|
|
|
|
return $v;
|
|
|
|
},
|
2021-04-04 02:34:05 +02:00
|
|
|
'filter' => sub {
|
|
|
|
my ($cb, $list) = @_;
|
|
|
|
die "map: First parameter must be a function" unless ref($cb) eq "CODE";
|
|
|
|
die "map: Second parameter must be a list" unless ref($list) eq "ARRAY";
|
|
|
|
|
|
|
|
return [ grep { $cb->($_) } @$list ];
|
|
|
|
},
|
2021-04-03 18:05:59 +02:00
|
|
|
'cons' => sub {
|
|
|
|
my ($v, $list) = @_;
|
|
|
|
return [ $v, @$list ];
|
|
|
|
},
|
|
|
|
'car' => sub {
|
|
|
|
my ($list) = @_;
|
|
|
|
return $list->[0];
|
|
|
|
},
|
|
|
|
'cdr' => sub {
|
|
|
|
my ($list) = @_;
|
|
|
|
my @newlist = @$list;
|
|
|
|
shift @newlist; # drop first element
|
|
|
|
return \@newlist;
|
|
|
|
},
|
2021-04-02 20:01:12 +02:00
|
|
|
|
2021-04-02 03:10:24 +02:00
|
|
|
# Constants
|
2021-04-05 00:06:49 +02:00
|
|
|
't' => LISP_TRUE,
|
|
|
|
'nil' => LISP_FALSE,
|
2021-04-02 20:52:57 +02:00
|
|
|
|
|
|
|
# Multi-purpose
|
|
|
|
'equal' => \&lisp_equal,
|
2021-04-03 18:47:23 +02:00
|
|
|
'length' => sub {
|
|
|
|
my ($a) = @_;
|
|
|
|
|
|
|
|
if (ref($a) eq "ARRAY")
|
|
|
|
{
|
|
|
|
return scalar(@$a);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return length($a);
|
|
|
|
}
|
|
|
|
},
|
2021-04-01 22:44:06 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
sub parser {
|
|
|
|
my @tokens = @_;
|
2021-04-02 02:45:49 +02:00
|
|
|
my $expr = parser_prog(\@tokens);
|
2021-04-04 22:15:04 +02:00
|
|
|
my $base_ctx = ctx_create(undef, \%stdctx);
|
2021-04-01 22:44:06 +02:00
|
|
|
|
|
|
|
return sub {
|
2021-04-04 22:15:04 +02:00
|
|
|
my $vars = shift;
|
|
|
|
my $ctx = ctx_create($base_ctx, $vars);
|
2021-04-01 22:44:06 +02:00
|
|
|
|
2021-04-04 22:15:04 +02:00
|
|
|
return $expr->($ctx);
|
2021-04-01 22:44:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-02 02:45:49 +02:00
|
|
|
sub parser_prog {
|
|
|
|
my $ts = shift;
|
|
|
|
|
|
|
|
my @steps;
|
|
|
|
|
|
|
|
while (scalar @$ts)
|
|
|
|
{
|
|
|
|
push @steps, parser_expr($ts);
|
|
|
|
}
|
|
|
|
|
|
|
|
return sub {
|
|
|
|
my $ctx = shift;
|
|
|
|
my $result;
|
|
|
|
$result = $_->($ctx) foreach (@steps);
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-01 22:44:06 +02:00
|
|
|
sub parser_expr {
|
|
|
|
my $ts = shift;
|
|
|
|
my $tok = shift @$ts;
|
|
|
|
if ($tok->{type} == LPAREN)
|
|
|
|
{
|
|
|
|
return parser_call($ts);
|
|
|
|
}
|
2021-04-03 18:09:20 +02:00
|
|
|
elsif($tok->{type} == LIST)
|
|
|
|
{
|
|
|
|
return parser_list($ts);
|
|
|
|
}
|
2021-04-01 22:44:06 +02:00
|
|
|
elsif($tok->{type} == IDENT)
|
|
|
|
{
|
|
|
|
return sub {
|
|
|
|
my $ctx = shift;
|
|
|
|
my $name = $tok->{value};
|
2021-04-04 22:15:04 +02:00
|
|
|
return ctx_get($ctx, $name);
|
2021-04-01 22:44:06 +02:00
|
|
|
}
|
|
|
|
}
|
2021-04-02 20:52:26 +02:00
|
|
|
elsif($tok->{type} == KEYWORD)
|
|
|
|
{
|
|
|
|
my $k = bless { value => $tok->{value} }, "KEYWORD";
|
|
|
|
return sub { return $k; };
|
|
|
|
}
|
2021-04-01 22:44:06 +02:00
|
|
|
elsif($tok->{type} == STRING || $tok->{type} == NUMBER)
|
|
|
|
{
|
|
|
|
return sub {
|
|
|
|
return $tok->{value};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
die "Unexpected token $tok->{type}\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-04 22:55:26 +02:00
|
|
|
sub parser_identifier {
|
|
|
|
my $ts = shift;
|
|
|
|
|
|
|
|
die "Expected identifier" unless $ts->[0]->{type} == IDENT;
|
|
|
|
|
|
|
|
my $tok = shift @$ts;
|
|
|
|
|
|
|
|
return $tok->{value};
|
|
|
|
}
|
|
|
|
|
2021-04-03 18:09:20 +02:00
|
|
|
sub parser_list {
|
|
|
|
my $ts = shift;
|
|
|
|
|
|
|
|
my $tok = shift @$ts;
|
|
|
|
die "Missing ( after ' for list" unless $tok->{type} == LPAREN;
|
|
|
|
|
|
|
|
my @elements;
|
|
|
|
while ($ts->[0]->{type} != RPAREN)
|
|
|
|
{
|
|
|
|
push @elements, parser_expr($ts);
|
|
|
|
}
|
|
|
|
|
|
|
|
shift @$ts; # Drop RPAREN
|
|
|
|
|
|
|
|
return sub {
|
|
|
|
my $ctx = shift;
|
|
|
|
|
|
|
|
return [ map { $_->($ctx) } @elements ];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-01 22:44:06 +02:00
|
|
|
my %macros;
|
|
|
|
|
|
|
|
sub parser_call {
|
|
|
|
my $ts = shift;
|
|
|
|
|
|
|
|
if ($ts->[0]->{type} == IDENT)
|
|
|
|
{
|
|
|
|
my $name = $ts->[0]->{value};
|
|
|
|
if (defined $macros{$name})
|
|
|
|
{
|
|
|
|
shift @$ts;
|
2021-04-04 01:44:28 +02:00
|
|
|
my $parsed = $macros{$name}->($ts);
|
|
|
|
my $rpar = shift @$ts;
|
|
|
|
die "Expected ) after macro $name" unless $rpar->{type} == RPAREN;
|
|
|
|
return $parsed;
|
2021-04-01 22:44:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
my $fn = parser_expr($ts);
|
|
|
|
|
|
|
|
my @params;
|
|
|
|
|
|
|
|
while ($ts->[0]->{type} != RPAREN)
|
|
|
|
{
|
|
|
|
push @params, parser_expr($ts);
|
|
|
|
}
|
|
|
|
|
|
|
|
my $tok = shift @$ts;
|
|
|
|
|
|
|
|
die "Missing )" unless $tok->{type} == RPAREN;
|
|
|
|
|
|
|
|
return sub {
|
|
|
|
my $ctx = shift;
|
|
|
|
my @p = map { $_->($ctx) } @params;
|
|
|
|
$fn->($ctx)->(@p);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-04-05 00:26:23 +02:00
|
|
|
sub gen_macro_let {
|
|
|
|
my $incremental = shift;
|
|
|
|
return sub {
|
|
|
|
my $ts = shift;
|
2021-04-01 22:44:06 +02:00
|
|
|
|
2021-04-05 00:26:23 +02:00
|
|
|
my $tok = shift @$ts;
|
|
|
|
die "Expected ( after let" unless $tok->{type} == LPAREN;
|
2021-04-02 01:02:35 +02:00
|
|
|
|
2021-04-05 00:26:23 +02:00
|
|
|
my @assignments;
|
2021-04-01 22:44:06 +02:00
|
|
|
|
2021-04-05 00:26:23 +02:00
|
|
|
while ($ts->[0]->{type} != RPAREN)
|
|
|
|
{
|
|
|
|
$tok = shift @$ts;
|
|
|
|
die "Expected pair in parameter list in let" unless $tok->{type} == LPAREN;
|
|
|
|
|
|
|
|
my $ident = parser_identifier($ts);
|
|
|
|
|
|
|
|
my $expr;
|
|
|
|
if ($ts->[0]->{type} == RPAREN)
|
|
|
|
{
|
|
|
|
$expr = sub { return undef };
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$expr = parser_expr($ts)
|
|
|
|
}
|
|
|
|
|
|
|
|
$tok = shift @$ts;
|
|
|
|
die "Expected ) after parameter pair" unless $tok->{type} == RPAREN;
|
|
|
|
|
|
|
|
push @assignments, {
|
|
|
|
ident => $ident,
|
|
|
|
expr => $expr
|
|
|
|
};
|
|
|
|
}
|
2021-04-01 22:44:06 +02:00
|
|
|
|
2021-04-05 00:26:23 +02:00
|
|
|
die "Expected ) after parameter list in let" unless $ts->[0]->{type} == RPAREN;
|
|
|
|
shift @$ts;
|
2021-04-01 22:44:06 +02:00
|
|
|
|
2021-04-05 00:26:23 +02:00
|
|
|
my $inner = macro_progn($ts);
|
2021-04-01 22:44:06 +02:00
|
|
|
|
2021-04-05 00:26:23 +02:00
|
|
|
return sub {
|
|
|
|
my $octx = shift;
|
|
|
|
my $ictx = ctx_create($octx);
|
|
|
|
ctx_set($ictx, $_->{ident}, $_->{expr}->($incremental ? $ictx : $octx)) foreach (@assignments);
|
2021-04-01 22:44:06 +02:00
|
|
|
|
2021-04-05 00:26:23 +02:00
|
|
|
return $inner->($ictx);
|
|
|
|
};
|
|
|
|
};
|
2021-04-01 22:44:06 +02:00
|
|
|
}
|
2021-04-05 00:26:23 +02:00
|
|
|
$macros{let} = gen_macro_let(0);
|
|
|
|
$macros{'let*'} = gen_macro_let(1);
|
2021-04-01 22:44:06 +02:00
|
|
|
|
|
|
|
sub macro_lambda {
|
|
|
|
my $ts = shift;
|
|
|
|
|
|
|
|
my $tok = shift @$ts;
|
|
|
|
die "Expected ( after lambda keyword" unless $tok->{type} = LPAREN;
|
|
|
|
|
|
|
|
my @param_list;
|
|
|
|
while ($ts->[0]->{type} != RPAREN)
|
|
|
|
{
|
2021-04-04 22:55:26 +02:00
|
|
|
my $ident = parser_identifier($ts);
|
|
|
|
push @param_list, $ident;
|
2021-04-01 22:44:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$tok = shift @$ts;
|
|
|
|
die "Expected ) after parameter list in lambda" unless $tok->{type} = LPAREN;
|
|
|
|
|
|
|
|
my $body = parser_expr($ts);
|
|
|
|
|
2021-04-04 01:44:28 +02:00
|
|
|
die "Expected ) after lambda" unless $ts->[0]->{type} == RPAREN;
|
2021-04-01 22:44:06 +02:00
|
|
|
|
|
|
|
return sub {
|
|
|
|
my $octx = shift;
|
|
|
|
return sub {
|
2021-04-04 22:15:04 +02:00
|
|
|
my $ictx = ctx_create($octx);
|
2021-04-01 22:44:06 +02:00
|
|
|
my @pnames = @param_list;
|
|
|
|
my @pvals = @_;
|
2021-04-04 22:15:04 +02:00
|
|
|
|
|
|
|
while ( @pnames && @pvals )
|
2021-04-01 22:44:06 +02:00
|
|
|
{
|
2021-04-04 22:15:04 +02:00
|
|
|
my $name = shift @pnames;
|
|
|
|
my $val = shift @pvals;
|
|
|
|
ctx_set($ictx, $name, $val);
|
2021-04-01 22:44:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
die "Too many arguments" if scalar(@pvals);
|
|
|
|
die "Not enough arguments" if scalar(@pnames);
|
|
|
|
|
|
|
|
return $body->($ictx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$macros{lambda} = \¯o_lambda;
|
|
|
|
|
|
|
|
sub macro_if {
|
|
|
|
my $ts = shift;
|
|
|
|
|
|
|
|
my $cond = parser_expr($ts);
|
|
|
|
|
|
|
|
my $tbranch = parser_expr($ts);
|
|
|
|
|
|
|
|
my $fbranch = parser_expr($ts);
|
|
|
|
|
2021-04-04 01:44:28 +02:00
|
|
|
die "Expected ) after else expression" unless $ts->[0]->{type} == RPAREN;
|
2021-04-01 22:44:06 +02:00
|
|
|
|
|
|
|
return sub {
|
|
|
|
my $ctx = shift;
|
|
|
|
|
|
|
|
my $condresult = $cond->($ctx);
|
2021-04-05 00:06:49 +02:00
|
|
|
if (defined $condresult) {
|
2021-04-01 22:44:06 +02:00
|
|
|
return $tbranch->($ctx);
|
|
|
|
} else {
|
|
|
|
return $fbranch->($ctx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$macros{if} = \¯o_if;
|
|
|
|
|
|
|
|
sub macro_progn {
|
|
|
|
my $ts = shift;
|
|
|
|
|
|
|
|
my @steps;
|
|
|
|
|
|
|
|
while ($ts->[0]->{type} != RPAREN)
|
|
|
|
{
|
|
|
|
push @steps, parser_expr($ts);
|
|
|
|
}
|
|
|
|
|
|
|
|
return sub {
|
|
|
|
my $ctx = shift;
|
|
|
|
|
|
|
|
my $result = undef;
|
|
|
|
|
|
|
|
$result = $_->($ctx) foreach (@steps);
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$macros{progn} = \¯o_progn;
|
|
|
|
|
2021-04-02 01:07:17 +02:00
|
|
|
sub macro_defun {
|
|
|
|
my $ts = shift;
|
|
|
|
|
2021-04-04 22:55:26 +02:00
|
|
|
my $ident = parser_identifier($ts);
|
2021-04-02 01:07:17 +02:00
|
|
|
|
|
|
|
my $body = macro_lambda($ts);
|
|
|
|
|
|
|
|
return sub {
|
|
|
|
my $ctx = shift;
|
2021-04-02 02:46:14 +02:00
|
|
|
my $fn;
|
|
|
|
my $self = sub {
|
|
|
|
return $fn->(@_);
|
|
|
|
};
|
2021-04-04 22:15:04 +02:00
|
|
|
my $ictx = ctx_create($ctx, {
|
2021-04-04 22:55:26 +02:00
|
|
|
'$ident' => $self,
|
2021-04-04 22:15:04 +02:00
|
|
|
});
|
|
|
|
$fn = $body->($ictx);
|
2021-04-04 22:55:26 +02:00
|
|
|
ctx_set($ctx, $ident, $fn);
|
2021-04-02 01:07:17 +02:00
|
|
|
return $fn;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$macros{defun} = \¯o_defun;
|
|
|
|
|
2021-04-04 01:44:47 +02:00
|
|
|
sub macro_cond {
|
|
|
|
my $ts = shift;
|
|
|
|
|
|
|
|
my @cases;
|
|
|
|
while ($ts->[0]->{type} != RPAREN)
|
|
|
|
{
|
|
|
|
die "Expected ( before case in cond"
|
|
|
|
unless (shift @$ts)->{type} == LPAREN;
|
|
|
|
|
|
|
|
my $condition = parser_expr($ts);
|
|
|
|
my $work = parser_expr($ts);
|
|
|
|
|
|
|
|
die "Expected ) after case in cond"
|
|
|
|
unless (shift @$ts)->{type} == RPAREN;
|
|
|
|
|
|
|
|
push @cases, {
|
|
|
|
condition => $condition,
|
|
|
|
work => $work,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return sub {
|
|
|
|
my $ctx = shift;
|
|
|
|
|
|
|
|
foreach my $case (@cases)
|
|
|
|
{
|
|
|
|
if($case->{condition}->($ctx))
|
|
|
|
{
|
|
|
|
return $case->{work}->($ctx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$macros{cond} = \¯o_cond;
|
|
|
|
|
2021-04-04 01:53:18 +02:00
|
|
|
sub macro_when {
|
|
|
|
my $ts = shift;
|
|
|
|
|
|
|
|
my $condition = parser_expr($ts);
|
|
|
|
my $work = parser_expr($ts);
|
|
|
|
|
|
|
|
return sub {
|
|
|
|
my $ctx = shift;
|
|
|
|
|
2021-04-05 00:06:49 +02:00
|
|
|
if (defined($condition->($ctx)))
|
2021-04-04 01:53:18 +02:00
|
|
|
{
|
|
|
|
return $work->($ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$macros{when} = \¯o_when;
|
|
|
|
|
|
|
|
sub macro_unless {
|
|
|
|
my $ts = shift;
|
|
|
|
|
|
|
|
my $condition = parser_expr($ts);
|
|
|
|
my $work = parser_expr($ts);
|
|
|
|
|
|
|
|
return sub {
|
|
|
|
my $ctx = shift;
|
|
|
|
|
2021-04-05 00:06:49 +02:00
|
|
|
if (!defined($condition->($ctx)))
|
2021-04-04 01:53:18 +02:00
|
|
|
{
|
|
|
|
return $work->($ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$macros{unless} = \¯o_unless;
|
|
|
|
|
2021-04-04 23:13:17 +02:00
|
|
|
sub macro_set {
|
|
|
|
my $ts = shift;
|
|
|
|
|
|
|
|
my $ident = parser_identifier($ts);
|
|
|
|
|
|
|
|
my $expr = parser_expr($ts);
|
|
|
|
|
|
|
|
return sub {
|
|
|
|
my $ctx = shift;
|
|
|
|
|
|
|
|
my $value = $expr->($ctx);
|
|
|
|
|
|
|
|
ctx_assign($ctx, $ident, $value);
|
|
|
|
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$macros{set} = \¯o_set;
|
|
|
|
|
2021-04-05 00:06:49 +02:00
|
|
|
sub macro_and {
|
|
|
|
my $ts = shift;
|
|
|
|
|
|
|
|
my @operands_parsed;
|
|
|
|
while ( $ts->[0]->{type} != RPAREN )
|
|
|
|
{
|
|
|
|
push @operands_parsed, parser_expr($ts);
|
|
|
|
}
|
|
|
|
|
|
|
|
return sub {
|
|
|
|
my $ctx = shift;
|
|
|
|
|
|
|
|
my $v = LISP_TRUE;
|
|
|
|
my @operands = @operands_parsed;
|
|
|
|
while (from_lisp_bool($v) && (my $op = shift @operands))
|
|
|
|
{
|
|
|
|
$v = $op->($ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $v;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
$macros{and} = \¯o_and;
|
|
|
|
|
|
|
|
sub macro_or {
|
|
|
|
my $ts = shift;
|
|
|
|
|
|
|
|
my @operands_parsed;
|
|
|
|
while ( $ts->[0]->{type} != RPAREN )
|
|
|
|
{
|
|
|
|
push @operands_parsed, parser_expr($ts);
|
|
|
|
}
|
|
|
|
|
|
|
|
return sub {
|
|
|
|
my $ctx = shift;
|
|
|
|
|
|
|
|
my $v = LISP_FALSE;
|
|
|
|
my @operands = @operands_parsed;
|
|
|
|
while (!from_lisp_bool($v) && (my $op = shift @operands))
|
|
|
|
{
|
|
|
|
$v = $op->($ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $v;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
$macros{or} = \¯o_or;
|
|
|
|
|
2021-04-01 22:44:06 +02:00
|
|
|
sub compile {
|
|
|
|
my ($term) = @_;
|
|
|
|
my @tokens = tokenize($term);
|
|
|
|
my $parsed = parser(@tokens);
|
|
|
|
|
|
|
|
return $parsed;
|
|
|
|
}
|
|
|
|
|
2021-04-04 01:17:18 +02:00
|
|
|
sub compile_file {
|
|
|
|
my $file = shift;
|
|
|
|
|
|
|
|
open(my $fh, "<", $file) or die "Could not open $file: $!";
|
|
|
|
my $script = do { local $/; <$fh> };
|
|
|
|
close($fh);
|
|
|
|
|
|
|
|
return compile($script);
|
|
|
|
}
|
|
|
|
|
2021-04-01 22:44:06 +02:00
|
|
|
1;
|