=head1 NAME

ASTGrammar -- A grammar for transforming the output of a PGE parse into
an abstract syntax tree.

=head1 SYNOPSYS

  .sub _main :main
      load_bytecode 'ASTGrammar.pir'
      .local pmc grammar
       grammar = new 'ASTGrammar'

      # Construct the "AST"
      load_bytecode "PAST.pbc"
      .local pmc astbuilder
      astbuilder = grammar.apply(match)
      .local pmc ast
      ast = astbuilder.get('past')

      end
  .end

=head1 DESCRIPTION


=cut

grammar ASTGrammar is TGE::Grammar;

transform past (ROOT) :language('PIR') {
    # Ask the child node for its result
    .local pmc child
    .local pmc result
    result = new 'PAST::Block'
    result.'init'('node'=>node, 'name'=>'anon')

    $I0 = defined node["lineseq"]
    unless $I0 goto err_no_tree
    $P0 = node["lineseq"]
    child = tree.get('past', $P0, 'PunieGrammar::lineseq')

    result.'push'(child)
    .return (result)

  err_no_tree:
    print "The top-level node doesn't contain an 'lineseq' match.\n"
    end
}

transform past (PunieGrammar::block) :language('PIR') {
    # Ask the child node for its result
    .local pmc child
    .local pmc result
    result = new 'PAST::Block'
    result.'init'('node'=>node, 'blocktype' => "immediate")

    $I0 = defined node["lineseq"]
    unless $I0 goto err_no_tree
    $P0 = node["lineseq"]
    child = tree.get('past', $P0, 'PunieGrammar::lineseq')

    result.'push'(child)
    .return (result)

  err_no_tree:
    print "The block node doesn't contain a 'lineseq' match.\n"
    end
}

transform past (PunieGrammar::lineseq) :language('PIR') {
    .local pmc result
    result = new 'PAST::Stmts'
    result.'init'('node'=>node)

    # Ask the child node for its result
    .local pmc child
    $I0 = defined node["line"]
    unless $I0 goto err_no_tree
    $P0 = node["line"]

    .local pmc iter
    iter = new Iterator, $P0    # setup iterator for node
    iter = 0
  iter_loop:
    unless iter, iter_end         # while (entries) ...
      shift $P2, iter
      $P3 = tree.get('past', $P2, 'PunieGrammar::line')
      result.'push'($P3)
      goto iter_loop
  iter_end:

    .return (result)
  err_no_tree:
    print "The 'lineseq' node doesn't contain a 'line' match.\n"
    end
}

transform past (PunieGrammar::line) :language('PIR') {
    .local pmc result
    result = new .ResizablePMCArray

    .local pmc iter
    iter = new Iterator, node    # setup iterator for node
    iter = 0
  iter_loop:
    unless iter, iter_end         # while (entries) ...
      shift $S1, iter           # get the key of the iterator
      $P2 = iter[$S1]
      $S1 = concat 'PunieGrammar::', $S1
      $P3 = tree.get('past', $P2, $S1)
      if null $P3 goto iter_loop
      push result, $P3
      goto iter_loop
  iter_end:
    $I0 = elements result
    if $I0 != 1 goto err_too_many
    $P4 = result[0]

    .return ($P4)
  err_too_many:
    print "A 'line' node can only have one child.\n"
    end
}

transform past (PunieGrammar::expr) :language('PIR') {
    .local pmc result
    result = new .ResizablePMCArray

    $P1 = node.get_hash()
    $P0 = new Iterator, $P1    # setup iterator for node
    set $P0, 0 # reset iterator, begin at start
  iter_loop:
    unless $P0, iter_end         # while (entries) ...
      shift $S2, $P0             # get key for next entry
      $P2 = $P0[$S2]      # get entry at current key
      $S2 = concat 'PunieGrammar::', $S2
      $P3 = tree.get('past', $P2, $S2)
      if null $P3 goto iter_loop
      push result, $P3
      goto iter_loop
  iter_end:
    $I0 = elements result
    if $I0 != 1 goto err_too_many
    $P4 = result[0]

    .return ($P4)
  err_too_many:
    print "An 'expr' node can only have one child.\n"
    end
}

transform past (PunieGrammar::gprint) :language('PIR') {
    .local pmc result
    result = new 'PAST::Op'
    $S1 = node[0]
    result.'name'($S1)

    $P1 = node.get_hash()
    $P0 = new Iterator, $P1    # setup iterator for node
    set $P0, 0 # reset iterator, begin at start
  iter_loop:
    unless $P0, iter_end         # while (entries) ...
      shift $S2, $P0             # get key for next entry
      $P2 = $P0[$S2]      # get entry at current key
      $S2 = concat 'PunieGrammar::', $S2
      $P3 = tree.get('past', $P2, $S2)
      if null $P3 goto iter_loop
      $S0 = $P3.'name'()
      if $S0 == 'infix:,' goto flatten_comma
      result.'init'($P3, 'node'=>node)
      goto iter_loop
    flatten_comma:
      $P4 = $P3.'get_array'()
      result.'init'($P4 :flat, 'node'=>node)
      goto iter_loop
  iter_end:

    .return (result)
}

