%{
/*
 * If you modify this grammar, please update the version number in
 * ./xhpast.cpp and libphutil/src/parser/xhpast/bin/xhpast_parse.php
 */

#include "ast.hpp"
#include "node_names.hpp"
// PHP's if/else rules use right reduction rather than left reduction which
// means while parsing nested if/else's the stack grows until it the last
// statement is read. This is annoying, particularly because of a quirk in
// bison.
// http://www.gnu.org/software/bison/manual/html_node/Memory-Management.html
// Apparently if you compile a bison parser with g++ it can no longer grow
// the stack. The work around is to just make your initial stack ridiculously
// large. Unfortunately that increases memory usage while parsing which is
// dumb. Anyway, putting a TODO here to fix PHP's if/else grammar.
#define YYINITDEPTH 500
%}

%{
#undef yyextra
#define yyextra static_cast<yy_extra_type*>(xhpastget_extra(yyscanner))
#undef yylineno
#define yylineno yyextra->first_lineno
#define push_state(s) xhp_new_push_state(s, (struct yyguts_t*) yyscanner)
#define pop_state() xhp_new_pop_state((struct yyguts_t*) yyscanner)
#define set_state(s) xhp_set_state(s, (struct yyguts_t*) yyscanner)

#define NNEW(t) \
  (new xhpast::Node(t))

#define NTYPE(n, type) \
  ((n)->setType(type))

#define NMORE(n, end) \
  ((n)->expandRange(end))

#define NSPAN(n, type, end) \
  (NMORE(NTYPE((n), type), end))

#define NEXPAND(l, n, r) \
  ((n)->expandRange(l)->expandRange(r))

using namespace std;

static void yyerror(void* yyscanner, void* _, const char* error) {
  if (yyextra->terminated) {
    return;
  }
  yyextra->terminated = true;
  yyextra->error = error;
}

%}

%expect 5
// 2: PHP's if/else grammar
// 7: expr '[' dim_offset ']' -- shift will default to first grammar
%pure-parser
%parse-param { void* yyscanner }
%parse-param { xhpast::Node** root }
%lex-param { void* yyscanner }
%error-verbose

%left T_INCLUDE T_INCLUDE_ONCE T_EVAL T_REQUIRE T_REQUIRE_ONCE
%left ','
%left T_LOGICAL_OR
%left T_LOGICAL_XOR
%left T_LOGICAL_AND
%right T_PRINT
%left '=' T_PLUS_EQUAL
  T_MINUS_EQUAL
  T_MUL_EQUAL
  T_DIV_EQUAL
  T_CONCAT_EQUAL
  T_MOD_EQUAL
  T_AND_EQUAL
  T_OR_EQUAL
  T_XOR_EQUAL
  T_SL_EQUAL
  T_SR_EQUAL
%left '?' ':'
%right T_COALESCE
%left T_BOOLEAN_OR
%left T_BOOLEAN_AND
%left '|'
%left '^'
%left '&'
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL
  T_SPACESHIP
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
%left T_SL T_SR
%left '+' '-' '.'
%left '*' '/' '%'
%right '!'
%nonassoc T_INSTANCEOF
%right '~' T_INC
  T_DEC
  T_INT_CAST
  T_DOUBLE_CAST
  T_STRING_CAST
  T_UNICODE_CAST
  T_BINARY_CAST
  T_ARRAY_CAST
  T_OBJECT_CAST
  T_BOOL_CAST
  T_UNSET_CAST
  '@'
%right '['
%nonassoc T_NEW T_CLONE
%token T_EXIT
%token T_IF
%left T_ELSEIF
%left T_ELSE
%left T_ENDIF

%token T_LNUMBER
%token T_DNUMBER
%token T_STRING
%token T_STRING_VARNAME /* unused in XHP: `foo` in `"$foo"` */
%token T_VARIABLE
%token T_NUM_STRING /* unused in XHP: `0` in `"$foo[0]"` */
%token T_INLINE_HTML
%token T_CHARACTER /* unused in vanilla PHP */
%token T_BAD_CHARACTER /* unused in vanilla PHP */
%token T_ENCAPSED_AND_WHITESPACE /* unused in XHP: ` ` in `" "` */
%token T_CONSTANT_ENCAPSED_STRING /* overloaded in XHP;
  replaces '"' encaps_list '"' */
%token T_BACKTICKS_EXPR /* new in XHP; replaces '`' backticks_expr '`' */
%token T_ECHO
%token T_DO
%token T_WHILE
%token T_ENDWHILE
%token T_FOR
%token T_ENDFOR
%token T_FOREACH
%token T_ENDFOREACH
%token T_DECLARE
%token T_ENDDECLARE
%token T_AS
%token T_SWITCH
%token T_ENDSWITCH
%token T_CASE
%token T_DEFAULT
%token T_BREAK
%token T_CONTINUE
%token T_GOTO
%token T_FUNCTION
%token T_CONST
%token T_RETURN
%token T_TRY
%token T_CATCH
%token T_THROW
%token T_USE
%token T_GLOBAL
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC
%token T_VAR
%token T_UNSET
%token T_ISSET
%token T_EMPTY
%token T_HALT_COMPILER
%token T_CLASS
%token T_INTERFACE
%token T_EXTENDS
%token T_IMPLEMENTS
%token T_OBJECT_OPERATOR
%token T_DOUBLE_ARROW
%token T_LIST
%token T_ARRAY
%token T_CLASS_C
%token T_METHOD_C
%token T_FUNC_C
%token T_LINE
%token T_FILE
%token T_COMMENT
%token T_DOC_COMMENT
%token T_OPEN_TAG
%token T_OPEN_TAG_WITH_ECHO
%token T_OPEN_TAG_FAKE
%token T_CLOSE_TAG
%token T_WHITESPACE
%token T_START_HEREDOC /* unused in XHP; replaced with T_HEREDOC */
%token T_END_HEREDOC /* unused in XHP; replaced with T_HEREDOC */
%token T_HEREDOC /* new in XHP;
  replaces start_heredoc encaps_list T_END_HEREDOC */
%token T_DOLLAR_OPEN_CURLY_BRACES /* unused in XHP: `${` in `"${foo}"` */
%token T_CURLY_OPEN /* unused in XHP: `{$` in `"{$foo}"` */
%token T_PAAMAYIM_NEKUDOTAYIM
%token T_BINARY_DOUBLE /* unsused in XHP: `b"` in `b"foo"` */
%token T_BINARY_HEREDOC /* unsused in XHP: `b<<<` in `b<<<FOO` */
%token T_NAMESPACE
%token T_NS_C
%token T_DIR
%token T_NS_SEPARATOR
%token T_INSTEADOF
%token T_CALLABLE
%token T_TRAIT
%token T_TRAIT_C
%token T_YIELD
%token T_FINALLY
%token T_ELLIPSIS

%%

start:
  top_statement_list {
    *root = NNEW(n_PROGRAM)->appendChild($1);
  }
;

top_statement_list:
  top_statement_list top_statement {
    $$ = $1->appendChild($2);
  }
| /* empty */ {
    $$ = NNEW(n_STATEMENT_LIST);
  }
;

namespace_name:
  T_STRING {
    $$ = NTYPE($1, n_SYMBOL_NAME);
  }
| namespace_name T_NS_SEPARATOR T_STRING {
    $$ = NMORE($1, $3);
  }
;

top_statement:
  statement
| function_declaration_statement
| class_declaration_statement
| T_HALT_COMPILER '(' ')' ';' {
    $1 = NSPAN($1, n_HALT_COMPILER, $3);
    $$ = NNEW(n_STATEMENT)->appendChild($1);
    NMORE($$, $4);
  }
| T_NAMESPACE namespace_name ';' {
    NSPAN($1, n_NAMESPACE, $2);
    $1->appendChild($2);
    $1->appendChild(NNEW(n_EMPTY));
    $$ = NNEW(n_STATEMENT)->appendChild($1);
    NMORE($$, $3);
  }
| T_NAMESPACE namespace_name '{' top_statement_list '}' {
  NSPAN($1, n_NAMESPACE, $5);
  $1->appendChild($2);
  $1->appendChild(NEXPAND($3, $4, $5));
  $$ = NNEW(n_STATEMENT)->appendChild($1);
  }
| T_NAMESPACE '{' top_statement_list '}' {
  NSPAN($1, n_NAMESPACE, $4);
  $1->appendChild(NNEW(n_EMPTY));
  NMORE($3, $4);
  NMORE($3, $2);
  $1->appendChild($3);
  $$ = NNEW(n_STATEMENT)->appendChild($1);
  }
| T_USE use_declarations ';' {
  NMORE($2, $1);
  $$ = NNEW(n_STATEMENT)->appendChild($2);
  NMORE($$, $3);
  }
| constant_declaration ';' {
  $$ = NNEW(n_STATEMENT)->appendChild($1);
  NMORE($$, $2);
  }
;

use_declarations:
  use_declarations ',' use_declaration {
    $$ = $1->appendChild($3);
  }
| use_declaration {
    $$ = NNEW(n_USE_LIST);
    $$->appendChild($1);
  }
;

use_declaration:
  namespace_name {
    $$ = NNEW(n_USE);
    $$->appendChild($1);
    $$->appendChild(NNEW(n_EMPTY));
  }
| namespace_name T_AS T_STRING {
    $$ = NNEW(n_USE);
    $$->appendChild($1);
    NTYPE($3, n_STRING);
    $$->appendChild($3);
  }
| T_NS_SEPARATOR namespace_name {
    $$ = NNEW(n_USE);
    NMORE($2, $1);
    $$->appendChild($2);
    $$->appendChild(NNEW(n_EMPTY));
  }
| T_NS_SEPARATOR namespace_name T_AS T_STRING {
    $$ = NNEW(n_USE);
    NMORE($2, $1);
    $$->appendChild($2);
    NTYPE($4, n_STRING);
    $$->appendChild($4);
  }
