=head1 NAME

OSTGrammar -- A grammar for transforming an abstract syntax tree to an
opcode syntax tree.

=head1 SYNOPSYS

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

      # Construct the "OST"
      load_bytecode "languages/punie/lib/POST.pir"
      .local pmc ostbuilder
      ostbuilder = grammar.apply(match)
      .local pmc ost
      ost = ostbuilder.get('result')

      end
  .end

=head1 DESCRIPTION


=cut

grammar OSTGrammar is TGE::Grammar;

transform result (ROOT) :language('PIR') {
    .local pmc result
    result = new 'POST::Ops'
    result.'clone'(node)
    .local pmc mainsub
    mainsub = new 'POST::Sub'
    mainsub.'clone'(node)
    mainsub.'name'('_main')
    mainsub.'attribute'('main')

    .local pmc loadop
    loadop = new 'POST::Call'
    loadop.'clone'(node)
    loadop.'name'('__load')
    mainsub.'add_child'(loadop)

    .local pmc iter
    iter = node.'child_iter'()
  iter_loop:
    unless iter, iter_end         # while (entries) ...
      shift $P2, iter
      $P3 = tree.get('result', $P2)
      mainsub.'add_child'($P3)
      goto iter_loop
  iter_end:

    result.'add_child'(mainsub)

    $P4 = tree.'get'('symbols', node, 'ROOT')

    result.'add_child'($P4)
    .return (result)
}

transform symbols (ROOT) :language('PIR') {
    .local pmc loadsub
    loadsub = new 'POST::Sub'
    loadsub.'clone'(node)
    loadsub.'name'('__load')
    loadsub.'attribute'('load')

    # Add an op to load the library of builtin operators
    .local pmc builtinsop
    builtinsop = new 'POST::Op'
    builtinsop.'clone'(node)
    builtinsop.'op'('load_bytecode')
    $P3 = new 'POST::Val'
    $P3.'value'('languages/punie/lib/PunieBuiltins.pir')
    $P3.'valtype'('strq')
    builtinsop.'add_child'($P3)
    loadsub.'add_child'(builtinsop)

    .local pmc temp_var
    .local pmc iter
    iter = self.'symbol_iter'()
  iter_loop:
    unless iter, iter_end # while (entries) ...
      shift $S2, iter
      $P1 = iter[$S2]

      # Create a temporary variable from the entry for this symbol
      temp_var = clone $P1
      $P2 = temp_var.new_dummy()
      loadsub.'add_child'($P2)

      # And put it in the global symbol table
      .local pmc storeop
      storeop = new 'POST::Op'
      storeop.'clone'(node)
      storeop.'op'('store_global')

      $P3 = new 'POST::Val'
      $S4 = temp_var.'hllname'()
      $P3.'value'($S4)
      $P3.'valtype'('strq')
      storeop.'add_child'($P3)

      $P4 = clone temp_var
      storeop.'add_child'($P4)

      loadsub.'add_child'(storeop)

      goto iter_loop
  iter_end:
    .return (loadsub)
}

transform result (PAST::Stmts) :language('PIR') {
    .local pmc result
    result = new 'POST::Ops'
    result.'clone'(node)

    .local pmc iter
    iter = node.'child_iter'()
  iter_loop:
    unless iter, iter_end         # while (entries) ...
      shift $P2, iter
      $P3 = tree.get('result', $P2)
      result.'add_child'($P3)
      goto iter_loop
  iter_end:
    .return (result)
}

transform result (PAST::Stmt) :language('PIR') {
    $P1 = node.children()
    $P2 = $P1[0]
    $P3 = tree.get('result', $P2)
    .return ($P3)
}

transform result (PAST::Exp) :language('PIR') {
    $P1 = node.children()
    $P2 = $P1[0]
    $P3 = tree.get('result', $P2)
    .return ($P3)
}

