『Rubyを256倍使うための本 無道編』の 13.defun/intp.y を移植(Perl 版)

関数定義可能に。ただし、変数スコープなし、グローバル

%{

use Data::Dumper;


package Node;

sub new
{
    my $class = shift;
    bless {}, $class;
}

sub exec_list
{
    my $self = shift;
    my ($nodes) = @_;

    foreach my $i (@$nodes) {
        $i->evaluate();
    }
}


package RootNode;
@ISA = qw(Node);

sub new
{
    my $class = shift;
    my $self = $class->SUPER::new();
    bless $self, $class;

    $self->{tree} = shift;
    return $self;
}

sub evaluate
{
    my $self = shift;

    $self->exec_list($self->{tree});
}


package DefNode;
@ISA = qw(Node);

sub new
{
    my $class = shift;
    my $self = $class->SUPER::new();
    bless $self, $class;

    $self->{ftab}     = shift;
    $self->{funcname} = shift;
    $self->{funcobj}  = shift;
    return $self;
}

sub evaluate
{
    my $self = shift;

    $self->{ftab}->{$self->{funcname}} = $self->{funcobj};
}


package FuncallNode;
@ISA = qw(Node);

sub new
{
    my $class = shift;
    my $self = $class->SUPER::new();
    bless $self, $class;

    $self->{ftab} = shift;
    $self->{func} = shift;
    $self->{args} = shift;
    return $self;
}

sub evaluate
{
    my $self = shift;
    my $args = $self->{args};

    @arg = map { $_->evaluate() } @$args;

    if (exists($self->{ftab}->{$self->{func}})) {
        $self->{ftab}->{$self->{func}}->call(\@arg);
    } elsif ($self->{func} =~ /^[+*\/-]$/) {
        eval $arg[0] . $self->{func} . "$arg[1];";
    } else {
        my $cmd = "$self->{func}(" . join(",", @arg) . ");";
        eval $cmd;
    }
}


package Function;
@ISA = qw(Node);

sub new
{
    my $class = shift;
    my $self = $class->SUPER::new();
    bless $self, $class;

    $self->{vtable} = shift;
    $self->{fname}  = shift;
    $self->{params} = shift;
    $self->{body}   = shift;
    return $self;
}

sub call
{
    my $self = shift;
    my $args = shift;

    my $size1 = @$args;
    my $size2 = @{$self->{params}};
    if ($size1 != $size2) {
        print "wrong # of arg for $self->{fname}() ($size1 for $size2)\n";
        exit(1);
    }

    $i = 0;
    foreach my $x (@$args) {
        $self->{vtable}->{$self->{params}->[$i++]} = $x;
    }
    $self->exec_list($self->{body});
}


package IfNode;
@ISA = qw(Node);

sub new
{
    my $class = shift;
    my $self = $class->SUPER::new();
    bless $self, $class;

    $self->{condition} = shift;
    $self->{tstmt}     = shift;
    $self->{fstmt}     = shift;
    return $self;
}

sub evaluate
{
    my $self = shift;

    if ($self->{condition}->evaluate()) {
        $self->exec_list($self->{tstmt});
    } else {
        $self->exec_list($self->{fstmt});
    }
}


package WhileNode;
@ISA = qw(Node);

sub new
{
    my $class = shift;
    my $self = $class->SUPER::new();
    bless $self, $class;

    $self->{condition} = shift;
    $self->{body}      = shift;
    return $self;
}

sub evaluate
{
    my $self = shift;

    while ($self->{condition}->evaluate()) {
        $self->exec_list($self->{body});
    }
}


package AssignNode;
@ISA = qw(Node);

sub new
{
    my $class = shift;
    my $self = $class->SUPER::new();
    bless $self, $class;

    $self->{vtable} = shift;
    $self->{vname}  = shift;
    $self->{val}    = shift;
    return $self;
}

sub evaluate
{
    my $self = shift;

    $self->{vtable}->{$self->{vname}} = $self->{val}->evaluate();
}


package VarRefNode;
@ISA = qw(Node);

sub new
{
    my $class = shift;
    my $self = $class->SUPER::new();
    bless $self, $class;

    $self->{vtable} = shift;
    $self->{ftab}   = shift;
    $self->{vname}  = shift;
    return $self;
}

sub evaluate
{
    my $self   = shift;
    my $vtable = $self->{vtable};
    my $ftab   = $self->{ftab};

    if (exists($vtable->{$self->{vname}})) {
        $vtable->{$self->{vname}};
    } elsif (exists($ftab->{$self->{vname}})) {
        $ftab->{$self->{vname}}->call([]);
    } else {
        eval $vtable->{$self->{vname}};
        if ($@) {
            print "unknown method or local variable $self->{vname}\n";
        }
    }
}


package StringNode;
@ISA = qw(Node);

sub new
{
    my $class = shift;
    my $self = $class->SUPER::new();
    bless $self, $class;

    $self->{val} = shift;
    return $self;
}

sub evaluate
{
    my $self = shift;

    $self->{val};
}


package LiteralNode;
@ISA = qw(Node);

sub new
{
    my $class = shift;
    my $self = $class->SUPER::new();
    bless $self, $class;

    $self->{val} = shift;
    return $self;
}

sub evaluate
{
    my $self = shift;

    $self->{val};
}


package main;

%}