transform past (PunieGrammar::cond) :language('PIR') {
    .local pmc result
    result = new 'PAST::Op'
    result.'init'('node'=>node)
    $S1 = node[0]
    result.'name'($S1)
    result.'pasttype'($S1)

    $P1 = node.get_hash()
    .local pmc iter
    iter = new Iterator, $P1    # setup iterator for node
    set iter, 0 # reset iterator, begin at start
  iter_loop:
    unless iter, iter_end         # while (entries) ...
      shift $S2, iter             # get key for next entry
      $P2 = iter[$S2]      # get entry at current key
      $S2 = concat 'PunieGrammar::', $S2
      $P3 = tree.get('past', $P2, $S2)
      if null $P3 goto iter_loop
      result.'push'($P3)
      goto iter_loop
  iter_end:

    .return (result)
}

transform past (PunieGrammar::else) :language('PIR') {
    .local pmc result, onechild
    onechild = node[0]
    unless onechild goto no_child
    .local string opname
    opname = onechild[0]

    unless opname == 'else' goto is_else_if
    $P1 = onechild['block']
    result = tree.get('past', $P1, 'PunieGrammar::block')
    goto transform_complete

  is_else_if:
    result = new 'PAST::Op'
    result.'init'('node'=>onechild, 'name'=>opname, 'pasttype'=>'if')

    $P1 = onechild.get_hash()
    .local pmc iter
    iter = new Iterator, $P1    # setup iterator for node
    set iter, 0 # reset iterator, begin at start
  iter_loop:
    unless iter, iter_end         # while (entries) ...
      shift $S2, iter             # get key for next entry
      $P2 = iter[$S2]      # get entry at current key
      $S2 = concat 'PunieGrammar::', $S2
      $P3 = tree.get('past', $P2, $S2)
      if null $P3 goto iter_loop
      result.'push'($P3)
      goto iter_loop
  iter_end:

  transform_complete:
    .return (result)
  no_child:
    .return ()
}

transform past (PunieGrammar::label) :language('PIR') {
    .return ()
}

transform past (PunieGrammar::oexpr) :language('PIR') {
    .local pmc result
    result = new .ResizablePMCArray

    $P1 = node.get_hash()
    .local pmc iter
    iter = new Iterator, $P1    # setup iterator for node
    set iter, 0
  iter_loop:
    unless iter, iter_end         # while (entries) ...
      shift $S1, iter             # get key for next entry
      $P2 = iter[$S1]      # get entry at current key
      $P3 = tree.get('past', $P2, $S1)
      if null $P3 goto iter_loop
      push result, $P3
      goto iter_loop
  iter_end:
    $I0 = elements result
    if $I0 != 1 goto err_too_many
    $P4 = result[0]

    .return ($P4)
  err_too_many:
    print "An 'oexpr' node can only have one child.\n"
    end
}

transform past (PunieGrammar::variable) :language('PIR') {
    .local pmc result
    result = new 'PAST::Var'
    result.'init'('node'=>node, 'viviself'=>'.Undef')
    .local string sigil
    .local string name
    sigil = node['sigil']
    $S1 = node['word']
    name = sigil . $S1

    result.'name'(name)
    result.'scope'('global')

#    unless sigil == '$' goto not_scalar
#      result.'vartype'('scalar')
  not_scalar:

    .return (result)
}

transform past (PunieGrammar::number) :language('PIR') {
    .local pmc result
    result = new 'PAST::Val'
    result.'init'('node'=>node, 'vtype'=>'.Float', 'name'=>node, 'ctype'=>'n+')
    .return (result)
}

transform past (PunieGrammar::integer) :language('PIR') {
    .local pmc result
    result = new 'PAST::Val'
    result.'init'('node'=>node, 'vtype'=>'.Integer', 'name'=>node, 'ctype'=>'i+')
    .return (result)
}

transform past (PunieGrammar::stringdouble) :language('PIR') {
    .local pmc result
    result = new 'PAST::Val'


    .local string value
    # Check if this is a string match
    $I0 = defined node["PGE::Text::bracketed"]
    if $I0 goto bracketed_value
    value = node
    goto no_bracketed_value
  bracketed_value:
    value = node["PGE::Text::bracketed"]
  no_bracketed_value:

    # NOTE: Will have to come back and handle interpolation properly.
    # Currently have no way to distinguish between single quoted and
    # double quoted strings.
    result.'init'('node'=>node, 'vtype'=>'.String', 'name'=>value)
    .return (result)
}