transform result (PAST::Op) :language('PIR') {
     .local string opname
     opname = node.op()

    if opname == 'infix:&&' goto and_op
    goto not_and_op
  and_op:
      $P4 = tree.get('and_shortcircuit', node)
      .return ($P4)
  not_and_op:

    if opname == 'infix:||' goto or_op
    goto not_or_op
  or_op:
      $P4 = tree.get('or_shortcircuit', node)
      .return ($P4)
  not_or_op:

    if opname == 'infix:=' goto not_infix
    $S1 = substr opname, 0, 5
    if $S1 == 'infix' goto infix
    goto not_infix
  infix:
      $P4 = tree.get('infix', node)
      .return ($P4)
  not_infix:
    if opname == 'print' goto print_op
    goto not_print_op
  print_op:
      $P4 = tree.get('print_op', node)
      .return ($P4)
  not_print_op:


    if opname == 'if' goto conditional
    if opname == 'unless' goto conditional
    if opname == 'elsif' goto conditional
    goto not_conditional
  conditional:
      $P4 = tree.get('conditional', node)
      .return ($P4)
  not_conditional:
    if opname == 'else' goto nullop
    goto not_nullop
  nullop:
      $P4 = tree.get('nullop', node)
      .return ($P4)
  not_nullop:

    # Lookup operator name to see if there is a direct PIR translation
    # for it.
    .local pmc oplookup
    oplookup = find_global 'PunieOpLookup', 'lookup'
    $S1 = oplookup(opname)
    unless $S1 goto no_trans
    opname = $S1
  no_trans:

    # Iterate through the children of the node, and generate the result
    # for each child.
    .local pmc childop
    childop = new 'POST::Op'
    childop.'clone'(node)
    childop.op(opname)

    # Create a node to contain the generated ops.
    .local pmc newops
    newops = new 'POST::Ops'
    .local pmc iter
    iter = node.'child_iter'()
  iter_loop:
    unless iter, iter_end         # while (entries) ...
      shift $P2, iter
      $P3 = tree.get('result', $P2)
      $S1 = typeof $P3
      # Does the argument have setup code?
      unless $S1 == 'POST::Ops' goto simple_result
        $P1 = $P3.tmpvar()
      if null $P1 goto simple_result
        childop.'add_child'($P1)
        newops.'add_child'($P3)
        goto iter_loop
      simple_result:
        childop.'add_child'($P3)
      goto iter_loop
  iter_end:

    # In the context of an Op node, collapse a child comma op, so the
    # child's children become the children of the current node.
#    $I0 = elements newchildren
#    if $I0 > 1 goto no_munge
#      $P5 = newchildren[0]
#      $S3 = typeof $P5
#    unless $S3 == 'POST::Op' goto no_munge
#      $S4 = $P5.op()
#    unless $S4 == 'O_COMMA' goto no_munge
#      newchildren = $P5.children()

  no_munge:
    
    # Check if we had setup code for one of the children. If so, bundle
    # it up in an Ops node.
    $I0 = elements newops
    if $I0 > 0 goto bundle_ops
      .return (childop)
  bundle_ops:
    newops.'add_child'(childop)

    .return (newops)
}

transform infix (PAST::Op) :language('PIR') {
    # Create the node to contain all the generated ops.
    .local pmc newops
    newops = new 'POST::Ops'
    newops.'clone'(node)

    # Create the op for the current node
    .local pmc oplookup
    .local string opname
    .local pmc childop
    oplookup = find_global 'PunieOpLookup', 'lookup'
    $S1 = node.op()
    opname = oplookup($S1)
    if opname goto op_is_simple
      childop = new 'POST::Call'
      $S2 = substr $S1, 6
      childop.'name'($S2)
      goto end_op_create
  op_is_simple:
      childop = new 'POST::Op'
      childop.'op'(opname)
  end_op_create:

    # Create a temporary variable
    .local pmc temp_var
    temp_var = new 'POST::Var'
    temp_var.'clone'(node)
    $P8 = temp_var.new_dummy()
    newops.'add_child'($P8)

    # Add the temporary variable as the destination register of the op
    childop.'add_child'(temp_var)

    .local pmc iter
    iter = node.'child_iter'()
  iter_loop:
    unless iter, iter_end         # while (entries) ...
      shift $P2, iter
      $P3 = tree.get('result', $P2)
      $S1 = typeof $P3
      if $S1 == 'POST::Ops' goto complex_result # the argument has setup
      if $S1 == 'POST::Val' goto create_tmp # the argument needs setup
        childop.'add_child'($P3)
        goto iter_loop
      create_tmp:
        # Create a temp variable
        $P4 = new 'POST::Var'
        $P4.'clone'(node)
        $P5 = $P4.new_dummy()
        newops.'add_child'($P5)
        childop.'add_child'($P4)
        # Assign the value node to the variable
        $P7 = new 'POST::Op'
        $P7.'clone'(node)
        $P7.'op'('set')
        $P7.'add_child'($P4) # the first argument is the variable
        $P7.'add_child'($P3) # the second argument is the value
        newops.'add_child'($P7)
        goto iter_loop
      complex_result:
        $P1 = $P3.tmpvar()
        childop.'add_child'($P1)
        newops.'add_child'($P3)
      goto iter_loop
  iter_end:

    # The childop goes after all the setup code.
    newops.'add_child'(childop)

    newops.'tmpvar'(temp_var)

    .return (newops)
}

