/*
 * Copyright 2016 WebAssembly Community Group participants
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

%{
#include <algorithm>
#include <cassert>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <utility>

#include "binary-error-handler.h"
#include "binary-reader.h"
#include "binary-reader-ir.h"
#include "literal.h"
#include "wast-parser.h"
#include "wast-parser-lexer-shared.h"

#define INVALID_VAR_INDEX (-1)

#define RELOCATE_STACK(type, array, stack_base, old_size, new_size)   \
  do {                                                                \
    type* new_stack = new type[new_size]();                           \
    std::move((stack_base), (stack_base) + (old_size), (new_stack));  \
    if ((stack_base) != (array)) {                                    \
      delete[](stack_base);                                           \
    } else {                                                          \
      for (size_t i = 0; i < (old_size); ++i) {                       \
        (stack_base)[i].~type();                                      \
      }                                                               \
    }                                                                 \
    /* Cache the pointer in the parser struct to be deleted later. */ \
    parser->array = (stack_base) = new_stack;                         \
  } while (0)

#define yyoverflow(message, ss, ss_size, vs, vs_size, ls, ls_size, new_size) \
  do {                                                                       \
    size_t old_size = *(new_size);                                           \
    *(new_size) *= 2;                                                        \
    RELOCATE_STACK(yytype_int16, yyssa, *(ss), old_size, *(new_size));       \
    RELOCATE_STACK(YYSTYPE, yyvsa, *(vs), old_size, *(new_size));            \
    RELOCATE_STACK(YYLTYPE, yylsa, *(ls), old_size, *(new_size));            \
  } while (0)

#define DUPTEXT(dst, src)                                \
  (dst).start = wabt_strndup((src).start, (src).length); \
  (dst).length = (src).length

#define YYLLOC_DEFAULT(Current, Rhs, N)                       \
  do                                                          \
    if (N) {                                                  \
      (Current).filename = YYRHSLOC(Rhs, 1).filename;         \
      (Current).line = YYRHSLOC(Rhs, 1).line;                 \
      (Current).first_column = YYRHSLOC(Rhs, 1).first_column; \
      (Current).last_column = YYRHSLOC(Rhs, N).last_column;   \
    } else {                                                  \
      (Current).filename = nullptr;                           \
      (Current).line = YYRHSLOC(Rhs, 0).line;                 \
      (Current).first_column = (Current).last_column =        \
          YYRHSLOC(Rhs, 0).last_column;                       \
    }                                                         \
  while (0)

#define APPEND_FIELD_TO_LIST(module, field, Kind, kind, loc_, item) \
  do {                                                              \
    field = append_module_field(module);                            \
    field->loc = loc_;                                              \
    field->type = ModuleFieldType::Kind;                            \
    field->kind = item;                                             \
  } while (0)

#define APPEND_ITEM_TO_VECTOR(module, kinds, item_ptr) \
  (module)->kinds.push_back(item_ptr)

#define INSERT_BINDING(module, kind, kinds, loc_, name) \
  do                                                    \
    if ((name).start) {                                 \
      (module)->kind##_bindings.emplace(                \
          string_slice_to_string(name),                 \
          Binding(loc_, (module)->kinds.size() - 1));   \
    }                                                   \
  while (0)

#define APPEND_INLINE_EXPORT(module, Kind, loc_, value, index_)         \
  do                                                                    \
    if ((value)->export_.has_export) {                                  \
      ModuleField* export_field;                                        \
      APPEND_FIELD_TO_LIST(module, export_field, Export, export_, loc_, \
                           (value)->export_.export_.release());         \
      export_field->export_->kind = ExternalKind::Kind;                 \
      export_field->export_->var.loc = loc_;                            \
      export_field->export_->var.index = index_;                        \
      APPEND_ITEM_TO_VECTOR(module, exports, export_field->export_);    \
      INSERT_BINDING(module, export, exports, export_field->loc,        \
                     export_field->export_->name);                      \
    }                                                                   \
  while (0)

#define CHECK_IMPORT_ORDERING(module, kind, kinds, loc_)            \
  do {                                                              \
    if ((module)->kinds.size() != (module)->num_##kind##_imports) { \
      wast_parser_error(                                            \
          &loc_, lexer, parser,                                     \
          "imports must occur before all non-import definitions");  \
    }                                                               \
  } while (0)

#define CHECK_END_LABEL(loc, begin_label, end_label)                       \
  do {                                                                     \
    if (!string_slice_is_empty(&(end_label))) {                            \
      if (string_slice_is_empty(&(begin_label))) {                         \
        wast_parser_error(&loc, lexer, parser,                             \
                          "unexpected label \"" PRIstringslice "\"",       \
                          WABT_PRINTF_STRING_SLICE_ARG(end_label));        \
      } else if (!string_slices_are_equal(&(begin_label), &(end_label))) { \
        wast_parser_error(&loc, lexer, parser,                             \
                          "mismatching label \"" PRIstringslice            \
                          "\" != \"" PRIstringslice "\"",                  \
                          WABT_PRINTF_STRING_SLICE_ARG(begin_label),       \
                          WABT_PRINTF_STRING_SLICE_ARG(end_label));        \
      }                                                                    \
      destroy_string_slice(&(end_label));                                  \
    }                                                                      \
  } while (0)

#define YYMALLOC(size) new char [size]
#define YYFREE(p) delete [] (p)

#define USE_NATURAL_ALIGNMENT (~0)

namespace wabt {

ExprList join_exprs1(Location* loc, Expr* expr1);
ExprList join_exprs2(Location* loc,
                         ExprList* expr1,
                         Expr* expr2);

Result parse_const(Type type,
                       LiteralType literal_type,
                       const char* s,
                       const char* end,
                       Const* out);
void dup_text_list(TextList* text_list, char** out_data, size_t* out_size);

bool is_empty_signature(const FuncSignature* sig);

void append_implicit_func_declaration(Location*,
                                      Module*,
                                      FuncDeclaration*);

class BinaryErrorHandlerModule : public BinaryErrorHandler {
 public:
  BinaryErrorHandlerModule(Location* loc, WastLexer* lexer, WastParser* parser);
  bool OnError(uint32_t offset, const std::string& error) override;

 private:
  Location* loc_;
  WastLexer* lexer_;
  WastParser* parser_;
};

#define wabt_wast_parser_lex wast_lexer_lex
#define wabt_wast_parser_error wast_parser_error

%}

%define api.prefix {wabt_wast_parser_}
%define api.pure true
%define api.value.type {::wabt::Token}
%define api.token.prefix {WABT_TOKEN_TYPE_}
%define parse.error verbose
%lex-param {::wabt::WastLexer* lexer} {::wabt::WastParser* parser}
%parse-param {::wabt::WastLexer* lexer} {::wabt::WastParser* parser}
%locations