%token IDENT NUMBER STRING
%token EOL IF ELSE WHILE THEN DO END DEF

%left '+' '-'
%left '*' '/'
%left UMINUS

%%

program        : stmt_list
                   {
                     program = RootNode->new(stmt_list);
                     return program;
                   }
;

r@stmt_list    :
                   {
                     r = [];
                   }
               | stmt_list stmt EOL
                   {
                     push(@{r}, stmt);
                   }
               | stmt_list EOL
;

stmt           : expr
               | assign
               | IDENT realprim
                   {
                     stmt = FuncallNode->new(\%ftab, IDENT, [realprim]);
                   }
               | if_stmt
               | while_stmt
               | defun
;

if_stmt        : IF stmt THEN EOL stmt_list else_stmt END
                   {
                     if_stmt = IfNode->new(stmt, stmt_list, else_stmt)
                   }
;

else_stmt      : ELSE EOL stmt_list
                   {
                     else_stmt = stmt_list;
                   }
               |
                   {
                     else_stmt = undef;
                   }
;

while_stmt     : WHILE stmt DO EOL stmt_list END
                   {
                     while_stmt = WhileNode->new(stmt, stmt_list);
                   }
;

defun          : DEF IDENT param EOL stmt_list END
                   {
                     defun = DefNode->new(\%ftab, IDENT,
                                          Function->new(\%vtable, IDENT, param, stmt_list));
                   }
;

param          : '(' name_list ')'
                   {
                     param = name_list;
                   }
               | '(' ')'
                   {
                     param = [];
                   }
               |
                   {
                     param = [];
                   }
;

r@name_list    : IDENT
                   {
                     name_list = [ IDENT ];
                   }
               | a@name_list ',' IDENT
                   {
                     push(@{r}, IDENT);
                   }
;

assign         : IDENT '=' expr
                   {
                     assign = AssignNode->new(\%vtable, IDENT, expr);
                   }
;

result@expr    : a@expr '+' b@expr
                   {
                     result = FuncallNode->new(\%ftab, '+', [a, b]);
                   }
               | a@expr '-' b@expr
                   {
                     result = FuncallNode->new(\%ftab, '-', [a, b]);
                   }
               | a@expr '*' b@expr
                   {
                     result = FuncallNode->new(\%ftab, '*', [a, b]);
                   }
               | a@expr '/' b@expr
                   {
                     result = FuncallNode->new(\%ftab, '/', [a, b]);
                   }
               | primary
;

result@primary : realprim
               | '(' expr ')'
                   {
                     result = expr;
                   }
               | '-' a@primary %prec UMINUS
                   {
                     result = FuncallNode->new(\%ftab, '-', [LiteralNode->new(0), a]);
                   }
;

realprim       : IDENT
                   {
                     realprim = VarRefNode->new(\%vtable, \%ftab, IDENT);
                   }
               | NUMBER
                   {
                     realprim = LiteralNode->new(NUMBER);
                   }
               | STRING
                   {
                     realprim = StringNode->new(STRING);
                   }
               | funcall
