
# Naming Rules
# ------------
# opt_XXX     : 0 or 1.
# XXXs        : 1 or more.
# opt_XXXs    : 0 or more.
# direct_XXX  : not preceded by a pointer.
# XXX_postfix : trailing modifiers such as [], (), ->.
# def         : e.g. static int a[3], b, c[2][2] ;
# decl        : e.g. a[3], b, c[2][2]

class Goose::Cparser

  preclow
    nonassoc prec_none
    left ',' comma_op
    right '*=' '/=' '%=' '+=' '-=' '<<=' '>>=' '^=' '&=' '|=' '='
    right '?' ':' comma_sep
    left '||'
    left '&&'
    left '|' or_op
    left '^' xor_op
    left '&' and_op
    left '==' '!=' eq_op
    left '<=' '>=' '<' '>' rel_op
    left '<<' '>>' shift_op
    left '+' '-' add_op
    left '*' '/' '%' mult_op
    right cast_exp
    nonassoc '!' "~" SIZEOF '++' '--' unary_exp
    left '->' '.' '(' '['
    nonassoc prec_high1
  prechigh

rule

  program: defs

  defs: def { result = NNode.reduce(:defs, val[0].value, val[0]) }
       | defs def {
            result.push(val[1])
            result.value = "#{val[0].value} #{val[1].value}"
         }

  def: defun | defvar | gdefun

  # function definition.
  defun: opt_def_specs opt_gcc_attribs decl opt_KnRdefvars compound_stmt {
           result = DefunNode.reduce(:defun, val[2].value, val[0], val[1], val[2], val[3], val[4])
         }
       | opt_def_specs decl opt_KnRdefvars compound_stmt {
           result = DefunNode.reduce(:defun, val[1].value, val[0], val[1], val[2], val[3])
       }

  opt_KnRdefvars: =prec_none { result = StubNode.new(@lineno, @filename) } # just for ancient K&R style.
             | defvars

  defvars: defvar { result = NNode.reduce(:defvars, val[0].value, val[0]) }
         | defvars defvar {
             result.push(val[1])
             result.value = "#{val[0].value} #{val[1].value}"
           }

  # storage definition & function prototype declaration.
  defvar: opt_def_specs init_decls ';' {
            result = DefvarNode.reduce(:defvar, val[1].value.join(','), val[0], val[1])

            # generate a new type and categorize it into MY_TYPE.
            if @type_defining
              val[1].value.each {|v|
                @reserved[v] = :MY_TYPE
                p "defined a new type: #{v} at #{val[1].lineno}" if @verbose > 4
              }
              @type_defining = false
              p "Exit from type-defining mode." if @verbose > 4
            end
          }
        | def_specs ';' # this is mainly for enum.
                        # you may want to write enum {A, B, C} d;
                        # without d, for instance.

  opt_def_specs: =prec_none { result = StubNode.new(@lineno, @filename) }
                | def_specs

  def_specs: def_spec { result = DefSpecsNode.reduce(:def_specs, val[0].value, val[0]) }
            | def_specs def_spec {
                result.push(val[1])
                result.value = "#{val[0].value} #{val[1].value}"
              }

  def_spec: storageclass_spec
           | type_spec
           | type_qual

  storageclass_spec: TYPEDEF {
                       @type_defining = true
                       p "Entered to type-defining mode." if @verbose > 4
                     }
                   | EXTERN | STATIC | AUTO | REGISTER

  init_decls: init_decl { result = DeclsNode.reduce(:init_decls, [val[0].value], val[0]) }
            | init_decls ',' init_decl {
                result.push(val[2])
                result.value.push(val[2].value)
              }

  init_decl: decl '=' initializer {
               result = InitDeclNode.reduce(:init_decl, "#{val[0].value}=#{val[2].value}", val[0], val[2])
             }
           | decl

  decl: pointer direct_decl opt_gcc_attribs =unary_exp {
          result = NNode.reduce(:decl, val[1].value, val[0], val[1])
        }
      | direct_decl opt_gcc_attribs {
          result = val[0]
        }

  direct_decl: IDENT {
                 result = VariableNode.new(:variable, val[0].value, val[0].lineno, val[0].filename)
               }
             | '(' decl ')' { result = val[1] }
             | direct_array_decl
             | direct_func_decl
             | direct_KnRfunc_decl

  direct_array_decl: direct_decl '[' opt_const_exp ']' {
                       result = NNode.reduce(:direct_array_decl, "#{val[0].value}", val[0], val[1], val[2], val[3])
                     }

  direct_func_decl: direct_decl '(' opt_param_defs_variablelen ')' {
                      result = NNode.reduce(:direct_func_decl, "#{val[0].value}", val[0], val[1], val[2], val[3])
                    }

  direct_KnRfunc_decl: direct_decl '(' idents ')' {
                      result = NNode.reduce(:direct_KnRfunc_decl, "#{val[0].value}", val[0], val[1], val[2], val[3])
                    }

  idents: IDENT { result = NNode.reduce(:idents, val[0], val[0]) }
        | idents ',' IDENT {
            result.push(val[2])
            result.value = "#{val[0].value},#{val[2].value}"
          }

  opt_pointer: =prec_none { result = StubNode.new(@lineno, @filename) }
             | pointer

  pointer: '*' opt_type_quals opt_pointer =unary_exp {
             result = NNode.reduce(:pointer, "*#{val[1].value}#{val[2].value}", val[1], val[2])
           }

  opt_type_quals: =prec_none { result = StubNode.new(@lineno, @filename) }
                | type_quals

  type_quals: type_qual { result = NNode.reduce(:type_qual, val[0].value, val[0]) }
            | type_quals type_qual {
                result.push(val[1])
                result.value = "#{val[0].value} #{val[1].value}"
              }

  type_qual: CONST | VOLATILE | GCC_CONST | GCC_RESTRICT

  opt_const_exp : =prec_none { result = StubNode.new(@lineno, @filename) }
                | const_exp

  opt_param_defs_variablelen: =prec_none { result = StubNode.new(@lineno, @filename) }
                            | param_defs_variablelen

  param_defs_variablelen: param_defs ',' '...'
        | param_defs

