whole_func_string: function() {
      switch (this.token) {
        case "{":
          return this.read_code_block(false);
  
        case this.tok.T_IF:
          return this.read_if();
  
        case this.tok.T_SWITCH:
          return this.read_switch();
  
        case this.tok.T_FOR:
          return this.read_for();
  
        case this.tok.T_FOREACH:
          return this.read_foreach();
  
        case this.tok.T_WHILE:
          return this.read_while();
  
        case this.tok.T_DO:
          return this.read_do();
  
        case this.tok.T_COMMENT:
          return this.read_comment();
  
        case this.tok.T_DOC_COMMENT:
          return this.read_doc_comment();
  
        case this.tok.T_RETURN: {
          const result = this.node("return");
          let expr = null;
          if (!this.next().is("EOS")) {
            expr = this.read_expr();
          }
          this.expectEndOfStatement();
          return result(expr);
        }
  
        // https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L429
        case this.tok.T_BREAK:
        case this.tok.T_CONTINUE: {
          const result = this.node(
            this.token === this.tok.T_CONTINUE ? "continue" : "break"
          );
          let level = null;
          this.next(); // look ahead
          if (this.token !== ";") {
            level = this.read_expr();
          }
          this.expectEndOfStatement();
          return result(level);
        }
  
        case this.tok.T_GLOBAL: {
          const result = this.node("global");
          const items = this.next().read_list(this.read_simple_variable, ",");
          this.expectEndOfStatement();
          return result(items);
        }
  
        case this.tok.T_STATIC: {
          const current = [this.token, this.lexer.getState()];
          const result = this.node();
          if (this.next().token === this.tok.T_DOUBLE_COLON) {
            // static keyword for a class
            this.lexer.tokens.push(current);
            const expr = this.next().read_expr();
            this.expectEndOfStatement(expr);
            return result("expressionstatement", expr);
          }
          if (this.token === this.tok.T_FUNCTION) {
            return this.read_function(true, [0, 1, 0]);
          }
          const items = this.read_variable_declarations();
          this.expectEndOfStatement();
          return result("static", items);
        }
  
        case this.tok.T_ECHO: {
          const result = this.node("echo");
          const text = this.text();
          const shortForm = text === "<?=" || text === "<%=";
          const expressions = this.next().read_list(this.read_expr, ",");
          this.expectEndOfStatement();
          return result(expressions, shortForm);
        }
  
        case this.tok.T_INLINE_HTML: {
          const value = this.text();
          let prevChar =
            this.lexer.yylloc.first_offset > 0
              ? this.lexer._input[this.lexer.yylloc.first_offset - 1]
              : null;
          const fixFirstLine = prevChar === "\r" || prevChar === "\n";
          // revert back the first stripped line
          if (fixFirstLine) {
            if (
              prevChar === "\n" &&
              this.lexer.yylloc.first_offset > 1 &&
              this.lexer._input[this.lexer.yylloc.first_offset - 2] === "\r"
            ) {
              prevChar = "\r\n";
            }
          }
          const result = this.node("inline");
          this.next();
          return result(value, fixFirstLine ? prevChar + value : value);
        }
  
        case this.tok.T_UNSET: {
          const result = this.node("unset");
          this.next().expect("(") && this.next();
          const variables = this.read_list(this.read_variable, ",");
          this.expect(")") && this.next();
          this.expect(";") && this.next();
          return result(variables);
        }
  
        case this.tok.T_DECLARE: {
          const result = this.node("declare");
          const body = [];
          let mode;
          this.next().expect("(") && this.next();
          const directives = this.read_declare_list();
          this.expect(")") && this.next();
          if (this.token === ":") {
            this.next();
            while (
              this.token != this.EOF &&
              this.token !== this.tok.T_ENDDECLARE
            ) {
              // @todo : check declare_statement from php / not valid
              body.push(this.read_top_statement());
            }
            this.expect(this.tok.T_ENDDECLARE) && this.next();
            this.expectEndOfStatement();
            mode = this.ast.declare.MODE_SHORT;
          } else if (this.token === "{") {
            this.next();
            while (this.token != this.EOF && this.token !== "}") {
              // @todo : check declare_statement from php / not valid
              body.push(this.read_top_statement());
            }
            this.expect("}") && this.next();
            mode = this.ast.declare.MODE_BLOCK;
          } else {
            this.expect(";") && this.next();
            mode = this.ast.declare.MODE_NONE;
          }
          return result(directives, body, mode);
        }
  
        case this.tok.T_TRY:
          return this.read_try();
  
        case this.tok.T_THROW: {
          const result = this.node("throw");
          const expr = this.next().read_expr();
          this.expectEndOfStatement();
          return result(expr);
        }
  
        // ignore this (extra ponctuation)
        case ";": {
          this.next();
          return null;
        }
  
        case this.tok.T_STRING: {
          const result = this.node();
          const current = [this.token, this.lexer.getState()];
          const label = this.text();
          // AST : https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L457
          if (this.next().token === ":") {
            this.next();
            return result("label", label);
          }
  
          // default fallback expr / T_STRING '::' (etc...)
          result.destroy();
          this.lexer.tokens.push(current);
          const statement = this.node("expressionstatement");
          const expr = this.next().read_expr();
          this.expectEndOfStatement(expr);
          return statement(expr);
        }
  
        case this.tok.T_GOTO: {
          const result = this.node("goto");
          let label = null;
          if (this.next().expect(this.tok.T_STRING)) {
            label = this.text();
            this.next().expectEndOfStatement();
          }
          return result(label);
        }
  
        default: {
          // default fallback expr
          const statement = this.node("expressionstatement");
          const expr = this.read_expr();
          this.expectEndOfStatement(expr);
          return statement(expr);
        }
      }
    }