transform past (PunieGrammar::stringsingle) :language('PIR') {
    .local pmc result
    result = new 'PAST::Val'

    .local string value
    # Check if this is a string match
    $I0 = defined node["PGE::Text::bracketed"]
    if $I0 goto bracketed_value
    value = node
    goto no_bracketed_value
  bracketed_value:
    value = node["PGE::Text::bracketed"]
  no_bracketed_value:

    result.'init'('node'=>node, 'vtype'=>'.String', 'name'=>value)
    .return (result)
}

transform past (PunieGrammar::subroutine) :language('PIR') {
    .local pmc result
    result = new 'PAST::Block'

    .local string name
    name = node['word']

    result.'init'('node'=>node, 'name'=> name, 'blocktype'=>'sub')

    .local pmc child
    $P1 = node['block'; 'lineseq']
    child = tree.'get'('past', $P1, 'PunieGrammar::lineseq')
    result.'push'(child)

    .return(result)
}

transform past (PunieGrammar::subcall) :language('PIR') {
    .local pmc result
    result = new 'PAST::Op'
    $S1 = node['word']
    result.'name'($S1)

    $P1 = node['expr']
    if null $P1 goto return_result
    $P2 = tree.get('past', $P1, 'PunieGrammar::expr')
    $S0 = $P2.'name'()
    if $S0 == 'infix:,' goto flatten_comma
    result.'init'($P2, 'node'=>node)
    goto return_result
  flatten_comma:
    $P3 = $P2.'get_array'()
    result.'init'($P3 :flat, 'node'=>node)
  return_result:

    .return (result)
}

# The following rules are for the results of the operator precedence
# parser. These operate very differently than the standard grammar
# rules, because they give the node type in a "type" hash key inside the
# node, instead of storing the node as the value of a hash key that
# is their type.

transform past (expr) :language('PIR') {
    .local string type
    type = node["type"]
    unless node goto error_no_node
    if type == 'term:' goto transform_term
      # else
      $P1 = tree.get('op', node, 'expr')
      .return($P1)
    transform_term:
      $P1 = tree.get('term', node, 'expr')
      .return($P1)
    error_no_node:
      print "error: no node\n"
      end
}

transform op (expr) :language('PIR') {
    .local pmc result
    result = new 'PAST::Op'
    .local string type
    type = node["type"]

    # XXX Slurped up directly from Perl 6 implementation, refactor later
    .local pmc optable, optok
    .local string pasttype, pirop
    .local int islvalue
    optable = get_hll_global [ 'PunieGrammar' ], '$optable'
    optok = optable[type]  
    pasttype = optok['past']
    pirop = optok['pirop'] 
    islvalue = optok['lvalue']

    result.'init'('node'=>node, 'name'=>type, 'pasttype'=>pasttype, 'pirop'=>pirop, 'islvalue'=>islvalue)

    $P1 = node.'get_array'()
    .local pmc iter
    iter = new Iterator, $P1    # setup iterator for node
    set iter, 0 # reset iterator, begin at start
  iter_loop:
    unless iter, iter_end         # while (entries) ...
      shift $P2, iter            # get entry
      $P3 = tree.get('past', $P2, 'expr')
      if null $P3 goto iter_loop
      result.'push'($P3)
      goto iter_loop
  iter_end:

    .return (result)
}

transform term (expr) :language('PIR') {
    .local pmc result
    .local pmc children
    children = new .ResizablePMCArray
    $P1 = node.get_hash()
    $P0 = new Iterator, $P1    # setup iterator for node
    set $P0, 0 # reset iterator, begin at start
  iter_loop:
    unless $P0, iter_end         # while (entries) ...
      shift $S2, $P0             # get key for next entry
      # skip 'type' keys added by the operator precedence parser
      if $S2 == 'type' goto iter_loop 
      $P2 = $P0[$S2]      # get entry at current key
      $S2 = concat 'PunieGrammar::', $S2
      $P3 = tree.get('past', $P2, $S2)
      if null $P3 goto iter_loop
      push children, $P3
      goto iter_loop
  iter_end:

    $I0 = elements children
    unless $I0 == 1 goto err_too_many
    result = children[0]
    .return (result)

  err_too_many:
    print "error: Currently, 'term' nodes should have only one child.\n"
    end
}

=head1 LICENSE

Copyright (C) 2006-2007, The Perl Foundation.

This is free software; you may redistribute it and/or modify
it under the same terms as Parrot.

=head1 AUTHOR

Allison Randal <allison@perl.org>

=cut