# ??? this should be correct. the sample has a bug, I guess.
# param_defs should not be empty.
# otherwise parame_defs can match ',...',
# which is not ANSI compliant.
  param_defs: param_def { result = ParamDefsNode.reduce(:param_defs, val[0].value, val[0]) }
            | param_defs ',' param_def {
                result.push(val[2])
                result.value = "#{val[0].value},#{val[2].value}"
              }

  param_def: def_specs decl { result = ParamDefNode.reduce(:param_def, "#{val[0].value} #{val[1].value}", val[0], val[1]) }
           | def_specs opt_abs_decl { result = ParamDefNode.reduce(:param_def, "#{val[0].value}#{val[1].value}", val[0], val[1]) }

  opt_abs_decl: =prec_none { result = StubNode.new(@lineno, @filename) }
              | abs_decl

  abs_decl: pointer opt_direct_abs_decl { result = NNode.reduce(:abs_decl, "#{val[0].value}#{val[1].value}", val[0], val[1]) }
          | direct_abs_decl

  opt_direct_abs_decl: =prec_none { result = StubNode.new(@lineno, @filename) }
                     | direct_abs_decl

  direct_abs_decl: direct_abs_decl_body opt_direct_abs_decl_postfixs {
                     result = NNode.reduce(:direct_abs_decl, "#{val[0].value}#{val[1].value}", val[0], val[1])
                   }

  direct_abs_decl_body: '(' abs_decl ')' { result = val[1] }
                      | direct_abs_decl_postfix

  opt_direct_abs_decl_postfixs: =prec_none { result = StubNode.new(@lineno, @filename) }
                              | direct_abs_decl_postfixs

  direct_abs_decl_postfixs: direct_abs_decl_postfix {
                              result = NNode.reduce(:direct_abs_decl_postfixs, val[0].value, val[0])
                            }
                          | direct_abs_decl_postfixs direct_abs_decl_postfix{
                              result.push(val[1])
                              result.value = "#{val[0].value}#{val[1].value}"
                            }

  direct_abs_decl_postfix: '[' const_exp ']' {
                             result = NNode.reduce(:direct_abs_decl_postfix, "[#{val[1].value}]", val[0], val[1], val[2])
                           }
                         | '[' ']' {
                             result = NNode.reduce(:direct_abs_decl_postfix, "[]", val[0], val[1])
                           }
                         | '(' opt_param_defs_variablelen ')' {
                             result = NNode.reduce(:direct_abs_decl_postfix, "(#{val[0].value})", val[0], val[1], val[2])
                           }

  initializer: initializer_re

  initializer_re: assign_exp
             | '{' initializers opt_comma '}'{
                  result = InitializerNode.reduce(:initializer_re, "{#{val[1].value}#{val[2].value}}", val[1], val[2])
               }

  opt_comma: { result = StubNode.new(@lineno, @filename) }
           | ',' =comma_sep

  initializers: initializer_re { result = InitializersNode.reduce(:initializers, val[0].value, val[0]) }
              | initializers ',' initializer_re =comma_op {
                  result.push(val[2])
                  result.value = "#{val[0].value},#{val[2].value}"
                }

  stmt: exp_stmt
      | case_stmt
      | default_stmt
      | labeled_stmt
      | compound_stmt
      | if_stmt
      | iteration_stmt
      | switch_stmt
      | break_stmt
      | continue_stmt
      | return_stmt
      | goto_stmt
      | null_stmt

  exp_stmt: exp ';' {
              result = ExpStmtNode.reduce(:exp_stmt, "#{val[0].value};", val[0])
              result.volatile = true
            }

  labeled_stmt: IDENT ':' stmt {
                  result = NNode.reduce(:labeled_stmt, "#{val[0].value}:#{val[2].value}", val[0], val[2])
                }

  case_stmt: CASE const_exp ':' stmt {
               result = NNode.reduce(:case_label, "case #{val[1].value}:#{val[3].value}", val[1], val[3])
             }

  default_stmt: DEFAULT ':' stmt {
                  result = NNode.reduce(:default_label, "#{val[0].value}:#{val[2].value}", val[0], val[2])
                }

  iteration_stmt: dowhile_stmt
                | while_stmt
                | for_stmt
                | gfor_stmt

  switch_stmt: SWITCH '(' exp ')' stmt {
                 # wrap a single-statement
                 if val[4].symbol == :compound_stmt
                   csnode = val[4]
                 else
                   csnode = NNode.reduce(:compound_stmt, "{#{val[4].value}}", val[4])
                 end
                 csnode.volatile = true
                 result = SwitchNode.reduce(:switch_stmt, "switch(#{val[2].value})#{csnode.value}", val[2], csnode)
               }

  break_stmt: BREAK ';'

  continue_stmt: CONTINUE ';' { result.symbol = :continue_stmt }

  return_stmt: RETURN opt_exp ';' { result = ReturnNode.reduce(:return_stmt, "return #{val[1].value}", val[0], val[1]) }

  goto_stmt: GOTO IDENT ';' { result = NNode.reduce(:goto_stmt, "goto #{val[1].value}", val[0], val[1]) }

  null_stmt: ';'

  if_stmt: IF '(' exp ')' stmt opt_else_stmt {
             # wrap a single-true statement
             if val[4].symbol == :compound_stmt
               tnode = val[4]
             else
               tnode = NNode.reduce(:compound_stmt, "{#{val[4].value}}", val[4])
             end
             tnode.volatile = true

             # wrap a single-false statement
             if val[5].symbol == :compound_stmt
               fnode = val[5]
             else
               fnode = NNode.reduce(:compound_stmt, "{#{val[5].value}}", val[5])
             end
             fnode.volatile = true

             result = IfNode.reduce(:if_stmt, "if(#{val[2].value})#{tnode.value}#{fnode.value}", val[2], tnode, fnode)
           }

  opt_else_stmt: =prec_none { result = StubNode.new(@lineno, @filename) }
               | ELSE stmt { result = val[1] ; result.value = "else#{val[1].value}"}

  dowhile_stmt: DO stmt WHILE '(' exp ')' ';' {
                  # wrap a single-statement
                  if val[1].symbol == :compound_stmt
                    csnode = val[1]
                  else
                    csnode = NNode.reduce(:compound_stmt, "{#{val[1].value}}", val[1])
                  end
                  csnode.volatile = true
                  result = DowhileNode.reduce(:dowhile_stmt, "do #{csnode.value} while (#{val[4].value})", csnode, val[4])
                }

  while_stmt: WHILE '(' exp ')' stmt {
                # wrap a single-statement
                if val[4].symbol == :compound_stmt
                  csnode = val[4]
                else
                  csnode = NNode.reduce(:compound_stmt, "{#{val[4].value}}", val[4])
                end
                csnode.volatile = true
                result = WhileNode.reduce(:while_stmt, "while(#{val[2].value})#{csnode.value}", val[2], csnode)
              }

  for_stmt: FOR '(' opt_exp ';' opt_exp ';' opt_exp ')' stmt {
              # wrap a single-statement
              if val[8].symbol == :compound_stmt
                csnode = val[8]
              else
                csnode = NNode.reduce(:compound_stmt, "{#{val[8].value}}", val[8])
              end
              csnode.volatile = true
              result = ForNode.reduce(:for_stmt, "for(#{val[2].value};#{val[4].value};#{val[6].value})#{csnode.value}",
                                      val[2], val[4], val[6], csnode)
            }

  gdefun: GFUNC_PRAGMA defun {
    result = NNode.reduce(:gdefun, "#{val[0].value} #{val[1].value}", val[0], val[1])
  }

  gfor_stmt: GFOR_PRAGMA opt_gfor_args for_stmt {
    @inGooseStatement = false
    p "Exit from Goose block." if @verbose > 2
    result = NNode.reduce(:gfor_stmt, "#{val[0].value} #{val[1].value}", val[0], val[1], val[2])
  }

  opt_gfor_args: =prec_none { result = StubNode.new(@lineno, @filename) }
               | gfor_args

  gfor_args: gfor_arg {
               result = NNode.reduce(:gfor_args, val[0].value, val[0])
               result.volatile = true
             }
           | gfor_args gfor_arg {
                      result.push(val[1])
                      result.value = "#{val[0].value} #{val[1].value}"
                    }

  gfor_arg: IDENT '(' gfor_params ')' {
              result = GforArgNode.reduce(("gfor_#{val[0].value}").intern,
                                    "#{val[0].value}(#{val[2].value})", val[0], val[2])
            }

  gfor_params: gfor_param {
                 result = GforParamsNode.reduce(:gfor_params, val[0].value, val[0])
                 result.volatile = true
               }
             | gfor_params ',' gfor_param {
                  result.push(val[2])
                  result.value = "#{val[0].value},#{val[2].value}"
                }

  gfor_param: postfix_exp

  compound_stmt: '{' opt_defvar_or_stmt_s '}' {
                   result = val[1]
                   result.symbol = :compound_stmt
                   result.value = "{#{val[1].value}}"
                 }

  opt_defvar_or_stmt_s: { result = StubNode.new(@lineno, @filename) }
                    | defvar_or_stmt_s

  defvar_or_stmt_s: defvar_or_stmt { result = NNode.reduce(:defvar_or_stmt_s, val[0].value, val[0]) }
                  | defvar_or_stmt_s defvar_or_stmt {
                      result.push(val[1])
                      result.value = "#{val[0].value}#{val[1].value}"
                    }

  defvar_or_stmt: defvar | stmt

  opt_exp: =prec_none { result = StubNode.new(@lineno, @filename) }
            | exp

  exp: assign_exp { result = AssignsNode.reduce(:exp, val[0].value, val[0]) }
     | exp ',' assign_exp {
         result.push(val[2])
         result.value = "(#{val[0].value},#{val[2].value})"
       }

  opt_assign_exps: =prec_none { result = StubNode.new(@lineno, @filename) }
                | assign_exps

  assign_exps: assign_exp { result = AssignsNode.reduce(:assign_exps, val[0].value, val[0]) }
             | assign_exps ',' assign_exp {
                 result.push(val[2])
                 result.value = "(#{val[0].value},#{val[2].value})"
               }

  assign_exp: cond_exp
           | unary_exp right_op assign_exp {
               result = AssignNode.reduce(:assign_exp, "#{val[0].value}#{val[1].value}#{val[2].value}", *val[0..2])
             }

  right_op: '*=' | '/=' | '%='
          | '+=' | '-='
          | '<<=' | '>>='
          | '^=' | '&=' | '|='
          | '='

  const_exp: cond_exp

  cond_exp: lor_exp
          | lor_exp '?' exp ':' cond_exp {
              result = TrinaryNode.reduce(:cond_exp, "#{val[0].value}?#{val[2].value}:#{val[4].value}", val[0], val[2], val[4])
            }

  lor_exp: land_exp =lor_op
          | lor_exp '||' land_exp =lor_op {
              result = BinaryNode.reduce(:lor_exp, "#{val[0].value}#{val[1].value}#{val[2].value}", *val[0..2])
            }

  land_exp: or_exp =land_op
          | land_exp '&&' or_exp =land_op {
              result = BinaryNode.reduce(:land_exp, "#{val[0].value}#{val[1].value}#{val[2].value}", *val[0..2])
            }

  or_exp: xor_exp =or_op
        | or_exp '|' xor_exp =or_op {
            result = BinaryNode.reduce(:or_exp, "#{val[0].value}#{val[1].value}#{val[2].value}", *val[0..2])
          }

  xor_exp: and_exp =xor_op
         | xor_exp '^' and_exp =xor_op {
             result = BinaryNode.reduce(:xor_exp, "#{val[0].value}#{val[1].value}#{val[2].value}", *val[0..2])
           }

  and_exp: eq_exp =and_op
         | and_exp '&' eq_exp =and_op {
             result = BinaryNode.reduce(:and_exp, "#{val[0].value}#{val[1].value}#{val[2].value}", *val[0..2])
           }

  eq_exp: rel_exp =eq_op
        | eq_exp eq_op rel_exp =eq_op {
            result = BinaryNode.reduce(:eq_exp, "#{val[0].value}#{val[1].value}#{val[2].value}", *val[0..2])
          }

  eq_op: '==' | '!='

  rel_exp: shift_exp =rel_op
         | rel_exp rel_op shift_exp =rel_op {
             result = BinaryNode.reduce(:rel_exp, "#{val[0].value}#{val[1].value}#{val[2].value}", *val[0..2])
           }

  rel_op: '<=' | '>=' | '<' | '>'

  shift_exp: add_exp =shift_op
           | shift_exp shift_op add_exp =shift_op {
               result = BinaryNode.reduce(:shift_exp, "#{val[0].value}#{val[1].value}#{val[2].value}", *val[0..2])
             }

  shift_op: '<<' | '>>' 

  add_exp: mult_exp =add_op
         | add_exp add_op mult_exp =add_op {
             result = BinaryNode.reduce(:add_exp, "#{val[0].value}#{val[1].value}#{val[2].value}", *val[0..2])
           }

  add_op: '+' | '-'

  mult_exp: cast_exp =mult_op
          | mult_exp mult_op cast_exp =mult_op {
              result = BinaryNode.reduce(:mult_exp, "#{val[0].value}#{val[1].value}#{val[2].value}", *val[0..2])
            }

  mult_op: '*' | '/' | '%'

  cast_exp: '(' type_name ')' cast_exp {
              result = NNode.reduce(:cast_exp, "(#{val[1].value})#{val[3].value}", val[1], val[3])
            }
          | unary_exp

  type_name: spec_or_quals opt_abs_decl {
               result = NNode.reduce(:type_name, "#{val[0].value} #{val[1].value}", val[0], val[1])
             }

  spec_or_quals: spec_or_qual { result = NNode.reduce(:spec_or_quals, val[0].value, val[0]) }
               | spec_or_quals spec_or_qual {
                   result.push(val[1])
                   result.value = "#{val[0].value} #{val[1].value}"
                 }

  spec_or_qual: type_spec | type_qual

  type_spec: VOID | CHAR | SHORT | INT | LONG | FLOAT | DOUBLE| SIGNED | UNSIGNED | GCC_EXT | GCC_INLINE
           | struct_spec
           | union_spec
           | enum_spec
           | mytype_spec

  mytype_spec: MY_TYPE

  unary_exp: postfix_exp
           | sizeof_exp
           | unary_minus_exp
           | unary_plus_exp
           | lneg_exp
           | neg_exp
           | pref_exp
           | pderef_exp
           | preinc_exp
           | predec_exp

  sizeof_exp: SIZEOF '(' type_name ')' { result = SizeofExpNode.reduce(:sizeof_exp, "sizeof(#{val[2].value})", val[0], val[2])}
            | SIZEOF unary_exp { result = SizeofExpNode.reduce(:sizeof_exp, "sizeof #{val[1].value}", val[0], val[1])}

  unary_minus_exp: '-' cast_exp { result = NNode.reduce(:unary_minus_exp, "#{val[0].value}#{val[1].value}", val[0], val[1])}

  unary_plus_exp: '+' cast_exp { result = NNode.reduce(:unary_plus_exp, "#{val[1].value}", val[1])}

  lneg_exp: '!' cast_exp { result = NNode.reduce(:lneg_exp, "#{val[0].value}#{val[1].value}", val[0], val[1])}

  neg_exp: '~' cast_exp { result = NNode.reduce(:neg_exp, "#{val[0].value}#{val[1].value}", val[0], val[1])}

  pref_exp: '&' cast_exp { result = PrefExpNode.reduce(:pref_exp, "#{val[0].value}#{val[1].value}", val[0], val[1])}

  pderef_exp: '*' cast_exp { result = PderefExpNode.reduce(:pderef_exp, "#{val[0].value}#{val[1].value}", val[0], val[1])}

  preinc_exp: '++' unary_exp { result = NNode.reduce(:preinc_exp, "++#{val[1].value}", val[0], val[1]) }

  predec_exp: '--' unary_exp { result = NNode.reduce(:predec_exp, "--#{val[1].value}", val[0], val[1]) }

  postfix_exp: varref_exp
             | '(' exp ')' { result = val[1] ; result.value = "(#{val[1].value})" }
             | literal
             | postinc_exp
             | postdec_exp

  varref_exp: IDENT {
                result = VarrefExpNode.reduce(:varref_exp, val[0].value, val[0])
              }
            | array_exp
            | array_range_exp
            | member_exp
            | memberp_exp
            | funcall_exp

  array_exp: postfix_exp '[' exp ']' {
               result = ArrayExpNode.reduce(:array_exp,
                                            "#{val[0].value}[#{val[2].value}]",
                                            val[0], val[1], val[2], val[3])
               }

  array_range_exp: postfix_exp '[' number '..' number ']' {
                     result = ArrayRangeExpNode.reduce(:array_range_exp,
                                                       "#{val[0].value}[#{val[1].value}..#{val[3].value}]",
                                                       val[0], val[1], val[2], val[3], val[4], val[5])
                   }

  member_exp: postfix_exp '.'  IDENT {
              result = MemberExpNode.reduce(:member_exp,
                                            "#{val[0].value}.#{val[1].value}", val[0], val[1], val[2])
                       }
  memberp_exp: postfix_exp '->' IDENT {
                 result = MemberpExpNode.reduce(:memberp_exp,
                                                "#{val[0].value}->#{val[1].value}", val[0], val[1], val[2])
                 }

  funcall_exp: postfix_exp '(' opt_assign_exps ')' {
                 result = FuncallExpNode.reduce(:funcall_exp, "(#{val[1].value})", val[0], val[1], val[2], val[3])
               }

  literal: STRING | number

  postinc_exp: postfix_exp '++' {
                 result = NNode.reduce(:postinc_exp, "#{val[0].value}#{val[1].value}", val[0] , val[1])
               }

  postdec_exp: postfix_exp '--' {
                 result = NNode.reduce(:postdec_exp, "#{val[0].value}#{val[1].value}", val[0] , val[1])
               }

  number: HEX_NUMBER | OCT_NUMBER | DEC_NUMBER

  struct_spec: STRUCT id_or_mytype '{' struct_member_defs '}' {
                 result = NNode.reduce(:union_spec, "struct #{val[1].value}{#{val[3].value}}", val[1], val[3]) }
             | STRUCT '{' struct_member_defs '}' { result = NNode.reduce(:union_spec, "struct {#{val[2].value}}", val[2]) }
             | STRUCT id_or_mytype { result = NNode.reduce(:union_spec, "struct #{val[1].value}", val[1]) }

  union_spec: UNION id_or_mytype '{' struct_member_defs '}' {
                result = NNode.reduce(:union_spec, "union #{val[1].value}{#{val[3].value}}", val[1], val[3])
              }
            | UNION '{' struct_member_defs '}' { result = NNode.reduce(:union_spec, "union {#{val[2].value}}", val[2]) }
            | UNION id_or_mytype { result = NNode.reduce(:union_spec, "union #{val[1].value}", val[1]) }

  struct_member_defs: struct_member_def { result = NNode.reduce(:struct_member_defs, val[0].value, val[0]) }
                    | struct_member_defs struct_member_def {
                        result.push(val[1])
                        result.value = "#{val[0].value} #{val[1].value}"
                      }

  struct_member_def: opt_def_specs struct_member_decls ';' {
                       result = NNode.reduce(:struct_member_def, "#{val[0].value} #{val[1].value};", val[0], val[1])
                     }
                   | def_specs ';'

  struct_member_decls: struct_member_decl { result = NNode.reduce(:struct_member_decls, val[0].value, val[0]) }
                     | struct_member_decls ',' struct_member_decl {
                         result.push(val[2])
                         result.value = "#{val[0].value},#{val[2].value}"
                       }

  struct_member_decl: decl ':' const_exp {
                        result = NNode.reduce(:struct_member_decl, "#{val[0].value}:#{val[2].value}", val[0], val[2])
                      }
                    | decl
                    | ':' const_exp { result = val[1]; result.value = ":#{val[1].value}"}

  enum_spec: ENUM id_or_mytype '{' enum_items '}' {
               result = NNode.reduce(:enum_spec, "enum #{val[1].value}{#{val[3].value}}", val[1], val[3])
             }
           | ENUM id_or_mytype '{' enum_items ',' '}' {
               result = NNode.reduce(:enum_spec, "enum #{val[1].value}{#{val[3].value},}", val[1], val[3])
             }
           | ENUM '{' enum_items '}' { result = NNode.reduce(:enum_spec, "enum {#{val[2].value}}", val[2]) }
           | ENUM '{' enum_items ',' '}' { result = NNode.reduce(:enum_spec, "enum {#{val[2].value},}", val[2]) }
           | ENUM id_or_mytype { result = NNode.reduce(:enum_spec, "enum #{val[1].value}", val[1]) }

  enum_items: enum_item { result = NNode.reduce(:enum_item, val[0].value, val[0]) }
            | enum_items ',' enum_item {
                result.push(val[2])
                result.value = "#{val[0].value},#{val[2].value}"
              }

  enum_item: IDENT '=' const_exp
           | IDENT

  id_or_mytype: IDENT | MY_TYPE

  opt_gcc_attribs: =prec_none { result = StubNode.new(@lineno, @filename) }
                 | gcc_attribs

  gcc_attribs: gcc_attrib
             | gcc_attribs gcc_attrib

  gcc_attrib: GCC_ATTR '(' '(' gcc_inner  ')' ')'

  gcc_inner: GCC_ATTR_IGNORE
           | GCC_ATTR_PACKED
           | GCC_ATTR_CDECL
           | GCC_ATTR_CONST
           | GCC_ATTR_NORETURN
           | GCC_ATTR_NOTHROW
           | GCC_ATTR_ALIGNED '(' number ')'
           | GCC_ATTR_MODE '(' IDENT ')'
           | GCC_ATTR_FORMAT '(' IDENT ',' number ',' number ')'
           | GCC_ATTR_NONNULL '(' numbers ')'
           | GCC_ATTR_PURE
           | GCC_ATTR_MALLOC
           | gcc_attr_warn { p "unknown gcc attribute: #{val[0].value}" if @verbose > 2}

  gcc_attr_warn: IDENT
               | IDENT '(' id_or_number_s ')'

  id_or_number_s: id_or_number
                | id_or_number_s ',' id_or_number

  id_or_number: INDENT
              | number

  numbers: number
         | numbers ',' number