;

funcall        : IDENT '(' args ')'
                   {
                     funcall = FuncallNode->new(\%ftab, IDENT, args);
                   }
               | IDENT '(' ')'
                   {
                     funcall = FuncallNode->new(\%ftab, IDENT, []);
                   }
;

r@args         : expr
                   {
                     r = [expr];
                   }
               | a@args ',' expr
                   {
                     push(@{r}, expr);
                   }
;

%%

$lexbuf = '';
%vtable;
%ftab;
$lineno = 1;

%reserved = (
  'if'    => $IF,
  'else'  => $ELSE,
  'while' => $WHILE,
  'then'  => $THEN,
  'do'    => $DO,
  'def'   => $DEF,
  'end'   => $END
);

sub yylex
{
    while (1) {
        if ($lexbuf =~ /^([\t ]+)/) {
            $lexbuf = substr($lexbuf, length($1));
        }
        last if ($lexbuf ne '');
        if (!($lexbuf = <>)) {
            return 0;
        }
    }

    if ($lexbuf =~ /^(\d+)/) {
        $yylval = $1 - 0;
        $lexbuf = substr($lexbuf, length($1));
        return $NUMBER;
    } elsif ($lexbuf =~ /^([a-zA-Z_]\w*)/) {
        $yylval = $1;
        $lexbuf = substr($lexbuf, length($1));
        if (exists($reserved{$yylval})) {
            return $reserved{$yylval};
        } else {
            return $IDENT;
        }
    } elsif ($lexbuf =~ /^"(?:[^"\\]+|\\.)*"/ ||
             $lexbuf =~ /^'(?:[^'\\]+|\\.)*'/) {
        $yylval = $&;
        $lexbuf = substr($lexbuf, length($&));
        return $STRING;
    } else {
        my $ret = ord($lexbuf);
        $lexbuf = substr($lexbuf, 1);
        if ($ret eq ord("\n")) {
            $lineno++;
            return $EOL;
        } else {
            return $ret;
        }
    }
}

sub yyerror
{
    my ($msg) = @_;
    print "$ARGV:$lineno: $msg\n";
}

$tree = &yyparse();
#print "* tree\n", Dumper($tree);
$tree->evaluate();

で、

message = "funcall ok"
print(message)
print "\n"
print message
print "\n"
print('operator', ' ok', "\n")
print('1 + 1 = ', 1+1, "\n")
print('1 + 2 * -3 = ', 1 + 2 * -3, "\n")

if 1 then
  print "if ok\n"
else
  print "if not ok\n"
end

i = 1
while i do
  print "while ok\n"
  i = 0
end

def func
  print "defun ok\n"
end
func()
func

def argfunc(arg)
  print 'defun with arg: '
  print(arg, "\n")
end
argfunc('ok')
argfunc 'ok'

def argfunc2(arg1, arg2)
  print 'defun with arg: '
  print(arg1, " ", arg2, "\n")
end
argfunc2(1, 2)

を入力すると、

funcall ok
funcall ok
operator ok
1 + 1 = 2
1 + 2 * -3 = -5
if ok
while ok
defun ok
defun ok
defun with arg: ok
defun with arg: ok
defun with arg: 1 2
print "foo\n"
print("foo\n")
print(1, " ", 1+2, " ", 1+2*3, " ", 10/3, "\n")

if 1 then
  print "if ok\n"
else
  print "if not ok\n"
end

i = 1
while i do
  print "while ok\n"
  i = 0
end

x = 1
def foo
  print(x, " in foo (1)\n")
  x = 2
  print(x, " in foo (2)\n")
end
print("x: ", x, "\n")
foo
print("x: ", x, "\n")

def foo(x)
  print(x, " in foo (3)\n")
end
print("x: ", x, "\n")
foo(10)
print("x: ", x, "\n")

を入力すると、

foo
foo
1 3 7 3.33333333333333
if ok
while ok
x: 1
1 in foo (1)
2 in foo (2)
x: 2
x: 2
10 in foo (3)
x: 10