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

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

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

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

1;

%{

use Data::Dumper;
use m20080613;

my %vtable;
my %ftab;

%}

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

%%

program    : stmt_list
               {
                 RootNode->new($_[1]);
               }
;

stmt_list  :
               {
                 []
               }
           | stmt_list stmt '\n'
               {
                 $_[0]->YYData->{LINENO}++;
                 push(@{$_[1]}, $_[2]); $_[1]
               }
           | stmt_list '\n'
               {
                 $_[0]->YYData->{LINENO}++;
                 $_[1];
               }
           | error      '\n' { $_[0]->YYErrok }
;

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

if_stmt    : IF stmt THEN '\n' stmt_list else_stmt END
               {
                 IfNode->new($_[2], $_[5], $_[6]);
               }
;

else_stmt  : ELSE '\n' stmt_list
               {
                 $_[3];
               }
           |
               {
                 undef;
               }
;

while_stmt : WHILE stmt DO '\n' stmt_list END
               {
                 WhileNode->new($_[2], $_[5]);
               }
;

defun      : DEF IDENT param '\n' stmt_list END
               {
                 DefNode->new(\%ftab, $_[2],
                              Function->new(\%vtable, $_[2], $_[3], $_[5]));
               }
;

param      : '(' name_list ')'
               {
                 $_[2];
               }
           | '(' ')'
               {
                 [];
               }
           |
               {
                 [];
               }
;

name_list  : IDENT
               {
                 [ $_[1] ];
               }
           | name_list ',' IDENT
               {
                 push(@{$_[1]}, $_[3]); $_[1]
               }
;

assign     : IDENT '=' expr
               {
                 AssignNode->new(\%vtable, $_[1], $_[3]);
               }
;

expr       : expr '+' expr  { FuncallNode->new(\%ftab, '+', [$_[1], $_[3]]); }
           | expr '-' expr  { FuncallNode->new(\%ftab, '-', [$_[1], $_[3]]); }
           | expr '*' expr  { FuncallNode->new(\%ftab, '*', [$_[1], $_[3]]); }
           | expr '/' expr  { FuncallNode->new(\%ftab, '/', [$_[1], $_[3]]); }
           | primary
;

primary    : realprim
           | '(' expr ')'             { $_[2] }
           | '-' primary %prec UMINUS { FuncallNode->new(\%ftab, '-', [LiteralNode->new(0), $_[2]]) }
;

realprim   : IDENT    { VarRefNode->new(\%vtable, \%ftab, $_[1]); }
           | NUMBER   { LiteralNode->new($_[1]);          }
           | STRING   { StringNode->new($_[1]);           }
           | funcall
;

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

args       : expr
               {
                 [$_[1]];
               }
           | args ',' expr
               {
                 push(@{$_[1]}, $_[3]); $_[1]
               }
;

%%

sub _Error {
        exists $_[0]->YYData->{ERRMSG}
    and do {
        print  $_[0]->YYData->{ERRMSG};
        delete $_[0]->YYData->{ERRMSG};
        return;
    };
    print "$ARGV:$_[0]->YYData->{LINENO} Syntax error.\n";
}

sub _Lexer {
    my($parser) = shift;

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

        $parser->YYData->{INPUT}
    or  $parser->YYData->{INPUT} = <>
    or  return('',undef);

    $parser->YYData->{INPUT} =~ s/^[ \t]+//;

    for ($parser->YYData->{INPUT}) {
        s/^(\d+)//
                and return('NUMBER', $1);
        if (s/^([a-zA-Z_]\w*)//) {
            my $sym = $1;
            if (exists($reserved{$sym})) {
                return($reserved{$sym}, $sym);
            } else {
                return('IDENT', $sym);
            }
        }
        (s/^"(?:[^"\\]+|\\.)*"// ||
         s/^'(?:[^'\\]+|\\.)*'//)
                and return('STRING', $&);
        s/^(.)//s
                and return($1, $1);
    }
}

sub Run {
    my($self) = shift;

    $self->YYData->{LINENO} = 1;
    my($tree) = $self->YYParse( yylex => \&_Lexer, yyerror => \&_Error );
    #print "* tree\n", Dumper($tree);
    $tree->evaluate();
}

my($parser) = new IntpParser;
$parser->Run;

で、

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