end

---- inner

  attr_accessor :yydebug,:verbose

  def initialize
  @reserved = {
    'for'      => :FOR,
    'if'       => :IF,
    'else'     => :ELSE,
    'while'    => :WHILE,
    'switch'   => :SWITCH,
    'case'     => :CASE,
    'do'       => :DO,
    'default'  => :DEFAULT,
    'break'    => :BREAK,
    'continue' => :CONTINUE,
    'goto'     => :GOTO,
    'return'   => :RETURN,
    'extern'   => :EXTERN,
    'static'   => :STATIC,
    'auto'     => :AUTO,
    'volatile' => :VOLATILE,
    'const'    => :CONST,
    'register' => :REGISTER,
    'signed'   => :SIGNED,
    'unsigned' => :UNSIGNED,
    'char'     => :CHAR,
    'short'    => :SHORT,
    'int'      => :INT,
    'long'     => :LONG,
    'float'    => :FLOAT,
    'double'   => :DOUBLE,
    'void'     => :VOID,
    'typedef'  => :TYPEDEF,
    'enum'     => :ENUM,
    'struct'   => :STRUCT,
    'union'    => :UNION,
    'sizeof'   => :SIZEOF,
    '__extension__'  => :GCC_EXT,
    '__inline'       => :GCC_INLINE,
    '__inline__'     => :GCC_INLINE,
    '__const'        => :GCC_CONST,
    '__restrict'     => :GCC_RESTRICT,
    '__attribute__'  => :GCC_ATTR,
    '__packed__'     => :GCC_PACKED,
    '__cdecl__'      => :GCC_CDECL,
    '__const__'      => :GCC_ATTR_CONST,
    '__noreturn__'   => :GCC_ATTR_NORETURN,
    '__nothrow__'    => :GCC_ATTR_NOTHROW,
    '__aligned__'    => :GCC_ATTR_ALIGNED,
    '__mode__'       => :GCC_ATTR_MODE,
    '__format__'     => :GCC_ATTR_FORMAT,
    '__nonnull__'    => :GCC_ATTR_NONNULL,
    '__pure__'       => :GCC_ATTR_PURE,
    '__malloc__'     => :GCC_ATTR_MALLOC,
    '__deprecated__' => :GCC_ATTR_IGNORE,
    '__builtin_va_list' => :MY_TYPE,
    '__asm__'        => :GCC_ASM,
    '__volatile__'   => :GCC_VOLATILE,
    }

    @lineno = 1
    @srcCode = ''
    @inGooseStatement = false
    @type_defining = false
    @verbose = 0
  end

  def parse(file)
    @filename = file
    open(file, "r").each_line {|line|
       @srcCode += line
    }
    @srcCode = @srcCode.gsub(/\\\n/, '')

    begin
      yyparse self, :scan