%token LPAR "("
%token RPAR ")"
%token NAT INT FLOAT TEXT VAR VALUE_TYPE ANYFUNC MUT
%token NOP DROP BLOCK END IF THEN ELSE LOOP BR BR_IF BR_TABLE
%token CALL CALL_INDIRECT RETURN
%token GET_LOCAL SET_LOCAL TEE_LOCAL GET_GLOBAL SET_GLOBAL
%token LOAD STORE OFFSET_EQ_NAT ALIGN_EQ_NAT
%token CONST UNARY BINARY COMPARE CONVERT SELECT
%token UNREACHABLE CURRENT_MEMORY GROW_MEMORY
%token FUNC START TYPE PARAM RESULT LOCAL GLOBAL
%token MODULE TABLE ELEM MEMORY DATA OFFSET IMPORT EXPORT
%token REGISTER INVOKE GET
%token ASSERT_MALFORMED ASSERT_INVALID ASSERT_UNLINKABLE
%token ASSERT_RETURN ASSERT_RETURN_CANONICAL_NAN ASSERT_RETURN_ARITHMETIC_NAN
%token ASSERT_TRAP ASSERT_EXHAUSTION
%token EOF 0 "EOF"

%type<opcode> BINARY COMPARE CONVERT LOAD STORE UNARY
%type<text> ALIGN_EQ_NAT OFFSET_EQ_NAT TEXT VAR
%type<type> SELECT
%type<type> CONST VALUE_TYPE
%type<literal> NAT INT FLOAT

%type<action> action
%type<block> block
%type<command> assertion cmd
%type<commands> cmd_list
%type<const_> const
%type<consts> const_list
%type<data_segment> data
%type<elem_segment> elem
%type<export_> export export_kind
%type<exported_func> func
%type<exported_global> global
%type<exported_table> table
%type<exported_memory> memory
%type<expr> plain_instr block_instr
%type<expr_list> instr instr_list expr expr1 expr_list if_ const_expr offset
%type<func_fields> func_fields func_body
%type<func> func_info
%type<func_sig> func_sig func_type
%type<func_type> type_def
%type<global> global_type
%type<import> import import_kind inline_import
%type<limits> limits
%type<memory> memory_sig
%type<module> module module_fields
%type<optional_export> inline_export inline_export_opt
%type<raw_module> raw_module
%type<literal> literal
%type<script> script
%type<table> table_sig
%type<text> bind_var bind_var_opt labeling_opt quoted_text
%type<text_list> non_empty_text_list text_list
%type<types> value_type_list
%type<u32> align_opt
%type<u64> nat offset_opt
%type<vars> var_list
%type<var> start type_use var script_var_opt

/* These non-terminals use the types below that have destructors, but the
 * memory is shared with the lexer, so should not be destroyed. */
%destructor {} ALIGN_EQ_NAT OFFSET_EQ_NAT TEXT VAR NAT INT FLOAT
%destructor { destroy_string_slice(&$$); } <text>
%destructor { destroy_string_slice(&$$.text); } <literal>
%destructor { delete $$; } <action>
%destructor { delete $$; } <block>
%destructor { delete $$; } <command>
%destructor { delete $$; } <commands>
%destructor { delete $$; } <consts>
%destructor { delete $$; } <data_segment>
%destructor { delete $$; } <elem_segment>
%destructor { delete $$; } <export_>
%destructor { delete $$; } <exported_func>
%destructor { delete $$; } <exported_memory>
%destructor { delete $$; } <exported_table>
%destructor { delete $$; } <expr>
%destructor { destroy_expr_list($$.first); } <expr_list>
%destructor { destroy_func_fields($$); } <func_fields>
%destructor { delete $$; } <func>
%destructor { delete $$; } <func_sig>
%destructor { delete $$; } <func_type>
%destructor { delete $$; } <global>
%destructor { delete $$; } <import>
%destructor { delete $$; } <optional_export>
%destructor { delete $$; } <memory>
%destructor { delete $$; } <module>
%destructor { delete $$; } <raw_module>
%destructor { delete $$; } <script>
%destructor { destroy_text_list(&$$); } <text_list>
%destructor { delete $$; } <types>
%destructor { destroy_var(&$$); } <var>
%destructor { delete $$; } <vars>


%nonassoc LOW
%nonassoc VAR

%start script_start

%%

/* Auxiliaries */

non_empty_text_list :
    TEXT {
      TextListNode* node = new TextListNode();
      DUPTEXT(node->text, $1);
      node->next = nullptr;
      $$.first = $$.last = node;
    }
  | non_empty_text_list TEXT {
      $$ = $1;
      TextListNode* node = new TextListNode();
      DUPTEXT(node->text, $2);
      node->next = nullptr;
      $$.last->next = node;
      $$.last = node;
    }
;
text_list :
    /* empty */ { $$.first = $$.last = nullptr; }
  | non_empty_text_list
;

quoted_text :
    TEXT {
      TextListNode node;
      node.text = $1;
      node.next = nullptr;
      TextList text_list;
      text_list.first = &node;
      text_list.last = &node;
      char* data;
      size_t size;
      dup_text_list(&text_list, &data, &size);
      $$.start = data;
      $$.length = size;
    }
;

/* Types */

value_type_list :
    /* empty */ { $$ = new TypeVector(); }
  | value_type_list VALUE_TYPE {
      $$ = $1;
      $$->push_back($2);
    }
;
elem_type :
    ANYFUNC {}
;
global_type :
    VALUE_TYPE {
      $$ = new Global();
      $$->type = $1;
      $$->mutable_ = false;
    }
  | LPAR MUT VALUE_TYPE RPAR {
      $$ = new Global();
      $$->type = $3;
      $$->mutable_ = true;
    }
;
func_type :
    LPAR FUNC func_sig RPAR { $$ = $3; }
;
func_sig :
    /* empty */ { $$ = new FuncSignature(); }
  | LPAR PARAM value_type_list RPAR {
      $$ = new FuncSignature();
      $$->param_types = std::move(*$3);
      delete $3;
    }
  | LPAR PARAM value_type_list RPAR LPAR RESULT value_type_list RPAR {
      $$ = new FuncSignature();
      $$->param_types = std::move(*$3);
      delete $3;
      $$->result_types = std::move(*$7);
      delete $7;
    }
  | LPAR RESULT value_type_list RPAR {
      $$ = new FuncSignature();
      $$->result_types = std::move(*$3);
      delete $3;
    }
;

table_sig :
    limits elem_type {
      $$ = new Table();
      $$->elem_limits = $1;
    }
;
memory_sig :
    limits {
      $$ = new Memory();
      $$->page_limits = $1;
    }
;
limits :
    nat {
      $$.has_max = false;
      $$.initial = $1;
      $$.max = 0;
    }
  | nat nat {
      $$.has_max = true;
      $$.initial = $1;
      $$.max = $2;
    }
;
type_use :
    LPAR TYPE var RPAR { $$ = $3; }