;

constant_declaration:
  constant_declaration ',' T_STRING '=' static_scalar {
    NMORE($$, $5);
    $$->appendChild(
      NNEW(n_CONSTANT_DECLARATION)
        ->appendChild(NTYPE($3, n_STRING))
        ->appendChild($5));
  }
| T_CONST T_STRING '=' static_scalar {
    NSPAN($$, n_CONSTANT_DECLARATION_LIST, $4);
    $$->appendChild(
      NNEW(n_CONSTANT_DECLARATION)
        ->appendChild(NTYPE($2, n_STRING))
        ->appendChild($4));
  }
;

inner_statement_list:
  inner_statement_list inner_statement {
    $$ = $1->appendChild($2);
  }
| /* empty */ {
    $$ = NNEW(n_STATEMENT_LIST);
  }
;

inner_statement:
  statement
| function_declaration_statement
| class_declaration_statement
| T_HALT_COMPILER '(' ')' ';' {
  $1 = NSPAN($1, n_HALT_COMPILER, $3);
  $$ = NNEW(n_STATEMENT)->appendChild($1);
  NMORE($$, $4);
  }
;

statement:
  unticked_statement
| T_STRING ':' {
    NTYPE($1, n_STRING);
    $$ = NNEW(n_LABEL);
    $$->appendChild($1);
    NMORE($$, $2);
  }
| T_OPEN_TAG {
    $$ = NTYPE($1, n_OPEN_TAG);
  }
| T_OPEN_TAG_WITH_ECHO {
    $$ = NTYPE($1, n_OPEN_TAG);
  }
| T_CLOSE_TAG {
    $$ = NTYPE($1, n_CLOSE_TAG);
  }
;

unticked_statement:
  '{' inner_statement_list '}' {
    $$ = NEXPAND($1, $2, $3);
  }
| T_IF '(' expr ')' statement elseif_list else_single {
    $$ = NNEW(n_CONDITION_LIST);

    $1 = NTYPE($1, n_IF);
    $1->appendChild(NSPAN($2, n_CONTROL_CONDITION, $4)->appendChild($3));
    $1->appendChild($5);

    $$->appendChild($1);
    $$->appendChildren($6);

    // Hacks: merge a list of if (x) { } else if (y) { } into a single condition
    // list instead of a condition tree.

    if ($7->type == n_EMPTY) {
      // Ignore.
    } else if ($7->type == n_ELSE) {
      xhpast::Node *stype = $7->firstChild()->firstChild();
      if (stype && stype->type == n_CONDITION_LIST) {
        NTYPE(stype->firstChild(), n_ELSEIF);
        stype->firstChild()->l_tok = $7->l_tok;
        $$->appendChildren(stype);
      } else {
        $$->appendChild($7);
      }
    } else {
      $$->appendChild($7);
    }

    $$ = NNEW(n_STATEMENT)->appendChild($$);
  }
| T_IF '(' expr ')' ':'
  inner_statement_list
  new_elseif_list
  new_else_single
  T_ENDIF ';' {

    $$ = NNEW(n_CONDITION_LIST);
    NTYPE($1, n_IF);
    $1->appendChild(NSPAN($2, n_CONTROL_CONDITION, $4)->appendChild($3));
    $1->appendChild($6);

    $$->appendChild($1);
    $$->appendChildren($7);
    $$->appendChild($8);
    NMORE($$, $9);

    $$ = NNEW(n_STATEMENT)->appendChild($$);
    NMORE($$, $10);
  }
| T_WHILE '(' expr ')' while_statement {
    NTYPE($1, n_WHILE);
    $1->appendChild(NSPAN($2, n_CONTROL_CONDITION, $4)->appendChild($3));
    $1->appendChild($5);

    $$ = NNEW(n_STATEMENT)->appendChild($1);
  }
| T_DO statement T_WHILE '(' expr ')' ';' {
    NTYPE($1, n_DO_WHILE);
    $1->appendChild($2);
    $1->appendChild(NSPAN($4, n_CONTROL_CONDITION, $6)->appendChild($5));

    $$ = NNEW(n_STATEMENT)->appendChild($1);
    NMORE($$, $7);
  }
| T_FOR '(' for_expr ';' for_expr ';' for_expr ')' for_statement {
    NTYPE($1, n_FOR);

    NSPAN($2, n_FOR_EXPRESSION, $8)
      ->appendChild($3)
      ->appendChild($5)
      ->appendChild($7);

    $1->appendChild($2);
    $1->appendChild($9);

    $$ = NNEW(n_STATEMENT)->appendChild($1);
  }
| T_SWITCH '(' expr ')' switch_case_list {
    NTYPE($1, n_SWITCH);
    $1->appendChild(NSPAN($2, n_CONTROL_CONDITION, $4)->appendChild($3));
    $1->appendChild($5);

    $$ = NNEW(n_STATEMENT)->appendChild($1);
  }
| T_BREAK ';' {
    NTYPE($1, n_BREAK);
    $1->appendChild(NNEW(n_EMPTY));

    $$ = NNEW(n_STATEMENT)->appendChild($1);
    NMORE($$, $2);
  }
| T_BREAK expr ';' {
    NTYPE($1, n_BREAK);
    $1->appendChild($2);

    $$ = NNEW(n_STATEMENT)->appendChild($1);
    NMORE($$, $3);
  }
| T_CONTINUE ';' {
    NTYPE($1, n_CONTINUE);
    $1->appendChild(NNEW(n_EMPTY));

    $$ = NNEW(n_STATEMENT)->appendChild($1);
    NMORE($$, $2);
  }
| T_CONTINUE expr ';' {
    NTYPE($1, n_CONTINUE);
    $1->appendChild($2);

    $$ = NNEW(n_STATEMENT)->appendChild($1);
    NMORE($$, $3);
  }
| T_RETURN ';' {
    NTYPE($1, n_RETURN);
    $1->appendChild(NNEW(n_EMPTY));

    $$ = NNEW(n_STATEMENT)->appendChild($1);
    NMORE($$, $2);
  }
| T_RETURN expr_without_variable ';' {
    NTYPE($1, n_RETURN);
    $1->appendChild($2);

    $$ = NNEW(n_STATEMENT)->appendChild($1);
    NMORE($$, $3);
  }
| T_RETURN variable ';' {
    NTYPE($1, n_RETURN);
    $1->appendChild($2);

    $$ = NNEW(n_STATEMENT)->appendChild($1);
    NMORE($$, $3);
  }
| T_GLOBAL global_var_list ';' {
    NMORE($2, $1);
    $$ = NNEW(n_STATEMENT)->appendChild($2);
    NMORE($$, $3);
  }
| T_STATIC static_var_list ';' {
    NMORE($2, $1);
    $$ = NNEW(n_STATEMENT)->appendChild($2);
    NMORE($$, $3);
  }
| T_ECHO echo_expr_list ';' {
    NMORE($2, $1);
    $$ = NNEW(n_STATEMENT)->appendChild($2);
    NMORE($$, $3);
  }
| T_INLINE_HTML {
    NTYPE($1, n_INLINE_HTML);
    $$ = $1;
  }
| expr ';' {
    $$ = NNEW(n_STATEMENT)->appendChild($1);
    NMORE($$, $2);
  }
| yield_expr ';' {
    $$ = NNEW(n_STATEMENT)->appendChild($1);
    NMORE($$, $2);
  }
| T_UNSET '(' unset_variables ')' ';' {
    NMORE($3, $4);
    NMORE($3, $1);
    $$ = NNEW(n_STATEMENT)->appendChild($3);
    NMORE($$, $5);
  }
| T_FOREACH '(' variable T_AS foreach_variable foreach_optional_arg ')'
  foreach_statement {
    NTYPE($1, n_FOREACH);
    NSPAN($2, n_FOREACH_EXPRESSION, $7);
    $2->appendChild($3);
    if ($6->type == n_EMPTY) {
      $2->appendChild($6);
      $2->appendChild($5);
    } else {
      $2->appendChild($5);
      $2->appendChild($6);
    }
    $1->appendChild($2);

    $1->appendChild($8);

    $$ = NNEW(n_STATEMENT)->appendChild($1);
  }
| T_FOREACH '(' expr_without_variable T_AS variable foreach_optional_arg ')'
  foreach_statement {
    NTYPE($1, n_FOREACH);
    NSPAN($2, n_FOREACH_EXPRESSION, $7);
    $2->appendChild($3);
    if ($6->type == n_EMPTY) {
      $2->appendChild($6);
      $2->appendChild($5);
    } else {
      $2->appendChild($5);
      $2->appendChild($6);
    }
    $1->appendChild($2);
    $1->appendChild($8);

    $$ = NNEW(n_STATEMENT)->appendChild($1);
  }
| T_DECLARE '(' declare_list ')' declare_statement {
    NTYPE($1, n_DECLARE);
    $1->appendChild($3);
    $1->appendChild($5);
    $$ = NNEW(n_STATEMENT)->appendChild($1);
  }
| ';' /* empty statement */ {
    $$ = NNEW(n_STATEMENT)->appendChild(NNEW(n_EMPTY));
    NMORE($$, $1);
  }
| T_TRY '{' inner_statement_list '}' catch_list finally_statement {
    NTYPE($1, n_TRY);
    $1->appendChild(NEXPAND($2, $3, $4));

    $1->appendChild($5);
    $1->appendChild($6);

    $$ = NNEW(n_STATEMENT)->appendChild($1);
  }
| T_TRY '{' inner_statement_list '}' non_empty_finally_statement {
    NTYPE($1, n_TRY);
    $1->appendChild(NEXPAND($2, $3, $4));

    $1->appendChild(NNEW(n_CATCH_LIST));
    $1->appendChild($5);

    $$ = NNEW(n_STATEMENT)->appendChild($1);
  }