#   rescue => ex
#     puts "#{ex.class}:#{ex.message} at line #{@lineno}..."
    end
  end

  def scan

    # DO NOT modify order of the operators below.
    # behavior of #scan relies on it.
    ops = [
       '...',                                    # optional args
       '..',                                     # array index expansion (GFOR local syntax)
       '->', '.',                                # accessor
       '*=', '/=', '%=',                         # mult-assign
       '+=', '-=',                               # add-assign
       '<<=', '>>=',                             # shift-assign
       '^=', '&=', '|=',                         # bit-assign

       '<<', '>>',                               # shift
       '==', '!=',                               # equality
       '<=', '>=', '<', '>',                     # relational

       '=',                                      # assign

       '||', '&&',                               # logical
       '^', '|', '&',                            # bit

       '?', ':', '&',                            # conditional

       '++', '--', '+', '-', '&', '*', '!', '~', # unary
       '*', '/', '%',                            # multicative
       '+', '-',                                 # additive
    ]

    ops = ops.map {|item| Regexp.escape(item)}
    # ops = Regexp.union(*ops)
    united_ops = ops[0..-2].inject('\A (?: ') {|sum, item| sum = sum + item + ' | '}
    united_ops += ops[-1] + ' )'
    operatorRe = Regexp.new(united_ops, Regexp::EXTENDED)

    while not @srcCode.empty?
      token = nil

