# Copyright (C) 2007, Parrot Foundation.
# $Id: actions.nqp 43107 2009-12-16 15:12:00Z fperrad $

=begin overview

abc::Grammar::Actions - ast transformations for abc

This file contains the methods that are used by the parse
grammar to build the PAST representation of an abc program.
Each method below corresponds to a rule in F<grammar.pg>,
and is invoked at the point where C<{*}> appears in the rule,
with the current match object as the first argument.  If the
line containing C<{*}> also has a C<#= key> comment, then the
value of the comment is passed as the second argument to the
method.

Some hopefully helpful hints for reading this file:

It often helps to refer to the rules in F<grammar.pg> when
looking at the corresponding methods here.

Within a method, C<< $<foo> >> refers to the named capture C<foo>
within the C< $/ > match object.  Normally this is either another
match object or an array of match objects.

The C<make> function and C< .ast > are used to set and retrieve
the I<result object> for a match.  Here, we use the result object
to hold the ast representation of any given match.  So, in the
code below, whenever you see an expression like C<< $<foo>.ast >>,
we're really saying "the ast of C<< <foo> >>".

=end overview

class abc::Grammar::Actions is HLL::Actions;

##  The ast of the entire program is the ast of the
##  top-level <statement_list>.
method TOP($/) {
    make PAST::Block.new(
        :blocktype('declaration'),
        :hll('abc'),
        $<statement_list>.ast);
}


##  statement_list:
##    All of the individual statements are held in $<statement>
##    (which is an array).  We start by creating an empty
##    PAST::Stmts node, and then loop through all of the
##    statements, adding the ast of each statement to the
##    PAST::Stmts  node.
method statement_list($/) {
    my $past := PAST::Stmts.new( :node($/) );
    for $<statement> {
        $past.push( $_.ast );
    }
    make $past;
}


##  statement:
##    In the parse grammar, we've set up $key to tell us the
##    name of whatever subrule was matched by this statement.
##    We can then use $key to quickly get the subrule's ast
##    with $/{$key}.ast .
##
##    bc(1) expression statements also have special semantics
##    which we handle here.  If a statement is an expression
##    other than assignment, then the value of the expression
##    is also assigned to the C<last> variable and the value
##    printed with a newline.
##    Similarly, if the statement consists of a simple string, it's
##    displayed on the output.
method statement:sym<expr>($/) {
    my $past := $<EXPR>.ast;
    if pir::typeof__sp($past) ne 'PAST::Op' && ~$past.name() ne '&infix:<=>' {
        my $last := PAST::Var.new( :name('last'),
                                   :scope('package'),
                                   :lvalue(1) );
        $past := PAST::Op.new( $last,
                               $past,
                               :pasttype('bind') );
        #PAST::Var.new( :name('saynum'), :namespace([]), :scope('package')),
        $past := PAST::Op.new( 
                               $past,
                               :name('saynum'),
                               :pasttype('call') );
    }
    make $past;
}


##  if_statement:
##    After parsing an if statement, the conditional
##    expression will be in $<EXPR>, the "then"
##    statement will be in $<statement>[0], and any
##    "else" statement will be in $<statement>[1].
##    So, we just obtain the asts of these subrule
##    matches and set them as the children of a
##    PAST::Op node with a pasttype of 'if'.
method statement:sym<if>($/) {
    my $past := PAST::Op.new( $<EXPR>.ast,
                              $<statement>[0].ast,
                              :pasttype('if'),
                              :node( $/ ) );
    if ( $<statement>[1] ) {
        $past.push( $<statement>[1].ast );
    }
    make $past;
}


##  while_statement:
##    This is basically the same as if_statement above, except
##    we use a pasttype of 'while'.
method statement:sym<while>($/) {
    make PAST::Op.new( $<EXPR>.ast,
                       $<statement>.ast,
                       :pasttype('while'),
                       :node($/) );
}


##  for_statement:
##    A bc(1) for statement has the form
##        for( expr0 ; expr1 ; expr2 ) body;
##    We transform this into an ast structure that looks like
##        expr0; while (expr1) { body; expr2; }
method statement:sym<for>($/) {
    my $past := PAST::Stmts.new( :node($/) );

    #  add the initial "expr0;" node
    $past.push( $<EXPR>[0].ast );

    #  create the "{ body; expr2; }" part
    my $body := PAST::Stmts.new( $<statement>.ast, $<EXPR>[2].ast );

    #  now create the "while (expr1) { body; expr2; }" part
    $past.push( PAST::Op.new( $<EXPR>[1].ast, $body, :pasttype('while')));

    make $past;
}


##  compound_statement:
##    A compound statement is just a list of statements, so we
##    return the ast of its embedded <statement_list>.
method statement:sym<compound>($/) {
    make $<statement_list>.ast;
}


##  string:
##    The <string_literal> subrule (inherited from PCT::Grammar)
##    will have set its result object to the string representation
##    of whatever literal we found.  So, we just use this as
##    the value of a PAST::Val node.
method statement:sym<string>($/) {
    make PAST::Op.new(
        $<quote_EXPR>.ast,
        :pirop('print'));
}




##  float/integer:
##    For floating point and integer constants, we simply create
##    a PAST::Val node with the value of the constant.  But, because
##    Parrot's Floats don't know how to properly stringify to ints,
##    we have to use the string representation of the value
##    and tell the compiler what type of value to return.
method term:sym<float>($/) {
    make PAST::Val.new( :value( ~$/ ), :returns('Float'), :node( $/ ) );
}

method term:sym<int>($/) {
    make PAST::Val.new( :value( ~$/ ), :returns('Integer'), :node( $/ ) );
}


##  variable:
##    The <variable> rule is used to match both simple
##    variables ("a") and unary function calls ("a(0)").
##    Once again, the $key tells us what sort of match we have.
##
##    If it's a simple variable, we create a PAST::Var node
##    with the name of the variable and a scope of 'package'.
##    The :viviself('Float') option says that this variable
##    should be initialized as a Float if it doesn't already
##    exist.
##
##    If it's a function call, we create a PAST::Op node
##    to call the subroutine given by $<name> and passing
##    the value of $<EXPR> as an argument.  The
##    available subroutines are held in F<builtins.pir>.
method term:sym<variable>($/) {
    if ($<EXPR>) {
        make PAST::Op.new( $<EXPR>[0].ast,
                           :name( ~$<name> ),
                           :pasttype('call'),
                           :node( $/ )
                         );
    }
    else {
        make PAST::Var.new( :name( ~$<name> ),
                            :scope('package'),
                            :viviself('Float'),
                            :lvalue(1),
                            :node( $/ )
                          );
    }
}

method term:sym<circumfix>($/) {
    make $<EXPR>.ast;
}

## vim: expandtab sw=4 ft=perl6