| T_THROW expr ';' {
  NTYPE($1, n_THROW);
  $1->appendChild($2);

  $$ = NNEW(n_STATEMENT)->appendChild($1);
  NMORE($$, $3);

  }
| T_GOTO T_STRING ';' {
  NTYPE($1, n_GOTO);
  NTYPE($2, n_STRING);
  $1->appendChild($2);

  $$ = NNEW(n_STATEMENT)->appendChild($1);
  NMORE($$, $3);
  }
;

catch_list:
  catch_list catch {
    $1->appendChild($2);
    $$ = $1;
  }
| catch {
  $$ = NNEW(n_CATCH_LIST);
  $$->appendChild($1);
}

catch:
  T_CATCH '(' fully_qualified_class_name T_VARIABLE ')'
  '{' inner_statement_list '}' {
    NTYPE($1, n_CATCH);
    $1->appendChild($3);
    $1->appendChild(NTYPE($4, n_VARIABLE));
    $1->appendChild(NEXPAND($6, $7, $8));
    NMORE($1, $8);
    $$ = $1;
  }
;

finally_statement:
  /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
| non_empty_finally_statement
;

non_empty_finally_statement:
  T_FINALLY '{' inner_statement_list '}' {
    NTYPE($1, n_FINALLY);
    $1->appendChild($3);
    NMORE($1, $4);
    $$ = $1;
  }
;

unset_variables:
  unset_variable {
    $$ = NNEW(n_UNSET_LIST);
    $$->appendChild($1);
  }
| unset_variables ',' unset_variable {
    $1->appendChild($3);
    $$ = $1;
  }
;

unset_variable:
  variable
;

function_declaration_statement:
  unticked_function_declaration_statement
;

class_declaration_statement:
  unticked_class_declaration_statement
;

is_reference:
  /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
| '&' {
    $$ = NTYPE($1, n_REFERENCE);
  }
;

unticked_function_declaration_statement:
  function is_reference T_STRING
  '(' parameter_list ')' return_type '{' inner_statement_list '}' {
    NSPAN($1, n_FUNCTION_DECLARATION, $9);
    $1->appendChild(NNEW(n_EMPTY));
    $1->appendChild($2);
    $1->appendChild(NTYPE($3, n_STRING));
    $1->appendChild(NEXPAND($4, $5, $6));
    $1->appendChild(NNEW(n_EMPTY));
    $1->appendChild($7);
    $1->appendChild(NEXPAND($8, $9, $10));

    $$ = NNEW(n_STATEMENT)->appendChild($1);
  }
;

unticked_class_declaration_statement:
  class_entry_type T_STRING extends_from implements_list
  '{' class_statement_list '}' {
    $$ = NNEW(n_CLASS_DECLARATION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_CLASS_NAME));
    $$->appendChild($3);
    $$->appendChild($4);
    $$->appendChild(NEXPAND($5, $6, $7));
    NMORE($$, $7);

    $$ = NNEW(n_STATEMENT)->appendChild($$);
  }
| interface_entry T_STRING interface_extends_list '{' class_statement_list '}' {
    $$ = NNEW(n_INTERFACE_DECLARATION);
    $$->appendChild(NNEW(n_CLASS_ATTRIBUTES));
    NMORE($$, $1);
    $$->appendChild(NTYPE($2, n_CLASS_NAME));
    $$->appendChild($3);
    $$->appendChild(NNEW(n_EMPTY));
    $$->appendChild(NEXPAND($4, $5, $6));
    NMORE($$, $6);

    $$ = NNEW(n_STATEMENT)->appendChild($$);
  }
;

class_entry_type:
  T_CLASS {
    NTYPE($1, n_CLASS_ATTRIBUTES);
    $$ = $1;
  }
| T_ABSTRACT T_CLASS {
    NTYPE($2, n_CLASS_ATTRIBUTES);
    NMORE($2, $1);
    $2->appendChild(NTYPE($1, n_STRING));

    $$ = $2;
  }
| T_FINAL T_CLASS {
    NTYPE($2, n_CLASS_ATTRIBUTES);
    NMORE($2, $1);
    $2->appendChild(NTYPE($1, n_STRING));

    $$ = $2;
  }
| T_TRAIT {
    $$ = NNEW(n_CLASS_ATTRIBUTES);
    $$->appendChild(NTYPE($1, n_STRING));
  }
;

extends_from:
  /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
| T_EXTENDS fully_qualified_class_name {
    $$ = NTYPE($1, n_EXTENDS_LIST)->appendChild($2);
  }
;

interface_entry:
  T_INTERFACE
;

interface_extends_list:
  /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
| T_EXTENDS interface_list {
    NTYPE($1, n_EXTENDS_LIST);
    $1->appendChildren($2);
    $$ = $1;
  }
;

implements_list:
  /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
| T_IMPLEMENTS interface_list {
    NTYPE($1, n_IMPLEMENTS_LIST);
    $1->appendChildren($2);
    $$ = $1;
  }
;

interface_list:
  fully_qualified_class_name {
    $$ = NNEW(n_IMPLEMENTS_LIST)->appendChild($1);
  }
| interface_list ',' fully_qualified_class_name {
    $$ = $1->appendChild($3);
  }
;

foreach_optional_arg:
  /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
| T_DOUBLE_ARROW foreach_variable {
    $$ = $2;
  }
;

foreach_variable:
  variable
| '&' variable {
    NTYPE($1, n_VARIABLE_REFERENCE);
    $1->appendChild($2);
    $$ = $1;
  }
;

for_statement:
  statement
| ':' inner_statement_list T_ENDFOR ';' {
  NMORE($2, $1);
  NMORE($2, $4);
  $$ = $2;
  }
;

foreach_statement:
  statement
| ':' inner_statement_list T_ENDFOREACH ';' {
  NMORE($2, $1);
  NMORE($2, $4);
  $$ = $2;
  }
;

declare_statement:
  statement
| ':' inner_statement_list T_ENDDECLARE ';' {
  NMORE($2, $1);
  NMORE($2, $4);
  $$ = $2;
  }
;

declare_list:
  T_STRING '=' static_scalar {
    $$ = NNEW(n_DECLARE_DECLARATION);
    $$->appendChild(NTYPE($1, n_STRING));
    $$->appendChild($3);
    $$ = NNEW(n_DECLARE_DECLARATION_LIST)->appendChild($$);
  }
| declare_list ',' T_STRING '=' static_scalar {
    $$ = NNEW(n_DECLARE_DECLARATION);
    $$->appendChild(NTYPE($3, n_STRING));
    $$->appendChild($5);

    $1->appendChild($$);
    $$ = $1;
  }
;

switch_case_list:
  '{' case_list '}' {
    $$ = NEXPAND($1, $2, $3);
  }
| '{' ';' case_list '}' {
    // ...why does this rule exist?

    NTYPE($2, n_STATEMENT);
    $1->appendChild(NNEW(n_EMPTY));

    $$ = NNEW(n_STATEMENT_LIST)->appendChild($2);
    $$->appendChildren($3);
    NEXPAND($1, $$, $4);
  }
| ':' case_list T_ENDSWITCH ';' {
    NMORE($2, $4);
    NMORE($2, $1);
    $$ = $2;
  }
| ':' ';' case_list T_ENDSWITCH ';' {
    NTYPE($2, n_STATEMENT);
    $1->appendChild(NNEW(n_EMPTY));

    $$ = NNEW(n_STATEMENT_LIST)->appendChild($2);
    $$->appendChildren($3);
    NMORE($$, $5);
    NMORE($$, $1);
  }
;

case_list:
  /* empty */ {
    $$ = NNEW(n_STATEMENT_LIST);
  }
| case_list T_CASE expr case_separator inner_statement_list {
    NTYPE($2, n_CASE);
    $2->appendChild($3);
    $2->appendChild($5);

    $1->appendChild($2);
    $$ = $1;
  }
| case_list T_DEFAULT case_separator inner_statement_list {
    NTYPE($2, n_DEFAULT);
    $2->appendChild($4);

    $1->appendChild($2);
    $$ = $1;
  }
;

case_separator:
  ':'
| ';'
;

while_statement:
  statement
| ':' inner_statement_list T_ENDWHILE ';' {
  NMORE($2, $4);
  NMORE($2, $1);
  $$ = $2;
  }
;

elseif_list:
  /* empty */ {
    $$ = NNEW(n_CONDITION_LIST);
  }
| elseif_list T_ELSEIF '(' expr ')' statement {
    NTYPE($2, n_ELSEIF);
    $2->appendChild(NSPAN($3, n_CONTROL_CONDITION, $5)->appendChild($4));
    $2->appendChild($6);

    $$ = $1->appendChild($2);
  }
;

new_elseif_list:
  /* empty */ {
    $$ = NNEW(n_CONDITION_LIST);
  }
| new_elseif_list T_ELSEIF '(' expr ')' ':' inner_statement_list {
    NTYPE($2, n_ELSEIF);
    $2->appendChild($4);
    $2->appendChild($7);

    $$ = $1->appendChild($2);
  }
;

else_single:
  /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
| T_ELSE statement {
    NTYPE($1, n_ELSE);
    $1->appendChild($2);
    $$ = $1;
  }
;

new_else_single:
  /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
| T_ELSE ':' inner_statement_list {
    NTYPE($1, n_ELSE);
    $1->appendChild($3);
    $$ = $1;
  }
;

parameter_list:
  non_empty_parameter_list
| /* empty */ {
    $$ = NNEW(n_DECLARATION_PARAMETER_LIST);
  }
;

