package Nana::Translator::Perl; use strict; use warnings; use utf8; use Data::Dumper; use Carp; use Nana::Node; sub new { my $self = shift; } our $FILENAME; our $IN_CLASS; sub compile { my ($self, $ast, $filename, $no_header) = @_; local $FILENAME = $filename || ""; my $res = ''; unless ($no_header) { $res .= join('', 'package main;', 'use strict;', 'use warnings;', 'use warnings FATAL => "recursion";', 'use utf8;', 'use Nana::Translator::Perl::Runtime;', 'use Nana::Translator::Perl::Builtins qw(%TORA_BUILTIN_CLASSES);', 'use JSON::XS;', 'use Sub::Name;', 'my $LIBPATH=$Nana::Translator::Perl::Runtime::LIBPATH;', 'my $STDOUT=$Nana::Translator::Perl::Runtime::STDOUT;', 'my $STDERR=$Nana::Translator::Perl::Runtime::STDERR;', 'my $STDIN=$Nana::Translator::Perl::Runtime::STDIN;', '$Nana::Translator::Perl::Runtime::CURRENT_PACKAGE = my $TORA_PACKAGE = {};', 'local $Nana::Translator::Perl::Runtime::TORA_FILENAME="' . $FILENAME .'";', ) . "\n"; } $res .= _compile($ast); return $res; } my @DISPATCHER; $DISPATCHER[NODE_STMTS()] = sub { my $node = shift; my $ret = ''; for (@{$node->[2]}) { $ret .= sprintf(qq{#line %d "$FILENAME"\n}, $_->[1]); $ret .= _compile($_) . ";\n"; } return $ret; }; $DISPATCHER[NODE_COMMA()] = sub { my $node = shift; return '('. _compile($node->[2]) . ',' . _compile($node->[3]).')'; }; $DISPATCHER[NODE_MINUS()] = sub { my $node = shift; return '('. _compile($node->[2]) . '-' . _compile($node->[3]).')'; }; $DISPATCHER[NODE_MOD()] = sub { my $node = shift; return '('. _compile($node->[2]) . '%' . _compile($node->[3]).')'; }; $DISPATCHER[NODE_RSHIFT()] = sub { my $node = shift; return '('. _compile($node->[2]) . '>>' . _compile($node->[3]).')'; }; $DISPATCHER[NODE_LSHIFT()] = sub { my $node = shift; return '('. _compile($node->[2]) . '<<' . _compile($node->[3]).')'; }; { my @foo = ( NODE_DIV_ASSIGN, NODE_DIV, NODE_MUL_ASSIGN, NODE_MUL, NODE_PLUS_ASSIGN, NODE_PLUS, NODE_MOD_ASSIGN, NODE_MOD, NODE_MINUS_ASSIGN, NODE_MINUS, NODE_LSHIFT_ASSIGN, NODE_LSHIFT, NODE_RSHIFT_ASSIGN, NODE_RSHIFT, NODE_POW_ASSIGN, NODE_POW, NODE_AND_ASSIGN, NODE_BITAND, NODE_OR_ASSIGN, NODE_BITOR, NODE_XOR_ASSIGN, NODE_BITXOR, NODE_OROR_ASSIGN, NODE_LOGICAL_OR, ); while (my ($x, $y) = splice @foo, 0, 2) { $DISPATCHER[$x] = sub { my $node = shift; return _compile([ NODE_ASSIGN(), $node->[1], $node->[2], [ $y, $node->[1], $node->[2], $node->[3], ] ]); }; } } $DISPATCHER[NODE_CMP()] = sub { my $node = shift; return '('. _compile($node->[2]) . '<=>' . _compile($node->[3]).')'; }; { my @foo = ( '|' => NODE_BITOR, '&' => NODE_BITAND, '^' => NODE_BITXOR, ' and ' => NODE_LOGICAL_AND, ' or ' => NODE_LOGICAL_OR, ' xor ' => NODE_LOGICAL_XOR, ); while (my ($x, $y) = splice @foo, 0, 2) { $DISPATCHER[$y] = sub { my $node = shift; return '('. _compile($node->[2]) . $x . _compile($node->[3]).')'; }; } } { my %binops = ( NODE_LT() => 'tora_op_lt', NODE_GT() => 'tora_op_gt', NODE_LE() => 'tora_op_le', NODE_GE() => 'tora_op_ge', NODE_EQ() => 'tora_op_eq', NODE_NE() => 'tora_op_ne', NODE_DOTDOT() => 'tora_make_range', NODE_PLUS() => 'tora_op_add', NODE_DIV() => 'tora_op_div', NODE_MUL() => 'tora_op_mul', NODE_POW() => 'tora_op_pow', ); while (my ($op, $func) = each %binops) { $DISPATCHER[$op] = sub { my $node = shift; return "$func(". _compile($node->[2]) . ',' . _compile($node->[3]).')'; }; } } $DISPATCHER[NODE_BLOCK()] = sub { my $node = shift; return '{' . _compile($node->[2]) . '}'; }; $DISPATCHER[NODE_MAKE_HASH()] = sub { my $node = shift; return '{' . join(',', map { _compile($_) } @{$node->[2]}) . '}'; }; $DISPATCHER[NODE_MAKE_ARRAY()] = sub { my $node = shift; return '[' . join(',', map { _compile($_) } @{$node->[2]}) . ']'; }; $DISPATCHER[NODE_VARIABLE()] = sub { my $node = shift; return $node->[2]; }; $DISPATCHER[NODE_UNARY_PLUS()] = sub { my $node = shift; return '(+' . _compile($node->[2]) . ')'; }; $DISPATCHER[NODE_UNARY_MINUS()] = sub { my $node = shift; return '(-' . _compile($node->[2]) . ')'; }; $DISPATCHER[NODE_UNARY_NOT()] = sub { my $node = shift; return 'tora_op_not('._compile($node->[2]).')'; }; $DISPATCHER[NODE_UNARY_TILDE()] = sub { my $node = shift; return '~' . _compile($node->[2]); }; $DISPATCHER[NODE_UNARY_MUL()] = sub { my $node = shift; return '@{' . _compile($node->[2]) . '}'; }; $DISPATCHER[NODE_UNARY_REF()] = sub { my $node = shift; return '\\' . _compile($node->[2]); }; $DISPATCHER[NODE_REGEXP_MATCH()] = sub { my $node = shift; return '('. _compile($node->[2]) . '=~' . _compile($node->[3]) .')'; }; sub _compile { my ($node) = @_; confess "Bad AST" unless $node; confess "Bad AST" unless ref $node eq 'ARRAY'; confess "Bad AST" unless @$node > 0; my $code = $DISPATCHER[$node->[0]]; return $code->($node) if $code; if ($node->[0] eq NODE_ASSIGN) { return _compile($node->[2]) . '=' . _compile($node->[3]); } if ($node->[0] eq NODE_FILE_TEST) { if ($node->[2] eq '-s') { return "(-s(" . _compile($node->[3]).'))'; } else { return "(($node->[2](" . _compile($node->[3]).'))?JSON::XS::true():JSON::XS::false())'; } } if ($node->[0] eq NODE_THREE) { return join( '', '((', _compile($node->[2]), ')?(', _compile($node->[3]), '):(', _compile($node->[4]), '))' ); } elsif ($node->[0] eq '()') { return '('. _compile($node->[2]) .')'; } elsif ($node->[0] eq NODE_PRE_INC) { return '++(' . _compile($node->[2]) . ')'; } elsif ($node->[0] eq NODE_PRE_DEC) { return '--(' . _compile($node->[2]) . ')'; } elsif ($node->[0] eq NODE_POST_INC) { return '(' . _compile($node->[2]) . ')++'; } elsif ($node->[0] eq NODE_POST_DEC) { return '(' . _compile($node->[2]) . ')--'; } elsif ($node->[0] eq NODE_MY) { if (ref $node->[2] eq 'ARRAY') { return 'my (' . join(', ', map { _compile($_) } @{$node->[2]}) . ')'; } else { return 'my ' . _compile($node->[2]); } } elsif ($node->[0] eq NODE_QW) { my $make_string = sub { local $_ = shift; s/'/\\'/g; q{'} . $_ . q{'}; }; return '[' . join(', ', map { $make_string->($_) } @{$node->[2]}) .']'; } elsif ($node->[0] eq NODE_DIE) { return "die Nana::Translator::Perl::Exception->new(" . _compile($node->[2]) . ')'; } elsif ($node->[0] eq NODE_TRY) { my $ret = "sub {my \@ret = eval {\n"; $ret .= _compile($node->[2]); $ret .= '}; my @ret2=($@ ? $@->val : undef, @ret); wantarray ? @ret2 : $ret2[0]; }->();'; return $ret; } elsif ($node->[0] eq NODE_GETITEM) { return 'tora_get_item(' . _compile($node->[2]) . ',' . _compile($node->[3]) .')'; } elsif ($node->[0] eq NODE_USE) { return 'tora_use($TORA_PACKAGE,' . _compile($node->[2]) . ',' . ($node->[3] && $node->[3] eq '*' ? 'q{*}' : _compile($node->[3])) . ')'; } elsif ($node->[0] eq NODE_DO) { my $ret = "do {\n"; $ret .= _compile($node->[2]) . '}'; return $ret; } elsif ($node->[0] eq NODE_LAMBDA) { my $ret = sprintf(qq{\n#line %d "$FILENAME"\n}, $node->[1]); $ret .= "(sub {;\n"; if (@{$node->[2]}) { # have args for (my $i=0; $i<@{$node->[2]}; $i++) { my $p = $node->[2]->[$i]; if ($p->[0] eq NODE_PARAMS_DEFAULT) { $ret .= "my "; $ret .= _compile($p->[2]); $ret .= "=\@_>0?shift \@_:" ._compile($p->[3]) .";"; } else { $ret .= "my "; $ret .= _compile($p); $ret .= "=shift;"; } } # remove arguments from stack. # for 'sub foo ($n) { }' $ret .= 'undef;'; } else { $ret .= "local \$_ = shift;"; # remove arguments from stack. # for 'sub foo ($n) { }' $ret .= 'undef;'; } my $block = _compile($node->[3]); if ($block =~ qr!\A\{\s*\}\Z!) { $block = ''; } $ret .= $block . '; })'; return $ret; } elsif ($node->[0] eq NODE_SUB) { my $ret = sprintf(qq{\n#line %d "$FILENAME"\n}, $node->[1]); my $end = ''; if ($node->[2]) { $ret .= 'local $Nana::Translator::Perl::Runtime::TORA_FILENAME="' . $FILENAME .'";', my $pkg = $IN_CLASS ? '$TORA_CLASS->{methods}' : '$TORA_PACKAGE'; $ret .= $pkg . "->{" . _compile($node->[2]) . "} = subname(" . _compile($node->[2]) .", sub {;\n"; } else { $ret .= "(sub {;\n"; } if ($node->[3]) { for (my $i=0; $i<@{$node->[3]}; $i++) { my $p = $node->[3]->[$i]; if ($p->[0] eq NODE_PARAMS_DEFAULT) { $ret .= "my "; $ret .= _compile($p->[2]); $ret .= "=\@_>0?shift \@_:" ._compile($p->[3]) .";"; } else { $ret .= "my "; $ret .= _compile($p); $ret .= "=shift;"; } } # remove arguments from stack. # for 'sub foo ($n) { }' $ret .= 'undef;'; } else { $ret .= 'local $_ = [@_];undef;'; } my $block = _compile($node->[4]); if ($block =~ qr!\A\{\s*\}\Z!) { $block = ''; } $ret .= $block . '; })'; return $ret; } elsif ($node->[0] eq NODE_CALL) { if ($node->[2]->[0] eq NODE_IDENT || $node->[2]->[0] eq NODE_PRIMARY_IDENT) { my $ret = 'tora_call_func($TORA_PACKAGE, q{' . $node->[2]->[2] . '}, ('; $ret .= join(',', map { sprintf('%s(%s)', $_->[0] eq 'CALL' ? 'scalar' : '', _compile($_)) } @{$node->[3]}); $ret .= '))'; return $ret; } elsif ($node->[2]->[0] eq NODE_VARIABLE) { my $ret = 'tora_call_func($TORA_PACKAGE, (' . _compile($node->[2]) . '), ('; $ret .= join(',', map { sprintf('%s(%s)', $_->[0] eq 'CALL' ? 'scalar' : '', _compile($_)) } @{$node->[3]}); $ret .= '))'; return $ret; } else { die "Compilation failed in subroutine call."; } } elsif ($node->[0] eq NODE_PRIMARY_IDENT) { return '($TORA_PACKAGE->{q!' . $node->[2] . '!} || $TORA_BUILTIN_CLASSES{q!' . $node->[2] . '!} || die qq{Unknown stuff named ' . $node->[2] . '})'; } elsif ($node->[0] eq NODE_IDENT) { return 'q{' . $node->[2] . '}'; } elsif ($node->[0] eq NODE_DEREF) { return 'tora_deref(' . _compile($node->[2]) . ')'; } elsif ($node->[0] eq NODE_NOP) { return ''; } elsif ($node->[0] eq NODE_REGEXP) { my $re = $node->[2]; $re =~ s!/!\\/!g; my $mode = $node->[3]; my $global = 0; if ($mode =~ s/g//) { $global++; } return join('', 'do {', 'Nana::Translator::Perl::Regexp->new(', "qr/$re/$mode,", ($global ? '1' : '0'), ');', '}', ); } elsif ($node->[0] eq NODE_NEXT) { return 'next;' } elsif ($node->[0] eq NODE_LAST) { return 'last;' } elsif ($node->[0] eq NODE_STRCONCAT) { return '('. _compile($node->[2]) .'.'. $node->[3].'.'._compile($node->[4]).')'; } elsif ($node->[0] eq NODE_STR2) { my $str = ${$node->[2]}; # TODO: more escape? $str =~ s/!/\\!/g; # escape return 'q!' . $str . '!'; } elsif ($node->[0] eq NODE_STR) { my $str = $node->[2]; # TODO: more escape? $str =~ s/!/\\!/g; # escape return 'q!' . $str . '!'; } elsif ($node->[0] eq NODE_BYTES) { my $str = $node->[2]; $str =~ s/!/\\!/g; # escape # TODO: more escape? return 'do { no utf8; tora_bytes(qq!' . $str . '!) }'; } elsif ($node->[0] eq NODE_HEREDOC) { my $buf = ${$node->[2]}; $buf =~ s/'/\\'/; return qq{'$buf'}; } elsif ($node->[0] eq NODE_INT) { return $node->[2]; } elsif ($node->[0] eq NODE_RETURN) { return 'return (' . _compile($node->[2]) . ');'; } elsif ($node->[0] eq NODE_IF) { my $ret = 'if (' . _compile($node->[2]) . ')' . _compile($node->[3]); if ($node->[4]) { $ret .= _compile($node->[4]); } return $ret; } elsif ($node->[0] eq NODE_WHILE) { return 'while (' . _compile($node->[2]) . ')' . _compile($node->[3]); } elsif ($node->[0] eq NODE_ELSIF) { my $ret = ' elsif ('. _compile($node->[2]) . ')' . _compile($node->[3]); if ($node->[4]) { $ret .= _compile($node->[4]); } return $ret; } elsif ($node->[0] eq NODE_ELSE) { return ' else {' . _compile($node->[2]) . '}'; } elsif ($node->[0] eq NODE_CLASS) { my $ret = '{my $TORA_CLASS=$Nana::Translator::Perl::Runtime::TORA_SELF=($TORA_PACKAGE->{' . _compile($node->[2]) . '} = Nana::Translator::Perl::Class->new(' . _compile($node->[2]) . ',' . ($node->[3] ? _compile($node->[3]) : 'undef') .'));'; $ret .= "\n"; $ret .= do { local $IN_CLASS=1; _compile($node->[4]); }; $ret .= ";}"; return $ret; } elsif ($node->[0] eq NODE_GET_METHOD) { return join('', 'do {', '[tora_get_method($TORA_PACKAGE,', _compile($node->[2]) .',', _compile($node->[3]), ')]->[0]', '}' ); } elsif ($node->[0] eq NODE_METHOD_CALL) { return join('', 'do {', 'my $self='._compile($node->[2]).';', 'my @args = tora_get_method($TORA_PACKAGE,', '$self,', _compile($node->[3]) . ',', '(' . join(',', map { _compile($_) } @{$node->[4]}) . ')', ');', 'local $Nana::Translator::Perl::Runtime::TORA_SELF=$self;', 'tora_call_method(@args);', '}' ); } elsif ($node->[0] eq NODE_FOREACH) { # use Data::Dumper; warn Dumper($node); my $ret = '{my $__tora_iteratee = (' . _compile($node->[2]) .");\n"; if (@{$node->[3]} > 2) { die "Too many parameters for foreach statement at line $node->[1]."; } elsif (@{$node->[3]} == 2) { # for hash $ret .= join('', qq!if (ref(\$__tora_iteratee) eq "HASH") {\n!, qq! for (keys \%\$__tora_iteratee) {\n!, sprintf(qq! my %s = \$_;\n!, _compile($node->[3]->[0])), sprintf(qq! my %s = \$__tora_iteratee->{\$_};\n!, _compile($node->[3]->[1])), _compile($node->[4]), qq! }\n!, qq!} else {\n!, qq! die "This is not a hash type. You cannot iterate by 2 or more variables."!, qq!}\n!, ); $ret .= '}'; } else { # 1 args $ret .= 'if (ref $__tora_iteratee eq "Nana::Translator::Perl::Object" && $__tora_iteratee->has_method("__iter__")) {' . "\n"; $ret .= ' my $__tora_iterator = ' . _compile( [NODE_METHOD_CALL, $node->[1], [NODE_VARIABLE, $node->[1], '$__tora_iteratee'], [NODE_IDENT, $node->[1], '__iter__'], []] ) . ';'; $ret .= sprintf(' while (%s = %s)', (@{$node->[3]} ? ('my ' . _compile($node->[3]->[0])) : 'local $_'), _compile( [NODE_METHOD_CALL, $node->[1], [NODE_VARIABLE, $node->[1], '$__tora_iterator'], [NODE_IDENT, $node->[1], '__next__'], []] )); $ret .= _compile($node->[4]); $ret .= '} else {' . "\n"; { $ret .= 'for '; if (@{$node->[3]}) { $ret .= join(',', map { 'my ' . _compile($_) } @{$node->[3]}); } $ret .= join('', '(', 'ref($__tora_iteratee) eq "ARRAY"', '? @{$__tora_iteratee}', ': ref($__tora_iteratee) eq "Nana::Translator::Perl::Range"', '? $__tora_iteratee->list', ': $__tora_iteratee', ') ' ). "\n". _compile($node->[4]) . ''; } $ret .= '}'; # if-else $ret .= '}'; } return $ret; } elsif ($node->[0] eq NODE_FOR) { join('', 'for (', _compile($node->[2]), ';', _compile($node->[3]), ';', _compile($node->[4]), ')', _compile($node->[5]), # block ); } elsif ($node->[0] eq NODE_UNDEF) { return 'undef'; } elsif ($node->[0] eq NODE_FALSE) { return 'JSON::XS::false()'; } elsif ($node->[0] eq NODE_SELF) { return '($Nana::Translator::Perl::Runtime::TORA_SELF || die "Do not call self out of class.")'; } elsif ($node->[0] eq NODE___FILE__) { return '__FILE__'; } elsif ($node->[0] eq NODE_TRUE) { return 'JSON::XS::true()'; } elsif ($node->[0] eq NODE_DOUBLE) { my $ret = "$node->[2]"; $ret .= ".0" unless $ret =~ /\./; return $ret; } else { die "Unknown node type " . node_name($node->[0]); # die "Unknown node type " . Dumper($node); } } 1; __END__ =head1 NAME Nana::Translator::Perl - AST to Perl =head1 SYNOPSIS use Nana::Translator::Perl; my $compiler = Nana::Translator::Perl->new(); my $perl = $compiler->compile($ast); eval $perl; =head1 DESCRIPTION This class translates tora AST to perl code. And you can eval() the code. =head1 MEHOTDS =over 4 =item my $compiler = Nana::Translator::Perl->new() Create a new instance. =item my $perl = $compiler->compile(ArrayRef $ast, Str $filename) Compile a AST to perl code. =back =head1 AUTHOR Tokuhiro Matsuno