#      p "||#{@srcCode}||"; p ""

      case @srcCode
      when /\A\n/ # increment the line number.
        @lineno += 1
      when /\A\s+/ # omit spaces.
        # nop.

      # remove gcc extensions.
      when /\A __volatile__ /x
        # nop.
        puts "omit #{$&}" if @verbose > 2

      when /\A __asm__ \s* \( [^)]* \) \s* /x
        # nop.
        puts "omit #{$&}" if @verbose > 2

      when /\A \#pragma \s+ goose \s+ parallel \s+ for \s+ /xms
      # note that # should be escaped when 'x' option given.
        if @inGooseStatement
          raise Racc::ParseError,
            "already in Goose block. "
            "Duplicated goose pragma in line#{@lineno}:'#{$&}'."
        end
        token = TNode.new(:GFOR_PRAGMA, $&, @lineno, @filename)
        @inGooseStatement = true
        p "entered to Goose block: #{$&}" if @verbose > 2

      when /\A \#pragma \s+ goose \s+ func \s+ /xms
        token = TNode.new(:GFUNC_PRAGMA, $&, @lineno, @filename)

      when /\A \# \s+ (\d+) \s+ " ([^"\n]+) " [^\n]* \n /xms
        @lineno = $1.to_i
        @filename = $2.dup

      when /\A \#pragma ([^\n]*) /xms  # skip #pragma other than goose's.
        #nop

      when /\A \# [^\n]* /xms  # skip a line beginning with #.
        #nop

      when operatorRe
        op = $&
        token = TNode.new(op, op, @lineno, @filename)

      when /\A 0x[\da-fA-F]+ (?: [uU]? [lL]{0,2} | [lL]{0,2} [uU]? ) (?! \.) \b/x # hex int.
        token = TNode.new(:HEX_NUMBER, $&, @lineno, @filename)
      when /\A 0[0-7]+ (?: [uU]? [lL]{0,2} | [lL]{0,2} [uU]? ) (?! \.) \b/x # octal int.
        token = TNode.new(:OCT_NUMBER, $&, @lineno, @filename)
      when /\A \d+ (?: [uU]? [lL]{0,2} | [lL]{0,2} [uU]? ) (?: (?! \.) \b | (?= \.\.) ) /x     # decimal int with format specifier.
        token = TNode.new(:DEC_NUMBER, $&, @lineno, @filename)
      when /\A [+-]? (?: \d+ (\.\d*)? | \.\d* ) ( [Ee]([+-]?\d+) )? (?= [^\w\.] | \z) /x # decimal float number.
        token = TNode.new(:DEC_NUMBER, $&, @lineno, @filename)
      when /\A " ((?: [^\n"] | \")*?) " /x
        str = $1