non_empty_parameter_list:
  optional_type parameter {
    $$ = NNEW(n_DECLARATION_PARAMETER);
    $$->appendChild($1);
    $$->appendChild($2);
    $$->appendChild(NNEW(n_EMPTY));

    $$ = NNEW(n_DECLARATION_PARAMETER_LIST)->appendChild($$);
  }
| optional_type '&' parameter {
    $$ = NNEW(n_DECLARATION_PARAMETER);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_VARIABLE_REFERENCE));
      $2->appendChild($3);
    $$->appendChild(NNEW(n_EMPTY));

    $$ = NNEW(n_DECLARATION_PARAMETER_LIST)->appendChild($$);
  }
| optional_type '&' parameter '=' static_scalar {
    $$ = NNEW(n_DECLARATION_PARAMETER);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_VARIABLE_REFERENCE));
      $2->appendChild($3);
    $$->appendChild($5);

    $$ = NNEW(n_DECLARATION_PARAMETER_LIST)->appendChild($$);
  }
| optional_type parameter '=' static_scalar {
    $$ = NNEW(n_DECLARATION_PARAMETER);
    $$->appendChild($1);
    $$->appendChild($2);
    $$->appendChild($4);

    $$ = NNEW(n_DECLARATION_PARAMETER_LIST)->appendChild($$);
  }
| non_empty_parameter_list ',' optional_type parameter {
    $$ = NNEW(n_DECLARATION_PARAMETER);
    $$->appendChild($3);
    $$->appendChild($4);
    $$->appendChild(NNEW(n_EMPTY));

    $$ = $1->appendChild($$);
  }
| non_empty_parameter_list ',' optional_type '&' parameter {
    $$ = NNEW(n_DECLARATION_PARAMETER);
    $$->appendChild($3);
    $$->appendChild(NTYPE($4, n_VARIABLE_REFERENCE));
      $4->appendChild($5);
    $$->appendChild(NNEW(n_EMPTY));

    $$ = $1->appendChild($$);
  }
| non_empty_parameter_list ',' optional_type '&'
  parameter '=' static_scalar {
    $$ = NNEW(n_DECLARATION_PARAMETER);
    $$->appendChild($3);
    $$->appendChild(NTYPE($4, n_VARIABLE_REFERENCE));
      $4->appendChild($5);
    $$->appendChild($7);

    $$ = $1->appendChild($$);
  }
| non_empty_parameter_list ',' optional_type
  parameter '=' static_scalar {
    $$ = NNEW(n_DECLARATION_PARAMETER);
    $$->appendChild($3);
    $$->appendChild($4);
    $$->appendChild($6);

    $$ = $1->appendChild($$);
  }
;

parameter:
  T_ELLIPSIS T_VARIABLE {
    NTYPE($1, n_UNPACK);
    $$ = $1->appendChild(NTYPE($2, n_VARIABLE));
  }
| T_VARIABLE {
    $$ = NTYPE($1, n_VARIABLE);
  }
;

optional_type:
  /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
| type
| '?' type {
    $$ = NNEW(n_NULLABLE_TYPE);
    $$->appendChild($2);
  }
;

type:
  fully_qualified_class_name {
    $$ = $1;
  }
| T_ARRAY {
    $$ = NTYPE($1, n_TYPE_NAME);
  }
| T_CALLABLE {
    $$ = NTYPE($1, n_TYPE_NAME);
  }
;

return_type:
  /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
| ':' optional_type {
    $$ = NNEW(n_DECLARATION_RETURN);
    $$->appendChild($2);
  }
;

function_call_parameter_list:
  non_empty_function_call_parameter_list
| /* empty */ {
    $$ = NNEW(n_CALL_PARAMETER_LIST);
  }
;

non_empty_function_call_parameter_list:
  argument {
    $$ = NNEW(n_CALL_PARAMETER_LIST)->appendChild($1);
  }
| non_empty_function_call_parameter_list ',' argument {
    $$ = $1->appendChild($3);
  }
;

argument:
  expr
| T_ELLIPSIS expr {
    NTYPE($1, n_UNPACK);
    $$ = $1->appendChild($2);
  }
| '&' w_variable {
    NTYPE($1, n_VARIABLE_REFERENCE);
    $$ = $1->appendChild($2);
  }
;

global_var_list:
  global_var_list ',' global_var {
    $1->appendChild($3);
    $$ = $1;
  }
| global_var {
    $$ = NNEW(n_GLOBAL_DECLARATION_LIST);
    $$->appendChild($1);
  }
;

global_var:
  T_VARIABLE {
    $$ = NTYPE($1, n_VARIABLE);
  }
| '$' r_variable {
    $$ = NTYPE($1, n_VARIABLE_VARIABLE);
    $$->appendChild($2);
  }
| '$' '{' expr '}' {
    $$ = NTYPE($1, n_VARIABLE_VARIABLE);
    $$->appendChild($3);
  }
;

static_var_list:
  static_var_list ',' T_VARIABLE {
    NTYPE($3, n_VARIABLE);
    $$ = NNEW(n_STATIC_DECLARATION);
    $$->appendChild($3);
    $$->appendChild(NNEW(n_EMPTY));

    $$ = $1->appendChild($$);
  }
| static_var_list ',' T_VARIABLE '=' static_scalar {
    NTYPE($3, n_VARIABLE);
    $$ = NNEW(n_STATIC_DECLARATION);
    $$->appendChild($3);
    $$->appendChild($5);

    $$ = $1->appendChild($$);
  }
| T_VARIABLE {
    NTYPE($1, n_VARIABLE);
    $$ = NNEW(n_STATIC_DECLARATION);
    $$->appendChild($1);
    $$->appendChild(NNEW(n_EMPTY));

    $$ = NNEW(n_STATIC_DECLARATION_LIST)->appendChild($$);
  }
| T_VARIABLE '=' static_scalar {
    NTYPE($1, n_VARIABLE);
    $$ = NNEW(n_STATIC_DECLARATION);
    $$->appendChild($1);
    $$->appendChild($3);

    $$ = NNEW(n_STATIC_DECLARATION_LIST)->appendChild($$);
  }
;

class_statement_list:
  class_statement_list class_statement {
    $$ = $1->appendChild($2);
  }
| /* empty */ {
    $$ = NNEW(n_STATEMENT_LIST);
  }
;

class_statement:
  variable_modifiers class_variable_declaration ';' {
    $$ = NNEW(n_CLASS_MEMBER_DECLARATION_LIST);
    $$->appendChild($1);
    $$->appendChildren($2);

    $$ = NNEW(n_STATEMENT)->appendChild($$);
    NMORE($$, $3);
  }
| class_constant_declaration ';' {
    $$ = NNEW(n_STATEMENT)->appendChild($1);
    NMORE($$, $2);
  }
| trait_use_statement {
    $$ = $1;
  }
| method_modifiers function {
    /* empty */
  } is_reference T_STRING '(' parameter_list ')' return_type method_body {
    $$ = NNEW(n_METHOD_DECLARATION);
    NMORE($$, $2);
    $$->appendChild($1);
    $$->appendChild($4);
    $$->appendChild(NTYPE($5, n_STRING));
    $$->appendChild(NEXPAND($6, $7, $8));
    $$->appendChild(NNEW(n_EMPTY));
    $$->appendChild($9);
    $$->appendChild($10);

    $$ = NNEW(n_STATEMENT)->appendChild($$);
  }
;

trait_use_statement:
  T_USE trait_list trait_adaptations {
    $$ = NTYPE($1, n_TRAIT_USE);
    $$->appendChildren($2);
    $$->appendChild($3);
  }
;

trait_list:
  fully_qualified_class_name {
    $$ = NNEW(n_TRAIT_USE_LIST)->appendChild($1);
  }
| trait_list ',' fully_qualified_class_name {
    $$ = $1->appendChild($3);
  }
;

trait_adaptations:
  ';' {
    $$ = NNEW(n_EMPTY);
  }
| '{' trait_adaptation_list '}' {
    $$ = NEXPAND($1, $2, $3);
  }
;

trait_adaptation_list:
  /* empty */ {
    $$ = NNEW(n_TRAIT_ADAPTATION_LIST);
  }
| non_empty_trait_adaptation_list {
    $$ = $1;
  }
;

non_empty_trait_adaptation_list:
  trait_adaptation_statement {
    $$ = NNEW(n_TRAIT_ADAPTATION_LIST);
    $$->appendChild($1);
  }
|  non_empty_trait_adaptation_list trait_adaptation_statement {
    $1->appendChild($2);
    $$ = $1;
  }
;

trait_adaptation_statement:
  trait_precedence ';' {
    $$ = NMORE($1, $2);
  }
|  trait_alias ';' {
    $$ = NMORE($1, $2);
  }
;

trait_precedence:
  trait_method_reference_fully_qualified T_INSTEADOF trait_reference_list  {
    $$ = NNEW(n_TRAIT_INSTEADOF);
    $$->appendChild($1);
    $$->appendChild($3);
  }
;

trait_reference_list:
  fully_qualified_class_name {
    $$ = NNEW(n_TRAIT_REFERENCE_LIST);
    $$->appendChild($1);
  }
| trait_reference_list ',' fully_qualified_class_name  {
    $1->appendChild($3);
    $$ = $1;
  }
;

trait_method_reference:
  T_STRING {
    $$ = NNEW(n_TRAIT_METHOD_REFERENCE);
    $$->appendChild(NTYPE($1, n_STRING));
  }
|  trait_method_reference_fully_qualified {
    $$ = $1;
  }
;

trait_method_reference_fully_qualified:
  fully_qualified_class_name T_PAAMAYIM_NEKUDOTAYIM T_STRING {
    NTYPE($2, n_TRAIT_METHOD_REFERENCE);
    NEXPAND($1, $2, NTYPE($3, n_STRING));
    $$ = $2;
  }
;