transform conditional (PAST::Op) :language('PIR') {
    # Create the node to contain all the generated ops.
    .local pmc newops
    newops = new 'POST::Ops'

    .local string opname
    .local pmc oplookup
    oplookup = find_global 'PunieOpLookup', 'lookup'
    $S1 = node.op()
    opname = oplookup($S1)
    .local pmc childop
    childop = new 'POST::Op'
    childop.'clone'(node)
    childop.'op'(opname)

    # Set up to handle children of Op node.
    .local pmc iter
    iter = node.'child_iter'()

    # First, handle the condition, which may be a series of statements
    # resulting in a single value.
    shift $P2, iter
    $P3 = tree.get('result', $P2)
    $S1 = typeof $P3
    if $S1 == 'POST::Ops' goto complex_result # the argument has setup
      childop.'add_child'($P3)
      goto end_condition
    complex_result:
      $P1 = $P3.'tmpvar'()
      childop.'add_child'($P1)
      newops.'add_child'($P3)
  end_condition:

    # Second, create the branching op. The first child is the result of
    # the condition, the second argument is a label.
    .local pmc falselabel
    falselabel = new 'POST::Label'
    falselabel.'new_dummy'('false')
    childop.'add_child'(falselabel)

    newops.'add_child'(childop)

    # Next handle the conditional body
    shift $P2, iter
    $P3 = tree.'get'('result', $P2)
    newops.'add_child'($P3)

    # When conditional body is selected, skip over any else/elsif
    .local pmc endlabel
    endlabel = new 'POST::Label'
    endlabel.'new_dummy'('endcond')
    $P4 = new 'POST::Op'
    $P4.'clone'(node)
    $P4.'op'('goto')
    $P4.'add_child'(endlabel)
    newops.'add_child'($P4)

    # Destination for the branching op
    $P6 = clone falselabel
    $P6.dest(1)
    newops.'add_child'($P6)

    # Finally handle the else/elsif block if it exists
    unless iter goto no_else_elsif
    shift $P2, iter
    $P3 = tree.'get'('result', $P2)
    newops.'add_child'($P3)
  no_else_elsif:

    # Destination for the end of the conditional
    $P6 = clone endlabel
    $P6.dest(1)
    newops.'add_child'($P6)

    .return (newops)
}

transform and_shortcircuit (PAST::Op) :language('PIR') {
    # Create the node to contain all the generated ops.
    .local pmc newops
    newops = new 'POST::Ops'

    .local string opname
    opname = node.op()

    # Create a temporary variable for the return value of the whole
    # expression
    .local pmc temp_var
    temp_var = new 'POST::Var'
    temp_var.'clone'(node)
    $P1 = temp_var.new_dummy()
    newops.'add_child'($P1)

    # The default return value of an AND op is an empty string (false)
    .local pmc defaultval
    defaultval = new 'POST::Val'
    defaultval.'value'('')
    defaultval.'valtype'('strq')

    .local pmc setdefault
    setdefault = new 'POST::Op'
    setdefault.'op'('set')
    $P2 = clone temp_var
    setdefault.'add_child'($P2)
    setdefault.'add_child'(defaultval)
    newops.'add_child'(setdefault)

    # Create a label as a destination for the end of the short-circuit
    # operator
    .local pmc falselabel
    falselabel = new 'POST::Label'
    falselabel.'new_dummy'('false')


    # Set up to handle children of Op node.
    .local pmc iter
    iter = node.'child_iter'()
    .local pmc test_arg
    .local pmc arg
    .local pmc arg_result
    .local pmc settrue

  iter_loop:
    unless iter goto iter_end         # while (entries) ...

    # Create a branching op for each argument to AND. The first child is
    # the result of evaluating the argument, the second child is a
    # label.
    test_arg = new 'POST::Op'
    test_arg.'clone'(node)
    test_arg.'op'('unless')

    # Handle an argument of AND, which may be a series of expressions
    # resulting in a single value.
    shift $P3, iter
    arg = tree.get('result', $P3)
    $S1 = typeof arg
    if $S1 == 'POST::Ops' goto complex_result # the argument has setup
      arg_result = arg
      goto end_condition
    complex_result:
      newops.'add_child'(arg)
      arg_result = arg.'tmpvar'()
  end_condition:

      test_arg.'add_child'(arg_result)

    # Create the second child of the branching op: a lable to jump to
    $P5 = clone falselabel
    test_arg.'add_child'($P5)
    newops.'add_child'(test_arg)

    goto iter_loop
  iter_end:

    # When both arguments of AND are true, return the value of the
    # second
    .local pmc settrue
    settrue = new 'POST::Op'
    settrue.'op'('set')
    $P1 = clone temp_var
    settrue.'add_child'($P1)
    settrue.'add_child'(arg_result)

    newops.'add_child'(settrue)

    # Destination for the branching op
    $P6 = clone falselabel
    $P6.dest(1)
    newops.'add_child'($P6)

    newops.'tmpvar'(temp_var)
    .return (newops)
}