#       str = str.gsub(/\\"/, '"')
        token = TNode.new(:STRING, str, @lineno, @filename)
      when /\A ' ((?: [^\n'] | \')*?) ' /x
        str = $1
        token = TNode.new(:STRING, str, @lineno, @filename)
      when /\A[a-zA-Z_]\w*/
        word = $&
        token = TNode.new(@reserved[word] || :IDENT, word, @lineno, @filename)
      when /\A./o
        char = $&
        token = TNode.new(char, char, @lineno, @filename)
      when /.*/
        raise Racc::ParseError, "syntax error. invalid text in line#{@lineno}:'#{$&}'."
      end # case @srcCode

      @srcCode = $' 

      if token
        p "scan yield symbol:#{token.symbol}  value:#{token.value}  lineno:#{token.lineno}" if @verbose > 4
        yield token.symbol, token
      end

    end # while @srcCode

    token = TNode.new(false, nil, @lineno, @filename)
    yield token.symbol, token

  end # scan

  def on_error(tokenid, token, stack)
    unless token
      token = TNode(nil, nil, 'last')
    end
#      raise Racc::ParseError,
#        "#{token.filename}:line #{token.lineno}: syntax error: on #{token.value}\n"
      fname = token.filename.gsub(/\.\.\/\.\//, '')
      $stderr.puts "#{fname}:line #{token.lineno}: syntax error: on #{token.value}.\n"
      exit 1
  end

---- footer