trait_alias:
  trait_method_reference T_AS trait_modifiers T_STRING {
    $$ = NNEW(n_TRAIT_AS);
    $$->appendChild($1);
    $$->appendChild($3);
    $$->appendChild(NTYPE($4, n_STRING));
  }
|  trait_method_reference T_AS member_modifier {
    $$ = NNEW(n_TRAIT_AS);
    $$->appendChild($1);
    $$->appendChild($3);
    $$->appendChild(NNEW(n_EMPTY));
  }
;

trait_modifiers:
  /* empty */  {
    $$ = NNEW(n_EMPTY);
  }
|  member_modifier  {
    $$ = NNEW(n_METHOD_MODIFIER_LIST);
    $$->appendChild($1);
  }
;


method_body:
  ';' /* abstract method */ {
    $$ = NNEW(n_EMPTY);
  }
| '{' inner_statement_list '}' {
    $$ = NEXPAND($1, $2, $3);
  }
;

variable_modifiers:
  non_empty_member_modifiers
| T_VAR {
    $$ = NNEW(n_CLASS_MEMBER_MODIFIER_LIST);
    $$->appendChild(NTYPE($1, n_STRING));
  }
;

method_modifiers:
  /* empty */ {
    $$ = NNEW(n_METHOD_MODIFIER_LIST);
  }
| non_empty_member_modifiers {
    NTYPE($1, n_METHOD_MODIFIER_LIST);
    $$ = $1;
  }
;

non_empty_member_modifiers:
  member_modifier {
    $$ = NNEW(n_CLASS_MEMBER_MODIFIER_LIST);
    $$->appendChild($1);
  }
| non_empty_member_modifiers member_modifier {
    $$ = $1->appendChild($2);
  }
;

member_modifier:
  basic_member_modifier {
    $$ = NTYPE($1, n_STRING);
  }
;

basic_member_modifier:
  T_PUBLIC
| T_PROTECTED
| T_PRIVATE
| T_STATIC
| T_ABSTRACT
| T_FINAL
;

class_variable_declaration:
  class_variable_declaration ',' T_VARIABLE {
    $$ = NNEW(n_CLASS_MEMBER_DECLARATION);
    $$->appendChild(NTYPE($3, n_VARIABLE));
    $$->appendChild(NNEW(n_EMPTY));

    $$ = $1->appendChild($$);
  }
| class_variable_declaration ',' T_VARIABLE '=' static_scalar {
    $$ = NNEW(n_CLASS_MEMBER_DECLARATION);
    $$->appendChild(NTYPE($3, n_VARIABLE));
    $$->appendChild($5);

    $$ = $1->appendChild($$);
  }
| T_VARIABLE {
    $$ = NNEW(n_CLASS_MEMBER_DECLARATION);
    $$->appendChild(NTYPE($1, n_VARIABLE));
    $$->appendChild(NNEW(n_EMPTY));

    $$ = NNEW(n_CLASS_MEMBER_DECLARATION_LIST)->appendChild($$);
  }
| T_VARIABLE '=' static_scalar {
    $$ = NNEW(n_CLASS_MEMBER_DECLARATION);
    $$->appendChild(NTYPE($1, n_VARIABLE));
    $$->appendChild($3);

    $$ = NNEW(n_CLASS_MEMBER_DECLARATION_LIST)->appendChild($$);
  }
;

class_constant_declaration:
  class_constant_declaration ',' T_STRING '=' static_scalar {
    $$ = NNEW(n_CLASS_CONSTANT_DECLARATION);
    $$->appendChild(NTYPE($3, n_STRING));
    $$->appendChild($5);

    $1->appendChild($$);

    $$ = $1;
  }
| T_CONST T_STRING '=' static_scalar {
    NTYPE($1, n_CLASS_CONSTANT_DECLARATION_LIST);
    $$ = NNEW(n_CLASS_CONSTANT_DECLARATION);
    $$->appendChild(NTYPE($2, n_STRING));
    $$->appendChild($4);
    $1->appendChild($$);

    $$ = $1;
  }
;

echo_expr_list:
  echo_expr_list ',' expr {
    $1->appendChild($3);
  }
| expr {
    $$ = NNEW(n_ECHO_LIST);
    $$->appendChild($1);
  }
;

for_expr:
  /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
| non_empty_for_expr
;


non_empty_for_expr:
  non_empty_for_expr ',' expr {
    $1->appendChild($3);
  }
| expr {
    $$ = NNEW(n_EXPRESSION_LIST);
    $$->appendChild($1);
  }
;

expr_without_variable:
  T_LIST '(' assignment_list ')' '=' expr {
    NTYPE($1, n_LIST);
    $1->appendChild(NEXPAND($2, $3, $4));
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($5, n_OPERATOR));
    $$->appendChild($6);
  }
| variable '=' expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| variable '=' '&' variable {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));

    NTYPE($3, n_VARIABLE_REFERENCE);
    $3->appendChild($4);

    $$->appendChild($3);
  }
| variable '=' '&' T_NEW class_name_reference ctor_arguments {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));

    NTYPE($4, n_NEW);
    $4->appendChild($5);
    $4->appendChild($6);

    NTYPE($3, n_VARIABLE_REFERENCE);
    $3->appendChild($4);

    $$->appendChild($3);
  }
| T_CLONE expr {
    $$ = NNEW(n_UNARY_PREFIX_EXPRESSION);
    $$->appendChild(NTYPE($1, n_OPERATOR));
    $$->appendChild($2);
  }