transform or_shortcircuit (PAST::Op) :language('PIR') {
    # Create the node to contain all the generated ops.
    .local pmc newops
    newops = new 'POST::Ops'

    .local string opname
    opname = node.op()

    # Create a temporary variable for the return value of the whole
    # expression
    .local pmc temp_var
    temp_var = new 'POST::Var'
    temp_var.'clone'(node)
    $P1 = temp_var.new_dummy()
    newops.'add_child'($P1)


    # Create a label as a destination for the end of the short-circuit
    # operator
    .local pmc endlabel
    endlabel = new 'POST::Label'
    endlabel.'new_dummy'('endshortcircuit')

    # Set up to handle children of Op node.
    .local pmc iter
    iter = node.'child_iter'()
    .local pmc test_arg
    .local pmc arg
    .local pmc arg_result
    .local pmc settrue
    .local pmc arg_label

  iter_loop:
    unless iter goto iter_end         # while (entries) ...

    # Create a branching op for each argument to OR. The first child
    # is the result of evaluating the argument, the second child is a
    # label.
    test_arg = new 'POST::Op'
    test_arg.'clone'(node)
    test_arg.'op'('unless')

    # Handle an argument to OR, which may be a series of expressions
    # resulting in a single value.
    shift $P3, iter
    arg = tree.get('result', $P3)
    $S1 = typeof arg
    if $S1 == 'POST::Ops' goto complex_result # the argument has setup
      arg_result = arg
      goto end_condition
    complex_result:
      newops.'add_child'(arg)
      arg_result = arg.'tmpvar'()
  end_condition:

    test_arg.'add_child'(arg_result)

    # Create the second child of the branching op: a lable to jump to
    arg_label = new 'POST::Label'
    arg_label.'new_dummy'('arg_false')
    test_arg.'add_child'(arg_label)
    newops.'add_child'(test_arg)

    # When an argument of OR is true, return it
    settrue = new 'POST::Op'
    settrue.'op'('set')
    $P1 = clone temp_var
    settrue.'add_child'($P1)
    settrue.'add_child'(arg_result)
    newops.'add_child'(settrue)

    # Then skip to the end of the OR op
    $P4 = new 'POST::Op'
    $P4.'clone'(node)
    $P4.'op'('goto')
    $P4.'add_child'(endlabel)
    newops.'add_child'($P4)

    # The destination when the argument is false
    $P5 = clone arg_label
    $P5.'dest'(1)
    newops.'add_child'($P5)

    goto iter_loop
  iter_end:

    # The default return value of an OR op is an empty string (false)
    .local pmc defaultval
    defaultval = new 'POST::Val'
    defaultval.'value'('')
    defaultval.'valtype'('strq')

    .local pmc setdefault
    setdefault = new 'POST::Op'
    setdefault.'op'('set')
    $P2 = clone temp_var
    setdefault.'add_child'($P2)
    setdefault.'add_child'(defaultval)
    newops.'add_child'(setdefault)

    # Destination for the end of the op
    $P6 = clone endlabel
    $P6.dest(1)
    newops.'add_child'($P6)

    newops.'tmpvar'(temp_var)
    .return (newops)
}