;

/* Expressions */

nat :
    NAT {
      if (WABT_FAILED(parse_uint64($1.text.start,
                                        $1.text.start + $1.text.length, &$$))) {
        wast_parser_error(&@1, lexer, parser,
                          "invalid int " PRIstringslice "\"",
                          WABT_PRINTF_STRING_SLICE_ARG($1.text));
      }
    }
;

literal :
    NAT {
      $$.type = $1.type;
      DUPTEXT($$.text, $1.text);
    }
  | INT {
      $$.type = $1.type;
      DUPTEXT($$.text, $1.text);
    }
  | FLOAT {
      $$.type = $1.type;
      DUPTEXT($$.text, $1.text);
    }
;

var :
    nat {
      $$.loc = @1;
      $$.type = VarType::Index;
      $$.index = $1;
    }
  | VAR {
      $$.loc = @1;
      $$.type = VarType::Name;
      DUPTEXT($$.name, $1);
    }
;
var_list :
    /* empty */ { $$ = new VarVector(); }
  | var_list var {
      $$ = $1;
      $$->push_back($2);
    }
;
bind_var_opt :
    /* empty */ { WABT_ZERO_MEMORY($$); }
  | bind_var
;
bind_var :
    VAR { DUPTEXT($$, $1); }
;

labeling_opt :
    /* empty */ %prec LOW { WABT_ZERO_MEMORY($$); }
  | bind_var
;

offset_opt :
    /* empty */ { $$ = 0; }
  | OFFSET_EQ_NAT {
    if (WABT_FAILED(parse_int64($1.start, $1.start + $1.length, &$$,
                                ParseIntType::SignedAndUnsigned))) {
      wast_parser_error(&@1, lexer, parser,
                        "invalid offset \"" PRIstringslice "\"",
                        WABT_PRINTF_STRING_SLICE_ARG($1));
      }
    }
;
align_opt :
    /* empty */ { $$ = USE_NATURAL_ALIGNMENT; }
  | ALIGN_EQ_NAT {
    if (WABT_FAILED(parse_int32($1.start, $1.start + $1.length, &$$,
                                ParseIntType::UnsignedOnly))) {
      wast_parser_error(&@1, lexer, parser,
                        "invalid alignment \"" PRIstringslice "\"",
                        WABT_PRINTF_STRING_SLICE_ARG($1));
      }
    }
;

instr :
    plain_instr { $$ = join_exprs1(&@1, $1); }
  | block_instr { $$ = join_exprs1(&@1, $1); }
  | expr { $$ = $1; }
;
plain_instr :
    UNREACHABLE {
      $$ = Expr::CreateUnreachable();
    }
  | NOP {
      $$ = Expr::CreateNop();
    }
  | DROP {
      $$ = Expr::CreateDrop();
    }
  | SELECT {
      $$ = Expr::CreateSelect();
    }
  | BR var {
      $$ = Expr::CreateBr($2);
    }
  | BR_IF var {
      $$ = Expr::CreateBrIf($2);
    }
  | BR_TABLE var_list var {
      $$ = Expr::CreateBrTable($2, $3);
    }
  | RETURN {
      $$ = Expr::CreateReturn();
    }
  | CALL var {
      $$ = Expr::CreateCall($2);
    }
  | CALL_INDIRECT var {
      $$ = Expr::CreateCallIndirect($2);
    }
  | GET_LOCAL var {
      $$ = Expr::CreateGetLocal($2);
    }
  | SET_LOCAL var {
      $$ = Expr::CreateSetLocal($2);
    }
  | TEE_LOCAL var {
      $$ = Expr::CreateTeeLocal($2);
    }
  | GET_GLOBAL var {
      $$ = Expr::CreateGetGlobal($2);
    }
  | SET_GLOBAL var {
      $$ = Expr::CreateSetGlobal($2);
    }
  | LOAD offset_opt align_opt {
      $$ = Expr::CreateLoad($1, $3, $2);
    }
  | STORE offset_opt align_opt {
      $$ = Expr::CreateStore($1, $3, $2);
    }
  | CONST literal {
      Const const_;
      WABT_ZERO_MEMORY(const_);
      const_.loc = @1;
      if (WABT_FAILED(parse_const($1, $2.type, $2.text.start,
                                  $2.text.start + $2.text.length, &const_))) {
        wast_parser_error(&@2, lexer, parser,
                          "invalid literal \"" PRIstringslice "\"",
                          WABT_PRINTF_STRING_SLICE_ARG($2.text));
      }
      delete [] $2.text.start;
      $$ = Expr::CreateConst(const_);
    }
  | UNARY {
      $$ = Expr::CreateUnary($1);
    }
  | BINARY {
      $$ = Expr::CreateBinary($1);
    }
  | COMPARE {
      $$ = Expr::CreateCompare($1);
    }
  | CONVERT {
      $$ = Expr::CreateConvert($1);
    }
  | CURRENT_MEMORY {
      $$ = Expr::CreateCurrentMemory();
    }
  | GROW_MEMORY {
      $$ = Expr::CreateGrowMemory();
    }
;
block_instr :
    BLOCK labeling_opt block END labeling_opt {
      $$ = Expr::CreateBlock($3);
      $$->block->label = $2;
      CHECK_END_LABEL(@5, $$->block->label, $5);
    }
  | LOOP labeling_opt block END labeling_opt {
      $$ = Expr::CreateLoop($3);
      $$->loop->label = $2;
      CHECK_END_LABEL(@5, $$->loop->label, $5);
    }
  | IF labeling_opt block END labeling_opt {
      $$ = Expr::CreateIf($3, nullptr);
      $$->if_.true_->label = $2;
      CHECK_END_LABEL(@5, $$->if_.true_->label, $5);
    }
  | IF labeling_opt block ELSE labeling_opt instr_list END labeling_opt {
      $$ = Expr::CreateIf($3, $6.first);
      $$->if_.true_->label = $2;
      CHECK_END_LABEL(@5, $$->if_.true_->label, $5);
      CHECK_END_LABEL(@8, $$->if_.true_->label, $8);
    }
;
block :
    value_type_list instr_list {
      $$ = new Block();
      $$->sig = std::move(*$1);
      delete $1;
      $$->first = $2.first;
    }
;

expr :
    LPAR expr1 RPAR { $$ = $2; }
;

expr1 :
    plain_instr expr_list {
      $$ = join_exprs2(&@1, &$2, $1);
    }
  | BLOCK labeling_opt block {
      Expr* expr = Expr::CreateBlock($3);
      expr->block->label = $2;
      $$ = join_exprs1(&@1, expr);
    }
  | LOOP labeling_opt block {
      Expr* expr = Expr::CreateLoop($3);
      expr->loop->label = $2;
      $$ = join_exprs1(&@1, expr);
    }
  | IF labeling_opt value_type_list if_ {
      $$ = $4;
      Expr* if_ = $4.last;
      assert(if_->type == ExprType::If);
      if_->if_.true_->label = $2;
      if_->if_.true_->sig = std::move(*$3);
      delete $3;
    }