func_code_string: function() {
      switch (this.token) {
        case "{":
          return this.read_code_block(false);
  
        case this.tok.T_IF:
          return this.read_if();
  
        case this.tok.T_SWITCH:
          return this.read_switch();
  
        case this.tok.T_FOR:
          return this.read_for();
  
        case this.tok.T_FOREACH:
          return this.read_foreach();
  
        case this.tok.T_WHILE:
          return this.read_while();
  
        case this.tok.T_DO:
          return this.read_do();
  
        case this.tok.T_COMMENT:
          return this.read_comment();
  
        case this.tok.T_DOC_COMMENT:
          return this.read_doc_comment();
  
        case this.tok.T_RETURN: {
          const result = this.node("return");
          let expr = null;
          if (!this.next().is("EOS")) {
            expr = this.read_expr();
          }
          this.expectEndOfStatement();
          return result(expr);
        }
  
        // https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L429
        case this.tok.T_BREAK:
        case this.tok.T_CONTINUE: {
          const result = this.node(
            this.token === this.tok.T_CONTINUE ? "continue" : "break"
          );
          let level = null;
          this.next(); // look ahead
          if (this.token !== ";") {
            level = this.read_expr();
          }
          this.expectEndOfStatement();
          return result(level);
        }
  
        case this.tok.T_GLOBAL: {
          const result = this.node("global");
          const items = this.next().read_list(this.read_simple_variable, ",");
          this.expectEndOfStatement();
          return result(items);
        }
  
        case this.tok.T_STATIC: {
          const current = [this.token, this.lexer.getState()];
          const result = this.node();
          if (this.next().token === this.tok.T_DOUBLE_COLON) {
            // static keyword for a class
            this.lexer.tokens.push(current);
            const expr = this.next().read_expr();
            this.expectEndOfStatement(expr);
            return result("expressionstatement", expr);
          }
          if (this.token === this.tok.T_FUNCTION) {
            return this.read_function(true, [0, 1, 0]);
          }
          const items = this.read_variable_declarations();
          this.expectEndOfStatement();
          return result("static", items);
        }
  
        case this.tok.T_ECHO: {
          const result = this.node("echo");
          const text = this.text();
          const shortForm = text === "<?=" || text === "<%=";
          const expressions = this.next().read_list(this.read_expr, ",");
          this.expectEndOfStatement();
          return result(expressions, shortForm);
        }
  
        case this.tok.T_INLINE_HTML: {
          const value = this.text();
          let prevChar =
            this.lexer.yylloc.first_offset > 0
              ? this.lexer._input[this.lexer.yylloc.first_offset - 1]
              : null;
          const fixFirstLine = prevChar === "\r" || prevChar === "\n";
          // revert back the first stripped line
          if (fixFirstLine) {
            if (
              prevChar === "\n" &&
              this.lexer.yylloc.first_offset > 1 &&
              this.lexer._input[this.lexer.yylloc.first_offset - 2] === "\r"
            ) {
              prevChar = "\r\n";
            }
          }
          const result = this.node("inline");
          this.next();
          return result(value, fixFirstLine ? prevChar + value : value);
        }
  
        case this.tok.T_UNSET: {
          const result = this.node("unset");
          this.next().expect("(") && this.next();
          const variables = this.read_list(this.read_variable, ",");
          this.expect(")") && this.next();
          this.expect(";") && this.next();
          return result(variables);
        }
  
        case this.tok.T_DECLARE: {
          const result = this.node("declare");
          const body = [];
          let mode;
          this.next().expect("(") && this.next();
          const directives = this.read_declare_list();
          this.expect(")") && this.next();
          if (this.token === ":") {
            this.next();
            while (
              this.token != this.EOF &&
              this.token !== this.tok.T_ENDDECLARE
            ) {
              // @todo : check declare_statement from php / not valid
              body.push(this.read_top_statement());
            }
            this.expect(this.tok.T_ENDDECLARE) && this.next();
            this.expectEndOfStatement();
            mode = this.ast.declare.MODE_SHORT;
          } else if (this.token === "{") {
            this.next();
            while (this.token != this.EOF && this.token !== "}") {
              // @todo : check declare_statement from php / not valid
              body.push(this.read_top_statement());
            }
            this.expect("}") && this.next();
            mode = this.ast.declare.MODE_BLOCK;
          } else {
            this.expect(";") && this.next();
            mode = this.ast.declare.MODE_NONE;
          }
          return result(directives, body, mode);
        }
  
        case this.tok.T_TRY:
          return this.read_try();
  
        case this.tok.T_THROW: {
          const result = this.node("throw");
          const expr = this.next().read_expr();
          this.expectEndOfStatement();
          return result(expr);
        }
  
        // ignore this (extra ponctuation)
        case ";": {
          this.next();
          return null;
        }
  
        case this.tok.T_STRING: {
          const result = this.node();
          const current = [this.token, this.lexer.getState()];
          const label = this.text();
          // AST : https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L457
          if (this.next().token === ":") {
            this.next();
            return result("label", label);
          }
  
          // default fallback expr / T_STRING '::' (etc...)
          result.destroy();
          this.lexer.tokens.push(current);
          const statement = this.node("expressionstatement");
          const expr = this.next().read_expr();
          this.expectEndOfStatement(expr);
          return statement(expr);
        }
  
        case this.tok.T_GOTO: {
          const result = this.node("goto");
          let label = null;
          if (this.next().expect(this.tok.T_STRING)) {
            label = this.text();
            this.next().expectEndOfStatement();
          }
          return result(label);
        }
  
        default: {
          // default fallback expr
          const statement = this.node("expressionstatement");
          const expr = this.read_expr();
          this.expectEndOfStatement(expr);
          return statement(expr);
        }
      }
    }
func_documentation_string: Reads statements