parrotcode: Untitled | |
Contents | Language Implementations | Lua |
class Lua::Grammar::Actions;
method TOP($/) { my $past := $( $<block> ); $past.blocktype('declaration'); make $past; }
method block($/, $key) { our @?BLOCK; our $?BLOCK;
## A chunk is a sequence of statements, and a chunk is also a block. ## See section 2.4.1 of Lua reference manual. if $key eq 'open' { $?BLOCK := PAST::Block.new( :blocktype('immediate'), :node($/) ); @?BLOCK.unshift($?BLOCK); } elsif $key eq 'close' { for $<statement> { $?BLOCK.push( $($_) ); } ## handle break or return if $<end_statement> { $?BLOCK.push( $($<last_statement>[0]) ); } make $?BLOCK; ## remove this block from scope stack and restore 'current'. @?BLOCK.shift(); $?BLOCK := @?BLOCK[0]; }
}
method last_statement($/, $key) { make $( $/{$key} ); }
method statement($/,$key) { make $( $/{$key} ); }
method expression_list($/) { my $past := PAST::Stmts.new( :node($/) ); for $<expression> { $past.push( $( $_ ) ); } make $past; }
method do_block($/) { make $( $<block> ); }
method if_stat($/) { my $cond := +$<expression> - 1; my $past := PAST::Op.new( $( $<expression>[$cond] ), $( $<block>[$cond] ), :pasttype('if'), :node( $/ ) ); if ( $<else> ) { $past.push( $( $<else>[0] ) ); } while ($cond != 0) { $cond := $cond - 1; $past := PAST::Op.new( $( $<expression>[$cond] ), $( $<block>[$cond] ), $past, :pasttype('if'), :node( $/ ) ); } make $past; }
method while_stat($/) { my $cond := $( $<expression> ); my $block := $( $<block> ); make PAST::Op.new( $cond, $block, :pasttype('while'), :node($/) ); }
method repeat_stat($/) { my $cond := $( $<expression> ); my $block := $( $<block> ); make PAST::Op.new( $cond, $block, :pasttype('repeat_until'), :node($/) ); }
method for_stat($/, $key) { make $( $/{$key} ); }
method fornum($/) { my $past := PAST::Block.new( :blocktype('immediate'), :node($/) );
my $var := $( $<Name> ); my $initval := $( $<var> ); my $limitval := $( $<limit> ); my $stepval; ## if there's a step value, get it, otherwise make it default to 1. if $<step> { $stepval := $( $<step>[0] ); } else { $stepval := PAST::Val.new( :value('1'), :returns('Integer'), :scope($/) ); } ## create the var, limit and step internal variables. Note their names which ## are not valid Lua identifiers; this prevents accidental reference to them. my $itervar := PAST::Var.new( :name('(var)'), :isdecl(1), :scope('lexical'), :viviself($initval), :node($/) ); my $limitvar := PAST::Var.new( :name('(limit)'), :isdecl(1), :scope('lexical'), :viviself($limitval), :node($/) ); my $stepvar := PAST::Var.new( :name('(step)'), :isdecl(1), :scope('lexical'), :viviself($stepval), :node($/) ); $past.push($itervar); $past.push($limitvar); $past.push($stepvar); ## XXX finish this, or, maybe add special functions for doing this for loop. ## my $body := $( $<block> ); ## XXX cond1 my $cond1 := PAST::Op.new( $var, $var, :pasttype('if'), :node($/) ); ## XXX cond2 my $cond2 := PAST::Op.new( $var, $var, :pasttype('if'), :node($/) ); my $cond := PAST::Op.new( $cond1, $cond2, :pasttype('unless'), :node($/) ); my $loop := PAST::Op.new( $cond, $body, :pasttype('while'), :node($/) ); ## XXX increment step here and init $var. $past.push($loop); make $past; }
method forlist($/) { # XXX todo make PAST::Op.new( :inline(' # for-list not implemented'), :node($/) ); }
method break_stat($/) { # XXX wait for PCT support. make PAST::Op.new( :inline(' # break not implemented'), :node($/) ); }
method return_stat($/) { my $past := PAST::Op.new( :node($/) ); # XXX return? wait for PCT support if $<expression_list> { my $retvals := $( $<expression_list> ); for @($retvals) { $past.push( $_ ); } } make $past; }
method local_stat($/, $key) { make $( $/{$key} ); }
method local_function($/) { our $?BLOCK; ## local function foo() ... end translates to 2 statements: ## local foo; foo = function() ... end. ## my $past := PAST::Stmts.new( :node($/) ); my $name := $( $<Name> ); $name.isdecl(1); $name.scope('lexical');
my $func := $( $<function_body> ); ## assign the function object to the specified name. It's only a binding; ## it's not the name of the function object, which remains anonymous. my $bind := PAST::Op.new( $name, $func, :pasttype('bind'), :node($/) ); $past.push($bind); ## register the name as a local in the current block's symbol table. $?BLOCK.symbol( $name.name(), :scope('lexical') ); make $past; }
method local_declaration($/) { our $?BLOCK; my $past := PAST::Stmts.new( :node($/) );
## get number of names and experssions my $numnames := +$<Name>; my $expressions; my $numexprs; if $<expression_list> { $expressions := $($<expression_list>[0]); $numexprs := +@($expressions); ##+$<expression_list>[0]<expression>; } else { $numexprs := 0; } ## while there are enough names and expressions, use the expressions ## to initialize the names. my $index := 0; while (($index != $numnames) && ($index != $numexprs)) { my $name := $( $<Name>[$index] ); ## XXX why doesn't @($expressions)[$index] work? my $expr := $( $<expression_list>[0]<expression>[$index] ); #my $expr := $expressions[$index]; $name.isdecl(1); $name.scope('lexical'); $name.viviself($expr); $past.push($name); $?BLOCK.symbol( $name.name(), :scope('lexical') ); $index := $index + 1; } ## we may have run out of expressions, initialize rest of names ## to 'nil' and declare them. while ($index != $numnames) { my $name := $( $<Name>[$index] ); $name.isdecl(1); $name.scope('lexical'); $past.push($name); $?BLOCK.symbol( $name.name(), :scope('lexical') ); $index := $index + 1; } make $past; }
method function_stat($/) { my $name := $( $<function_name> ); my $body := $( $<function_body> ); make PAST::Op.new( $name, $body, :pasttype('bind'), :node($/) ); }
method function_name($/) { ## XXX improve this ## how to handle method name (the part after ":")? ## how to implement Lua's "object model"? Lua doesn't have really an object model, ## but maybe it's nice to cheat and use OO-like model anyhow.
my $count := +$<Name>; my $past := $( $<Name>[0] ); my $idx := 1; while ($idx != $count) { my $name := $( $<Name>[$idx] ); $name.scope('keyed'); my $field := PAST::Val.new( :value($name.name()), :returns('String') ); ## XXX viviself should not be here; you have to create the tables yourself ## but for now this is nice. $past.viviself('Hash'); $past := PAST::Var.new( $past, $field, :scope('keyed'), :node($/) ); $idx := $idx + 1; } make $past; }
method function_body($/) { my $past := $( $<parameter_list> ); $past.push( $( $<block> ) ); make $past; }
method name_list($/) { my $past := PAST::VarList.new( :node($/) ); for $<Name> { $past.push( $($_) ); } make $past; }
method parameter_list($/) { my $past := PAST::Block.new( :blocktype('declaration'), :node($/) ); if $<name_list> { my $params := $( $<name_list> ); for @($params) { $_.scope('parameter'); $past.push( $_ );
## enter the symbol into this function block's symbol table. $past.symbol( $_.name(), :scope('lexical') ); } } if $<vararg> { $past.push( PAST::Var.new( :name('arg'), :scope('parameter'), :slurpy(1), :node($/) ) ); ## XXX check out this name "arg"; ## otherwise, how to refer to it? ## Maybe munge it a bit, so it becomes ## an illegal Lua identifer, such as "(arg)". } make $past; }
method expression_stat($/) { ## if there's an expression list,this is an assignment, otherwise it should ## be a function call. if $<expression_list> { ## XXX just handle 1 rhs value now my $lhs := $( $<primary_expression>[0] ); my $rhs := $( $<expression_list>[0] ); $rhs := $rhs[0]; make PAST::Op.new( $lhs, $rhs, :pasttype('bind'), :node($/) );
} else { ## primary expression must be a function call my $past := $( $<primary_expression>[0] ); my $iscall := $past['iscall']; ## Check here whether it's a call. If it's not, that's an error. ## This is the same way that Lua checks this (see lparser.c). unless $iscall { # XXX use $/.panic + emit line no where error occurred. print("Syntax error: '=' or function arguments expected\n"); } make $past; } }
method primary_expression($/) { my $past := $( $<prefix_expression> );
for $<slice_expression> { my $slice := $( $_ ); $slice.unshift($past); $past := $slice; } ## get a reference to the last slice, this will later be ## checked for being a function call. my $numslices := +$<slice_expression>; if $numslices { my $lastslice := $($<slice_expression>[$numslices - 1]); ## copy the 'iscall' flag from this last slice to the ## primary_expression past op. ## XXX Not all slice expressions set a flag yet. Fix that. $past['iscall'] := $lastslice['iscall']; } make $past; }
method function_args($/, $key) { my $past := $( $/{$key} ); ## set a flag on this past that it's a function call. $past['iscall'] := 1; make $past; }
method string_argument($/) { ## this rule is there to create a :pasttype('call') node; ## something that is otherwise done by method arguments(), ## but not by <quote>. This is a wrapper rule to solve that. my $arg := $( $<quote> ); make PAST::Op.new( $arg, :pasttype('call'), :node($/) ); }
method arguments($/) { my $past := PAST::Op.new( :pasttype('call'), :node($/) ); if $<expression_list> { ## get the PAST node of expression_list, which is just a ## Stmts node. my $args := $( $<expression_list>[0] ); ## interpret this object as an array, and stuff all elements ## into the :pasttype('call') $past object. for @($args) { $past.push( $_ ); } } make $past; }
method slice_expression($/, $key) { make $( $/{$key} ); }
method field($/, $key) { make $( $/{$key} ); }
method field_name($/) { my $field := $( $<Name> ); $field := PAST::Val.new( :returns('String'), :value($field.name()), :node($/) ); ## XXX __index meta-method should be invoked from the "mt" table... ## Read more lua reference stuff. make PAST::Op.new( $field, :name('__index'), :pasttype('callmethod'), :node($/) ); }
method method_call($/) { ## XXX handle the 'self' parameter, this is the 'table' on which ## this method is invoked. This is the 'previous slice_expression. ## Possibly need to rewrite grammar in order to make this easier. my $past := $( $<function_args> ); ## set the name of the method on the PAST::Op( :pasttype('call')) object. my $method := $( $<Name> ); $past.name( $method.name() ); make $past; }
method constructor($/) { if $<tablefieldlist> { make $( $<tablefieldlist>[0] ); } else { make PAST::Op.new( :inline(' %r = new "Hash"'), :node($/) ); } }
method tablefieldlist($/) { my $past := PAST::Stmts.new( :node($/) ); my $ctor := PAST::Op.new( :inline(' %r = new "Hash"'), :node($/) ); $past.push($ctor);
## XXX think a bit more about how to initialize tables and the indexing ## mechanism; use __index meta-method? ## Currently it doesn't work correctly. ## for $<tablefield> { my $field := $($_); ## how to reuse this %r generated in $ctor? $field.unshift($ctor); $past.push( $field ); } make $past; }
method tablefield($/, $key) { make $( $/{$key} ); }
method expr_field($/) { # XXX get index somehwere; keep track. my $index := PAST::Val.new( :returns('Integer'), :value('0'), :node($/) );
my $expr := $( $<expression> ); make PAST::Op.new( $index, $expr, :pasttype('callmethod'), :name('__index'), :node($/) ); }
method record_field($/) { my $field := $( $<field> ); my $expr := $( $<expression> ); ## should this be "rawset"? See Lua ref.man. ## <field> already creates a call to '__index', add the 2nd operand to it here: $field.push($expr); make $field; }
method index($/) { my $expr := $( $<expression> ); make PAST::Op.new( $expr, :name('__index'), :pasttype('callmethod'), :node($/) ); }
method prefix_expression($/, $key) { make $( $/{$key} ); }
method simple_expression($/, $key) { make $( $/{$key} ); }
method Name($/) { our $?BLOCK;
my $name := ~$/; my $scope; if $?BLOCK.symbol($name) { $scope := 'lexical'; } else { $scope := 'package'; } make PAST::Var.new( :name($name), :scope($scope), :viviself('Undef'), :node($/) ); }
method integer($/) { make PAST::Val.new( :value( ~$/ ), :returns('Integer'), :node($/) ); }
method number($/) { make PAST::Val.new( :value( ~$/ ), :returns('Float'), :node($/) ); }
method quote($/) { make PAST::Val.new( :value( $($<string_literal>) ), :node($/) ); }
method nil($/) { ## XXX does this work? Maybe get_global "nil"? make PAST::Val.new( :returns('Undef'), :node($/) ); }
method true($/) { # XXX change type into boolean make PAST::Val.new( :value('1'), :returns('Integer'), :node($/) ); }
method false($/) { # XXX change type into boolean make PAST::Val.new( :value('0'), :returns('Integer'), :node($/) ); }
method expression($/, $key) { if ($key eq 'end') { make $($<expr>); } else { my $past := PAST::Op.new( :name($<type>), :pasttype($<top><pasttype>), :pirop($<top><pirop>), :lvalue($<top><lvalue>), :node($/) ); for @($/) { $past.push( $($_) ); } make $past; } }
# Local Variables: # mode: cperl # cperl-indent-level: 4 # fill-column: 100 # End: # vim: expandtab shiftwidth=4:
|