;
if_ :
    LPAR THEN instr_list RPAR LPAR ELSE instr_list RPAR {
      Expr* expr = Expr::CreateIf(new Block($3.first), $7.first);
      $$ = join_exprs1(&@1, expr);
    }
  | LPAR THEN instr_list RPAR {
      Expr* expr = Expr::CreateIf(new Block($3.first), nullptr);
      $$ = join_exprs1(&@1, expr);
    }
  | expr LPAR THEN instr_list RPAR LPAR ELSE instr_list RPAR {
      Expr* expr = Expr::CreateIf(new Block($4.first), $8.first);
      $$ = join_exprs2(&@1, &$1, expr);
    }
  | expr LPAR THEN instr_list RPAR {
      Expr* expr = Expr::CreateIf(new Block($4.first), nullptr);
      $$ = join_exprs2(&@1, &$1, expr);
    }
  | expr expr expr {
      Expr* expr = Expr::CreateIf(new Block($2.first), $3.first);
      $$ = join_exprs2(&@1, &$1, expr);
    }
  | expr expr {
      Expr* expr = Expr::CreateIf(new Block($2.first), nullptr);
      $$ = join_exprs2(&@1, &$1, expr);
    }
;

instr_list :
    /* empty */ { WABT_ZERO_MEMORY($$); }
  | instr instr_list {
      $$.first = $1.first;
      $1.last->next = $2.first;
      $$.last = $2.last ? $2.last : $1.last;
      $$.size = $1.size + $2.size;
    }
;
expr_list :
    /* empty */ { WABT_ZERO_MEMORY($$); }
  | expr expr_list {
      $$.first = $1.first;
      $1.last->next = $2.first;
      $$.last = $2.last ? $2.last : $1.last;
      $$.size = $1.size + $2.size;
    }

const_expr :
    instr_list
;

/* Functions */
func_fields :
    func_body
  | LPAR RESULT value_type_list RPAR func_body {
      $$ = new FuncField();
      $$->type = FuncFieldType::ResultTypes;
      $$->types = $3;
      $$->next = $5;
    }
  | LPAR PARAM value_type_list RPAR func_fields {
      $$ = new FuncField();
      $$->type = FuncFieldType::ParamTypes;
      $$->types = $3;
      $$->next = $5;
    }
  | LPAR PARAM bind_var VALUE_TYPE RPAR func_fields {
      $$ = new FuncField();
      $$->type = FuncFieldType::BoundParam;
      $$->bound_type.loc = @2;
      $$->bound_type.name = $3;
      $$->bound_type.type = $4;
      $$->next = $6;
    }
;
func_body :
    instr_list {
      $$ = new FuncField();
      $$->type = FuncFieldType::Exprs;
      $$->first_expr = $1.first;
      $$->next = nullptr;
    }
  | LPAR LOCAL value_type_list RPAR func_body {
      $$ = new FuncField();
      $$->type = FuncFieldType::LocalTypes;
      $$->types = $3;
      $$->next = $5;
    }
  | LPAR LOCAL bind_var VALUE_TYPE RPAR func_body {
      $$ = new FuncField();
      $$->type = FuncFieldType::BoundLocal;
      $$->bound_type.loc = @2;
      $$->bound_type.name = $3;
      $$->bound_type.type = $4;
      $$->next = $6;
    }
;
func_info :
    func_fields {
      $$ = new Func();
      FuncField* field = $1;

      while (field) {
        FuncField* next = field->next;
        switch (field->type) {
          case FuncFieldType::Exprs:
            $$->first_expr = field->first_expr;
            field->first_expr = nullptr;
            break;

          case FuncFieldType::ParamTypes:
          case FuncFieldType::LocalTypes: {
            TypeVector& types = field->type == FuncFieldType::ParamTypes
                                    ? $$->decl.sig.param_types
                                    : $$->local_types;
            types.insert(types.end(), field->types->begin(),
                         field->types->end());
            break;
          }

          case FuncFieldType::BoundParam:
          case FuncFieldType::BoundLocal: {
            TypeVector* types;
            BindingHash* bindings;
            if (field->type == FuncFieldType::BoundParam) {
              types = &$$->decl.sig.param_types;
              bindings = &$$->param_bindings;
            } else {
              types = &$$->local_types;
              bindings = &$$->local_bindings;
            }

            types->push_back(field->bound_type.type);
            bindings->emplace(
                string_slice_to_string(field->bound_type.name),
                Binding(field->bound_type.loc, types->size() - 1));
            break;
          }

          case FuncFieldType::ResultTypes:
            $$->decl.sig.result_types = std::move(*field->types);
            break;
        }

        delete field;
        field = next;
      }
    }
;
func :
    LPAR FUNC bind_var_opt inline_export type_use func_info RPAR {
      $$ = new ExportedFunc();
      $$->func.reset($6);
      $$->func->decl.has_func_type = true;
      $$->func->decl.type_var = $5;
      $$->func->name = $3;
      $$->export_ = std::move(*$4);
      delete $4;
    }
  /* Duplicate above for empty inline_export_opt to avoid LR(1) conflict. */
  | LPAR FUNC bind_var_opt type_use func_info RPAR {
      $$ = new ExportedFunc();
      $$->func.reset($5);
      $$->func->decl.has_func_type = true;
      $$->func->decl.type_var = $4;
      $$->func->name = $3;
    }
  | LPAR FUNC bind_var_opt inline_export func_info RPAR {
      $$ = new ExportedFunc();
      $$->func.reset($5);
      $$->func->name = $3;
      $$->export_ = std::move(*$4);
      delete $4;
    }
  /* Duplicate above for empty inline_export_opt to avoid LR(1) conflict. */
  | LPAR FUNC bind_var_opt func_info RPAR {
      $$ = new ExportedFunc();
      $$->func.reset($4);
      $$->func->name = $3;
    }
;

/* Tables & Memories */

offset :
    LPAR OFFSET const_expr RPAR {
      $$ = $3;
    }
  | expr
;

elem :
    LPAR ELEM var offset var_list RPAR {
      $$ = new ElemSegment();
      $$->table_var = $3;
      $$->offset = $4.first;
      $$->vars = std::move(*$5);
      delete $5;
    }
  | LPAR ELEM offset var_list RPAR {
      $$ = new ElemSegment();
      $$->table_var.loc = @2;
      $$->table_var.type = VarType::Index;
      $$->table_var.index = 0;
      $$->offset = $3.first;
      $$->vars = std::move(*$4);
      delete $4;
    }
;