transform nullop (PAST::Op) :language('PIR') {
    $P1 = node.children()
    $P2 = $P1[0]
    $P3 = tree.get('result', $P2)
    .return ($P3)
}

transform print_op (PAST::Op) :language('PIR') {
    # This fairly lengthy bit collapses comma ops, by traversing down
    # the tree and yanking up the children of the comma op to the
    # current level.
    $P1 = node.children()
    $I0 = elements node 
    if $I0 > 1 goto no_munge
      $P2 = $P1[0]
      $S3 = typeof $P2
    unless $S3 == 'PAST::Exp' goto no_munge
      $P3 = $P2.children()
      $P4 = $P3[0]
      $S3 = typeof $P4
    unless $S3 == 'PAST::Op' goto no_munge
      $S4 = $P4.op()
    unless $S4 == 'O_COMMA' goto no_munge
      $P1 = $P4.'child_iter'()
  no_munge:
     # Done collapsing comma ops

    # Create a node to contain the generated ops.
    .local pmc newops
    newops = new 'POST::Ops'
    newops.'clone'(node)

    # Store the opname for later reuse
    .local string opname
    opname = node.op()

    # Iterate through the children of the node, and generate the result
    # for each child.
    .local pmc iter
    iter = new Iterator, $P1
    iter = 0
  iter_loop:
    unless iter goto iter_end         # while (entries) ...
      shift $P2, iter
      $P3 = tree.get('result', $P2)
      $S1 = typeof $P3
      $P5 = new 'POST::Op'
      $P5.'clone'(node)
      $P5.'op'(opname)
      $S1 = typeof $P3
      if $S1 == 'POST::Ops' goto complex_result # the argument has setup
	# The default case, create a new 'print' op node with the child
	# as an argument and push it on the list of new ops.
        $P5.'add_child'($P3)
        newops.'add_child'($P5)
        goto iter_loop
      complex_result:
	# The complex case, retrieve the temp variable from the Ops
	# node, and push the Ops node on the list of new ops. Then
	# create a new 'print' op node with the temp variable as an
	# argument and push it on the list of new ops.
        $P1 = $P3.tmpvar()
        newops.'add_child'($P3)
        $P5.'add_child'($P1)
        newops.'add_child'($P5)
      goto iter_loop
  iter_end:

    .return (newops)
}

transform result (PAST::Val) :language('PIR') {
    .local pmc result
    result = new 'POST::Val'
    result.'clone'(node)
    $P1 = node.'value'()
    result.'value'($P1)
    $P2 = node.'valtype'()
    result.'valtype'($P2)
    .return (result)
}

transform result (PAST::Var) :language('PIR') {
    # The node to contain all the generated ops for a variable access
    .local pmc newops
    newops = new 'POST::Ops'
    newops.'clone'(node)

    # The PIR variable that corresponds to the HLL variable for this
    # piece of code
    .local pmc hllname
    hllname = node.'varname'()

    .local pmc newvar
    newvar = new 'POST::Var'
    newvar.'clone'(node)
    newvar.'new_temp'()
    newvar.'hllname'(hllname)
    $P1 = node.'vartype'()
    newvar.'vartype'($P1)
    $P1 = node.'scope'()
    newvar.'scope'($P1)

    # Let the grammar know that it needs to add initialization code for
    # this HLL variable in the global symbol table
    self.'add_symbol'(hllname, newvar)

    # A string constant argument of the name of the variable to
    # lookup in the global symbol table
    .local pmc name
    name = node.'varname'()
    .local pmc namenode
    namenode = new 'POST::Val'
    namenode.'clone'(node)
    namenode.'value'(name)
    namenode.'valtype'('strq')

    # The operation to lookup the variable
    .local pmc findop
    findop = new 'POST::Op'
    findop.'clone'(node)
    findop.'op'('find_global')
    findop.'add_child'(newvar)
    findop.'add_child'(namenode)
    newops.'add_child'(findop)

    # The final resulting variable from the operations
    $P1 = clone newvar
    newops.'tmpvar'($P1)

    .return (newops)
}

=head1 LICENSE

Copyright (C) 2006, 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