| variable T_PLUS_EQUAL expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| variable T_MINUS_EQUAL expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| variable T_MUL_EQUAL expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| variable T_DIV_EQUAL expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| variable T_CONCAT_EQUAL expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| variable T_MOD_EQUAL expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| variable T_AND_EQUAL expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| variable T_OR_EQUAL expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| variable T_XOR_EQUAL expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| variable T_SL_EQUAL expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| variable T_SR_EQUAL expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| rw_variable T_INC {
    $$ = NNEW(n_UNARY_POSTFIX_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
  }
| T_INC rw_variable {
    $$ = NNEW(n_UNARY_PREFIX_EXPRESSION);
    $$->appendChild(NTYPE($1, n_OPERATOR));
    $$->appendChild($2);
  }
| rw_variable T_DEC {
    $$ = NNEW(n_UNARY_POSTFIX_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
  }
| T_DEC rw_variable {
    $$ = NNEW(n_UNARY_PREFIX_EXPRESSION);
    $$->appendChild(NTYPE($1, n_OPERATOR));
    $$->appendChild($2);
  }
| expr T_BOOLEAN_OR expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr T_BOOLEAN_AND expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr T_LOGICAL_OR expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr T_LOGICAL_AND expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr T_LOGICAL_XOR expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr '|' expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr '&' expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr '^' expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr '.' expr {

    /* The concatenation operator generates n_CONCATENATION_LIST instead of
       n_BINARY_EXPRESSION because we tend to run into stack depth issues in a
       lot of real-world cases otherwise (e.g., in PHP and JSON decoders). */

    if ($1->type == n_CONCATENATION_LIST && $3->type == n_CONCATENATION_LIST) {
      $1->appendChild(NTYPE($2, n_OPERATOR));
      $1->appendChildren($3);
      $$ = $1;
    } else if ($1->type == n_CONCATENATION_LIST) {
      $1->appendChild(NTYPE($2, n_OPERATOR));
      $1->appendChild($3);
      $$ = $1;
    } else if ($3->type == n_CONCATENATION_LIST) {
      $$ = NNEW(n_CONCATENATION_LIST);
      $$->appendChild($1);
      $$->appendChild(NTYPE($2, n_OPERATOR));
      $$->appendChildren($3);
    } else {
      $$ = NNEW(n_CONCATENATION_LIST);
      $$->appendChild($1);
      $$->appendChild(NTYPE($2, n_OPERATOR));
      $$->appendChild($3);
    }
  }
| expr '+' expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr '-' expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr '*' expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr '/' expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr '%' expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr T_SL expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr T_SR expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| '+' expr %prec T_INC {
    $$ = NNEW(n_UNARY_PREFIX_EXPRESSION);
    $$->appendChild(NTYPE($1, n_OPERATOR));
    $$->appendChild($2);
  }
| '-' expr %prec T_INC {
    $$ = NNEW(n_UNARY_PREFIX_EXPRESSION);
    $$->appendChild(NTYPE($1, n_OPERATOR));
    $$->appendChild($2);
  }
| '!' expr {
    $$ = NNEW(n_UNARY_PREFIX_EXPRESSION);
    $$->appendChild(NTYPE($1, n_OPERATOR));
    $$->appendChild($2);
  }
| '~' expr {
    $$ = NNEW(n_UNARY_PREFIX_EXPRESSION);
    $$->appendChild(NTYPE($1, n_OPERATOR));
    $$->appendChild($2);
  }
| expr T_IS_IDENTICAL expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr T_IS_NOT_IDENTICAL expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr T_IS_EQUAL expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr T_IS_NOT_EQUAL expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr '<' expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr T_IS_SMALLER_OR_EQUAL expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr '>' expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr T_IS_GREATER_OR_EQUAL expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr T_SPACESHIP expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| expr T_INSTANCEOF class_name_reference {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| parenthesis_expr
| new_expr
| expr '?' expr ':' expr {
    $$ = NNEW(n_TERNARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
    $$->appendChild(NTYPE($4, n_OPERATOR));
    $$->appendChild($5);
  }
| expr '?' ':' expr {
    $$ = NNEW(n_TERNARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild(NNEW(n_EMPTY));
    $$->appendChild(NTYPE($3, n_OPERATOR));
    $$->appendChild($4);
  }
| expr T_COALESCE expr {
    $$ = NNEW(n_BINARY_EXPRESSION);
    $$->appendChild($1);
    $$->appendChild(NTYPE($2, n_OPERATOR));
    $$->appendChild($3);
  }
| internal_functions_in_yacc
| T_INT_CAST expr {
    $$ = NNEW(n_CAST_EXPRESSION);
    $$->appendChild(NTYPE($1, n_CAST));
    $$->appendChild($2);
  }
| T_DOUBLE_CAST expr {
    $$ = NNEW(n_CAST_EXPRESSION);
    $$->appendChild(NTYPE($1, n_CAST));
    $$->appendChild($2);
  }
| T_STRING_CAST expr {
    $$ = NNEW(n_CAST_EXPRESSION);
    $$->appendChild(NTYPE($1, n_CAST));
    $$->appendChild($2);
  }
| T_ARRAY_CAST expr {
    $$ = NNEW(n_CAST_EXPRESSION);
    $$->appendChild(NTYPE($1, n_CAST));
    $$->appendChild($2);
  }
| T_OBJECT_CAST expr {
    $$ = NNEW(n_CAST_EXPRESSION);
    $$->appendChild(NTYPE($1, n_CAST));
    $$->appendChild($2);
  }
| T_BOOL_CAST expr {
    $$ = NNEW(n_CAST_EXPRESSION);
    $$->appendChild(NTYPE($1, n_CAST));
    $$->appendChild($2);
  }
| T_UNSET_CAST expr {
    $$ = NNEW(n_CAST_EXPRESSION);
    $$->appendChild(NTYPE($1, n_CAST));
    $$->appendChild($2);
  }
| T_EXIT exit_expr {
    $$ = NNEW(n_UNARY_PREFIX_EXPRESSION);
    $$->appendChild(NTYPE($1, n_OPERATOR));
    $$->appendChild($2);
  }
| '@' expr {
    $$ = NNEW(n_UNARY_PREFIX_EXPRESSION);
    $$->appendChild(NTYPE($1, n_OPERATOR));
    $$->appendChild($2);
  }
| T_BACKTICKS_EXPR {
    NTYPE($1, n_BACKTICKS_EXPRESSION);
    $$ = $1;
  }
| scalar
| combined_scalar_offset
| combined_scalar
| T_PRINT expr {
    $$ = NNEW(n_UNARY_PREFIX_EXPRESSION);
    $$->appendChild(NTYPE($1, n_OPERATOR));
    $$->appendChild($2);
  }
| T_YIELD {
    NTYPE($1, n_YIELD);
    $1->appendChild(NNEW(n_EMPTY));
    $1->appendChild(NNEW(n_EMPTY));
    $$ = $1;
  }
| function is_reference
  '(' parameter_list ')'
  lexical_vars return_type
  '{' inner_statement_list '}' {
    NSPAN($1, n_FUNCTION_DECLARATION, $9);
    $1->appendChild(NNEW(n_EMPTY));
    $1->appendChild($2);
    $1->appendChild(NNEW(n_EMPTY));
    $1->appendChild(NEXPAND($3, $4, $5));
    $1->appendChild($6);
    $1->appendChild($7);
    $1->appendChild(NEXPAND($8, $9, $10));

    $$ = $1;
  }
| T_STATIC function is_reference
  '(' parameter_list ')'
  lexical_vars return_type
  '{' inner_statement_list '}' {
    NSPAN($2, n_FUNCTION_DECLARATION, $10);
    NMORE($2, $1);

    $$ = NNEW(n_FUNCTION_MODIFIER_LIST);
    $$->appendChild(NTYPE($1, n_STRING));
    $2->appendChild($1);

    $2->appendChild(NNEW(n_EMPTY));
    $2->appendChild($3);
    $2->appendChild(NNEW(n_EMPTY));
    $2->appendChild(NEXPAND($4, $5, $6));
    $2->appendChild($7);
    $2->appendChild($8);
    $2->appendChild(NEXPAND($9, $10, $11));

    $$ = $2;
  }
;

yield_expr:
  T_YIELD expr_without_variable {
    NTYPE($1, n_YIELD);
    $2->appendChild(NNEW(n_EMPTY));
    $1->appendChild($2);
    $$ = $1;
  }
| T_YIELD variable {
    NTYPE($1, n_YIELD);
    $2->appendChild(NNEW(n_EMPTY));
    $1->appendChild($2);
    $$ = $1;
  }
|  T_YIELD expr T_DOUBLE_ARROW expr_without_variable {
    NTYPE($1, n_YIELD);
    $1->appendChild($2);
    $1->appendChild($4);
    $$ = $1;
  }
|  T_YIELD expr T_DOUBLE_ARROW variable {
    NTYPE($1, n_YIELD);
    $1->appendChild($2);
    $1->appendChild($4);
    $$ = $1;
  }
;

function:
  T_FUNCTION
;

lexical_vars:
  /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
| T_USE '(' lexical_var_list ')' {
    NTYPE($1, n_LEXICAL_VARIABLE_LIST);
    $1->appendChildren($3);
    $$ = $1;
  }
;

lexical_var_list:
  lexical_var_list ',' T_VARIABLE {
    $$ = $1->appendChild(NTYPE($3, n_VARIABLE));
  }
| lexical_var_list ',' '&' T_VARIABLE {
    NTYPE($3, n_VARIABLE_REFERENCE);
    $3->appendChild(NTYPE($4, n_VARIABLE));
    $$ = $1->appendChild($3);
  }
| T_VARIABLE {
    $$ = NNEW(n_LEXICAL_VARIABLE_LIST);
    $$->appendChild(NTYPE($1, n_VARIABLE));
  }
| '&' T_VARIABLE {
    NTYPE($1, n_VARIABLE_REFERENCE);
    $1->appendChild(NTYPE($2, n_VARIABLE));
    $$ = NNEW(n_LEXICAL_VARIABLE_LIST);
    $$->appendChild($1);
  }
;

function_call:
  namespace_name '(' function_call_parameter_list ')' {
    $$ = NNEW(n_FUNCTION_CALL);
    $$->appendChild($1);
    $$->appendChild(NEXPAND($2, $3, $4));
  }
| T_NAMESPACE T_NS_SEPARATOR namespace_name
  '(' function_call_parameter_list ')' {
    NMORE($3, $1);
    $$ = NNEW(n_FUNCTION_CALL);
    $$->appendChild($3);
    $$->appendChild(NEXPAND($4, $5, $6));
  }
| T_NS_SEPARATOR namespace_name '(' function_call_parameter_list ')' {
    NMORE($2, $1);
    $$ = NNEW(n_FUNCTION_CALL);
    $$->appendChild($2);
    $$->appendChild(NEXPAND($3, $4, $5));
  }
| class_name T_PAAMAYIM_NEKUDOTAYIM T_STRING
  '(' function_call_parameter_list ')' {
    $$ = NNEW(n_CLASS_STATIC_ACCESS);
    $$->appendChild($1);
    $$->appendChild(NTYPE($3, n_STRING));

    $$ = NNEW(n_FUNCTION_CALL)->appendChild($$);
    $$->appendChild(NEXPAND($4, $5, $6));
  }
| variable_class_name T_PAAMAYIM_NEKUDOTAYIM T_STRING
  '(' function_call_parameter_list ')' {
    $$ = NNEW(n_CLASS_STATIC_ACCESS);
    $$->appendChild($1);
    $$->appendChild(NTYPE($3, n_STRING));

    $$ = NNEW(n_FUNCTION_CALL)->appendChild($$);
    $$->appendChild(NEXPAND($4, $5, $6));
  }
| variable_class_name T_PAAMAYIM_NEKUDOTAYIM variable_without_objects
  '(' function_call_parameter_list ')' {
    $$ = NNEW(n_CLASS_STATIC_ACCESS);
    $$->appendChild($1);
    $$->appendChild(NTYPE($3, n_STRING));

    $$ = NNEW(n_FUNCTION_CALL)->appendChild($$);
    $$->appendChild(NEXPAND($4, $5, $6));
  }
| class_name T_PAAMAYIM_NEKUDOTAYIM variable_without_objects
  '(' function_call_parameter_list ')' {
    $$ = NNEW(n_CLASS_STATIC_ACCESS);
    $$->appendChild($1);
    $$->appendChild(NTYPE($3, n_STRING));

    $$ = NNEW(n_FUNCTION_CALL)->appendChild($$);
    $$->appendChild(NEXPAND($4, $5, $6));
  }
| variable_without_objects '(' function_call_parameter_list ')' {
    $$ = NNEW(n_FUNCTION_CALL);
    $$->appendChild($1);
    $$->appendChild(NEXPAND($2, $3, $4));
  }
;

class_name:
  T_STATIC {
    $$ = NTYPE($1, n_CLASS_NAME);
  }
| namespace_name {
    $$ = NTYPE($1, n_CLASS_NAME);
  }
| T_NAMESPACE T_NS_SEPARATOR namespace_name {
    NMORE($3, $1);
    $$ = NTYPE($3, n_CLASS_NAME);
  }
| T_NS_SEPARATOR namespace_name {
    NMORE($2, $1);
    $$ = NTYPE($2, n_CLASS_NAME);
  }
;

fully_qualified_class_name:
  namespace_name {
    $$ = NTYPE($1, n_CLASS_NAME);
  }
| T_NAMESPACE T_NS_SEPARATOR namespace_name {
    NMORE($3, $1);
    $$ = NTYPE($3, n_CLASS_NAME);
  }
| T_NS_SEPARATOR namespace_name {
    NMORE($2, $1);
    $$ = NTYPE($2, n_CLASS_NAME);
  }
;

class_name_reference:
  class_name
| dynamic_class_name_reference
;

dynamic_class_name_reference:
  base_variable
  T_OBJECT_OPERATOR
  object_property
  dynamic_class_name_variable_properties {
    $$ = NNEW(n_OBJECT_PROPERTY_ACCESS);
    $$->appendChild($1);
    $$->appendChild($3);
    for (xhpast::node_list_t::iterator ii = $4->children.begin();
      ii != $4->children.end();
      ++ii) {

      $$ = NNEW(n_OBJECT_PROPERTY_ACCESS)->appendChild($$);
      $$->appendChild(*ii);
    }
  }
| base_variable
;

dynamic_class_name_variable_properties:
  dynamic_class_name_variable_properties dynamic_class_name_variable_property {
    $$ = $1->appendChild($2);
  }
| /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
;

dynamic_class_name_variable_property:
  T_OBJECT_OPERATOR object_property {
    $$ = $2;
  }
;

exit_expr:
  /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
| '(' ')' {
    NSPAN($1, n_EMPTY, $2);
    $$ = $1;
  }
| '(' expr ')' {
    NSPAN($1, n_PARENTHETICAL_EXPRESSION, $3);
    $1->appendChild($2);
    $$ = $1;
  }
;

ctor_arguments:
  /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
| '(' function_call_parameter_list ')' {
    $$ = NEXPAND($1, $2, $3);
  }
;

common_scalar:
  T_LNUMBER {
    $$ = NTYPE($1, n_NUMERIC_SCALAR);
  }
| T_DNUMBER {
    $$ = NTYPE($1, n_NUMERIC_SCALAR);
  }
| T_CONSTANT_ENCAPSED_STRING {
    $$ = NTYPE($1, n_STRING_SCALAR);
  }
| T_LINE {
    $$ = NTYPE($1, n_MAGIC_SCALAR);
  }
| T_FILE {
    $$ = NTYPE($1, n_MAGIC_SCALAR);
  }
| T_DIR {
    $$ = NTYPE($1, n_MAGIC_SCALAR);
  }
| T_CLASS_C {
    $$ = NTYPE($1, n_MAGIC_SCALAR);
  }
| T_METHOD_C {
    $$ = NTYPE($1, n_MAGIC_SCALAR);
  }
| T_TRAIT_C {
    $$ = NTYPE($1, n_MAGIC_SCALAR);
  }
| T_FUNC_C {
    $$ = NTYPE($1, n_MAGIC_SCALAR);
  }
| T_NS_C {
    $$ = NTYPE($1, n_MAGIC_SCALAR);
  }
| T_HEREDOC {
    $$ = NTYPE($1, n_HEREDOC);
  }
;

static_scalar: /* compile-time evaluated scalars */
  common_scalar
| namespace_name
| T_NAMESPACE T_NS_SEPARATOR namespace_name {
    NMORE($3, $1);
    $$ = $3;
  }
| T_NS_SEPARATOR namespace_name {
    NMORE($2, $1);
    $$ = $2;
  }
| '+' static_scalar {
    $$ = NNEW(n_UNARY_PREFIX_EXPRESSION);
    $$->appendChild(NTYPE($1, n_OPERATOR));
    $$->appendChild($2);
  }
| '-' static_scalar {
    $$ = NNEW(n_UNARY_PREFIX_EXPRESSION);
    $$->appendChild(NTYPE($1, n_OPERATOR));
    $$->appendChild($2);
  }
| T_ARRAY '(' static_array_pair_list ')' {
    NTYPE($1, n_ARRAY_LITERAL);
    $1->appendChild(NEXPAND($2, $3, $4));
    $$ = $1;
  }
| '[' static_array_pair_list ']' {
    NTYPE($1, n_ARRAY_LITERAL);
    $1->appendChild(NEXPAND($1, $2, $3));
    $$ = $1;
  }
| static_class_constant
;

static_class_constant:
  class_name T_PAAMAYIM_NEKUDOTAYIM T_STRING {
    $$ = NNEW(n_CLASS_STATIC_ACCESS);
    $$->appendChild($1);
    $$->appendChild(NTYPE($3, n_STRING));
  }
;

scalar:
  T_STRING_VARNAME
| class_constant
| namespace_name
| T_NAMESPACE T_NS_SEPARATOR namespace_name {
    $$ = NMORE($3, $1);
  }
| T_NS_SEPARATOR namespace_name {
    $$ = NMORE($2, $1);
  }
| common_scalar
;

static_array_pair_list:
  /* empty */ {
    $$ = NNEW(n_ARRAY_VALUE_LIST);
  }
| non_empty_static_array_pair_list possible_comma {
    $$ = NMORE($1, $2);
  }
;

possible_comma:
  /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
| ','
;

non_empty_static_array_pair_list:
  non_empty_static_array_pair_list
  ','
  static_scalar
  T_DOUBLE_ARROW
  static_scalar {
    $$ = NNEW(n_ARRAY_VALUE);
    $$->appendChild($3);
    $$->appendChild($5);

    $$ = $1->appendChild($$);
  }
| non_empty_static_array_pair_list ',' static_scalar {
    $$ = NNEW(n_ARRAY_VALUE);
    $$->appendChild(NNEW(n_EMPTY));
    $$->appendChild($3);

    $$ = $1->appendChild($$);
  }
| static_scalar T_DOUBLE_ARROW static_scalar {
    $$ = NNEW(n_ARRAY_VALUE);
    $$->appendChild($1);
    $$->appendChild($3);

    $$ = NNEW(n_ARRAY_VALUE_LIST)->appendChild($$);
  }
| static_scalar {
    $$ = NNEW(n_ARRAY_VALUE);
    $$->appendChild(NNEW(n_EMPTY));
    $$->appendChild($1);

    $$ = NNEW(n_ARRAY_VALUE_LIST)->appendChild($$);
  }
;

expr:
  r_variable
| expr_without_variable
;

r_variable:
  variable
;

w_variable:
  variable
;

rw_variable:
  variable
;

variable:
  base_variable_with_function_calls
  T_OBJECT_OPERATOR
  object_property method_or_not
  variable_properties {
    $$ = NNEW(n_OBJECT_PROPERTY_ACCESS);
    $$->appendChild($1);
    $$->appendChild($3);

    if ($4->type != n_EMPTY) {
      $$ = NNEW(n_METHOD_CALL)->appendChild($$);
      $$->appendChild($4);
    }

    for (xhpast::node_list_t::iterator ii = $5->children.begin();
      ii != $5->children.end();
      ++ii) {

      if ((*ii)->type == n_CALL_PARAMETER_LIST) {
        $$ = NNEW(n_METHOD_CALL)->appendChild($$);
        $$->appendChild((*ii));
      } else {
        $$ = NNEW(n_OBJECT_PROPERTY_ACCESS)->appendChild($$);
        $$->appendChild((*ii));
      }
    }
  }
| base_variable_with_function_calls
;

variable_properties:
  variable_properties variable_property {
    $$ = $1->appendChildren($2);
  }
| /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
;

variable_property:
  T_OBJECT_OPERATOR object_property method_or_not {
    $$ = NNEW(n_EMPTY);
    $$->appendChild($2);
    if ($3->type != n_EMPTY) {
      $$->appendChild($3);
    }
  }
;

array_method_dereference:
  array_method_dereference '[' dim_offset ']' {
    $$ = NNEW(n_INDEX_ACCESS);
    $$->appendChild($1);
    $$->appendChild($3);
    NMORE($$, $4);
  }
| method '[' dim_offset ']' {
    $$ = NNEW(n_INDEX_ACCESS);
    $$->appendChild($1);
    $$->appendChild($3);
    NMORE($$, $4);
  }
;

method:
  '(' function_call_parameter_list ')' {
    $$ = NEXPAND($1, $2, $3);
  }
;

method_or_not:
  method
| array_method_dereference
| /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
;

variable_without_objects:
  reference_variable
| simple_indirect_reference reference_variable {
    xhpast::Node *last = $1;
    NMORE($1, $2);
    while (last->firstChild() &&
           last->firstChild()->type == n_VARIABLE_VARIABLE) {
      NMORE(last, $2);
      last = last->firstChild();
    }
    last->appendChild($2);

    $$ = $1;
  }
;

static_member:
  class_name T_PAAMAYIM_NEKUDOTAYIM variable_without_objects {
    $$ = NNEW(n_CLASS_STATIC_ACCESS);
    $$->appendChild($1);
    $$->appendChild($3);
  }
| variable_class_name T_PAAMAYIM_NEKUDOTAYIM variable_without_objects {
    $$ = NNEW(n_CLASS_STATIC_ACCESS);
    $$->appendChild($1);
    $$->appendChild($3);
  }
;

variable_class_name:
  reference_variable
;

array_function_dereference:
  array_function_dereference '[' dim_offset ']' {
    $$ = NNEW(n_INDEX_ACCESS);
    $$->appendChild($1);
    $$->appendChild($3);
    NMORE($$, $4);
  }
| function_call '[' dim_offset ']' {
    $$ = NNEW(n_INDEX_ACCESS);
    $$->appendChild($1);
    $$->appendChild($3);
    NMORE($$, $4);
  }
;

base_variable_with_function_calls:
  base_variable
| array_function_dereference
| function_call
;

base_variable:
  reference_variable
| '(' new_expr ')' {
    $$ = NEXPAND($1, $2, $3);
  }
| simple_indirect_reference reference_variable {
    xhpast::Node *last = $1;
    NMORE($1, $2);
    while (last->firstChild() &&
           last->firstChild()->type == n_VARIABLE_VARIABLE) {
      NMORE(last, $2);
      last = last->firstChild();
    }
    last->appendChild($2);

    $$ = $1;
  }
| static_member
;

reference_variable:
  reference_variable '[' dim_offset ']' {
    $$ = NNEW(n_INDEX_ACCESS);
    $$->appendChild($1);
    $$->appendChild($3);
    NMORE($$, $4);
  }
| reference_variable '{' expr '}' {
    $$ = NNEW(n_INDEX_ACCESS);
    $$->appendChild($1);
    $$->appendChild($3);
    NMORE($$, $4);
  }
| compound_variable
;

compound_variable:
  T_VARIABLE {
    NTYPE($1, n_VARIABLE);
  }
| '$' '{' expr '}' {
    NSPAN($1, n_VARIABLE_EXPRESSION, $4);
    $1->appendChild($3);
    $$ = $1;
  }
;

dim_offset:
  /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
| expr {
    $$ = $1;
  }
;

object_property:
  object_dim_list
| variable_without_objects
;

object_dim_list:
  object_dim_list '[' dim_offset ']' {
    $$ = NNEW(n_INDEX_ACCESS);
    $$->appendChild($1);
    $$->appendChild($3);
    NMORE($$, $4);
  }
| object_dim_list '{' expr '}' {
    $$ = NNEW(n_INDEX_ACCESS);
    $$->appendChild($1);
    $$->appendChild($3);
    NMORE($$, $4);
  }
| variable_name
;

variable_name:
  T_STRING {
    NTYPE($1, n_STRING);
    $$ = $1;
  }
| '{' expr '}' {
  $$ = NEXPAND($1, $2, $3);
  }
;

simple_indirect_reference:
  '$' {
    $$ = NTYPE($1, n_VARIABLE_VARIABLE);
  }
| simple_indirect_reference '$' {
    $2 = NTYPE($2, n_VARIABLE_VARIABLE);

    xhpast::Node *last = $1;
    while (last->firstChild() &&
           last->firstChild()->type == n_VARIABLE_VARIABLE) {
      last = last->firstChild();
    }
    last->appendChild($2);

    $$ = $1;
  }
;

assignment_list:
  assignment_list ',' assignment_list_element {
    $$ = $1->appendChild($3);
  }
| assignment_list_element {
    $$ = NNEW(n_ASSIGNMENT_LIST);
    $$->appendChild($1);
  }
;

assignment_list_element:
  variable
| T_LIST '(' assignment_list ')' {
    $$ = NNEW(n_LIST);
    $$->appendChild(NEXPAND($2, $3, $4));
  }
| /* empty */ {
    $$ = NNEW(n_EMPTY);
  }
;

array_pair_list:
  /* empty */ {
    $$ = NNEW(n_ARRAY_VALUE_LIST);
  }
| non_empty_array_pair_list possible_comma {
    $$ = NMORE($1, $2);
  }
;

non_empty_array_pair_list:
  non_empty_array_pair_list ',' expr T_DOUBLE_ARROW expr {
    $$ = NNEW(n_ARRAY_VALUE);
    $$->appendChild($3);
    $$->appendChild($5);

    $$ = $1->appendChild($$);
  }
| non_empty_array_pair_list ',' expr {
    $$ = NNEW(n_ARRAY_VALUE);
    $$->appendChild(NNEW(n_EMPTY));
    $$->appendChild($3);

    $$ = $1->appendChild($$);
  }
| expr T_DOUBLE_ARROW expr {
    $$ = NNEW(n_ARRAY_VALUE);
    $$->appendChild($1);
    $$->appendChild($3);

    $$ = NNEW(n_ARRAY_VALUE_LIST)->appendChild($$);
  }
| expr {
    $$ = NNEW(n_ARRAY_VALUE);
    $$->appendChild(NNEW(n_EMPTY));
    $$->appendChild($1);

    $$ = NNEW(n_ARRAY_VALUE_LIST)->appendChild($$);
  }
| non_empty_array_pair_list ',' expr T_DOUBLE_ARROW '&' w_variable {
    $$ = NNEW(n_ARRAY_VALUE);
    $$->appendChild($3);
    $$->appendChild(NTYPE($5, n_VARIABLE_REFERENCE)->appendChild($6));

    $$ = $1->appendChild($$);
  }
| non_empty_array_pair_list ',' '&' w_variable {
    $$ = NNEW(n_ARRAY_VALUE);
    $$->appendChild(NNEW(n_EMPTY));
    $$->appendChild(NTYPE($3, n_VARIABLE_REFERENCE)->appendChild($4));

    $$ = $1->appendChild($$);
  }
| expr T_DOUBLE_ARROW '&' w_variable {
    $$ = NNEW(n_ARRAY_VALUE);
    $$->appendChild($1);
    $$->appendChild(NTYPE($3, n_VARIABLE_REFERENCE)->appendChild($4));

    $$ = NNEW(n_ARRAY_VALUE_LIST)->appendChild($$);
  }
| '&' w_variable {
    $$ = NNEW(n_ARRAY_VALUE);
    $$->appendChild(NNEW(n_EMPTY));
    $$->appendChild(NTYPE($1, n_VARIABLE_REFERENCE)->appendChild($2));

    $$ = NNEW(n_ARRAY_VALUE_LIST)->appendChild($$);
  }
;

internal_functions_in_yacc:
  T_ISSET '(' isset_variables ')' {
    NTYPE($1, n_SYMBOL_NAME);

    NSPAN($2, n_CALL_PARAMETER_LIST, $4);
    $2->appendChildren($3);

    $$ = NNEW(n_FUNCTION_CALL);
    $$->appendChild($1);
    $$->appendChild($2);
  }
| T_EMPTY '(' variable ')' {
    NTYPE($1, n_SYMBOL_NAME);

    NSPAN($2, n_CALL_PARAMETER_LIST, $4);
    $2->appendChild($3);

    $$ = NNEW(n_FUNCTION_CALL);
    $$->appendChild($1);
    $$->appendChild($2);
  }
| T_INCLUDE expr {
    $$ = NTYPE($1, n_INCLUDE_FILE)->appendChild($2);
  }
| T_INCLUDE_ONCE expr {
    $$ = NTYPE($1, n_INCLUDE_FILE)->appendChild($2);
  }
| T_EVAL '(' expr ')' {
    NTYPE($1, n_SYMBOL_NAME);

    NSPAN($2, n_CALL_PARAMETER_LIST, $4);
    $2->appendChild($3);

    $$ = NNEW(n_FUNCTION_CALL);
    $$->appendChild($1);
    $$->appendChild($2);
  }
| T_REQUIRE expr {
    $$ = NTYPE($1, n_INCLUDE_FILE)->appendChild($2);
  }
| T_REQUIRE_ONCE expr {
    $$ = NTYPE($1, n_INCLUDE_FILE)->appendChild($2);
  }
;

isset_variables:
  variable {
    $$ = NNEW(n_EMPTY);
    $$->appendChild($1);
  }
| isset_variables ',' variable {
    $$ = $1->appendChild($3);
  }
;

parenthesis_expr:
  '(' expr ')' {
    NSPAN($1, n_PARENTHETICAL_EXPRESSION, $3);
    $1->appendChild($2);
    $$ = $1;
  }
| '(' yield_expr ')' {
    $$ = NEXPAND($1, $2, $3);
  }
;

combined_scalar_offset:
  combined_scalar '[' dim_offset ']' {
    $$ = NNEW(n_INDEX_ACCESS);
    $$->appendChild($1);
    $$->appendChild($3);
    NMORE($$, $4);
  }
| combined_scalar_offset '[' dim_offset ']' {
    $$ = NNEW(n_INDEX_ACCESS);
    $$->appendChild($1);
    $$->appendChild($3);
    NMORE($$, $4);
  }
| T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']' {
    $$ = NNEW(n_INDEX_ACCESS);
    $$->appendChild(NTYPE($1, n_STRING_SCALAR));
    $$->appendChild($3);
    NMORE($$, $4);
  }
| class_constant '[' dim_offset ']' {
    $$ = NNEW(n_INDEX_ACCESS);
    $$->appendChild($1);
    $$->appendChild($3);
    NMORE($$, $4);
  }
| T_STRING '[' dim_offset ']' {
    $$ = NNEW(n_INDEX_ACCESS);
    $$->appendChild(NTYPE($1, n_STRING));
    $$->appendChild($3);
    NMORE($$, $4);
  }
;

combined_scalar:
  T_ARRAY '(' array_pair_list ')' {
    NTYPE($1, n_ARRAY_LITERAL);
    $1->appendChild(NEXPAND($2, $3, $4));
    $$ = $1;
  }
| '[' array_pair_list ']' {
    NTYPE($1, n_ARRAY_LITERAL);
    $1->appendChild(NEXPAND($1, $2, $3));
    $$ = $1;
  }
;

new_expr:
  T_NEW class_name_reference ctor_arguments {
    NTYPE($1, n_NEW);
    $1->appendChild($2);
    $1->appendChild($3);
    $$ = $1;
  }
| T_NEW T_CLASS ctor_arguments extends_from implements_list
  '{' class_statement_list '}' {
    $$ = NNEW(n_CLASS_DECLARATION);
    $$->appendChild(NNEW(n_EMPTY));
    $$->appendChild(NNEW(n_EMPTY));
    $$->appendChild($4);
    $$->appendChild($5);
    $$->appendChild(NEXPAND($6, $7, $8));
    NMORE($$, $8);

    NTYPE($1, n_NEW);
    $1->appendChild($$);
    $1->appendChild($3);
    $$ = $1;
  }
;

class_constant:
  class_name T_PAAMAYIM_NEKUDOTAYIM T_STRING {
    $$ = NNEW(n_CLASS_STATIC_ACCESS);
    $$->appendChild($1);
    $$->appendChild(NTYPE($3, n_STRING));
  }
| variable_class_name T_PAAMAYIM_NEKUDOTAYIM T_STRING {
    $$ = NNEW(n_CLASS_STATIC_ACCESS);
    $$->appendChild($1);
    $$->appendChild(NTYPE($3, n_STRING));
  }
;

%%

const char* yytokname(int tok) {
  if (tok < 255) {
    return NULL;
  }
  return yytname[YYTRANSLATE(tok)];
}