table :
    LPAR TABLE bind_var_opt inline_export_opt table_sig RPAR {
      $$ = new ExportedTable();
      $$->table.reset($5);
      $$->table->name = $3;
      $$->has_elem_segment = false;
      $$->export_ = std::move(*$4);
      delete $4;
    }
  | LPAR TABLE bind_var_opt inline_export_opt elem_type
         LPAR ELEM var_list RPAR RPAR {
      Expr* expr = Expr::CreateConst(Const(Const::I32(), 0));
      expr->loc = @2;

      $$ = new ExportedTable();
      $$->table.reset(new Table());
      $$->table->name = $3;
      $$->table->elem_limits.initial = $8->size();
      $$->table->elem_limits.max = $8->size();
      $$->table->elem_limits.has_max = true;
      $$->has_elem_segment = true;
      $$->elem_segment.reset(new ElemSegment());
      $$->elem_segment->offset = expr;
      $$->elem_segment->vars = std::move(*$8);
      delete $8;
      $$->export_ = std::move(*$4);
      delete $4;
    }
;

data :
    LPAR DATA var offset text_list RPAR {
      $$ = new DataSegment();
      $$->memory_var = $3;
      $$->offset = $4.first;
      dup_text_list(&$5, &$$->data, &$$->size);
      destroy_text_list(&$5);
    }
  | LPAR DATA offset text_list RPAR {
      $$ = new DataSegment();
      $$->memory_var.loc = @2;
      $$->memory_var.type = VarType::Index;
      $$->memory_var.index = 0;
      $$->offset = $3.first;
      dup_text_list(&$4, &$$->data, &$$->size);
      destroy_text_list(&$4);
    }
;

memory :
    LPAR MEMORY bind_var_opt inline_export_opt memory_sig RPAR {
      $$ = new ExportedMemory();
      $$->memory.reset($5);
      $$->memory->name = $3;
      $$->has_data_segment = false;
      $$->export_ = std::move(*$4);
      delete $4;
    }
  | LPAR MEMORY bind_var_opt inline_export LPAR DATA text_list RPAR RPAR {
      Expr* expr = Expr::CreateConst(Const(Const::I32(), 0));
      expr->loc = @2;

      $$ = new ExportedMemory();
      $$->has_data_segment = true;
      $$->data_segment.reset(new DataSegment());
      $$->data_segment->offset = expr;
      dup_text_list(&$7, &$$->data_segment->data, &$$->data_segment->size);
      destroy_text_list(&$7);
      uint32_t byte_size = WABT_ALIGN_UP_TO_PAGE($$->data_segment->size);
      uint32_t page_size = WABT_BYTES_TO_PAGES(byte_size);
      $$->memory.reset(new Memory());
      $$->memory->name = $3;
      $$->memory->page_limits.initial = page_size;
      $$->memory->page_limits.max = page_size;
      $$->memory->page_limits.has_max = true;
      $$->export_ = std::move(*$4);
      delete $4;
    }
  /* Duplicate above for empty inline_export_opt to avoid LR(1) conflict. */
  | LPAR MEMORY bind_var_opt LPAR DATA text_list RPAR RPAR {
      Expr* expr = Expr::CreateConst(Const(Const::I32(), 0));
      expr->loc = @2;

      $$ = new ExportedMemory();
      $$->has_data_segment = true;
      $$->data_segment.reset(new DataSegment());
      $$->data_segment->offset = expr;
      dup_text_list(&$6, &$$->data_segment->data, &$$->data_segment->size);
      destroy_text_list(&$6);
      uint32_t byte_size = WABT_ALIGN_UP_TO_PAGE($$->data_segment->size);
      uint32_t page_size = WABT_BYTES_TO_PAGES(byte_size);
      $$->memory.reset(new Memory());
      $$->memory->name = $3;
      $$->memory->page_limits.initial = page_size;
      $$->memory->page_limits.max = page_size;
      $$->memory->page_limits.has_max = true;
      $$->export_.has_export = false;
    }
;

global :
    LPAR GLOBAL bind_var_opt inline_export global_type const_expr RPAR {
      $$ = new ExportedGlobal();
      $$->global.reset($5);
      $$->global->name = $3;
      $$->global->init_expr = $6.first;
      $$->export_ = std::move(*$4);
      delete $4;
    }
  | LPAR GLOBAL bind_var_opt global_type const_expr RPAR {
      $$ = new ExportedGlobal();
      $$->global.reset($4);
      $$->global->name = $3;
      $$->global->init_expr = $5.first;
      $$->export_.has_export = false;
    }
;


/* Imports & Exports */

import_kind :
    LPAR FUNC bind_var_opt type_use RPAR {
      $$ = new Import();
      $$->kind = ExternalKind::Func;
      $$->func = new Func();
      $$->func->name = $3;
      $$->func->decl.has_func_type = true;
      $$->func->decl.type_var = $4;
    }
  | LPAR FUNC bind_var_opt func_sig RPAR {
      $$ = new Import();
      $$->kind = ExternalKind::Func;
      $$->func = new Func();
      $$->func->name = $3;
      $$->func->decl.sig = std::move(*$4);
      delete $4;
    }
  | LPAR TABLE bind_var_opt table_sig RPAR {
      $$ = new Import();
      $$->kind = ExternalKind::Table;
      $$->table = $4;
      $$->table->name = $3;
    }
  | LPAR MEMORY bind_var_opt memory_sig RPAR {
      $$ = new Import();
      $$->kind = ExternalKind::Memory;
      $$->memory = $4;
      $$->memory->name = $3;
    }
  | LPAR GLOBAL bind_var_opt global_type RPAR {
      $$ = new Import();
      $$->kind = ExternalKind::Global;
      $$->global = $4;
      $$->global->name = $3;
    }
;
import :
    LPAR IMPORT quoted_text quoted_text import_kind RPAR {
      $$ = $5;
      $$->module_name = $3;
      $$->field_name = $4;
    }
  | LPAR FUNC bind_var_opt inline_import type_use RPAR {
      $$ = $4;
      $$->kind = ExternalKind::Func;
      $$->func = new Func();
      $$->func->name = $3;
      $$->func->decl.has_func_type = true;
      $$->func->decl.type_var = $5;
    }
  | LPAR FUNC bind_var_opt inline_import func_sig RPAR {
      $$ = $4;
      $$->kind = ExternalKind::Func;
      $$->func = new Func();
      $$->func->name = $3;
      $$->func->decl.sig = std::move(*$5);
      delete $5;
    }
  | LPAR TABLE bind_var_opt inline_import table_sig RPAR {
      $$ = $4;
      $$->kind = ExternalKind::Table;
      $$->table = $5;
      $$->table->name = $3;
    }
  | LPAR MEMORY bind_var_opt inline_import memory_sig RPAR {
      $$ = $4;
      $$->kind = ExternalKind::Memory;
      $$->memory = $5;
      $$->memory->name = $3;
    }
  | LPAR GLOBAL bind_var_opt inline_import global_type RPAR {
      $$ = $4;
      $$->kind = ExternalKind::Global;
      $$->global = $5;
      $$->global->name = $3;
    }
;

inline_import :
    LPAR IMPORT quoted_text quoted_text RPAR {
      $$ = new Import();
      $$->module_name = $3;
      $$->field_name = $4;
    }
;

export_kind :
    LPAR FUNC var RPAR {
      $$ = new Export();
      $$->kind = ExternalKind::Func;
      $$->var = $3;
    }
  | LPAR TABLE var RPAR {
      $$ = new Export();
      $$->kind = ExternalKind::Table;
      $$->var = $3;
    }
  | LPAR MEMORY var RPAR {
      $$ = new Export();
      $$->kind = ExternalKind::Memory;
      $$->var = $3;
    }
  | LPAR GLOBAL var RPAR {
      $$ = new Export();
      $$->kind = ExternalKind::Global;
      $$->var = $3;
    }
;
export :
    LPAR EXPORT quoted_text export_kind RPAR {
      $$ = $4;
      $$->name = $3;
    }
;

inline_export_opt :
    /* empty */ {
      $$ = new OptionalExport();
      $$->has_export = false;
    }
  | inline_export
;
inline_export :
    LPAR EXPORT quoted_text RPAR {
      $$ = new OptionalExport();
      $$->has_export = true;
      $$->export_.reset(new Export());
      $$->export_->name = $3;
    }
;


/* Modules */

type_def :
    LPAR TYPE func_type RPAR {
      $$ = new FuncType();
      $$->sig = std::move(*$3);
      delete $3;
    }
  | LPAR TYPE bind_var func_type RPAR {
      $$ = new FuncType();
      $$->name = $3;
      $$->sig = std::move(*$4);
      delete $4;
    }
;

start :
    LPAR START var RPAR { $$ = $3; }
;

module_fields :
    /* empty */ {
      $$ = new Module();
    }
  | module_fields type_def {
      $$ = $1;
      ModuleField* field;
      APPEND_FIELD_TO_LIST($$, field, FuncType, func_type, @2, $2);
      APPEND_ITEM_TO_VECTOR($$, func_types, field->func_type);
      INSERT_BINDING($$, func_type, func_types, @2, $2->name);
    }
  | module_fields global {
      $$ = $1;
      ModuleField* field;
      APPEND_FIELD_TO_LIST($$, field, Global, global, @2, $2->global.release());
      APPEND_ITEM_TO_VECTOR($$, globals, field->global);
      INSERT_BINDING($$, global, globals, @2, field->global->name);
      APPEND_INLINE_EXPORT($$, Global, @2, $2, $$->globals.size() - 1);
      delete $2;
    }
  | module_fields table {
      $$ = $1;
      ModuleField* field;
      APPEND_FIELD_TO_LIST($$, field, Table, table, @2, $2->table.release());
      APPEND_ITEM_TO_VECTOR($$, tables, field->table);
      INSERT_BINDING($$, table, tables, @2, field->table->name);
      APPEND_INLINE_EXPORT($$, Table, @2, $2, $$->tables.size() - 1);

      if ($2->has_elem_segment) {
        ModuleField* elem_segment_field;
        APPEND_FIELD_TO_LIST($$, elem_segment_field, ElemSegment, elem_segment,
                             @2, $2->elem_segment.release());
        APPEND_ITEM_TO_VECTOR($$, elem_segments,
                              elem_segment_field->elem_segment);
      }
      delete $2;
    }
  | module_fields memory {
      $$ = $1;
      ModuleField* field;
      APPEND_FIELD_TO_LIST($$, field, Memory, memory, @2, $2->memory.release());
      APPEND_ITEM_TO_VECTOR($$, memories, field->memory);
      INSERT_BINDING($$, memory, memories, @2, field->memory->name);
      APPEND_INLINE_EXPORT($$, Memory, @2, $2, $$->memories.size() - 1);

      if ($2->has_data_segment) {
        ModuleField* data_segment_field;
        APPEND_FIELD_TO_LIST($$, data_segment_field, DataSegment, data_segment,
                             @2, $2->data_segment.release());
        APPEND_ITEM_TO_VECTOR($$, data_segments,
                              data_segment_field->data_segment);
      }
      delete $2;
    }
  | module_fields func {
      $$ = $1;
      ModuleField* field;
      // Append the implicit func declaration first so it occurs before the
      // func definition when serialized out to the text format.
      append_implicit_func_declaration(&@2, $$, &$2->func->decl);
      APPEND_FIELD_TO_LIST($$, field, Func, func, @2, $2->func.release());
      APPEND_ITEM_TO_VECTOR($$, funcs, field->func);
      INSERT_BINDING($$, func, funcs, @2, field->func->name);
      APPEND_INLINE_EXPORT($$, Func, @2, $2, $$->funcs.size() - 1);
      delete $2;
    }
  | module_fields elem {
      $$ = $1;
      ModuleField* field;
      APPEND_FIELD_TO_LIST($$, field, ElemSegment, elem_segment, @2, $2);
      APPEND_ITEM_TO_VECTOR($$, elem_segments, field->elem_segment);
    }
  | module_fields data {
      $$ = $1;
      ModuleField* field;
      APPEND_FIELD_TO_LIST($$, field, DataSegment, data_segment, @2, $2);
      APPEND_ITEM_TO_VECTOR($$, data_segments, field->data_segment);
    }
  | module_fields start {
      $$ = $1;
      ModuleField* field;
      APPEND_FIELD_TO_LIST($$, field, Start, start, @2, $2);
      $$->start = &field->start;
    }
  | module_fields import {
      $$ = $1;
      ModuleField* field;
      APPEND_FIELD_TO_LIST($$, field, Import, import, @2, $2);
      CHECK_IMPORT_ORDERING($$, func, funcs, @2);
      CHECK_IMPORT_ORDERING($$, table, tables, @2);
      CHECK_IMPORT_ORDERING($$, memory, memories, @2);
      CHECK_IMPORT_ORDERING($$, global, globals, @2);
      switch ($2->kind) {
        case ExternalKind::Func:
          append_implicit_func_declaration(&@2, $$, &field->import->func->decl);
          APPEND_ITEM_TO_VECTOR($$, funcs, field->import->func);
          INSERT_BINDING($$, func, funcs, @2, field->import->func->name);
          $$->num_func_imports++;
          break;
        case ExternalKind::Table:
          APPEND_ITEM_TO_VECTOR($$, tables, field->import->table);
          INSERT_BINDING($$, table, tables, @2, field->import->table->name);
          $$->num_table_imports++;
          break;
        case ExternalKind::Memory:
          APPEND_ITEM_TO_VECTOR($$, memories, field->import->memory);
          INSERT_BINDING($$, memory, memories, @2, field->import->memory->name);
          $$->num_memory_imports++;
          break;
        case ExternalKind::Global:
          APPEND_ITEM_TO_VECTOR($$, globals, field->import->global);
          INSERT_BINDING($$, global, globals, @2, field->import->global->name);
          $$->num_global_imports++;
          break;
      }
      APPEND_ITEM_TO_VECTOR($$, imports, field->import);
    }
  | module_fields export {
      $$ = $1;
      ModuleField* field;
      APPEND_FIELD_TO_LIST($$, field, Export, export_, @2, $2);
      APPEND_ITEM_TO_VECTOR($$, exports, field->export_);
      INSERT_BINDING($$, export, exports, @2, field->export_->name);
    }
;

raw_module :
    LPAR MODULE bind_var_opt module_fields RPAR {
      $$ = new RawModule();
      $$->type = RawModuleType::Text;
      $$->text = $4;
      $$->text->name = $3;
      $$->text->loc = @2;

      /* resolve func type variables where the signature was not specified
       * explicitly */
      for (Func* func: $4->funcs) {
        if (decl_has_func_type(&func->decl) &&
            is_empty_signature(&func->decl.sig)) {
          FuncType* func_type =
              get_func_type_by_var($4, &func->decl.type_var);
          if (func_type) {
            func->decl.sig = func_type->sig;
          }
        }
      }
    }
  | LPAR MODULE bind_var_opt non_empty_text_list RPAR {
      $$ = new RawModule();
      $$->type = RawModuleType::Binary;
      $$->binary.name = $3;
      $$->binary.loc = @2;
      dup_text_list(&$4, &$$->binary.data, &$$->binary.size);
      destroy_text_list(&$4);
    }
;

module :
    raw_module {
      if ($1->type == RawModuleType::Text) {
        $$ = $1->text;
        $1->text = nullptr;
      } else {
        assert($1->type == RawModuleType::Binary);
        $$ = new Module();
        ReadBinaryOptions options = WABT_READ_BINARY_OPTIONS_DEFAULT;
        BinaryErrorHandlerModule error_handler(&$1->binary.loc, lexer, parser);
        read_binary_ir($1->binary.data, $1->binary.size, &options,
                       &error_handler, $$);
        $$->name = $1->binary.name;
        $$->loc = $1->binary.loc;
        WABT_ZERO_MEMORY($1->binary.name);
      }
      delete $1;
    }
;

/* Scripts */

script_var_opt :
    /* empty */ {
      WABT_ZERO_MEMORY($$);
      $$.type = VarType::Index;
      $$.index = INVALID_VAR_INDEX;
    }
  | VAR {
      WABT_ZERO_MEMORY($$);
      $$.type = VarType::Name;
      DUPTEXT($$.name, $1);
    }
;

action :
    LPAR INVOKE script_var_opt quoted_text const_list RPAR {
      $$ = new Action();
      $$->loc = @2;
      $$->module_var = $3;
      $$->type = ActionType::Invoke;
      $$->name = $4;
      $$->invoke = new ActionInvoke();
      $$->invoke->args = std::move(*$5);
      delete $5;
    }
  | LPAR GET script_var_opt quoted_text RPAR {
      $$ = new Action();
      $$->loc = @2;
      $$->module_var = $3;
      $$->type = ActionType::Get;
      $$->name = $4;
    }
;

assertion :
    LPAR ASSERT_MALFORMED raw_module quoted_text RPAR {
      $$ = new Command();
      $$->type = CommandType::AssertMalformed;
      $$->assert_malformed.module = $3;
      $$->assert_malformed.text = $4;
    }
  | LPAR ASSERT_INVALID raw_module quoted_text RPAR {
      $$ = new Command();
      $$->type = CommandType::AssertInvalid;
      $$->assert_invalid.module = $3;
      $$->assert_invalid.text = $4;
    }
  | LPAR ASSERT_UNLINKABLE raw_module quoted_text RPAR {
      $$ = new Command();
      $$->type = CommandType::AssertUnlinkable;
      $$->assert_unlinkable.module = $3;
      $$->assert_unlinkable.text = $4;
    }
  | LPAR ASSERT_TRAP raw_module quoted_text RPAR {
      $$ = new Command();
      $$->type = CommandType::AssertUninstantiable;
      $$->assert_uninstantiable.module = $3;
      $$->assert_uninstantiable.text = $4;
    }
  | LPAR ASSERT_RETURN action const_list RPAR {
      $$ = new Command();
      $$->type = CommandType::AssertReturn;
      $$->assert_return.action = $3;
      $$->assert_return.expected = $4;
    }
  | LPAR ASSERT_RETURN_CANONICAL_NAN action RPAR {
      $$ = new Command();
      $$->type = CommandType::AssertReturnCanonicalNan;
      $$->assert_return_canonical_nan.action = $3;
    }
  | LPAR ASSERT_RETURN_ARITHMETIC_NAN action RPAR {
      $$ = new Command();
      $$->type = CommandType::AssertReturnArithmeticNan;
      $$->assert_return_arithmetic_nan.action = $3;
    }
  | LPAR ASSERT_TRAP action quoted_text RPAR {
      $$ = new Command();
      $$->type = CommandType::AssertTrap;
      $$->assert_trap.action = $3;
      $$->assert_trap.text = $4;
    }
  | LPAR ASSERT_EXHAUSTION action quoted_text RPAR {
      $$ = new Command();
      $$->type = CommandType::AssertExhaustion;
      $$->assert_trap.action = $3;
      $$->assert_trap.text = $4;
    }
;

cmd :
    action {
      $$ = new Command();
      $$->type = CommandType::Action;
      $$->action = $1;
    }
  | assertion
  | module {
      $$ = new Command();
      $$->type = CommandType::Module;
      $$->module = $1;
    }
  | LPAR REGISTER quoted_text script_var_opt RPAR {
      $$ = new Command();
      $$->type = CommandType::Register;
      $$->register_.module_name = $3;
      $$->register_.var = $4;
      $$->register_.var.loc = @4;
    }
;
cmd_list :
    /* empty */ { $$ = new CommandPtrVector(); }
  | cmd_list cmd {
      $$ = $1;
      $$->emplace_back($2);
    }
;

const :
    LPAR CONST literal RPAR {
      $$.loc = @2;
      if (WABT_FAILED(parse_const($2, $3.type, $3.text.start,
                                  $3.text.start + $3.text.length, &$$))) {
        wast_parser_error(&@3, lexer, parser,
                          "invalid literal \"" PRIstringslice "\"",
                          WABT_PRINTF_STRING_SLICE_ARG($3.text));
      }
      delete [] $3.text.start;
    }
;
const_list :
    /* empty */ { $$ = new ConstVector(); }
  | const_list const {
      $$ = $1;
      $$->push_back($2);
    }
;

script :
    cmd_list {
      $$ = new Script();
      $$->commands = std::move(*$1);
      delete $1;

      int last_module_index = -1;
      for (size_t i = 0; i < $$->commands.size(); ++i) {
        Command& command = *$$->commands[i].get();
        Var* module_var = nullptr;
        switch (command.type) {
          case CommandType::Module: {
            last_module_index = i;

            /* Wire up module name bindings. */
            Module* module = command.module;
            if (module->name.length == 0)
              continue;

            $$->module_bindings.emplace(string_slice_to_string(module->name),
                                        Binding(module->loc, i));
            break;
          }

          case CommandType::AssertReturn:
            module_var = &command.assert_return.action->module_var;
            goto has_module_var;
          case CommandType::AssertReturnCanonicalNan:
            module_var =
                &command.assert_return_canonical_nan.action->module_var;
            goto has_module_var;
          case CommandType::AssertReturnArithmeticNan:
            module_var =
                &command.assert_return_arithmetic_nan.action->module_var;
            goto has_module_var;
          case CommandType::AssertTrap:
          case CommandType::AssertExhaustion:
            module_var = &command.assert_trap.action->module_var;
            goto has_module_var;
          case CommandType::Action:
            module_var = &command.action->module_var;
            goto has_module_var;
          case CommandType::Register:
            module_var = &command.register_.var;
            goto has_module_var;

          has_module_var: {
            /* Resolve actions with an invalid index to use the preceding
             * module. */
            if (module_var->type == VarType::Index &&
                module_var->index == INVALID_VAR_INDEX) {
              module_var->index = last_module_index;
            }
            break;
          }

          default:
            break;
        }
      }
      parser->script = $$;
    }
;

/* bison destroys the start symbol even on a successful parse. We want to keep
 script from being destroyed, so create a dummy start symbol. */
script_start :
    script
;

%%

void append_expr_list(ExprList* expr_list, ExprList* expr) {
  if (!expr->first)
    return;
  if (expr_list->last)
    expr_list->last->next = expr->first;
  else
    expr_list->first = expr->first;
  expr_list->last = expr->last;
  expr_list->size += expr->size;
}

void append_expr(ExprList* expr_list, Expr* expr) {
  if (expr_list->last)
    expr_list->last->next = expr;
  else
    expr_list->first = expr;
  expr_list->last = expr;
  expr_list->size++;
}

ExprList join_exprs1(Location* loc, Expr* expr1) {
  ExprList result;
  WABT_ZERO_MEMORY(result);
  append_expr(&result, expr1);
  expr1->loc = *loc;
  return result;
}

ExprList join_exprs2(Location* loc, ExprList* expr1, Expr* expr2) {
  ExprList result;
  WABT_ZERO_MEMORY(result);
  append_expr_list(&result, expr1);
  append_expr(&result, expr2);
  expr2->loc = *loc;
  return result;
}

Result parse_const(Type type,
                   LiteralType literal_type,
                   const char* s,
                   const char* end,
                   Const* out) {
  out->type = type;
  switch (type) {
    case Type::I32:
      return parse_int32(s, end, &out->u32, ParseIntType::SignedAndUnsigned);
    case Type::I64:
      return parse_int64(s, end, &out->u64, ParseIntType::SignedAndUnsigned);
    case Type::F32:
      return parse_float(literal_type, s, end, &out->f32_bits);
    case Type::F64:
      return parse_double(literal_type, s, end, &out->f64_bits);
    default:
      assert(0);
      break;
  }
  return Result::Error;
}

size_t copy_string_contents(StringSlice* text, char* dest) {
  const char* src = text->start + 1;
  const char* end = text->start + text->length - 1;

  char* dest_start = dest;

  while (src < end) {
    if (*src == '\\') {
      src++;
      switch (*src) {
        case 'n':
          *dest++ = '\n';
          break;
        case 't':
          *dest++ = '\t';
          break;
        case '\\':
          *dest++ = '\\';
          break;
        case '\'':
          *dest++ = '\'';
          break;
        case '\"':
          *dest++ = '\"';
          break;
        default: {
          /* The string should be validated already, so we know this is a hex
           * sequence */
          uint32_t hi;
          uint32_t lo;
          if (WABT_SUCCEEDED(parse_hexdigit(src[0], &hi)) &&
              WABT_SUCCEEDED(parse_hexdigit(src[1], &lo))) {
            *dest++ = (hi << 4) | lo;
          } else {
            assert(0);
          }
          src++;
          break;
        }
      }
      src++;
    } else {
      *dest++ = *src++;
    }
  }
  /* return the data length */
  return dest - dest_start;
}

void dup_text_list(TextList* text_list, char** out_data, size_t* out_size) {
  /* walk the linked list to see how much total space is needed */
  size_t total_size = 0;
  for (TextListNode* node = text_list->first; node; node = node->next) {
    /* Always allocate enough space for the entire string including the escape
     * characters. It will only get shorter, and this way we only have to
     * iterate through the string once. */
    const char* src = node->text.start + 1;
    const char* end = node->text.start + node->text.length - 1;
    size_t size = (end > src) ? (end - src) : 0;
    total_size += size;
  }
  char* result = new char [total_size];
  char* dest = result;
  for (TextListNode* node = text_list->first; node; node = node->next) {
    size_t actual_size = copy_string_contents(&node->text, dest);
    dest += actual_size;
  }
  *out_data = result;
  *out_size = dest - result;
}

bool is_empty_signature(const FuncSignature* sig) {
  return sig->result_types.empty() && sig->param_types.empty();
}

void append_implicit_func_declaration(Location* loc,
                                      Module* module,
                                      FuncDeclaration* decl) {
  if (decl_has_func_type(decl))
    return;

  int sig_index = get_func_type_index_by_decl(module, decl);
  if (sig_index == -1) {
    append_implicit_func_type(loc, module, &decl->sig);
  } else {
    decl->sig = module->func_types[sig_index]->sig;
  }
}

Result parse_wast(WastLexer* lexer, Script** out_script,
                 SourceErrorHandler* error_handler) {
  WastParser parser;
  WABT_ZERO_MEMORY(parser);
  parser.error_handler = error_handler;
  int result = wabt_wast_parser_parse(lexer, &parser);
  delete [] parser.yyssa;
  delete [] parser.yyvsa;
  delete [] parser.yylsa;
  *out_script = parser.script;
  return result == 0 && parser.errors == 0 ? Result::Ok : Result::Error;
}

BinaryErrorHandlerModule::BinaryErrorHandlerModule(
    Location* loc, WastLexer* lexer, WastParser* parser)
  : loc_(loc), lexer_(lexer), parser_(parser) {}

bool BinaryErrorHandlerModule::OnError(uint32_t offset,
                                       const std::string& error) {
  if (offset == WABT_UNKNOWN_OFFSET) {
    wast_parser_error(loc_, lexer_, parser_, "error in binary module: %s",
                      error.c_str());
  } else {
    wast_parser_error(loc_, lexer_, parser_,
                      "error in binary module: @0x%08x: %s", offset,
                      error.c_str());
  }
  return true;
}

}  // namespace wabt
