/*
 * Copyright 2017 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 "wabt/wast-parser.h"

#include "wabt/binary-reader-ir.h"
#include "wabt/binary-reader.h"
#include "wabt/cast.h"
#include "wabt/expr-visitor.h"
#include "wabt/resolve-names.h"
#include "wabt/stream.h"
#include "wabt/utf8.h"
#include "wabt/validator.h"

#define WABT_TRACING 0
#include "wabt/tracing.h"

#define EXPECT(token_type) CHECK_RESULT(Expect(TokenType::token_type))

namespace wabt {

namespace {

static const size_t kMaxErrorTokenLength = 80;

bool IsPowerOfTwo(Address x) {
  return x && ((x & (x - 1)) == 0);
}

template <typename OutputIter>
void RemoveEscapes(std::string_view text, OutputIter dest) {
  // Remove surrounding quotes; if any. This may be empty if the string was
  // invalid (e.g. if it contained a bad escape sequence).
  if (text.size() <= 2) {
    return;
  }

  const char* src = text.data();
  const char* end = text.data() + text.size() - 1;

  // For identifiers, keep the starting $ character.
  if (*src == '$') {
    *dest++ = '$';
    src++;
  }
  // Remove the starting quote. The loop below is correct
  // even if this increase reaches the end of an invalid string.
  src++;

  while (src < end) {
    if (*src == '\\') {
      src++;
      switch (*src) {
        case 'n':
          *dest++ = '\n';
          break;
        case 'r':
          *dest++ = '\r';
          break;
        case 't':
          *dest++ = '\t';
          break;
        case '\\':
          *dest++ = '\\';
          break;
        case '\'':
          *dest++ = '\'';
          break;
        case '\"':
          *dest++ = '\"';
          break;
        case 'u': {
          // The string should be validated already,
          // so this must be a valid unicode escape sequence.
          uint32_t digit;
          uint32_t scalar_value = 0;

          // Skip u and { characters.
          src += 2;

          do {
            if (Succeeded(ParseHexdigit(src[0], &digit))) {
              scalar_value = (scalar_value << 4) | digit;
            } else {
              assert(0);
            }
            src++;
          } while (src[0] != '}');

          // Maximum value of a unicode scalar value
          assert(scalar_value < 0x110000);

          // Encode the unicode scalar value as UTF8 sequence
          if (scalar_value < 0x80) {
            *dest++ = static_cast<uint8_t>(scalar_value);
          } else {
            if (scalar_value < 0x800) {
              *dest++ = static_cast<uint8_t>(0xc0 | (scalar_value >> 6));
            } else {
              if (scalar_value < 0x10000) {
                *dest++ = static_cast<uint8_t>(0xe0 | (scalar_value >> 12));
              } else {
                *dest++ = static_cast<uint8_t>(0xf0 | (scalar_value >> 18));
                *dest++ =
                    static_cast<uint8_t>(0x80 | ((scalar_value >> 12) & 0x3f));
              }

              *dest++ =
                  static_cast<uint8_t>(0x80 | ((scalar_value >> 6) & 0x3f));
            }

            *dest++ = static_cast<uint8_t>(0x80 | (scalar_value & 0x3f));
          }
          break;
        }
        default: {
          // The string should be validated already, so we know this is a hex
          // sequence.
          uint32_t hi;
          uint32_t lo;
          if (Succeeded(ParseHexdigit(src[0], &hi)) &&
              Succeeded(ParseHexdigit(src[1], &lo))) {
            *dest++ = (hi << 4) | lo;
          } else {
            assert(0);
          }
          src++;
          break;
        }
      }
      src++;
    } else {
      *dest++ = *src++;
    }
  }
}

using TextVector = std::vector<std::string_view>;

template <typename OutputIter>
void RemoveEscapes(const TextVector& texts, OutputIter out) {
  for (std::string_view text : texts)
    RemoveEscapes(text, out);
}

bool IsPlainInstr(TokenType token_type) {
  switch (token_type) {
    case TokenType::Unreachable:
    case TokenType::Nop:
    case TokenType::Drop:
    case TokenType::Select:
    case TokenType::Br:
    case TokenType::BrIf:
    case TokenType::BrOnNonNull:
    case TokenType::BrOnNull:
    case TokenType::BrTable:
    case TokenType::Return:
    case TokenType::ReturnCall:
    case TokenType::ReturnCallIndirect:
    case TokenType::ReturnCallRef:
    case TokenType::Call:
    case TokenType::CallIndirect:
    case TokenType::CallRef:
    case TokenType::LocalGet:
    case TokenType::LocalSet:
    case TokenType::LocalTee:
    case TokenType::GlobalGet:
    case TokenType::GlobalSet:
    case TokenType::Load:
    case TokenType::Store:
    case TokenType::Const:
    case TokenType::Unary:
    case TokenType::Binary:
    case TokenType::Quaternary:
    case TokenType::Compare:
    case TokenType::Convert:
    case TokenType::MemoryCopy:
    case TokenType::DataDrop:
    case TokenType::MemoryFill:
    case TokenType::MemoryGrow:
    case TokenType::MemoryInit:
    case TokenType::MemorySize:
    case TokenType::TableCopy:
    case TokenType::ElemDrop:
    case TokenType::TableInit:
    case TokenType::TableGet:
    case TokenType::TableSet:
    case TokenType::TableGrow:
    case TokenType::TableSize:
    case TokenType::TableFill:
    case TokenType::Throw:
    case TokenType::ThrowRef:
    case TokenType::Rethrow:
    case TokenType::RefAsNonNull:
    case TokenType::RefFunc:
    case TokenType::RefNull:
    case TokenType::RefIsNull:
    case TokenType::AtomicLoad:
    case TokenType::AtomicStore:
    case TokenType::AtomicRmw:
    case TokenType::AtomicRmwCmpxchg:
    case TokenType::AtomicNotify:
    case TokenType::AtomicFence:
    case TokenType::AtomicWait:
    case TokenType::Ternary:
    case TokenType::SimdLaneOp:
    case TokenType::SimdLoadLane:
    case TokenType::SimdStoreLane:
    case TokenType::SimdShuffleOp:
      return true;
    default:
      return false;
  }
}

bool IsBlockInstr(TokenType token_type) {
  switch (token_type) {
    case TokenType::Block:
    case TokenType::Loop:
    case TokenType::If:
    case TokenType::Try:
    case TokenType::TryTable:
      return true;
    default:
      return false;
  }
}

bool IsPlainOrBlockInstr(TokenType token_type) {
  return IsPlainInstr(token_type) || IsBlockInstr(token_type);
}

bool IsExpr(TokenTypePair pair) {
  return pair[0] == TokenType::Lpar && IsPlainOrBlockInstr(pair[1]);
}

bool IsInstr(TokenTypePair pair) {
  return IsPlainOrBlockInstr(pair[0]) || IsExpr(pair);
}

bool IsLparAnn(TokenTypePair pair) {
  return pair[0] == TokenType::LparAnn;
}

bool IsCatch(TokenType token_type) {
  return token_type == TokenType::Catch || token_type == TokenType::CatchAll;
}

bool IsTryTableCatch(TokenTypePair pair) {
  return pair[0] == TokenType::Lpar &&
         (pair[1] == TokenType::Catch || pair[1] == TokenType::CatchAll ||
          pair[1] == TokenType::CatchRef || pair[1] == TokenType::CatchAllRef);
}

bool IsModuleField(TokenTypePair pair) {
  if (pair[0] != TokenType::Lpar) {
    return false;
  }

  switch (pair[1]) {
    case TokenType::Data:
    case TokenType::Elem:
    case TokenType::Tag:
    case TokenType::Export:
    case TokenType::Func:
    case TokenType::Type:
    case TokenType::Global:
    case TokenType::Import:
    case TokenType::Memory:
    case TokenType::Start:
    case TokenType::Table:
      return true;
    default:
      return false;
  }
}

bool IsCommand(TokenTypePair pair) {
  if (pair[0] != TokenType::Lpar) {
    return false;
  }

  switch (pair[1]) {
    case TokenType::AssertException:
    case TokenType::AssertExhaustion:
    case TokenType::AssertInvalid:
    case TokenType::AssertMalformed:
    case TokenType::AssertReturn:
    case TokenType::AssertTrap:
    case TokenType::AssertUnlinkable:
    case TokenType::Get:
    case TokenType::Invoke:
    case TokenType::Input:
    case TokenType::Module:
    case TokenType::Output:
    case TokenType::Register:
      return true;
    default:
      return false;
  }
}

bool IsEmptySignature(const FuncSignature& sig) {
  return sig.result_types.empty() && sig.param_types.empty();
}

bool ResolveFuncTypeWithEmptySignature(const Module& module,
                                       FuncDeclaration* decl) {
  // Resolve func type variables where the signature was not specified
  // explicitly, e.g.: (func (type 1) ...)
  if (decl->has_func_type && IsEmptySignature(decl->sig)) {
    const FuncType* func_type = module.GetFuncType(decl->type_var);
    if (func_type) {
      decl->sig = func_type->sig;
      return true;
    }
  }
  return false;
}

void ResolveImplicitlyDefinedFunctionType(const Location& loc,
                                          Module* module,
                                          const FuncDeclaration& decl) {
  // Resolve implicitly defined function types, e.g.: (func (param i32) ...)
  if (!decl.has_func_type) {
    Index func_type_index = module->GetFuncTypeIndex(decl.sig);
    if (func_type_index == kInvalidIndex) {
      auto func_type_field = std::make_unique<TypeModuleField>(loc);
      auto func_type = std::make_unique<FuncType>();
      func_type->sig = decl.sig;
      func_type_field->type = std::move(func_type);
      module->AppendField(std::move(func_type_field));
    }
  }
}

Result CheckTypeIndex(const Location& loc,
                      std::string_view filename,
                      Type actual,
                      Type expected,
                      const char* desc,
                      Index index,
                      const char* index_kind,
                      Errors* errors) {
  // Types must match exactly; no subtyping should be allowed.
  if (actual != expected) {
    errors->emplace_back(
        ErrorLevel::Error, loc, filename,
        StringPrintf("type mismatch for %s %" PRIindex
                     " of %s. got %s, expected %s",
                     index_kind, index, desc, actual.GetName().c_str(),
                     expected.GetName().c_str()));
    return Result::Error;
  }
  return Result::Ok;
}

Result CheckTypes(const Location& loc,
                  std::string_view filename,
                  const TypeVector& actual,
                  const TypeVector& expected,
                  const char* desc,
                  const char* index_kind,
                  Errors* errors) {
  Result result = Result::Ok;
  if (actual.size() == expected.size()) {
    for (size_t i = 0; i < actual.size(); ++i) {
      result |= CheckTypeIndex(loc, filename, actual[i], expected[i], desc, i,
                               index_kind, errors);
    }
  } else {
    errors->emplace_back(
        ErrorLevel::Error, loc, filename,
        StringPrintf("expected %" PRIzd " %ss, got %" PRIzd, expected.size(),
                     index_kind, actual.size()));
    result = Result::Error;
  }
  return result;
}

Result CheckFuncTypeVarMatchesExplicit(const Location& loc,
                                       const Module& module,
                                       const FuncDeclaration& decl,
                                       Errors* errors) {
  Result result = Result::Ok;
  if (decl.has_func_type) {
    const FuncType* func_type = module.GetFuncType(decl.type_var);
    if (func_type) {
      result |=
          CheckTypes(loc, module.filename, decl.sig.result_types,
                     func_type->sig.result_types, "function", "result", errors);
      result |= CheckTypes(loc, module.filename, decl.sig.param_types,
                           func_type->sig.param_types, "function", "argument",
                           errors);
    } else if (!(decl.sig.param_types.empty() &&
                 decl.sig.result_types.empty())) {
      // We want to check whether the function type at the explicit index
      // matches the given param and result types. If they were omitted then
      // they'll be resolved automatically (see
      // ResolveFuncTypeWithEmptySignature), but if they are provided then we
      // have to check. If we get here then the type var is invalid, so we
      // can't check whether they match.
      if (decl.type_var.is_index()) {
        errors->emplace_back(ErrorLevel::Error, loc, module.filename,
                             StringPrintf("invalid func type index %" PRIindex,
                                          decl.type_var.index()));
      } else {
        errors->emplace_back(ErrorLevel::Error, loc, module.filename,
                             StringPrintf("expected func type identifier %s",
                                          decl.type_var.name().c_str()));
      }
      result = Result::Error;
    }
  }
  return result;
}

bool IsInlinableFuncSignature(const FuncSignature& sig) {
  return sig.GetNumParams() == 0 && sig.GetNumResults() <= 1;
}

class ResolveFuncTypesExprVisitorDelegate : public ExprVisitor::DelegateNop {
 public:
  explicit ResolveFuncTypesExprVisitorDelegate(Module* module, Errors* errors)
      : module_(module), errors_(errors) {}

  void ResolveBlockDeclaration(const Location& loc, BlockDeclaration* decl) {
    ResolveFuncTypeWithEmptySignature(*module_, decl);
    if (!IsInlinableFuncSignature(decl->sig)) {
      ResolveImplicitlyDefinedFunctionType(loc, module_, *decl);
    }
  }

  Result BeginBlockExpr(BlockExpr* expr) override {
    ResolveBlockDeclaration(expr->loc, &expr->block.decl);
    return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_,
                                           expr->block.decl, errors_);
  }

  Result BeginIfExpr(IfExpr* expr) override {
    ResolveBlockDeclaration(expr->loc, &expr->true_.decl);
    return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_,
                                           expr->true_.decl, errors_);
  }

  Result BeginLoopExpr(LoopExpr* expr) override {
    ResolveBlockDeclaration(expr->loc, &expr->block.decl);
    return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_,
                                           expr->block.decl, errors_);
  }

  Result BeginTryExpr(TryExpr* expr) override {
    ResolveBlockDeclaration(expr->loc, &expr->block.decl);
    return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_,
                                           expr->block.decl, errors_);
  }

  Result OnCallIndirectExpr(CallIndirectExpr* expr) override {
    ResolveFuncTypeWithEmptySignature(*module_, &expr->decl);
    ResolveImplicitlyDefinedFunctionType(expr->loc, module_, expr->decl);
    return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, expr->decl,
                                           errors_);
  }

  Result OnReturnCallIndirectExpr(ReturnCallIndirectExpr* expr) override {
    ResolveFuncTypeWithEmptySignature(*module_, &expr->decl);
    ResolveImplicitlyDefinedFunctionType(expr->loc, module_, expr->decl);
    return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, expr->decl,
                                           errors_);
  }

 private:
  Module* module_;
  Errors* errors_;
};

Result ResolveFuncTypes(Module* module, Errors* errors) {
  Result result = Result::Ok;
  for (ModuleField& field : module->fields) {
    Func* func = nullptr;
    FuncDeclaration* decl = nullptr;
    if (auto* func_field = dyn_cast<FuncModuleField>(&field)) {
      func = &func_field->func;
      decl = &func->decl;
    } else if (auto* tag_field = dyn_cast<TagModuleField>(&field)) {
      decl = &tag_field->tag.decl;
    } else if (auto* import_field = dyn_cast<ImportModuleField>(&field)) {
      if (auto* func_import =
              dyn_cast<FuncImport>(import_field->import.get())) {
        // Only check the declaration, not the function itself, since it is an
        // import.
        decl = &func_import->func.decl;
      } else if (auto* tag_import =
                     dyn_cast<TagImport>(import_field->import.get())) {
        decl = &tag_import->tag.decl;
      } else {
        continue;
      }
    } else {
      continue;
    }

    bool has_func_type_and_empty_signature = false;

    if (decl) {
      has_func_type_and_empty_signature =
          ResolveFuncTypeWithEmptySignature(*module, decl);
      ResolveImplicitlyDefinedFunctionType(field.loc, module, *decl);
      result |=
          CheckFuncTypeVarMatchesExplicit(field.loc, *module, *decl, errors);
    }

    if (func) {
      if (has_func_type_and_empty_signature) {
        // The call to ResolveFuncTypeWithEmptySignature may have updated the
        // function signature so there are parameters. Since parameters and
        // local variables share the same index space, we need to increment the
        // local indexes bound to a given name by the number of parameters in
        // the function.
        for (auto& [name, binding] : func->bindings) {
          binding.index += func->GetNumParams();
        }
      }

      ResolveFuncTypesExprVisitorDelegate delegate(module, errors);
      ExprVisitor visitor(&delegate);
      result |= visitor.VisitFunc(func);
    }
  }
  return result;
}

void AppendInlineExportFields(Module* module,
                              ModuleFieldList* fields,
                              Index index) {
  Location last_field_loc = module->fields.back().loc;

  for (ModuleField& field : *fields) {
    auto* export_field = cast<ExportModuleField>(&field);
    export_field->export_.var = Var(index, last_field_loc);
  }

  module->AppendFields(fields);
}

}  // End of anonymous namespace

WastParser::WastParser(WastLexer* lexer,
                       Errors* errors,
                       WastParseOptions* options)
    : lexer_(lexer), errors_(errors), options_(options) {}

void WastParser::Error(Location loc, const char* format, ...) {
  WABT_SNPRINTF_ALLOCA(buffer, length, format);
  errors_->emplace_back(ErrorLevel::Error, loc, lexer_->Filename(), buffer);
}

Token WastParser::GetToken() {
  if (tokens_.empty()) {
    tokens_.push_back(lexer_->GetToken());
  }
  return tokens_.front();
}

Location WastParser::GetLocation() {
  return GetToken().loc;
}

TokenType WastParser::Peek(size_t n) {
  assert(n <= 1);
  while (tokens_.size() <= n) {
    Token cur = lexer_->GetToken();
    if (cur.token_type() != TokenType::LparAnn) {
      tokens_.push_back(cur);
    } else {
      // Custom annotation. For now, discard until matching Rpar, unless it is
      // a code metadata annotation or custom section. In those cases, we know
      // how to parse it.
      if (!options_->features.annotations_enabled()) {
        Error(cur.loc, "annotations not enabled: %s", cur.to_string().c_str());
        tokens_.push_back(Token(cur.loc, TokenType::Invalid));
        continue;
      }
      if ((options_->features.code_metadata_enabled() &&
           cur.text().find("metadata.code.") == 0) ||
          cur.text() == "custom") {
        tokens_.push_back(cur);
        continue;
      }
      int indent = 1;
      while (indent > 0) {
        cur = lexer_->GetToken();
        switch (cur.token_type()) {
          case TokenType::Lpar:
          case TokenType::LparAnn:
            indent++;
            break;

          case TokenType::Rpar:
            indent--;
            break;

          case TokenType::Eof:
            indent = 0;
            Error(cur.loc, "unterminated annotation");
            break;

          default:
            break;
        }
      }
    }
  }
  return tokens_.at(n).token_type();
}

TokenTypePair WastParser::PeekPair() {
  return TokenTypePair{{Peek(), Peek(1)}};
}

bool WastParser::PeekMatch(TokenType type, size_t n) {
  return Peek(n) == type;
}

bool WastParser::PeekMatchLpar(TokenType type) {
  return Peek() == TokenType::Lpar && Peek(1) == type;
}

bool WastParser::PeekMatchExpr() {
  return IsExpr(PeekPair());
}

bool WastParser::PeekMatchRefType() {
  return (options_->features.function_references_enabled() ||
          options_->features.exceptions_enabled()) &&
         PeekMatchLpar(TokenType::Ref);
}

bool WastParser::PeekMatchVar() {
  TokenType token_type = Peek(0);
  return token_type == TokenType::Nat || token_type == TokenType::Var;
}

bool WastParser::Match(TokenType type) {
  if (PeekMatch(type)) {
    DropToken();
    return true;
  }
  return false;
}

bool WastParser::MatchLpar(TokenType type) {
  if (PeekMatchLpar(type)) {
    DropTwoTokens();
    return true;
  }
  return false;
}

Result WastParser::Expect(TokenType type) {
  if (!Match(type)) {
    Token token = Consume();
    Error(token.loc, "unexpected token %s, expected %s.",
          token.to_string_clamp(kMaxErrorTokenLength).c_str(),
          GetTokenTypeName(type));
    return Result::Error;
  }

  return Result::Ok;
}

Token WastParser::Consume() {
  assert(!tokens_.empty());
  Token token = tokens_.front();
  tokens_.pop_front();
  return token;
}

Result WastParser::Synchronize(SynchronizeFunc func) {
  static const int kMaxConsumed = 10;
  for (int i = 0; i < kMaxConsumed; ++i) {
    if (func(PeekPair())) {
      return Result::Ok;
    }

    Token token = Consume();
    if (token.token_type() == TokenType::Reserved) {
      Error(token.loc, "unexpected token %s.",
            token.to_string_clamp(kMaxErrorTokenLength).c_str());
    }
  }

  return Result::Error;
}

Result WastParser::CheckIndexRange(Location& loc,
                                   size_t size,
                                   const char* decl) {
  if (size >= kInvalidIndex) {
    Error(loc, "too many %s declarations", decl);
    return Result::Error;
  }
  return Result::Ok;
}

void WastParser::ErrorUnlessOpcodeEnabled(const Token& token) {
  Opcode opcode = token.opcode();
  if (!opcode.IsEnabled(options_->features)) {
    Error(token.loc, "opcode not allowed: %s", opcode.GetName());
  }
}

Result WastParser::ErrorExpected(const std::vector<std::string>& expected,
                                 const char* example) {
  GetToken();
  Token token = Consume();
  std::string expected_str;
  if (!expected.empty()) {
    expected_str = ", expected ";
    for (size_t i = 0; i < expected.size(); ++i) {
      if (i != 0) {
        if (i == expected.size() - 1) {
          expected_str += " or ";
        } else {
          expected_str += ", ";
        }
      }

      expected_str += expected[i];
    }

    if (example) {
      expected_str += " (e.g. ";
      expected_str += example;
      expected_str += ")";
    }
  }

  Error(token.loc, "unexpected token \"%s\"%s.",
        token.to_string_clamp(kMaxErrorTokenLength).c_str(),
        expected_str.c_str());
  return Result::Error;
}

Result WastParser::ErrorIfLpar(const std::vector<std::string>& expected,
                               const char* example) {
  if (Match(TokenType::Lpar)) {
    return ErrorExpected(expected, example);
  }
  return Result::Ok;
}

Result WastParser::ParseVarText(Token& token, std::string* out_text) {
  // Parses and validates identifiers.
  assert(token.token_type() == TokenType::Var);

  if (token.text().length() >= 2) {
    if (token.text()[1] != '"') {
      *out_text = std::string(token.text());
      return Result::Ok;
    }

    if (!options_->features.annotations_enabled()) {
      Error(token.loc,
            "quoted identifiers are not supported without annotations");
      return Result::Error;
    }

    RemoveEscapes(token.text(), std::back_inserter(*out_text));
    size_t length = out_text->length();

    if (length >= 2) {
      if (!IsValidUtf8(out_text->data(), length)) {
        Error(token.loc, "quoted identifier has an invalid utf-8 encoding");
        return Result::Error;
      }
      return Result::Ok;
    }
  }

  Error(token.loc, "empty identifier.");
  return Result::Error;
}

Result WastParser::ParseBindVarOpt(std::string* name) {
  WABT_TRACE(ParseBindVarOpt);
  if (!PeekMatch(TokenType::Var)) {
    return Result::Ok;
  }
  Token token = Consume();
  return ParseVarText(token, name);
}

Result WastParser::ParseVar(Var* out_var) {
  WABT_TRACE(ParseVar);
  if (PeekMatch(TokenType::Nat)) {
    Token token = Consume();
    std::string_view sv = token.literal().text;
    uint64_t index = kInvalidIndex;
    if (Failed(ParseUint64(sv, &index))) {
      // Print an error, but don't fail parsing.
      Error(token.loc, "invalid int \"" PRIstringview "\"",
            WABT_PRINTF_STRING_VIEW_ARG(sv));
    }

    *out_var = Var(index, token.loc);
    return Result::Ok;
  } else if (PeekMatch(TokenType::Var)) {
    Token token = Consume();
    if (token.text().length() >= 2 && token.text()[1] != '"') {
      *out_var = Var(token.text(), token.loc);
      return Result::Ok;
    }

    std::string name;
    CHECK_RESULT(ParseVarText(token, &name));
    *out_var = Var(name, token.loc);
    return Result::Ok;
  } else {
    return ErrorExpected({"a numeric index", "a name"}, "12 or $foo");
  }
}

Result WastParser::ParseVarOpt(Var* out_var, Var default_var) {
  WABT_TRACE(ParseVarOpt);
  if (PeekMatchVar()) {
    return ParseVar(out_var);
  }

  *out_var = default_var;
  return Result::Ok;
}

Result WastParser::ParseOffsetExpr(ExprList* out_expr_list) {
  WABT_TRACE(ParseOffsetExpr);
  if (!ParseOffsetExprOpt(out_expr_list)) {
    return ErrorExpected({"an offset expr"}, "(i32.const 123)");
  }
  return Result::Ok;
}

bool WastParser::ParseOffsetExprOpt(ExprList* out_expr_list) {
  WABT_TRACE(ParseOffsetExprOpt);
  if (MatchLpar(TokenType::Offset)) {
    CHECK_RESULT(ParseTerminatingInstrList(out_expr_list));
    EXPECT(Rpar);
    return true;
  } else if (PeekMatchExpr()) {
    CHECK_RESULT(ParseExpr(out_expr_list));
    return true;
  } else {
    return false;
  }
}

Result WastParser::ParseTextList(std::vector<uint8_t>* out_data) {
  WABT_TRACE(ParseTextList);
  if (!ParseTextListOpt(out_data)) {
    // TODO(binji): Add error message here.
    return Result::Error;
  }

  return Result::Ok;
}

bool WastParser::ParseTextListOpt(std::vector<uint8_t>* out_data) {
  WABT_TRACE(ParseTextListOpt);
  TextVector texts;
  while (PeekMatch(TokenType::Text))
    texts.push_back(Consume().text());

  RemoveEscapes(texts, std::back_inserter(*out_data));
  return !texts.empty();
}

Result WastParser::ParseVarList(VarVector* out_var_list) {
  WABT_TRACE(ParseVarList);
  Var var;
  while (PeekMatchVar()) {
    CHECK_RESULT(ParseVar(&var));
    out_var_list->emplace_back(var);
  }
  if (out_var_list->empty()) {
    return ErrorExpected({"a var"}, "12 or $foo");
  } else {
    return Result::Ok;
  }
}

bool WastParser::ParseElemExprOpt(ExprList* out_elem_expr) {
  WABT_TRACE(ParseElemExprOpt);
  bool item = MatchLpar(TokenType::Item);
  ExprList exprs;
  if (item) {
    if (ParseTerminatingInstrList(&exprs) != Result::Ok) {
      return false;
    }
    EXPECT(Rpar);
  } else {
    if (!IsExpr(PeekPair()) || ParseExpr(&exprs) != Result::Ok) {
      return false;
    }
  }
  if (!exprs.size()) {
    return false;
  }
  *out_elem_expr = std::move(exprs);
  return true;
}

bool WastParser::ParseElemExprListOpt(ExprListVector* out_list) {
  ExprList elem_expr;
  while (ParseElemExprOpt(&elem_expr)) {
    out_list->push_back(std::move(elem_expr));
  }
  return !out_list->empty();
}

Result WastParser::ParseElemExprVarListOpt(ExprListVector* out_list) {
  WABT_TRACE(ParseElemExprVarListOpt);
  Var var;
  ExprList init_expr;
  while (PeekMatchVar()) {
    CHECK_RESULT(ParseVar(&var));
    init_expr.push_back(std::make_unique<RefFuncExpr>(var));
    out_list->push_back(std::move(init_expr));
  }
  return Result::Ok;
}

Result WastParser::ParseRefDeclaration(Var* out_type) {
  EXPECT(Lpar);
  EXPECT(Ref);

  Type::Enum opt_type = Type::Reference;

  if (options_->features.function_references_enabled()) {
    opt_type = Type::Ref;

    if (Match(TokenType::Null)) {
      opt_type = Type::RefNull;
    }
  }

  if (PeekMatch(TokenType::Func) || PeekMatch(TokenType::Extern)) {
    TokenType token = Consume().token_type();
    out_type->set_opt_type(token == TokenType::Func ? Type::FuncRef
                                                    : Type::ExternRef);
    out_type->set_index(opt_type == Type::Ref ? Type::ReferenceNonNull
                                              : Type::ReferenceOrNull);
  } else {
    CHECK_RESULT(ParseVar(out_type));
    out_type->set_opt_type(opt_type);
  }

  EXPECT(Rpar);
  return Result::Ok;
}

Result WastParser::ParseValueType(Var* out_type) {
  WABT_TRACE(ParseValueType);

  if (PeekMatchRefType()) {
    return ParseRefDeclaration(out_type);
  }

  if (!PeekMatch(TokenType::ValueType)) {
    return ErrorExpected(
        {"i32", "i64", "f32", "f64", "v128", "externref", "exnref", "funcref"});
  }

  Token token = Consume();
  Type type = token.type();
  bool is_enabled;
  switch (type) {
    case Type::V128:
      is_enabled = options_->features.simd_enabled();
      break;
    case Type::FuncRef:
    case Type::ExternRef:
      is_enabled = options_->features.reference_types_enabled();
      break;
    case Type::ExnRef:
      is_enabled = options_->features.exceptions_enabled();
      break;
    default:
      is_enabled = true;
      break;
  }

  if (!is_enabled) {
    Error(token.loc, "value type not allowed: %s", type.GetName().c_str());
    return Result::Error;
  }

  *out_type = Var(0, GetLocation());
  out_type->set_opt_type(type);
  return Result::Ok;
}

Result WastParser::ParseValueTypeList(TypeVector* out_type_list,
                                      ReferenceVars* type_vars) {
  WABT_TRACE(ParseValueTypeList);
  while (true) {
    if (!PeekMatchRefType() && !PeekMatch(TokenType::ValueType)) {
      break;
    }

    Var type;
    CHECK_RESULT(ParseValueType(&type));

    if (type.is_index()) {
      out_type_list->push_back(type.to_type());
    } else {
      assert(type.is_name());
      assert(options_->features.function_references_enabled());
      type_vars->push_back(ReferenceVar(out_type_list->size(), type));
      out_type_list->push_back(Type(type.opt_type(), kInvalidIndex));
    }
  }

  return Result::Ok;
}

Result WastParser::ParseRefKind(Var* out_type) {
  WABT_TRACE(ParseRefKind);

  if (options_->features.function_references_enabled() &&
      (PeekMatch(TokenType::Nat) || PeekMatch(TokenType::Var))) {
    CHECK_RESULT(ParseVar(out_type));

    out_type->set_opt_type(Type::RefNull);
    return Result::Ok;
  }

  if (!IsTokenTypeRefKind(Peek())) {
    return ErrorExpected({"func", "extern", "exn"});
  }

  Token token = Consume();
  Type type = token.type();

  if ((type == Type::ExternRef &&
       !options_->features.reference_types_enabled()) ||
      ((type == Type::Struct || type == Type::Array) &&
       !options_->features.gc_enabled())) {
    Error(token.loc, "value type not allowed: %s", type.GetName().c_str());
    return Result::Error;
  }

  *out_type = Var(0, GetLocation());
  out_type->set_opt_type(type);
  return Result::Ok;
}

Result WastParser::ParseRefType(Var* out_type) {
  WABT_TRACE(ParseRefType);
  if (PeekMatchRefType()) {
    return ParseRefDeclaration(out_type);
  }

  if (!PeekMatch(TokenType::ValueType)) {
    return ErrorExpected({"funcref", "externref", "exnref"});
  }

  Token token = Consume();
  Type type = token.type();
  if (!CheckRefType(type)) {
    Error(token.loc, "value type not allowed: %s", type.GetName().c_str());
    return Result::Error;
  }

  *out_type = Var(0, GetLocation());
  out_type->set_opt_type(type);
  return Result::Ok;
}

bool WastParser::ParseRefTypeOpt(Var* out_type, Result& result) {
  WABT_TRACE(ParseRefTypeOpt);

  if (PeekMatchRefType()) {
    result |= ParseRefDeclaration(out_type);
    return true;
  }

  if (!PeekMatch(TokenType::ValueType)) {
    return false;
  }

  Token token = Consume();
  Type type = token.type();
  if (!CheckRefType(type)) {
    Error(token.loc, "value type not allowed: %s", type.GetName().c_str());
    result = Result::Error;
    return true;
  }

  *out_type = Var(0, GetLocation());
  out_type->set_opt_type(type);
  return true;
}

Result WastParser::ParseQuotedText(std::string* text, bool check_utf8) {
  WABT_TRACE(ParseQuotedText);
  if (!PeekMatch(TokenType::Text)) {
    return ErrorExpected({"a quoted string"}, "\"foo\"");
  }

  Token token = Consume();
  RemoveEscapes(token.text(), std::back_inserter(*text));
  if (check_utf8 && !IsValidUtf8(text->data(), text->length())) {
    Error(token.loc, "quoted string has an invalid utf-8 encoding");
  }
  return Result::Ok;
}

bool WastParser::ParseOffsetOpt(Address* out_offset) {
  WABT_TRACE(ParseOffsetOpt);
  if (PeekMatch(TokenType::OffsetEqNat)) {
    Token token = Consume();
    uint64_t offset64;
    std::string_view sv = token.text();
    if (Failed(ParseInt64(sv, &offset64, ParseIntType::SignedAndUnsigned))) {
      Error(token.loc, "invalid offset \"" PRIstringview "\"",
            WABT_PRINTF_STRING_VIEW_ARG(sv));
    }
    // With memory64, offsets > UINT32_MAX for i32 memories are no longer
    // malformed (just invalid)
    if ((!options_->features.memory64_enabled()) && (offset64 > UINT32_MAX)) {
      Error(token.loc, "offset must be less than or equal to 0xffffffff");
    }
    *out_offset = offset64;
    return true;
  } else {
    *out_offset = 0;
    return false;
  }
}

bool WastParser::ParseAlignOpt(Address* out_align) {
  WABT_TRACE(ParseAlignOpt);
  if (PeekMatch(TokenType::AlignEqNat)) {
    Token token = Consume();
    std::string_view sv = token.text();
    if (Failed(ParseInt64(sv, out_align, ParseIntType::UnsignedOnly))) {
      Error(token.loc, "invalid alignment \"" PRIstringview "\"",
            WABT_PRINTF_STRING_VIEW_ARG(sv));
    }

    if (!IsPowerOfTwo(*out_align)) {
      Error(token.loc, "alignment must be power-of-two");
    }

    return true;
  } else {
    *out_align = WABT_USE_NATURAL_ALIGNMENT;
    return false;
  }
}

Result WastParser::ParseMemidx(Location loc, Var* out_memidx) {
  WABT_TRACE(ParseMemidx);
  if (PeekMatchLpar(TokenType::Memory)) {
    if (!options_->features.multi_memory_enabled()) {
      Error(loc, "Specifying memory variable is not allowed");
      return Result::Error;
    }
    EXPECT(Lpar);
    EXPECT(Memory);
    CHECK_RESULT(ParseVar(out_memidx));
    EXPECT(Rpar);
  } else {
    if (PeekMatchVar() && !options_->features.multi_memory_enabled()) {
      Error(loc, "Specifying memory variable is not allowed");
      return Result::Error;
    }
    return ParseVarOpt(out_memidx, Var(0, loc));
  }
  return Result::Ok;
}

Result WastParser::ParseLimitsIndex(Limits* out_limits) {
  WABT_TRACE(ParseLimitsIndex);

  if (PeekMatch(TokenType::ValueType)) {
    if (GetToken().type() == Type::I64) {
      DropToken();
      out_limits->is_64 = true;
    } else if (GetToken().type() == Type::I32) {
      DropToken();
      out_limits->is_64 = false;
    }
  }

  return Result::Ok;
}

Result WastParser::ParseLimits(Limits* out_limits) {
  WABT_TRACE(ParseLimits);

  CHECK_RESULT(ParseNat(&out_limits->initial, out_limits->is_64));
  if (PeekMatch(TokenType::Nat)) {
    CHECK_RESULT(ParseNat(&out_limits->max, out_limits->is_64));
    out_limits->has_max = true;
  } else {
    out_limits->has_max = false;
  }

  if (Match(TokenType::Shared)) {
    out_limits->is_shared = true;
  }

  return Result::Ok;
}

Result WastParser::ParsePageSize(uint32_t* out_page_size) {
  WABT_TRACE(ParsePageSize);

  Result result = Result::Ok;

  if (PeekMatchLpar(TokenType::PageSize)) {
    if (!options_->features.custom_page_sizes_enabled()) {
      Error(GetLocation(), "Specifying memory page size is not allowed");
      return Result::Error;
    }
    EXPECT(Lpar);
    EXPECT(PageSize);
    auto token = GetToken();
    if (!token.HasLiteral()) {
      Error(GetLocation(), "malformed custom page size");
      return Result::Error;
    }
    auto sv = token.literal().text;
    result |= ParseInt32(sv, out_page_size, ParseIntType::UnsignedOnly);
    if (*out_page_size > UINT32_MAX || *out_page_size <= 0 ||
        (*out_page_size & (*out_page_size - 1))) {
      Error(GetLocation(), "malformed custom page size");
      return Result::Error;
    }
    DropToken();
    EXPECT(Rpar);
  }

  return result;
}

Result WastParser::ParseNat(uint64_t* out_nat, bool is_64) {
  WABT_TRACE(ParseNat);
  if (!PeekMatch(TokenType::Nat)) {
    return ErrorExpected({"a natural number"}, "123");
  }

  Token token = Consume();
  std::string_view sv = token.literal().text;
  if (Failed(ParseUint64(sv, out_nat)) || (!is_64 && *out_nat > 0xffffffffu)) {
    Error(token.loc, "invalid int \"" PRIstringview "\"",
          WABT_PRINTF_STRING_VIEW_ARG(sv));
  }

  return Result::Ok;
}

Result WastParser::ParseModule(std::unique_ptr<Module>* out_module) {
  WABT_TRACE(ParseModule);
  auto module = std::make_unique<Module>();
  module->filename = lexer_->Filename();

  if (PeekMatchLpar(TokenType::Module)) {
    // Starts with "(module". Allow text and binary modules, but no quoted
    // modules.
    CommandPtr command;
    CHECK_RESULT(ParseModuleCommand(nullptr, &command));
    if (isa<ModuleCommand>(command.get())) {
      auto module_command = cast<ModuleCommand>(std::move(command));
      *module = std::move(module_command->module);
    } else {
      assert(isa<ScriptModuleCommand>(command.get()));
      auto module_command = cast<ScriptModuleCommand>(std::move(command));
      *module = std::move(module_command->module);
    }
  } else if (IsModuleField(PeekPair()) || PeekIsCustom()) {
    // Parse an inline module (i.e. one with no surrounding (module)).
    CHECK_RESULT(ParseModuleFieldList(module.get()));
  } else if (PeekMatch(TokenType::Eof)) {
    errors_->emplace_back(ErrorLevel::Warning, GetLocation(),
                          lexer_->Filename(), "empty module");
  } else {
    ConsumeIfLpar();
    ErrorExpected({"a module field", "a module"});
  }

  EXPECT(Eof);
  if (!HasError()) {
    *out_module = std::move(module);
    return Result::Ok;
  } else {
    return Result::Error;
  }
}

Result WastParser::ParseScript(std::unique_ptr<Script>* out_script) {
  WABT_TRACE(ParseScript);
  auto script = std::make_unique<Script>();
  script->filename = lexer_->Filename();

  // Don't consume the Lpar yet, even though it is required. This way the
  // sub-parser functions (e.g. ParseFuncModuleField) can consume it and keep
  // the parsing structure more regular.
  if (IsModuleField(PeekPair()) || PeekIsCustom()) {
    // Parse an inline module (i.e. one with no surrounding (module)).
    auto command = std::make_unique<ModuleCommand>();
    command->module.loc = GetLocation();
    CHECK_RESULT(ParseModuleFieldList(&command->module));
    script->commands.emplace_back(std::move(command));
  } else if (IsCommand(PeekPair())) {
    CHECK_RESULT(ParseCommandList(script.get(), &script->commands));
  } else if (PeekMatch(TokenType::Eof)) {
    errors_->emplace_back(ErrorLevel::Warning, GetLocation(),
                          lexer_->Filename(), "empty script");
  } else {
    ConsumeIfLpar();
    ErrorExpected({"a module field", "a command"});
  }

  EXPECT(Eof);
  if (!HasError()) {
    *out_script = std::move(script);
    return Result::Ok;
  } else {
    return Result::Error;
  }
}

Result WastParser::ParseCustomSectionAnnotation(Module* module) {
  WABT_TRACE(ParseCustomSectionAnnotation);
  Location loc = GetLocation();
  Token token = Consume();
  if (token.text() != "custom") {
    assert(
        !"ParseCustomSectionAnnotation should only be called if PeekIsCustom() is true");
    return Result::Error;
  }
  std::string section_name;
  CHECK_RESULT(ParseQuotedText(&section_name));
  if (Match(TokenType::Lpar)) {
    if (!PeekMatch(TokenType::After) && !PeekMatch(TokenType::Before)) {
      return ErrorExpected({"before", "after"});
    }
    DropToken();
    switch (Peek()) {
      case TokenType::Function:
      case TokenType::Type:
      case TokenType::Import:
      case TokenType::Export:
      case TokenType::Table:
      case TokenType::Global:
      case TokenType::Elem:
      case TokenType::Data:
      case TokenType::Memory:
      case TokenType::Code:
      case TokenType::Start: {
        DropToken();
        break;
      }
      default: {
        return ErrorExpected({"type", "import", "function", "table", "memory",
                              "global", "export", "start", "elem", "code",
                              "data"});
      }
    }
    EXPECT(Rpar);
  }
  std::vector<uint8_t> data;
  CHECK_RESULT(ParseTextList(&data));
  EXPECT(Rpar);

  Custom custom = Custom(loc, section_name, data);
  module->customs.push_back(custom);

  return Result::Ok;
}

bool WastParser::PeekIsCustom() {
  // If IsLparAnn succeeds, tokens_.front() must have text, as it is an LparAnn
  // token.
  return options_->features.annotations_enabled() && IsLparAnn(PeekPair()) &&
         tokens_.front().text() == "custom";
}

Result WastParser::ResolveTargetRefType(const Module& module,
                                        Type* type,
                                        const Var& var,
                                        Errors* errors) {
  assert(type->IsReferenceWithIndex() && !var.is_index());

  if (type->GetReferenceIndex() != kInvalidIndex) {
    // Type has either a name or an absolute index. The former might
    // be resolved earlier, and the latter does not need any resolving.
    return Result::Ok;
  }

  Index type_index = module.type_bindings.FindIndex(var.name());

  if (type_index != kInvalidIndex) {
    *type = Type(static_cast<Type::Enum>(*type), type_index);
    return Result::Ok;
  }

  errors->emplace_back(
      ErrorLevel::Error, var.loc, module.filename,
      StringPrintf("undefined reference type name %s", var.name().c_str()));
  return Result::Ok;
}

Result WastParser::ResolveTargetTypeVector(const Module& module,
                                           TypeVector* types,
                                           ReferenceVars* ref_vars,
                                           Errors* errors) {
  Result result = Result::Ok;

  for (auto& ref_var : *ref_vars) {
    uint32_t index = ref_var.index;

    // The index of resolved variables is converted to kInvalidIndex.
    if (index == kInvalidIndex) {
      continue;
    }

    ref_var.index = kInvalidIndex;

    assert(index < types->size());
    result |= ResolveTargetRefType(module, types->data() + index, ref_var.var,
                                   errors);
  }

  return Result::Ok;
}

Result WastParser::ParseModuleFieldList(Module* module) {
  WABT_TRACE(ParseModuleFieldList);

  // Reset module-specific state.
  resolve_ref_types_.clear();
  resolve_type_vectors_.clear();
  resolve_funcs_.clear();

  while (IsModuleField(PeekPair()) || PeekIsCustom()) {
    if (PeekIsCustom()) {
      CHECK_RESULT(ParseCustomSectionAnnotation(module));
      continue;
    }
    if (Failed(ParseModuleField(module))) {
      CHECK_RESULT(Synchronize(IsModuleField));
    }
  }

  // Module parsing is completed, type names can be resolved now.
  Result result = Result::Ok;

  for (auto it : resolve_ref_types_) {
    result |= ResolveTargetRefType(*module, it.target_type, it.var, errors_);
  }

  for (auto it : resolve_type_vectors_) {
    result |=
        ResolveTargetTypeVector(*module, it.target_vector, &it.vars, errors_);
  }

  for (auto it : resolve_funcs_) {
    result |= ResolveTargetTypeVector(*module, &it.types, &it.vars, errors_);
    it.target_func->local_types.Set(it.types);
  }

  CHECK_RESULT(result);
  CHECK_RESULT(ResolveFuncTypes(module, errors_));
  CHECK_RESULT(ResolveNamesModule(module, errors_));
  return Result::Ok;
}

Result WastParser::ParseModuleField(Module* module) {
  WABT_TRACE(ParseModuleField);
  switch (Peek(1)) {
    case TokenType::Data:   return ParseDataModuleField(module);
    case TokenType::Elem:   return ParseElemModuleField(module);
    case TokenType::Tag:    return ParseTagModuleField(module);
    case TokenType::Export: return ParseExportModuleField(module);
    case TokenType::Func:   return ParseFuncModuleField(module);
    case TokenType::Type:   return ParseTypeModuleField(module);
    case TokenType::Global: return ParseGlobalModuleField(module);
    case TokenType::Import: return ParseImportModuleField(module);
    case TokenType::Memory: return ParseMemoryModuleField(module);
    case TokenType::Start:  return ParseStartModuleField(module);
    case TokenType::Table:  return ParseTableModuleField(module);
    default:
      assert(
          !"ParseModuleField should only be called if IsModuleField() is true");
      return Result::Error;
  }
}

Result WastParser::ParseDataModuleField(Module* module) {
  WABT_TRACE(ParseDataModuleField);
  EXPECT(Lpar);
  Location loc = GetLocation();
  EXPECT(Data);
  CHECK_RESULT(CheckIndexRange(loc, module->data_segments.size(), "data"));
  std::string name;
  CHECK_RESULT(ParseBindVarOpt(&name));
  auto field = std::make_unique<DataSegmentModuleField>(loc, name);

  if (PeekMatchLpar(TokenType::Memory)) {
    EXPECT(Lpar);
    EXPECT(Memory);
    CHECK_RESULT(ParseVar(&field->data_segment.memory_var));
    EXPECT(Rpar);
    CHECK_RESULT(ParseOffsetExpr(&field->data_segment.offset));
  } else if (PeekMatchVar()) {
    CHECK_RESULT(ParseVar(&field->data_segment.memory_var));
    CHECK_RESULT(ParseOffsetExpr(&field->data_segment.offset));
  } else {
    field->data_segment.memory_var = Var(0, loc);

    if (!ParseOffsetExprOpt(&field->data_segment.offset)) {
      if (!options_->features.bulk_memory_enabled()) {
        Error(loc, "passive data segments are not allowed");
        return Result::Error;
      }

      field->data_segment.kind = SegmentKind::Passive;
    }
  }

  ParseTextListOpt(&field->data_segment.data);
  EXPECT(Rpar);
  module->AppendField(std::move(field));
  return Result::Ok;
}

Result WastParser::ParseElemModuleField(Module* module) {
  WABT_TRACE(ParseElemModuleField);
  EXPECT(Lpar);
  Location loc = GetLocation();
  EXPECT(Elem);
  CHECK_RESULT(CheckIndexRange(loc, module->elem_segments.size(), "elem"));

  // With MVP text format the name here was intended to refer to the table
  // that the elem segment was part of, but we never did anything with this name
  // since there was only one table anyway.
  // With bulk-memory enabled this introduces a new name for the particular
  // elem segment.
  std::string initial_name;
  CHECK_RESULT(ParseBindVarOpt(&initial_name));

  std::string segment_name = initial_name;
  if (!options_->features.bulk_memory_enabled()) {
    segment_name = "";
  }
  auto field = std::make_unique<ElemSegmentModuleField>(loc, segment_name);
  if (options_->features.reference_types_enabled() &&
      Match(TokenType::Declare)) {
    field->elem_segment.kind = SegmentKind::Declared;
  }

  // Optional table specifier
  if (options_->features.bulk_memory_enabled()) {
    if (PeekMatchLpar(TokenType::Table)) {
      EXPECT(Lpar);
      EXPECT(Table);
      CHECK_RESULT(ParseVar(&field->elem_segment.table_var));
      EXPECT(Rpar);
    } else {
      CHECK_RESULT(ParseVarOpt(&field->elem_segment.table_var, Var(0, loc)));
    }
  } else {
    if (!initial_name.empty()) {
      field->elem_segment.table_var = Var(initial_name, loc);
    } else {
      CHECK_RESULT(ParseVarOpt(&field->elem_segment.table_var, Var(0, loc)));
    }
  }

  // Parse offset expression, if not declared/passive segment.
  if (options_->features.bulk_memory_enabled()) {
    if (field->elem_segment.kind != SegmentKind::Declared &&
        !ParseOffsetExprOpt(&field->elem_segment.offset)) {
      field->elem_segment.kind = SegmentKind::Passive;
    }
  } else {
    CHECK_RESULT(ParseOffsetExpr(&field->elem_segment.offset));
  }

  Result result;
  Var elem_type;
  if (ParseRefTypeOpt(&elem_type, result)) {
    CHECK_RESULT(result);
    VarToType(elem_type, &field->elem_segment.elem_type);
    ParseElemExprListOpt(&field->elem_segment.elem_exprs);
  } else {
    field->elem_segment.elem_type =
        Type(Type::FuncRef, options_->features.function_references_enabled()
                                ? Type::ReferenceNonNull
                                : Type::ReferenceOrNull);
    if (PeekMatch(TokenType::Func)) {
      EXPECT(Func);
    }
    CHECK_RESULT(ParseElemExprVarListOpt(&field->elem_segment.elem_exprs));
  }
  EXPECT(Rpar);
  module->AppendField(std::move(field));
  return Result::Ok;
}

Result WastParser::ParseTagModuleField(Module* module) {
  WABT_TRACE(ParseTagModuleField);
  if (!options_->features.exceptions_enabled()) {
    Error(Consume().loc, "tag not allowed");
    return Result::Error;
  }
  EXPECT(Lpar);
  EXPECT(Tag);
  Location loc = GetLocation();
  CHECK_RESULT(CheckIndexRange(loc, module->tags.size(), "tag"));
  std::string name;
  CHECK_RESULT(ParseBindVarOpt(&name));

  ModuleFieldList export_fields;
  CHECK_RESULT(ParseInlineExports(&export_fields, ExternalKind::Tag));

  if (PeekMatchLpar(TokenType::Import)) {
    CheckImportOrdering(module);
    CHECK_RESULT(CheckIndexRange(loc, module->imports.size(), "import"));
    auto import = std::make_unique<TagImport>(name);
    Tag& tag = import->tag;
    CHECK_RESULT(ParseInlineImport(import.get()));
    CHECK_RESULT(ParseTypeUseOpt(&tag.decl));
    CHECK_RESULT(ParseUnboundFuncSignature(&tag.decl.sig));
    CHECK_RESULT(ErrorIfLpar({"type", "param", "result"}));
    auto field =
        std::make_unique<ImportModuleField>(std::move(import), GetLocation());
    module->AppendField(std::move(field));
  } else {
    auto field = std::make_unique<TagModuleField>(loc, name);
    CHECK_RESULT(ParseTypeUseOpt(&field->tag.decl));
    CHECK_RESULT(ParseUnboundFuncSignature(&field->tag.decl.sig));
    module->AppendField(std::move(field));
  }

  AppendInlineExportFields(module, &export_fields, module->tags.size() - 1);

  EXPECT(Rpar);
  return Result::Ok;
}

Result WastParser::ParseExportModuleField(Module* module) {
  WABT_TRACE(ParseExportModuleField);
  EXPECT(Lpar);
  auto field = std::make_unique<ExportModuleField>(GetLocation());
  Location loc = GetLocation();
  EXPECT(Export);
  CHECK_RESULT(CheckIndexRange(loc, module->exports.size(), "export"));
  CHECK_RESULT(ParseQuotedText(&field->export_.name));
  CHECK_RESULT(ParseExportDesc(&field->export_));
  EXPECT(Rpar);
  module->AppendField(std::move(field));
  return Result::Ok;
}

Result WastParser::ParseFuncModuleField(Module* module) {
  WABT_TRACE(ParseFuncModuleField);
  EXPECT(Lpar);
  Location loc = GetLocation();
  EXPECT(Func);
  CHECK_RESULT(CheckIndexRange(loc, module->funcs.size(), "func"));
  std::string name;
  CHECK_RESULT(ParseBindVarOpt(&name));

  ModuleFieldList export_fields;
  CHECK_RESULT(ParseInlineExports(&export_fields, ExternalKind::Func));

  if (PeekMatchLpar(TokenType::Import)) {
    CheckImportOrdering(module);
    CHECK_RESULT(CheckIndexRange(loc, module->imports.size(), "import"));
    auto import = std::make_unique<FuncImport>(name);
    Func& func = import->func;
    CHECK_RESULT(ParseInlineImport(import.get()));
    CHECK_RESULT(ParseTypeUseOpt(&func.decl));
    CHECK_RESULT(ParseFuncSignature(&func.decl.sig, &func.bindings));
    CHECK_RESULT(ErrorIfLpar({"type", "param", "result"}));
    auto field =
        std::make_unique<ImportModuleField>(std::move(import), GetLocation());
    module->AppendField(std::move(field));
  } else {
    auto field = std::make_unique<FuncModuleField>(loc, name);
    Func& func = field->func;
    func.loc = GetLocation();
    CHECK_RESULT(ParseTypeUseOpt(&func.decl));
    CHECK_RESULT(ParseFuncSignature(&func.decl.sig, &func.bindings));

    ResolveFunc references(&func);

    CHECK_RESULT(ParseBoundValueTypeList(TokenType::Local, &references.types,
                                         &func.bindings, &references.vars,
                                         func.GetNumParams()));

    if (references.vars.empty()) {
      // No named references in the list, local types can be processed now.
      func.local_types.Set(references.types);
    } else {
      resolve_funcs_.push_back(references);
    }

    CHECK_RESULT(ParseTerminatingInstrList(&func.exprs));
    module->AppendField(std::move(field));
  }

  AppendInlineExportFields(module, &export_fields, module->funcs.size() - 1);

  EXPECT(Rpar);
  return Result::Ok;
}

Result WastParser::ParseTypeModuleField(Module* module) {
  WABT_TRACE(ParseTypeModuleField);
  EXPECT(Lpar);
  auto field = std::make_unique<TypeModuleField>(GetLocation());
  EXPECT(Type);

  std::string name;
  CHECK_RESULT(ParseBindVarOpt(&name));
  EXPECT(Lpar);
  Location loc = GetLocation();
  CHECK_RESULT(CheckIndexRange(loc, module->types.size(), "type"));

  if (Match(TokenType::Func)) {
    auto func_type = std::make_unique<FuncType>(name);
    BindingHash bindings;
    CHECK_RESULT(ParseFuncSignature(&func_type->sig, &bindings));
    CHECK_RESULT(ErrorIfLpar({"param", "result"}));
    field->type = std::move(func_type);
  } else if (Match(TokenType::Struct)) {
    if (!options_->features.gc_enabled()) {
      Error(loc, "struct not allowed");
      return Result::Error;
    }
    auto struct_type = std::make_unique<StructType>(name);
    CHECK_RESULT(ParseFieldList(&struct_type->fields));
    field->type = std::move(struct_type);
  } else if (Match(TokenType::Array)) {
    if (!options_->features.gc_enabled()) {
      Error(loc, "array type not allowed");
    }
    auto array_type = std::make_unique<ArrayType>(name);
    CHECK_RESULT(ParseField(&array_type->field));
    field->type = std::move(array_type);
  } else {
    return ErrorExpected({"func", "struct", "array"});
  }

  EXPECT(Rpar);
  EXPECT(Rpar);
  module->AppendField(std::move(field));
  return Result::Ok;
}

Result WastParser::ParseField(Field* field) {
  WABT_TRACE(ParseField);
  auto parse_mut_valuetype = [&]() -> Result {
    // TODO: Share with ParseGlobalType?
    if (MatchLpar(TokenType::Mut)) {
      field->mutable_ = true;
      Var type;
      CHECK_RESULT(ParseValueType(&type));
      field->type = Type(type.opt_type());
      EXPECT(Rpar);
    } else {
      field->mutable_ = false;
      Var type;
      CHECK_RESULT(ParseValueType(&type));
      field->type = Type(type.opt_type());
    }
    return Result::Ok;
  };

  if (MatchLpar(TokenType::Field)) {
    CHECK_RESULT(ParseBindVarOpt(&field->name));
    CHECK_RESULT(parse_mut_valuetype());
    EXPECT(Rpar);
  } else {
    CHECK_RESULT(parse_mut_valuetype());
  }

  return Result::Ok;
}

Result WastParser::ParseFieldList(std::vector<Field>* fields) {
  WABT_TRACE(ParseFieldList);
  while (PeekMatch(TokenType::ValueType) || PeekMatch(TokenType::Lpar)) {
    Field field;
    CHECK_RESULT(ParseField(&field));
    fields->push_back(field);
  }
  return Result::Ok;
}

Result WastParser::ParseGlobalModuleField(Module* module) {
  WABT_TRACE(ParseGlobalModuleField);
  EXPECT(Lpar);
  Location loc = GetLocation();
  EXPECT(Global);
  CHECK_RESULT(CheckIndexRange(loc, module->globals.size(), "global"));
  std::string name;
  CHECK_RESULT(ParseBindVarOpt(&name));

  ModuleFieldList export_fields;
  CHECK_RESULT(ParseInlineExports(&export_fields, ExternalKind::Global));

  if (PeekMatchLpar(TokenType::Import)) {
    CheckImportOrdering(module);
    CHECK_RESULT(CheckIndexRange(loc, module->imports.size(), "import"));
    auto import = std::make_unique<GlobalImport>(name);
    CHECK_RESULT(ParseInlineImport(import.get()));
    CHECK_RESULT(ParseGlobalType(&import->global));
    auto field =
        std::make_unique<ImportModuleField>(std::move(import), GetLocation());
    module->AppendField(std::move(field));
  } else {
    auto field = std::make_unique<GlobalModuleField>(loc, name);
    CHECK_RESULT(ParseGlobalType(&field->global));
    CHECK_RESULT(ParseTerminatingInstrList(&field->global.init_expr));
    module->AppendField(std::move(field));
  }

  AppendInlineExportFields(module, &export_fields, module->globals.size() - 1);

  EXPECT(Rpar);
  return Result::Ok;
}

Result WastParser::ParseImportModuleField(Module* module) {
  WABT_TRACE(ParseImportModuleField);
  EXPECT(Lpar);
  Location loc = GetLocation();
  CheckImportOrdering(module);
  EXPECT(Import);
  CHECK_RESULT(CheckIndexRange(loc, module->imports.size(), "import"));
  std::string module_name;
  std::string field_name;
  CHECK_RESULT(ParseQuotedText(&module_name));
  CHECK_RESULT(ParseQuotedText(&field_name));
  EXPECT(Lpar);

  std::unique_ptr<ImportModuleField> field;
  std::string name;

  switch (Peek()) {
    case TokenType::Func: {
      CHECK_RESULT(CheckIndexRange(loc, module->funcs.size(), "func"));
      DropToken();
      CHECK_RESULT(ParseBindVarOpt(&name));
      auto import = std::make_unique<FuncImport>(name);
      CHECK_RESULT(ParseTypeUseOpt(&import->func.decl));
      CHECK_RESULT(
          ParseFuncSignature(&import->func.decl.sig, &import->func.bindings));
      CHECK_RESULT(ErrorIfLpar({"param", "result"}));
      EXPECT(Rpar);
      field = std::make_unique<ImportModuleField>(std::move(import), loc);
      break;
    }

    case TokenType::Table: {
      CHECK_RESULT(CheckIndexRange(loc, module->tables.size(), "table"));
      DropToken();
      CHECK_RESULT(ParseBindVarOpt(&name));
      auto import = std::make_unique<TableImport>(name);
      CHECK_RESULT(ParseLimitsIndex(&import->table.elem_limits));
      CHECK_RESULT(ParseLimits(&import->table.elem_limits));
      Var elem_type;
      CHECK_RESULT(ParseRefType(&elem_type));
      VarToType(elem_type, &import->table.elem_type);
      EXPECT(Rpar);
      field = std::make_unique<ImportModuleField>(std::move(import), loc);
      break;
    }

    case TokenType::Memory: {
      CHECK_RESULT(CheckIndexRange(loc, module->memories.size(), "memory"));
      DropToken();
      CHECK_RESULT(ParseBindVarOpt(&name));
      auto import = std::make_unique<MemoryImport>(name);
      import->memory.page_size = WABT_DEFAULT_PAGE_SIZE;
      CHECK_RESULT(ParseLimitsIndex(&import->memory.page_limits));
      CHECK_RESULT(ParseLimits(&import->memory.page_limits));
      CHECK_RESULT(ParsePageSize(&import->memory.page_size));
      EXPECT(Rpar);
      field = std::make_unique<ImportModuleField>(std::move(import), loc);
      break;
    }

    case TokenType::Global: {
      CHECK_RESULT(CheckIndexRange(loc, module->globals.size(), "global"));
      DropToken();
      CHECK_RESULT(ParseBindVarOpt(&name));
      auto import = std::make_unique<GlobalImport>(name);
      CHECK_RESULT(ParseGlobalType(&import->global));
      EXPECT(Rpar);
      field = std::make_unique<ImportModuleField>(std::move(import), loc);
      break;
    }

    case TokenType::Tag: {
      CHECK_RESULT(CheckIndexRange(loc, module->tags.size(), "tag"));
      DropToken();
      CHECK_RESULT(ParseBindVarOpt(&name));
      auto import = std::make_unique<TagImport>(name);
      CHECK_RESULT(ParseTypeUseOpt(&import->tag.decl));
      CHECK_RESULT(ParseUnboundFuncSignature(&import->tag.decl.sig));
      EXPECT(Rpar);
      field = std::make_unique<ImportModuleField>(std::move(import), loc);
      break;
    }

    default:
      return ErrorExpected({"an external kind"});
  }

  field->import->module_name = module_name;
  field->import->field_name = field_name;

  module->AppendField(std::move(field));
  EXPECT(Rpar);
  return Result::Ok;
}

Result WastParser::ParseMemoryModuleField(Module* module) {
  WABT_TRACE(ParseMemoryModuleField);
  EXPECT(Lpar);
  Location loc = GetLocation();
  EXPECT(Memory);
  CHECK_RESULT(CheckIndexRange(loc, module->memories.size(), "memory"));
  std::string name;
  CHECK_RESULT(ParseBindVarOpt(&name));

  ModuleFieldList export_fields;
  CHECK_RESULT(ParseInlineExports(&export_fields, ExternalKind::Memory));

  if (PeekMatchLpar(TokenType::Import)) {
    CheckImportOrdering(module);
    CHECK_RESULT(CheckIndexRange(loc, module->imports.size(), "import"));
    auto import = std::make_unique<MemoryImport>(name);
    import->memory.page_size = WABT_DEFAULT_PAGE_SIZE;
    CHECK_RESULT(ParseInlineImport(import.get()));
    CHECK_RESULT(ParseLimitsIndex(&import->memory.page_limits));
    CHECK_RESULT(ParseLimits(&import->memory.page_limits));
    CHECK_RESULT(ParsePageSize(&import->memory.page_size));
    auto field =
        std::make_unique<ImportModuleField>(std::move(import), GetLocation());
    module->AppendField(std::move(field));
  } else {
    auto field = std::make_unique<MemoryModuleField>(loc, name);
    field->memory.page_size = WABT_DEFAULT_PAGE_SIZE;
    CHECK_RESULT(ParseLimitsIndex(&field->memory.page_limits));
    if (PeekMatchLpar(TokenType::PageSize)) {
      // this is the data abbreviation (no limits)
      CHECK_RESULT(ParsePageSize(&field->memory.page_size));
      if (!PeekMatchLpar(TokenType::Data)) {
        ConsumeIfLpar();
        return ErrorExpected({"inline data segment"});
      }
    }
    if (MatchLpar(TokenType::Data)) {
      auto data_segment_field = std::make_unique<DataSegmentModuleField>(loc);
      DataSegment& data_segment = data_segment_field->data_segment;
      data_segment.memory_var = Var(module->memories.size(), GetLocation());
      data_segment.offset.push_back(std::make_unique<ConstExpr>(
          field->memory.page_limits.is_64 ? Const::I64(0) : Const::I32(0)));
      data_segment.offset.back().loc = loc;
      ParseTextListOpt(&data_segment.data);
      EXPECT(Rpar);

      uint32_t num_pages = WABT_BYTES_TO_MIN_PAGES(data_segment.data.size(),
                                                   field->memory.page_size);
      field->memory.page_limits.initial = num_pages;
      field->memory.page_limits.max = num_pages;
      field->memory.page_limits.has_max = true;

      module->AppendField(std::move(field));
      module->AppendField(std::move(data_segment_field));
    } else {
      CHECK_RESULT(ParseLimits(&field->memory.page_limits));
      CHECK_RESULT(ParsePageSize(&field->memory.page_size));
      module->AppendField(std::move(field));
    }
  }

  AppendInlineExportFields(module, &export_fields, module->memories.size() - 1);

  EXPECT(Rpar);
  return Result::Ok;
}

Result WastParser::ParseStartModuleField(Module* module) {
  WABT_TRACE(ParseStartModuleField);
  EXPECT(Lpar);
  Location loc = GetLocation();
  if (module->starts.size() > 0) {
    Error(loc, "multiple start sections");
    return Result::Error;
  }
  EXPECT(Start);
  Var var;
  CHECK_RESULT(ParseVar(&var));
  EXPECT(Rpar);
  module->AppendField(std::make_unique<StartModuleField>(var, loc));
  return Result::Ok;
}

Result WastParser::ParseTableModuleField(Module* module) {
  WABT_TRACE(ParseTableModuleField);
  EXPECT(Lpar);
  Location loc = GetLocation();
  EXPECT(Table);
  CHECK_RESULT(CheckIndexRange(loc, module->tables.size(), "table"));
  std::string name;
  CHECK_RESULT(ParseBindVarOpt(&name));

  ModuleFieldList export_fields;
  CHECK_RESULT(ParseInlineExports(&export_fields, ExternalKind::Table));

  if (PeekMatchLpar(TokenType::Import)) {
    CheckImportOrdering(module);
    CHECK_RESULT(CheckIndexRange(loc, module->imports.size(), "import"));
    auto import = std::make_unique<TableImport>(name);
    CHECK_RESULT(ParseInlineImport(import.get()));
    CHECK_RESULT(ParseLimitsIndex(&import->table.elem_limits));
    CHECK_RESULT(ParseLimits(&import->table.elem_limits));
    Var elem_type;
    CHECK_RESULT(ParseRefType(&elem_type));
    VarToType(elem_type, &import->table.elem_type);
    auto field =
        std::make_unique<ImportModuleField>(std::move(import), GetLocation());
    module->AppendField(std::move(field));
  } else {
    auto field = std::make_unique<TableModuleField>(loc, name);
    auto& table = field->table;
    CHECK_RESULT(ParseLimitsIndex(&table.elem_limits));
    if (PeekMatch(TokenType::ValueType) || PeekMatchRefType()) {
      Var elem_type;
      CHECK_RESULT(ParseRefType(&elem_type));

      EXPECT(Lpar);
      EXPECT(Elem);
      CHECK_RESULT(CheckIndexRange(loc, module->elem_segments.size(), "elem"));

      auto elem_segment_field = std::make_unique<ElemSegmentModuleField>(loc);
      ElemSegment& elem_segment = elem_segment_field->elem_segment;
      elem_segment.table_var = Var(module->tables.size(), GetLocation());
      auto offset = table.elem_limits.is_64 ? Const::I64(0) : Const::I32(0);
      elem_segment.offset.push_back(std::make_unique<ConstExpr>(offset));
      elem_segment.offset.back().loc = loc;
      VarToType(elem_type, &elem_segment.elem_type);
      // Syntax is either an optional list of var (legacy), or a non-empty list
      // of elem expr.
      ExprList elem_expr;
      if (ParseElemExprOpt(&elem_expr)) {
        elem_segment.elem_exprs.push_back(std::move(elem_expr));
        // Parse the rest.
        ParseElemExprListOpt(&elem_segment.elem_exprs);
      } else {
        CHECK_RESULT(ParseElemExprVarListOpt(&elem_segment.elem_exprs));
      }
      EXPECT(Rpar);

      table.elem_limits.initial = elem_segment.elem_exprs.size();
      table.elem_limits.max = elem_segment.elem_exprs.size();
      table.elem_limits.has_max = true;
      VarToType(elem_type, &table.elem_type);
      module->AppendField(std::move(field));
      module->AppendField(std::move(elem_segment_field));
    } else {
      CHECK_RESULT(ParseLimits(&table.elem_limits));
      Var elem_type;
      CHECK_RESULT(ParseRefType(&elem_type));
      VarToType(elem_type, &table.elem_type);
      if (PeekMatch(TokenType::Lpar)) {
        CHECK_RESULT(ParseTerminatingInstrList(&table.init_expr));
      }
      module->AppendField(std::move(field));
    }
  }

  AppendInlineExportFields(module, &export_fields, module->tables.size() - 1);

  EXPECT(Rpar);
  return Result::Ok;
}

Result WastParser::ParseExportDesc(Export* export_) {
  WABT_TRACE(ParseExportDesc);
  EXPECT(Lpar);
  switch (Peek()) {
    case TokenType::Func:   export_->kind = ExternalKind::Func; break;
    case TokenType::Table:  export_->kind = ExternalKind::Table; break;
    case TokenType::Memory: export_->kind = ExternalKind::Memory; break;
    case TokenType::Global: export_->kind = ExternalKind::Global; break;
    case TokenType::Tag:    export_->kind = ExternalKind::Tag; break;
    default:
      return ErrorExpected({"an external kind"});
  }
  DropToken();
  CHECK_RESULT(ParseVar(&export_->var));
  EXPECT(Rpar);
  return Result::Ok;
}

Result WastParser::ParseInlineExports(ModuleFieldList* fields,
                                      ExternalKind kind) {
  WABT_TRACE(ParseInlineExports);
  while (PeekMatchLpar(TokenType::Export)) {
    EXPECT(Lpar);
    auto field = std::make_unique<ExportModuleField>(GetLocation());
    field->export_.kind = kind;
    EXPECT(Export);
    CHECK_RESULT(ParseQuotedText(&field->export_.name));
    EXPECT(Rpar);
    fields->push_back(std::move(field));
  }
  return Result::Ok;
}

Result WastParser::ParseInlineImport(Import* import) {
  WABT_TRACE(ParseInlineImport);
  EXPECT(Lpar);
  EXPECT(Import);
  CHECK_RESULT(ParseQuotedText(&import->module_name));
  CHECK_RESULT(ParseQuotedText(&import->field_name));
  EXPECT(Rpar);
  return Result::Ok;
}

Result WastParser::ParseTypeUseOpt(FuncDeclaration* decl) {
  WABT_TRACE(ParseTypeUseOpt);
  if (MatchLpar(TokenType::Type)) {
    decl->has_func_type = true;
    CHECK_RESULT(ParseVar(&decl->type_var));
    EXPECT(Rpar);
  } else {
    decl->has_func_type = false;
  }
  return Result::Ok;
}

Result WastParser::ParseFuncSignature(FuncSignature* sig,
                                      BindingHash* param_bindings) {
  WABT_TRACE(ParseFuncSignature);

  ResolveTypeVector param_references(&sig->param_types);
  ResolveTypeVector result_references(&sig->result_types);

  CHECK_RESULT(ParseBoundValueTypeList(TokenType::Param, &sig->param_types,
                                       param_bindings, &param_references.vars));
  CHECK_RESULT(ParseResultList(&sig->result_types, &result_references.vars));

  if (!param_references.vars.empty()) {
    resolve_type_vectors_.push_back(param_references);
  }

  if (!result_references.vars.empty()) {
    resolve_type_vectors_.push_back(result_references);
  }

  return Result::Ok;
}

Result WastParser::ParseUnboundFuncSignature(FuncSignature* sig) {
  WABT_TRACE(ParseUnboundFuncSignature);

  ResolveTypeVector param_references(&sig->param_types);
  ResolveTypeVector result_references(&sig->result_types);

  CHECK_RESULT(ParseUnboundValueTypeList(TokenType::Param, &sig->param_types,
                                         &param_references.vars));
  CHECK_RESULT(ParseResultList(&sig->result_types, &result_references.vars));

  if (!param_references.vars.empty()) {
    resolve_type_vectors_.push_back(param_references);
  }

  if (!result_references.vars.empty()) {
    resolve_type_vectors_.push_back(result_references);
  }

  return Result::Ok;
}

Result WastParser::ParseBoundValueTypeList(TokenType token,
                                           TypeVector* types,
                                           BindingHash* bindings,
                                           ReferenceVars* type_vars,
                                           Index binding_index_offset) {
  WABT_TRACE(ParseBoundValueTypeList);
  while (MatchLpar(token)) {
    if (PeekMatch(TokenType::Var)) {
      std::string name;
      Var type;
      Location loc = GetLocation();
      CHECK_RESULT(ParseBindVarOpt(&name));
      CHECK_RESULT(ParseValueType(&type));
      bindings->emplace(name,
                        Binding(loc, binding_index_offset + types->size()));
      if (type.is_index()) {
        types->push_back(type.to_type());
      } else {
        assert(type.is_name());
        assert(options_->features.function_references_enabled());
        type_vars->push_back(ReferenceVar(types->size(), type));
        types->push_back(Type(type.opt_type(), kInvalidIndex));
      }
    } else {
      CHECK_RESULT(ParseValueTypeList(types, type_vars));
    }
    EXPECT(Rpar);
  }
  return Result::Ok;
}

Result WastParser::ParseUnboundValueTypeList(TokenType token,
                                             TypeVector* types,
                                             ReferenceVars* type_vars) {
  WABT_TRACE(ParseUnboundValueTypeList);
  while (MatchLpar(token)) {
    CHECK_RESULT(ParseValueTypeList(types, type_vars));
    EXPECT(Rpar);
  }
  return Result::Ok;
}

Result WastParser::ParseResultList(TypeVector* result_types,
                                   ReferenceVars* type_vars) {
  WABT_TRACE(ParseResultList);
  return ParseUnboundValueTypeList(TokenType::Result, result_types, type_vars);
}

Result WastParser::ParseInstrList(ExprList* exprs) {
  WABT_TRACE(ParseInstrList);
  ExprList new_exprs;
  while (true) {
    auto pair = PeekPair();
    if (IsInstr(pair)) {
      if (Succeeded(ParseInstr(&new_exprs))) {
        exprs->splice(exprs->end(), new_exprs);
      } else {
        CHECK_RESULT(Synchronize(IsInstr));
      }
    } else if (IsLparAnn(pair)) {
      if (Succeeded(ParseCodeMetadataAnnotation(&new_exprs))) {
        exprs->splice(exprs->end(), new_exprs);
      } else {
        CHECK_RESULT(Synchronize(IsLparAnn));
      }
    } else {
      break;
    }
  }
  return Result::Ok;
}

Result WastParser::ParseTerminatingInstrList(ExprList* exprs) {
  WABT_TRACE(ParseTerminatingInstrList);
  Result result = ParseInstrList(exprs);
  // An InstrList often has no further Lpar following it, because it would have
  // gobbled it up. So if there is a following Lpar it is an error. If we
  // handle it here we can produce a nicer error message.
  CHECK_RESULT(ErrorIfLpar({"an instr"}));
  return result;
}

Result WastParser::ParseInstr(ExprList* exprs) {
  WABT_TRACE(ParseInstr);
  if (IsPlainInstr(Peek())) {
    std::unique_ptr<Expr> expr;
    CHECK_RESULT(ParsePlainInstr(&expr));
    exprs->push_back(std::move(expr));
    return Result::Ok;
  } else if (IsBlockInstr(Peek())) {
    std::unique_ptr<Expr> expr;
    CHECK_RESULT(ParseBlockInstr(&expr));
    exprs->push_back(std::move(expr));
    return Result::Ok;
  } else if (PeekMatchExpr()) {
    return ParseExpr(exprs);
  } else {
    assert(!"ParseInstr should only be called when IsInstr() is true");
    return Result::Error;
  }
}

Result WastParser::ParseCodeMetadataAnnotation(ExprList* exprs) {
  WABT_TRACE(ParseCodeMetadataAnnotation);
  Token tk = Consume();
  std::string_view name = tk.text();
  name.remove_prefix(sizeof("metadata.code.") - 1);
  std::string data_text;
  CHECK_RESULT(ParseQuotedText(&data_text, false));
  std::vector<uint8_t> data(data_text.begin(), data_text.end());
  exprs->push_back(std::make_unique<CodeMetadataExpr>(name, std::move(data)));
  EXPECT(Rpar);
  return Result::Ok;
}

template <typename T>
Result WastParser::ParsePlainInstrVar(Location loc,
                                      std::unique_ptr<Expr>* out_expr) {
  Var var;
  CHECK_RESULT(ParseVar(&var));
  out_expr->reset(new T(var, loc));
  return Result::Ok;
}

template <typename T>
Result WastParser::ParseMemoryInstrVar(Location loc,
                                       std::unique_ptr<Expr>* out_expr) {
  Var memidx;
  Var var;
  if (PeekMatchLpar(TokenType::Memory)) {
    if (!options_->features.multi_memory_enabled()) {
      Error(loc, "Specifying memory variable is not allowed");
      return Result::Error;
    }
    CHECK_RESULT(ParseMemidx(loc, &memidx));
    CHECK_RESULT(ParseVar(&var));
    out_expr->reset(new T(var, memidx, loc));
  } else {
    CHECK_RESULT(ParseVar(&memidx));
    if (PeekMatchVar()) {
      CHECK_RESULT(ParseVar(&var));
      if (!options_->features.multi_memory_enabled()) {
        Error(loc, "Specifiying memory variable is not allowed");
        return Result::Error;
      }
      out_expr->reset(new T(var, memidx, loc));
    } else {
      out_expr->reset(new T(memidx, Var(0, loc), loc));
    }
  }
  return Result::Ok;
}

template <typename T>
Result WastParser::ParseLoadStoreInstr(Location loc,
                                       Token token,
                                       std::unique_ptr<Expr>* out_expr) {
  Opcode opcode = token.opcode();
  Var memidx;
  Address offset;
  Address align;
  CHECK_RESULT(ParseMemidx(loc, &memidx));
  ParseOffsetOpt(&offset);
  ParseAlignOpt(&align);
  out_expr->reset(new T(opcode, memidx, align, offset, loc));
  return Result::Ok;
}

template <typename T>
Result WastParser::ParseSIMDLoadStoreInstr(Location loc,
                                           Token token,
                                           std::unique_ptr<Expr>* out_expr) {
  ErrorUnlessOpcodeEnabled(token);

  Var memidx(0, loc);

  if (options_->features.multi_memory_enabled()) {
    // We have to be a little careful when reading the memeory index.
    // If there is just a single integer folloing the instruction that
    // represents the lane index, so we check for either a pair of intergers
    // or an integers followed by offset= or align=.
    bool try_read_mem_index = true;
    if (PeekMatch(TokenType::Nat)) {
      // The next token could be a memory index or a lane index
      if (!PeekMatch(TokenType::OffsetEqNat, 1) &&
          !PeekMatch(TokenType::AlignEqNat, 1) &&
          !PeekMatch(TokenType::Nat, 1)) {
        try_read_mem_index = false;
      }
    }
    if (try_read_mem_index) {
      CHECK_RESULT(ParseMemidx(loc, &memidx));
    }
  }
  Address offset;
  Address align;
  ParseOffsetOpt(&offset);
  ParseAlignOpt(&align);

  uint64_t lane_idx = 0;
  Result result = ParseSimdLane(loc, &lane_idx);

  if (Failed(result)) {
    return Result::Error;
  }

  out_expr->reset(new T(token.opcode(), memidx, align, offset, lane_idx, loc));
  return Result::Ok;
}

template <typename T>
Result WastParser::ParseMemoryExpr(Location loc,
                                   std::unique_ptr<Expr>* out_expr) {
  Var memidx;
  CHECK_RESULT(ParseMemidx(loc, &memidx));
  out_expr->reset(new T(memidx, loc));
  return Result::Ok;
}

template <typename T>
Result WastParser::ParseMemoryBinaryExpr(Location loc,
                                         std::unique_ptr<Expr>* out_expr) {
  Var destmemidx;
  Var srcmemidx;
  CHECK_RESULT(ParseMemidx(loc, &destmemidx));
  CHECK_RESULT(ParseMemidx(loc, &srcmemidx));
  out_expr->reset(new T(destmemidx, srcmemidx, loc));
  return Result::Ok;
}

Result WastParser::ParseSimdLane(Location loc, uint64_t* lane_idx) {
  if (!PeekMatch(TokenType::Nat) && !PeekMatch(TokenType::Int)) {
    return ErrorExpected({"a natural number in range [0, 32)"});
  }

  Literal literal = Consume().literal();

  Result result =
      ParseInt64(literal.text, lane_idx, ParseIntType::UnsignedOnly);

  if (Failed(result)) {
    Error(loc, "invalid literal \"" PRIstringview "\"",
          WABT_PRINTF_STRING_VIEW_ARG(literal.text));
    return Result::Error;
  }

  // The valid range is only [0, 32), but it's only malformed if it can't
  // fit in a byte.
  if (*lane_idx > 255) {
    Error(loc, "lane index \"" PRIstringview "\" out-of-range [0, 32)",
          WABT_PRINTF_STRING_VIEW_ARG(literal.text));
    return Result::Error;
  }

  return Result::Ok;
}

Result WastParser::ParsePlainInstr(std::unique_ptr<Expr>* out_expr) {
  WABT_TRACE(ParsePlainInstr);
  Location loc = GetLocation();
  switch (Peek()) {
    case TokenType::Unreachable:
      DropToken();
      out_expr->reset(new UnreachableExpr(loc));
      break;

    case TokenType::Nop:
      DropToken();
      out_expr->reset(new NopExpr(loc));
      break;

    case TokenType::Drop:
      DropToken();
      out_expr->reset(new DropExpr(loc));
      break;

    case TokenType::Select: {
      DropToken();
      auto expr = std::make_unique<SelectExpr>(loc);
      ResolveTypeVector result_type(&expr->result_type);
      if (options_->features.reference_types_enabled() &&
          PeekMatchLpar(TokenType::Result)) {
        CHECK_RESULT(ParseResultList(&expr->result_type, &result_type.vars));
      }
      *out_expr = std::move(expr);
      break;
    }

    case TokenType::Br:
      DropToken();
      CHECK_RESULT(ParsePlainInstrVar<BrExpr>(loc, out_expr));
      break;

    case TokenType::BrIf:
      DropToken();
      CHECK_RESULT(ParsePlainInstrVar<BrIfExpr>(loc, out_expr));
      break;

    case TokenType::BrOnNonNull:
      DropToken();
      CHECK_RESULT(ParsePlainInstrVar<BrOnNonNullExpr>(loc, out_expr));
      break;

    case TokenType::BrOnNull:
      DropToken();
      CHECK_RESULT(ParsePlainInstrVar<BrOnNullExpr>(loc, out_expr));
      break;

    case TokenType::BrTable: {
      DropToken();
      auto expr = std::make_unique<BrTableExpr>(loc);
      CHECK_RESULT(ParseVarList(&expr->targets));
      expr->default_target = expr->targets.back();
      expr->targets.pop_back();
      *out_expr = std::move(expr);
      break;
    }

    case TokenType::Return:
      DropToken();
      out_expr->reset(new ReturnExpr(loc));
      break;

    case TokenType::Call:
      DropToken();
      CHECK_RESULT(ParsePlainInstrVar<CallExpr>(loc, out_expr));
      break;

    case TokenType::CallIndirect: {
      DropToken();
      auto expr = std::make_unique<CallIndirectExpr>(loc);
      CHECK_RESULT(ParseVarOpt(&expr->table, Var(0, loc)));
      CHECK_RESULT(ParseTypeUseOpt(&expr->decl));
      CHECK_RESULT(ParseUnboundFuncSignature(&expr->decl.sig));
      *out_expr = std::move(expr);
      break;
    }

    case TokenType::CallRef: {
      ErrorUnlessOpcodeEnabled(Consume());
      auto expr = std::make_unique<CallRefExpr>(loc);
      CHECK_RESULT(ParseVar(&expr->sig_type));
      expr->sig_type.set_opt_type(Type::RefNull);
      *out_expr = std::move(expr);
      break;
    }

    case TokenType::ReturnCall:
      ErrorUnlessOpcodeEnabled(Consume());
      CHECK_RESULT(ParsePlainInstrVar<ReturnCallExpr>(loc, out_expr));
      break;

    case TokenType::ReturnCallIndirect: {
      ErrorUnlessOpcodeEnabled(Consume());
      auto expr = std::make_unique<ReturnCallIndirectExpr>(loc);
      CHECK_RESULT(ParseVarOpt(&expr->table, Var(0, loc)));
      CHECK_RESULT(ParseTypeUseOpt(&expr->decl));
      CHECK_RESULT(ParseUnboundFuncSignature(&expr->decl.sig));
      *out_expr = std::move(expr);
      break;
    }

    case TokenType::ReturnCallRef: {
      ErrorUnlessOpcodeEnabled(Consume());
      auto expr = std::make_unique<ReturnCallRefExpr>(loc);
      CHECK_RESULT(ParseVar(&expr->sig_type));
      expr->sig_type.set_opt_type(Type::RefNull);
      *out_expr = std::move(expr);
      break;
    }

    case TokenType::LocalGet:
      DropToken();
      CHECK_RESULT(ParsePlainInstrVar<LocalGetExpr>(loc, out_expr));
      break;

    case TokenType::LocalSet:
      DropToken();
      CHECK_RESULT(ParsePlainInstrVar<LocalSetExpr>(loc, out_expr));
      break;

    case TokenType::LocalTee:
      DropToken();
      CHECK_RESULT(ParsePlainInstrVar<LocalTeeExpr>(loc, out_expr));
      break;

    case TokenType::GlobalGet:
      DropToken();
      CHECK_RESULT(ParsePlainInstrVar<GlobalGetExpr>(loc, out_expr));
      break;

    case TokenType::GlobalSet:
      DropToken();
      CHECK_RESULT(ParsePlainInstrVar<GlobalSetExpr>(loc, out_expr));
      break;

    case TokenType::Load:
      CHECK_RESULT(ParseLoadStoreInstr<LoadExpr>(loc, Consume(), out_expr));
      break;

    case TokenType::Store:
      CHECK_RESULT(ParseLoadStoreInstr<StoreExpr>(loc, Consume(), out_expr));
      break;

    case TokenType::Const: {
      Const const_;
      CHECK_RESULT(ParseConst(&const_, ConstType::Normal));
      out_expr->reset(new ConstExpr(const_, loc));
      break;
    }

    case TokenType::Unary: {
      Token token = Consume();
      ErrorUnlessOpcodeEnabled(token);
      out_expr->reset(new UnaryExpr(token.opcode(), loc));
      break;
    }

    case TokenType::Binary: {
      Token token = Consume();
      ErrorUnlessOpcodeEnabled(token);
      out_expr->reset(new BinaryExpr(token.opcode(), loc));
      break;
    }

    case TokenType::Quaternary: {
      Token token = Consume();
      ErrorUnlessOpcodeEnabled(token);
      out_expr->reset(new QuaternaryExpr(token.opcode(), loc));
      break;
    }

    case TokenType::Compare:
      out_expr->reset(new CompareExpr(Consume().opcode(), loc));
      break;

    case TokenType::Convert: {
      Token token = Consume();
      ErrorUnlessOpcodeEnabled(token);
      out_expr->reset(new ConvertExpr(token.opcode(), loc));
      break;
    }

    case TokenType::MemoryCopy:
      ErrorUnlessOpcodeEnabled(Consume());
      CHECK_RESULT(ParseMemoryBinaryExpr<MemoryCopyExpr>(loc, out_expr));
      break;

    case TokenType::MemoryFill:
      ErrorUnlessOpcodeEnabled(Consume());
      CHECK_RESULT(ParseMemoryExpr<MemoryFillExpr>(loc, out_expr));
      break;

    case TokenType::DataDrop:
      ErrorUnlessOpcodeEnabled(Consume());
      CHECK_RESULT(ParsePlainInstrVar<DataDropExpr>(loc, out_expr));
      break;

    case TokenType::MemoryInit:
      ErrorUnlessOpcodeEnabled(Consume());
      CHECK_RESULT(ParseMemoryInstrVar<MemoryInitExpr>(loc, out_expr));
      break;

    case TokenType::MemorySize:
      DropToken();
      CHECK_RESULT(ParseMemoryExpr<MemorySizeExpr>(loc, out_expr));
      break;

    case TokenType::MemoryGrow:
      DropToken();
      CHECK_RESULT(ParseMemoryExpr<MemoryGrowExpr>(loc, out_expr));
      break;

    case TokenType::TableCopy: {
      ErrorUnlessOpcodeEnabled(Consume());
      Var dst(0, loc);
      Var src(0, loc);
      if (options_->features.reference_types_enabled()) {
        CHECK_RESULT(ParseVarOpt(&dst, dst));
        CHECK_RESULT(ParseVarOpt(&src, src));
      }
      out_expr->reset(new TableCopyExpr(dst, src, loc));
      break;
    }

    case TokenType::ElemDrop:
      ErrorUnlessOpcodeEnabled(Consume());
      CHECK_RESULT(ParsePlainInstrVar<ElemDropExpr>(loc, out_expr));
      break;

    case TokenType::TableInit: {
      ErrorUnlessOpcodeEnabled(Consume());
      Var segment_index(0, loc);
      CHECK_RESULT(ParseVar(&segment_index));
      Var table_index(0, loc);
      if (PeekMatchVar()) {
        CHECK_RESULT(ParseVar(&table_index));
        // Here are the two forms:
        //
        //   table.init $elemidx ...
        //   table.init $tableidx $elemidx ...
        //
        // So if both indexes are provided, we need to swap them.
        std::swap(segment_index, table_index);
      }
      out_expr->reset(new TableInitExpr(segment_index, table_index, loc));
      break;
    }

    case TokenType::TableGet: {
      ErrorUnlessOpcodeEnabled(Consume());
      Var table_index(0, loc);
      CHECK_RESULT(ParseVarOpt(&table_index, table_index));
      out_expr->reset(new TableGetExpr(table_index, loc));
      break;
    }

    case TokenType::TableSet: {
      ErrorUnlessOpcodeEnabled(Consume());
      Var table_index(0, loc);
      CHECK_RESULT(ParseVarOpt(&table_index, table_index));
      out_expr->reset(new TableSetExpr(table_index, loc));
      break;
    }

    case TokenType::TableGrow: {
      ErrorUnlessOpcodeEnabled(Consume());
      Var table_index(0, loc);
      CHECK_RESULT(ParseVarOpt(&table_index, table_index));
      out_expr->reset(new TableGrowExpr(table_index, loc));
      break;
    }

    case TokenType::TableSize: {
      ErrorUnlessOpcodeEnabled(Consume());
      Var table_index(0, loc);
      CHECK_RESULT(ParseVarOpt(&table_index, table_index));
      out_expr->reset(new TableSizeExpr(table_index, loc));
      break;
    }

    case TokenType::TableFill: {
      ErrorUnlessOpcodeEnabled(Consume());
      Var table_index(0, loc);
      CHECK_RESULT(ParseVarOpt(&table_index, table_index));
      out_expr->reset(new TableFillExpr(table_index, loc));
      break;
    }

    case TokenType::RefAsNonNull:
      ErrorUnlessOpcodeEnabled(Consume());
      out_expr->reset(new RefAsNonNullExpr(Opcode::RefAsNonNull, loc));
      break;

    case TokenType::RefFunc:
      ErrorUnlessOpcodeEnabled(Consume());
      CHECK_RESULT(ParsePlainInstrVar<RefFuncExpr>(loc, out_expr));
      break;

    case TokenType::RefNull: {
      ErrorUnlessOpcodeEnabled(Consume());
      Var type;
      CHECK_RESULT(ParseRefKind(&type));
      out_expr->reset(new RefNullExpr(type, loc));
      break;
    }

    case TokenType::RefIsNull:
      ErrorUnlessOpcodeEnabled(Consume());
      out_expr->reset(new RefIsNullExpr(loc));
      break;

    case TokenType::Throw:
      ErrorUnlessOpcodeEnabled(Consume());
      CHECK_RESULT(ParsePlainInstrVar<ThrowExpr>(loc, out_expr));
      break;

    case TokenType::ThrowRef:
      ErrorUnlessOpcodeEnabled(Consume());
      out_expr->reset(new ThrowRefExpr(loc));
      break;

    case TokenType::Rethrow:
      ErrorUnlessOpcodeEnabled(Consume());
      CHECK_RESULT(ParsePlainInstrVar<RethrowExpr>(loc, out_expr));
      break;

    case TokenType::AtomicNotify: {
      Token token = Consume();
      ErrorUnlessOpcodeEnabled(token);
      CHECK_RESULT(ParseLoadStoreInstr<AtomicNotifyExpr>(loc, token, out_expr));
      break;
    }

    case TokenType::AtomicFence: {
      Token token = Consume();
      ErrorUnlessOpcodeEnabled(token);
      uint32_t consistency_model = 0x0;
      out_expr->reset(new AtomicFenceExpr(consistency_model, loc));
      break;
    }

    case TokenType::AtomicWait: {
      Token token = Consume();
      ErrorUnlessOpcodeEnabled(token);
      CHECK_RESULT(ParseLoadStoreInstr<AtomicWaitExpr>(loc, token, out_expr));
      break;
    }

    case TokenType::AtomicLoad: {
      Token token = Consume();
      ErrorUnlessOpcodeEnabled(token);
      CHECK_RESULT(ParseLoadStoreInstr<AtomicLoadExpr>(loc, token, out_expr));
      break;
    }

    case TokenType::AtomicStore: {
      Token token = Consume();
      ErrorUnlessOpcodeEnabled(token);
      CHECK_RESULT(ParseLoadStoreInstr<AtomicStoreExpr>(loc, token, out_expr));
      break;
    }

    case TokenType::AtomicRmw: {
      Token token = Consume();
      ErrorUnlessOpcodeEnabled(token);
      CHECK_RESULT(ParseLoadStoreInstr<AtomicRmwExpr>(loc, token, out_expr));
      break;
    }

    case TokenType::AtomicRmwCmpxchg: {
      Token token = Consume();
      ErrorUnlessOpcodeEnabled(token);
      CHECK_RESULT(
          ParseLoadStoreInstr<AtomicRmwCmpxchgExpr>(loc, token, out_expr));
      break;
    }

    case TokenType::Ternary: {
      Token token = Consume();
      ErrorUnlessOpcodeEnabled(token);
      out_expr->reset(new TernaryExpr(token.opcode(), loc));
      break;
    }

    case TokenType::SimdLaneOp: {
      Token token = Consume();
      ErrorUnlessOpcodeEnabled(token);

      uint64_t lane_idx = 0;
      Result result = ParseSimdLane(loc, &lane_idx);

      if (Failed(result)) {
        return Result::Error;
      }

      out_expr->reset(new SimdLaneOpExpr(token.opcode(), lane_idx, loc));
      break;
    }

    case TokenType::SimdLoadLane: {
      CHECK_RESULT(
          ParseSIMDLoadStoreInstr<SimdLoadLaneExpr>(loc, Consume(), out_expr));
      break;
    }

    case TokenType::SimdStoreLane: {
      CHECK_RESULT(
          ParseSIMDLoadStoreInstr<SimdStoreLaneExpr>(loc, Consume(), out_expr));
      break;
    }

    case TokenType::SimdShuffleOp: {
      Token token = Consume();
      ErrorUnlessOpcodeEnabled(token);
      v128 values;
      for (int lane = 0; lane < 16; ++lane) {
        Location loc = GetLocation();
        uint64_t lane_idx;
        Result result = ParseSimdLane(loc, &lane_idx);
        if (Failed(result)) {
          return Result::Error;
        }

        values.set_u8(lane, static_cast<uint8_t>(lane_idx));
      }

      out_expr->reset(new SimdShuffleOpExpr(token.opcode(), values, loc));
      break;
    }

    default:
      assert(
          !"ParsePlainInstr should only be called when IsPlainInstr() is true");
      return Result::Error;
  }

  return Result::Ok;
}

Result WastParser::ParseSimdV128Const(Const* const_,
                                      TokenType token_type,
                                      ConstType const_type) {
  WABT_TRACE(ParseSimdV128Const);

  uint8_t lane_count = 0;
  bool integer = true;
  switch (token_type) {
    case TokenType::I8X16: { lane_count = 16; break; }
    case TokenType::I16X8: { lane_count = 8; break; }
    case TokenType::I32X4: { lane_count = 4; break; }
    case TokenType::I64X2: { lane_count = 2; break; }
    case TokenType::F32X4: { lane_count = 4; integer = false; break; }
    case TokenType::F64X2: { lane_count = 2; integer = false; break; }
    default: {
      Error(const_->loc,
            "Unexpected type at start of simd constant. "
            "Expected one of: i8x16, i16x8, i32x4, i64x2, f32x4, f64x2. "
            "Found \"%s\".",
            GetTokenTypeName(token_type));
      return Result::Error;
    }
  }
  DropToken();

  const_->loc = GetLocation();

  for (int lane = 0; lane < lane_count; ++lane) {
    Location loc = GetLocation();

    // Check that the lane literal type matches the element type of the v128:
    Token token = GetToken();
    switch (token.token_type()) {
      case TokenType::Nat:
      case TokenType::Int:
        // OK.
        break;

      case TokenType::Float:
      case TokenType::NanArithmetic:
      case TokenType::NanCanonical:
        if (integer) {
          goto error;
        }
        break;

      error:
      default:
        if (integer) {
          return ErrorExpected({"a Nat or Integer literal"}, "123");
        } else {
          return ErrorExpected({"a Float literal"}, "42.0");
        }
    }

    Result result;

    // For each type, parse the next literal, bound check it, and write it to
    // the array of bytes:
    if (integer) {
      std::string_view sv = Consume().literal().text;

      switch (lane_count) {
        case 16: {
          uint8_t value = 0;
          result = ParseInt8(sv, &value, ParseIntType::SignedAndUnsigned);
          const_->set_v128_u8(lane, value);
          break;
        }
        case 8: {
          uint16_t value = 0;
          result = ParseInt16(sv, &value, ParseIntType::SignedAndUnsigned);
          const_->set_v128_u16(lane, value);
          break;
        }
        case 4: {
          uint32_t value = 0;
          result = ParseInt32(sv, &value, ParseIntType::SignedAndUnsigned);
          const_->set_v128_u32(lane, value);
          break;
        }
        case 2: {
          uint64_t value = 0;
          result = ParseInt64(sv, &value, ParseIntType::SignedAndUnsigned);
          const_->set_v128_u64(lane, value);
          break;
        }
      }
    } else {
      Const lane_const_;
      switch (lane_count) {
        case 4:
          result = ParseF32(&lane_const_, const_type);
          const_->set_v128_f32(lane, lane_const_.f32_bits());
          break;

        case 2:
          result = ParseF64(&lane_const_, const_type);
          const_->set_v128_f64(lane, lane_const_.f64_bits());
          break;
      }

      const_->set_expected_nan(lane, lane_const_.expected_nan());
    }

    if (Failed(result)) {
      Error(loc, "invalid literal \"%s\"", token.to_string().c_str());
      return Result::Error;
    }
  }

  return Result::Ok;
}

Result WastParser::ParseExpectedNan(ExpectedNan* expected) {
  WABT_TRACE(ParseExpectedNan);
  TokenType token_type = Peek();
  switch (token_type) {
    case TokenType::NanArithmetic:
      *expected = ExpectedNan::Arithmetic;
      break;
    case TokenType::NanCanonical:
      *expected = ExpectedNan::Canonical;
      break;
    default:
      return Result::Error;
  }
  DropToken();
  return Result::Ok;
}

Result WastParser::ParseF32(Const* const_, ConstType const_type) {
  ExpectedNan expected;
  if (const_type == ConstType::Expectation &&
      Succeeded(ParseExpectedNan(&expected))) {
    const_->set_f32(expected);
    return Result::Ok;
  }

  auto token = Consume();
  if (!token.HasLiteral()) {
    return Result::Error;
  }

  auto literal = token.literal();
  uint32_t f32_bits = 0;
  Result result = ParseFloat(literal.type, literal.text, &f32_bits);
  const_->set_f32(f32_bits);
  return result;
}

Result WastParser::ParseF64(Const* const_, ConstType const_type) {
  ExpectedNan expected;
  if (const_type == ConstType::Expectation &&
      Succeeded(ParseExpectedNan(&expected))) {
    const_->set_f64(expected);
    return Result::Ok;
  }

  auto token = Consume();
  if (!token.HasLiteral()) {
    return Result::Error;
  }

  auto literal = token.literal();
  uint64_t f64_bits = 0;
  Result result = ParseDouble(literal.type, literal.text, &f64_bits);
  const_->set_f64(f64_bits);
  return result;
}

Result WastParser::ParseConst(Const* const_, ConstType const_type) {
  WABT_TRACE(ParseConst);
  Token opcode_token = Consume();
  Opcode opcode = opcode_token.opcode();
  const_->loc = GetLocation();
  Token token = GetToken();

  // V128 is fully handled by ParseSimdV128Const:
  if (opcode != Opcode::V128Const) {
    switch (token.token_type()) {
      case TokenType::Nat:
      case TokenType::Int:
      case TokenType::Float:
        // OK.
        break;
      case TokenType::NanArithmetic:
      case TokenType::NanCanonical:
        break;
      default:
        return ErrorExpected({"a numeric literal"}, "123, -45, 6.7e8");
    }
  }

  Result result;
  switch (opcode) {
    case Opcode::I32Const: {
      auto token = Consume();
      if (!token.HasLiteral()) {
        result = Result::Error;
        break;
      }
      auto sv = token.literal().text;
      uint32_t u32;
      result = ParseInt32(sv, &u32, ParseIntType::SignedAndUnsigned);
      const_->set_u32(u32);
      break;
    }

    case Opcode::I64Const: {
      auto token = Consume();
      if (!token.HasLiteral()) {
        result = Result::Error;
        break;
      }
      auto sv = token.literal().text;
      uint64_t u64;
      result = ParseInt64(sv, &u64, ParseIntType::SignedAndUnsigned);
      const_->set_u64(u64);
      break;
    }

    case Opcode::F32Const:
      result = ParseF32(const_, const_type);
      break;

    case Opcode::F64Const:
      result = ParseF64(const_, const_type);
      break;

    case Opcode::V128Const:
      ErrorUnlessOpcodeEnabled(opcode_token);
      // Parse V128 Simd Const (16 bytes).
      result = ParseSimdV128Const(const_, token.token_type(), const_type);
      // ParseSimdV128Const report error already, just return here if parser get
      // errors.
      if (Failed(result)) {
        return Result::Error;
      }
      break;

    default:
      assert(!"ParseConst called with invalid opcode");
      return Result::Error;
  }

  if (Failed(result)) {
    Error(const_->loc, "invalid literal \"%s\"", token.to_string().c_str());
    // Return if parser get errors.
    return Result::Error;
  }

  return Result::Ok;
}

Result WastParser::ParseExternref(Const* const_) {
  WABT_TRACE(ParseExternref);
  Token token = Consume();
  if (!options_->features.reference_types_enabled()) {
    Error(token.loc, "externref not allowed");
    return Result::Error;
  }

  Literal literal;
  std::string_view sv;
  const_->loc = GetLocation();
  TokenType token_type = Peek();

  switch (token_type) {
    case TokenType::Nat:
    case TokenType::Int: {
      literal = Consume().literal();
      sv = literal.text;
      break;
    }
    default:
      return ErrorExpected({"a numeric literal"}, "123");
  }

  uint64_t ref_bits;
  Result result = ParseInt64(sv, &ref_bits, ParseIntType::UnsignedOnly);

  if (ref_bits != 0 && options_->features.function_references_enabled()) {
    const_->set_extern(static_cast<uintptr_t>(ref_bits));
  } else {
    const_->set_externref(static_cast<uintptr_t>(ref_bits));
  }

  if (Failed(result)) {
    Error(const_->loc, "invalid literal \"" PRIstringview "\"",
          WABT_PRINTF_STRING_VIEW_ARG(literal.text));
    // Return if parser get errors.
    return Result::Error;
  }

  return Result::Ok;
}

Result WastParser::ParseConstList(ConstVector* consts, ConstType type) {
  WABT_TRACE(ParseConstList);
  while (PeekMatchLpar(TokenType::Const) || PeekMatchLpar(TokenType::RefNull) ||
         PeekMatchLpar(TokenType::RefExtern) ||
         PeekMatchLpar(TokenType::RefFunc)) {
    DropToken();
    Const const_;
    switch (Peek()) {
      case TokenType::Const:
        CHECK_RESULT(ParseConst(&const_, type));
        break;
      case TokenType::RefNull: {
        auto token = Consume();
        Var type;
        if (Peek() != TokenType::Rpar) {
          CHECK_RESULT(ParseRefKind(&type));
        }
        ErrorUnlessOpcodeEnabled(token);
        const_.loc = GetLocation();
        const_.set_null(type.has_opt_type() ? type.opt_type() : Type::FuncRef);
        break;
      }
      case TokenType::RefFunc: {
        auto token = Consume();
        ErrorUnlessOpcodeEnabled(token);
        const_.loc = GetLocation();
        const_.set_funcref();
        break;
      }
      case TokenType::RefExtern:
        CHECK_RESULT(ParseExternref(&const_));
        break;
      default:
        assert(!"unreachable");
        return Result::Error;
    }
    EXPECT(Rpar);
    consts->push_back(const_);
  }

  return Result::Ok;
}

Result WastParser::ParseBlockInstr(std::unique_ptr<Expr>* out_expr) {
  WABT_TRACE(ParseBlockInstr);
  Location loc = GetLocation();

  switch (Peek()) {
    case TokenType::Block: {
      DropToken();
      auto expr = std::make_unique<BlockExpr>(loc);
      CHECK_RESULT(ParseLabelOpt(&expr->block.label));
      CHECK_RESULT(ParseBlock(&expr->block));
      EXPECT(End);
      CHECK_RESULT(ParseEndLabelOpt(expr->block.label));
      *out_expr = std::move(expr);
      break;
    }

    case TokenType::Loop: {
      DropToken();
      auto expr = std::make_unique<LoopExpr>(loc);
      CHECK_RESULT(ParseLabelOpt(&expr->block.label));
      CHECK_RESULT(ParseBlock(&expr->block));
      EXPECT(End);
      CHECK_RESULT(ParseEndLabelOpt(expr->block.label));
      *out_expr = std::move(expr);
      break;
    }

    case TokenType::If: {
      DropToken();
      auto expr = std::make_unique<IfExpr>(loc);
      CHECK_RESULT(ParseLabelOpt(&expr->true_.label));
      CHECK_RESULT(ParseBlock(&expr->true_));
      if (Match(TokenType::Else)) {
        CHECK_RESULT(ParseEndLabelOpt(expr->true_.label));
        CHECK_RESULT(ParseTerminatingInstrList(&expr->false_));
        expr->false_end_loc = GetLocation();
      }
      EXPECT(End);
      CHECK_RESULT(ParseEndLabelOpt(expr->true_.label));
      *out_expr = std::move(expr);
      break;
    }

    case TokenType::Try: {
      ErrorUnlessOpcodeEnabled(Consume());
      auto expr = std::make_unique<TryExpr>(loc);
      CatchVector catches;
      CHECK_RESULT(ParseLabelOpt(&expr->block.label));
      CHECK_RESULT(ParseBlock(&expr->block));
      if (IsCatch(Peek())) {
        CHECK_RESULT(ParseCatchInstrList(&expr->catches));
        expr->kind = TryKind::Catch;
      } else if (PeekMatch(TokenType::Delegate)) {
        DropToken();
        Var var;
        CHECK_RESULT(ParseVar(&var));
        expr->delegate_target = var;
        expr->kind = TryKind::Delegate;
      }
      CHECK_RESULT(ErrorIfLpar({"a valid try clause"}));
      expr->block.end_loc = GetLocation();
      if (expr->kind != TryKind::Delegate) {
        EXPECT(End);
      }
      CHECK_RESULT(ParseEndLabelOpt(expr->block.label));
      *out_expr = std::move(expr);
      break;
    }

    case TokenType::TryTable: {
      ErrorUnlessOpcodeEnabled(Consume());
      auto expr = std::make_unique<TryTableExpr>(loc);
      CHECK_RESULT(ParseLabelOpt(&expr->block.label));
      CHECK_RESULT(ParseBlockDeclaration(&expr->block.decl));
      CHECK_RESULT(ParseTryTableCatches(&expr->catches));
      CHECK_RESULT(ParseInstrList(&expr->block.exprs));
      EXPECT(End);
      CHECK_RESULT(ParseEndLabelOpt(expr->block.label));
      *out_expr = std::move(expr);
      break;
    }

    default:
      assert(
          !"ParseBlockInstr should only be called when IsBlockInstr() is true");
      return Result::Error;
  }

  return Result::Ok;
}

Result WastParser::ParseLabelOpt(std::string* out_label) {
  WABT_TRACE(ParseLabelOpt);
  if (PeekMatch(TokenType::Var)) {
    Token token = Consume();
    ParseVarText(token, out_label);
  } else {
    out_label->clear();
  }
  return Result::Ok;
}

Result WastParser::ParseEndLabelOpt(const std::string& begin_label) {
  WABT_TRACE(ParseEndLabelOpt);
  Location loc = GetLocation();
  std::string end_label;
  CHECK_RESULT(ParseLabelOpt(&end_label));
  if (!end_label.empty()) {
    if (begin_label.empty()) {
      Error(loc, "unexpected label \"%s\"", end_label.c_str());
    } else if (begin_label != end_label) {
      Error(loc, "mismatching label \"%s\" != \"%s\"", begin_label.c_str(),
            end_label.c_str());
    }
  }
  return Result::Ok;
}

Result WastParser::ParseBlockDeclaration(BlockDeclaration* decl) {
  WABT_TRACE(ParseBlockDeclaration);
  CHECK_RESULT(ParseTypeUseOpt(decl));
  CHECK_RESULT(ParseUnboundFuncSignature(&decl->sig));
  return Result::Ok;
}

Result WastParser::ParseBlock(Block* block) {
  WABT_TRACE(ParseBlock);
  CHECK_RESULT(ParseBlockDeclaration(&block->decl));
  CHECK_RESULT(ParseInstrList(&block->exprs));
  block->end_loc = GetLocation();
  return Result::Ok;
}

Result WastParser::ParseExprList(ExprList* exprs) {
  WABT_TRACE(ParseExprList);
  ExprList new_exprs;
  while (PeekMatchExpr()) {
    if (Succeeded(ParseExpr(&new_exprs))) {
      exprs->splice(exprs->end(), new_exprs);
    } else {
      CHECK_RESULT(Synchronize(IsExpr));
    }
  }
  return Result::Ok;
}

Result WastParser::ParseExpr(ExprList* exprs) {
  WABT_TRACE(ParseExpr);
  if (!PeekMatch(TokenType::Lpar)) {
    return Result::Error;
  }

  if (IsPlainInstr(Peek(1))) {
    DropToken();
    std::unique_ptr<Expr> expr;
    CHECK_RESULT(ParsePlainInstr(&expr));
    CHECK_RESULT(ParseExprList(exprs));
    CHECK_RESULT(ErrorIfLpar({"an expr"}));
    exprs->push_back(std::move(expr));
  } else {
    Location loc = GetLocation();

    switch (Peek(1)) {
      case TokenType::Block: {
        DropTwoTokens();
        auto expr = std::make_unique<BlockExpr>(loc);
        CHECK_RESULT(ParseLabelOpt(&expr->block.label));
        CHECK_RESULT(ParseBlock(&expr->block));
        exprs->push_back(std::move(expr));
        break;
      }

      case TokenType::Loop: {
        DropTwoTokens();
        auto expr = std::make_unique<LoopExpr>(loc);
        CHECK_RESULT(ParseLabelOpt(&expr->block.label));
        CHECK_RESULT(ParseBlock(&expr->block));
        exprs->push_back(std::move(expr));
        break;
      }

      case TokenType::If: {
        DropTwoTokens();
        auto expr = std::make_unique<IfExpr>(loc);

        CHECK_RESULT(ParseLabelOpt(&expr->true_.label));
        CHECK_RESULT(ParseBlockDeclaration(&expr->true_.decl));

        while (PeekMatchExpr()) {
          ExprList cond;
          CHECK_RESULT(ParseExpr(&cond));
          exprs->splice(exprs->end(), cond);
        }

        EXPECT(Lpar);
        if (!Match(TokenType::Then)) {
          return ErrorExpected({"then block"}, "(then ...)");
        }

        CHECK_RESULT(ParseTerminatingInstrList(&expr->true_.exprs));
        expr->true_.end_loc = GetLocation();
        EXPECT(Rpar);

        if (MatchLpar(TokenType::Else)) {
          CHECK_RESULT(ParseTerminatingInstrList(&expr->false_));
          EXPECT(Rpar);
        }
        expr->false_end_loc = GetLocation();

        exprs->push_back(std::move(expr));
        break;
      }

      case TokenType::Try: {
        DropToken();
        ErrorUnlessOpcodeEnabled(Consume());

        auto expr = std::make_unique<TryExpr>(loc);
        CHECK_RESULT(ParseLabelOpt(&expr->block.label));
        CHECK_RESULT(ParseBlockDeclaration(&expr->block.decl));
        EXPECT(Lpar);
        EXPECT(Do);
        CHECK_RESULT(ParseInstrList(&expr->block.exprs));
        EXPECT(Rpar);
        if (PeekMatch(TokenType::Lpar)) {
          DropToken();
          TokenType type = Peek();
          switch (type) {
            case TokenType::Catch:
            case TokenType::CatchAll:
              CHECK_RESULT(ParseCatchExprList(&expr->catches));
              expr->kind = TryKind::Catch;
              break;
            case TokenType::Delegate: {
              DropToken();
              Var var;
              CHECK_RESULT(ParseVar(&var));
              expr->delegate_target = var;
              expr->kind = TryKind::Delegate;
              EXPECT(Rpar);
              break;
            }
            default:
              ErrorExpected({"catch", "catch_all", "delegate"});
              break;
          }
        }
        CHECK_RESULT(ErrorIfLpar({"a valid try clause"}));
        expr->block.end_loc = GetLocation();
        exprs->push_back(std::move(expr));
        break;
      }

      case TokenType::TryTable: {
        DropToken();
        ErrorUnlessOpcodeEnabled(Consume());
        auto expr = std::make_unique<TryTableExpr>(loc);
        CHECK_RESULT(ParseLabelOpt(&expr->block.label));
        CHECK_RESULT(ParseBlockDeclaration(&expr->block.decl));
        CHECK_RESULT(ParseTryTableCatches(&expr->catches));
        CHECK_RESULT(ParseInstrList(&expr->block.exprs));
        expr->block.end_loc = GetLocation();
        exprs->push_back(std::move(expr));
        break;
      }

      default:
        assert(!"ParseExpr should only be called when IsExpr() is true");
        return Result::Error;
    }
  }

  EXPECT(Rpar);
  return Result::Ok;
}

Result WastParser::ParseCatchInstrList(CatchVector* catches) {
  WABT_TRACE(ParseCatchInstrList);
  bool parsedCatch = false;
  bool parsedCatchAll = false;

  while (IsCatch(Peek())) {
    Catch catch_(GetLocation());

    auto token = Consume();
    if (token.token_type() == TokenType::Catch) {
      CHECK_RESULT(ParseVar(&catch_.var));
    } else {
      if (parsedCatchAll) {
        Error(token.loc, "multiple catch_all clauses not allowed");
        return Result::Error;
      }
      parsedCatchAll = true;
    }

    CHECK_RESULT(ParseInstrList(&catch_.exprs));
    catches->push_back(std::move(catch_));
    parsedCatch = true;
  }

  if (!parsedCatch) {
    return ErrorExpected({"catch"});
  }

  return Result::Ok;
}

Result WastParser::ParseCatchExprList(CatchVector* catches) {
  WABT_TRACE(ParseCatchExprList);
  bool parsedCatchAll = false;

  do {
    Catch catch_(GetLocation());

    auto token = Consume();
    if (token.token_type() == TokenType::Catch) {
      CHECK_RESULT(ParseVar(&catch_.var));
    } else {
      if (parsedCatchAll) {
        Error(token.loc, "multiple catch_all clauses not allowed");
        return Result::Error;
      }
      parsedCatchAll = true;
    }

    CHECK_RESULT(ParseTerminatingInstrList(&catch_.exprs));
    EXPECT(Rpar);
    catches->push_back(std::move(catch_));
  } while (Match(TokenType::Lpar) && IsCatch(Peek()));

  return Result::Ok;
}

Result WastParser::ParseTryTableCatches(TryTableVector* catches) {
  WABT_TRACE(ParseTryTableCatches);

  while (IsTryTableCatch(PeekPair())) {
    DropToken();
    TableCatch catch_(GetLocation());
    auto token = Consume();
    switch (token.token_type()) {
      case TokenType::Catch:
        catch_.kind = CatchKind::Catch;
        break;
      case TokenType::CatchRef:
        catch_.kind = CatchKind::CatchRef;
        break;
      case TokenType::CatchAll:
        catch_.kind = CatchKind::CatchAll;
        break;
      case TokenType::CatchAllRef:
        catch_.kind = CatchKind::CatchAllRef;
        break;
      default:
        WABT_UNREACHABLE;
    }
    if (catch_.kind == CatchKind::Catch || catch_.kind == CatchKind::CatchRef) {
      CHECK_RESULT(ParseVar(&catch_.tag));
    }
    CHECK_RESULT(ParseVar(&catch_.target));
    EXPECT(Rpar);
    catches->push_back(std::move(catch_));
  }

  return Result::Ok;
}

Result WastParser::ParseGlobalType(Global* global) {
  WABT_TRACE(ParseGlobalType);
  if (MatchLpar(TokenType::Mut)) {
    global->mutable_ = true;
    Var type;
    CHECK_RESULT(ParseValueType(&type));
    VarToType(type, &global->type);
    CHECK_RESULT(ErrorIfLpar({"i32", "i64", "f32", "f64"}));
    EXPECT(Rpar);
  } else {
    Var type;
    CHECK_RESULT(ParseValueType(&type));
    VarToType(type, &global->type);
  }

  return Result::Ok;
}

Result WastParser::ParseCommandList(Script* script,
                                    CommandPtrVector* commands) {
  WABT_TRACE(ParseCommandList);
  while (IsCommand(PeekPair())) {
    CommandPtr command;
    if (Succeeded(ParseCommand(script, &command))) {
      commands->push_back(std::move(command));
    } else {
      CHECK_RESULT(Synchronize(IsCommand));
    }
  }
  return Result::Ok;
}

Result WastParser::ParseCommand(Script* script, CommandPtr* out_command) {
  WABT_TRACE(ParseCommand);
  switch (Peek(1)) {
    case TokenType::AssertException:
      return ParseAssertExceptionCommand(out_command);

    case TokenType::AssertExhaustion:
      return ParseAssertExhaustionCommand(out_command);

    case TokenType::AssertInvalid:
      return ParseAssertInvalidCommand(out_command);

    case TokenType::AssertMalformed:
      return ParseAssertMalformedCommand(out_command);

    case TokenType::AssertReturn:
      return ParseAssertReturnCommand(out_command);

    case TokenType::AssertTrap:
      return ParseAssertTrapCommand(out_command);

    case TokenType::AssertUnlinkable:
      return ParseAssertUnlinkableCommand(out_command);

    case TokenType::Get:
    case TokenType::Invoke:
      return ParseActionCommand(out_command);

    case TokenType::Module:
      return ParseModuleCommand(script, out_command);

    case TokenType::Register:
      return ParseRegisterCommand(out_command);

    case TokenType::Input:
      return ParseInputCommand(out_command);

    case TokenType::Output:
      return ParseOutputCommand(out_command);

    default:
      assert(!"ParseCommand should only be called when IsCommand() is true");
      return Result::Error;
  }
}

Result WastParser::ParseAssertExceptionCommand(CommandPtr* out_command) {
  WABT_TRACE(ParseAssertExceptionCommand);
  return ParseAssertActionCommand<AssertExceptionCommand>(
      TokenType::AssertException, out_command);
}

Result WastParser::ParseAssertExhaustionCommand(CommandPtr* out_command) {
  WABT_TRACE(ParseAssertExhaustionCommand);
  return ParseAssertActionTextCommand<AssertExhaustionCommand>(
      TokenType::AssertExhaustion, out_command);
}

Result WastParser::ParseAssertInvalidCommand(CommandPtr* out_command) {
  WABT_TRACE(ParseAssertInvalidCommand);
  return ParseAssertScriptModuleCommand<AssertInvalidCommand>(
      TokenType::AssertInvalid, out_command);
}

Result WastParser::ParseAssertMalformedCommand(CommandPtr* out_command) {
  WABT_TRACE(ParseAssertMalformedCommand);
  return ParseAssertScriptModuleCommand<AssertMalformedCommand>(
      TokenType::AssertMalformed, out_command);
}

Result WastParser::ParseAssertReturnCommand(CommandPtr* out_command) {
  WABT_TRACE(ParseAssertReturnCommand);
  EXPECT(Lpar);
  EXPECT(AssertReturn);
  auto command = std::make_unique<AssertReturnCommand>();
  CHECK_RESULT(ParseAction(&command->action));
  CHECK_RESULT(ParseExpectedValues(&command->expected));
  EXPECT(Rpar);
  *out_command = std::move(command);
  return Result::Ok;
}

Result WastParser::ParseAssertTrapCommand(CommandPtr* out_command) {
  WABT_TRACE(ParseAssertTrapCommand);
  EXPECT(Lpar);
  EXPECT(AssertTrap);
  if (PeekMatchLpar(TokenType::Module)) {
    auto command = std::make_unique<AssertUninstantiableCommand>();
    CHECK_RESULT(ParseScriptModule(&command->module));
    CHECK_RESULT(ParseQuotedText(&command->text));
    *out_command = std::move(command);
  } else {
    auto command = std::make_unique<AssertTrapCommand>();
    CHECK_RESULT(ParseAction(&command->action));
    CHECK_RESULT(ParseQuotedText(&command->text));
    *out_command = std::move(command);
  }
  EXPECT(Rpar);
  return Result::Ok;
}

Result WastParser::ParseAssertUnlinkableCommand(CommandPtr* out_command) {
  WABT_TRACE(ParseAssertUnlinkableCommand);
  return ParseAssertScriptModuleCommand<AssertUnlinkableCommand>(
      TokenType::AssertUnlinkable, out_command);
}

Result WastParser::ParseActionCommand(CommandPtr* out_command) {
  WABT_TRACE(ParseActionCommand);
  auto command = std::make_unique<ActionCommand>();
  CHECK_RESULT(ParseAction(&command->action));
  *out_command = std::move(command);
  return Result::Ok;
}

Result WastParser::ParseModuleCommand(Script* script, CommandPtr* out_command) {
  WABT_TRACE(ParseModuleCommand);
  std::unique_ptr<ScriptModule> script_module;
  CHECK_RESULT(ParseScriptModule(&script_module));

  Module* module = nullptr;

  switch (script_module->type()) {
    case ScriptModuleType::Text: {
      auto command = std::make_unique<ModuleCommand>();
      module = &command->module;
      *module = std::move(cast<TextScriptModule>(script_module.get())->module);
      *out_command = std::move(command);
      break;
    }

    case ScriptModuleType::Binary: {
      auto command = std::make_unique<ScriptModuleCommand>();
      module = &command->module;
      auto* bsm = cast<BinaryScriptModule>(script_module.get());
      ReadBinaryOptions options;
#if WABT_TRACING
      auto log_stream = FileStream::CreateStdout();
      options.log_stream = log_stream.get();
#endif
      options.features = options_->features;
      Errors errors;
      const char* filename = "<text>";
      if (options_->parse_binary_modules) {
        ReadBinaryIr(filename, bsm->data.data(), bsm->data.size(), options,
                     &errors, module);
      }
      module->name = bsm->name;
      module->loc = bsm->loc;
      for (const auto& error : errors) {
        if (error.loc.offset == kInvalidOffset) {
          Error(bsm->loc, "error in binary module: %s", error.message.c_str());
        } else {
          Error(bsm->loc, "error in binary module: @0x%08" PRIzx ": %s",
                error.loc.offset, error.message.c_str());
        }
      }

      command->script_module = std::move(script_module);
      *out_command = std::move(command);
      break;
    }

    case ScriptModuleType::Quoted:
      auto command = std::make_unique<ModuleCommand>();
      module = &command->module;
      auto* qsm = cast<QuotedScriptModule>(script_module.get());
      Errors errors;
      const char* filename = "<text>";
      std::unique_ptr<Module> m;
      std::unique_ptr<WastLexer> lexer = WastLexer::CreateBufferLexer(
          filename, qsm->data.data(), qsm->data.size(), &errors);
      auto result = ParseWatModule(lexer.get(), &m, &errors, options_);
      for (const auto& error : errors) {
        if (error.loc.offset == kInvalidOffset) {
          Error(qsm->loc, "error in quoted module: %s", error.message.c_str());
        } else {
          Error(qsm->loc, "error in quoted module: @0x%08" PRIzx ": %s",
                error.loc.offset, error.message.c_str());
        }
      }
      if (Succeeded(result)) {
        *module = std::move(*m.get());
      }
      *out_command = std::move(command);
      break;
  }

  // script is nullptr when ParseModuleCommand is called from ParseModule.
  if (script) {
    Index command_index = script->commands.size();

    if (!module->name.empty()) {
      script->module_bindings.emplace(module->name,
                                      Binding(module->loc, command_index));
    }

    last_module_index_ = command_index;
  }

  return Result::Ok;
}

Result WastParser::ParseRegisterCommand(CommandPtr* out_command) {
  WABT_TRACE(ParseRegisterCommand);
  EXPECT(Lpar);
  Location loc = GetLocation();
  EXPECT(Register);
  std::string text;
  Var var;
  CHECK_RESULT(ParseQuotedText(&text));
  CHECK_RESULT(ParseVarOpt(&var, Var(last_module_index_, loc)));
  EXPECT(Rpar);
  out_command->reset(new RegisterCommand(text, var));
  return Result::Ok;
}

Result WastParser::ParseInputCommand(CommandPtr*) {
  // Parse the input command, but always fail since this command is not
  // actually supported.
  WABT_TRACE(ParseInputCommand);
  EXPECT(Lpar);
  Location loc = GetLocation();
  EXPECT(Input);
  Error(loc, "input command is not supported");
  Var var;
  std::string text;
  CHECK_RESULT(ParseVarOpt(&var));
  CHECK_RESULT(ParseQuotedText(&text));
  EXPECT(Rpar);
  return Result::Error;
}

Result WastParser::ParseOutputCommand(CommandPtr*) {
  // Parse the output command, but always fail since this command is not
  // actually supported.
  WABT_TRACE(ParseOutputCommand);
  EXPECT(Lpar);
  Location loc = GetLocation();
  EXPECT(Output);
  Error(loc, "output command is not supported");
  Var var;
  std::string text;
  CHECK_RESULT(ParseVarOpt(&var));
  if (Peek() == TokenType::Text) {
    CHECK_RESULT(ParseQuotedText(&text));
  }
  EXPECT(Rpar);
  return Result::Error;
}

Result WastParser::ParseAction(ActionPtr* out_action) {
  WABT_TRACE(ParseAction);
  EXPECT(Lpar);
  Location loc = GetLocation();

  switch (Peek()) {
    case TokenType::Invoke: {
      DropToken();
      auto action = std::make_unique<InvokeAction>(loc);
      CHECK_RESULT(
          ParseVarOpt(&action->module_var, Var(last_module_index_, loc)));
      CHECK_RESULT(ParseQuotedText(&action->name));
      CHECK_RESULT(ParseConstList(&action->args, ConstType::Normal));
      *out_action = std::move(action);
      break;
    }

    case TokenType::Get: {
      DropToken();
      auto action = std::make_unique<GetAction>(loc);
      CHECK_RESULT(
          ParseVarOpt(&action->module_var, Var(last_module_index_, loc)));
      CHECK_RESULT(ParseQuotedText(&action->name));
      *out_action = std::move(action);
      break;
    }

    default:
      return ErrorExpected({"invoke", "get"});
  }
  EXPECT(Rpar);
  return Result::Ok;
}

Result WastParser::ParseExpectedValues(ExpectationPtr* expectation) {
  WABT_TRACE(ParseExpectedValues);
  Location loc = GetLocation();
  if (PeekMatchLpar(TokenType::Either)) {
    auto either = std::make_unique<EitherExpectation>(loc);
    CHECK_RESULT(ParseEither(&either->expected));
    *expectation = std::move(either);
  } else {
    auto values = std::make_unique<ValueExpectation>(loc);
    CHECK_RESULT(ParseConstList(&values->expected, ConstType::Expectation));
    *expectation = std::move(values);
  }
  return Result::Ok;
}

Result WastParser::ParseEither(ConstVector* alternatives) {
  WABT_TRACE(ParseEither);
  MatchLpar(TokenType::Either);
  CHECK_RESULT(ParseConstList(alternatives, ConstType::Expectation));
  EXPECT(Rpar);
  return Result::Ok;
}

Result WastParser::ParseScriptModule(
    std::unique_ptr<ScriptModule>* out_module) {
  WABT_TRACE(ParseScriptModule);
  EXPECT(Lpar);
  Location loc = GetLocation();
  EXPECT(Module);
  std::string name;
  CHECK_RESULT(ParseBindVarOpt(&name));

  switch (Peek()) {
    case TokenType::Bin: {
      DropToken();
      std::vector<uint8_t> data;
      // TODO(binji): The spec allows this to be empty, switch to
      // ParseTextListOpt.
      CHECK_RESULT(ParseTextList(&data));

      auto bsm = std::make_unique<BinaryScriptModule>();
      bsm->name = name;
      bsm->loc = loc;
      bsm->data = std::move(data);
      *out_module = std::move(bsm);
      break;
    }

    case TokenType::Quote: {
      DropToken();
      std::vector<uint8_t> data;
      // TODO(binji): The spec allows this to be empty, switch to
      // ParseTextListOpt.
      CHECK_RESULT(ParseTextList(&data));

      auto qsm = std::make_unique<QuotedScriptModule>();
      qsm->name = name;
      qsm->loc = loc;
      qsm->data = std::move(data);
      *out_module = std::move(qsm);
      break;
    }

    default: {
      auto tsm = std::make_unique<TextScriptModule>();
      tsm->module.name = name;
      tsm->module.loc = loc;
      tsm->module.filename = lexer_->Filename();
      if (IsModuleField(PeekPair()) || PeekIsCustom()) {
        CHECK_RESULT(ParseModuleFieldList(&tsm->module));
      } else if (!PeekMatch(TokenType::Rpar)) {
        ConsumeIfLpar();
        return ErrorExpected({"a module field"});
      }
      *out_module = std::move(tsm);
      break;
    }
  }

  EXPECT(Rpar);
  return Result::Ok;
}

template <typename T>
Result WastParser::ParseAssertActionCommand(TokenType token_type,
                                            CommandPtr* out_command) {
  WABT_TRACE(ParseAssertActionCommand);
  EXPECT(Lpar);
  CHECK_RESULT(Expect(token_type));
  auto command = std::make_unique<T>();
  CHECK_RESULT(ParseAction(&command->action));
  EXPECT(Rpar);
  *out_command = std::move(command);
  return Result::Ok;
}

template <typename T>
Result WastParser::ParseAssertActionTextCommand(TokenType token_type,
                                                CommandPtr* out_command) {
  WABT_TRACE(ParseAssertActionTextCommand);
  EXPECT(Lpar);
  CHECK_RESULT(Expect(token_type));
  auto command = std::make_unique<T>();
  CHECK_RESULT(ParseAction(&command->action));
  CHECK_RESULT(ParseQuotedText(&command->text));
  EXPECT(Rpar);
  *out_command = std::move(command);
  return Result::Ok;
}

template <typename T>
Result WastParser::ParseAssertScriptModuleCommand(TokenType token_type,
                                                  CommandPtr* out_command) {
  WABT_TRACE(ParseAssertScriptModuleCommand);
  EXPECT(Lpar);
  CHECK_RESULT(Expect(token_type));
  auto command = std::make_unique<T>();
  CHECK_RESULT(ParseScriptModule(&command->module));
  CHECK_RESULT(ParseQuotedText(&command->text));
  EXPECT(Rpar);
  *out_command = std::move(command);
  return Result::Ok;
}

void WastParser::CheckImportOrdering(Module* module) {
  if (module->funcs.size() != module->num_func_imports ||
      module->tables.size() != module->num_table_imports ||
      module->memories.size() != module->num_memory_imports ||
      module->globals.size() != module->num_global_imports ||
      module->tags.size() != module->num_tag_imports) {
    Error(GetLocation(),
          "imports must occur before all non-import definitions");
  }
}

bool WastParser::HasError() const {
  return std::any_of(errors_->begin(), errors_->end(), [](const auto& x) {
    return x.error_level == ErrorLevel::Error;
  });
}

bool WastParser::CheckRefType(Type::Enum type) {
  switch (type) {
    case Type::FuncRef:
      return true;
    case Type::ExternRef:
      return options_->features.reference_types_enabled();
    case Type::ExnRef:
      return options_->features.exceptions_enabled();
    default:
      assert(!Type::EnumIsNonTypedRef(type));
      return false;
  }
}

void WastParser::VarToType(const Var& var, Type* type) {
  if (!Type::EnumIsReferenceWithIndex(var.opt_type()) || var.is_index()) {
    *type = var.to_type();
    return;
  }

  *type = Type(var.opt_type(), kInvalidIndex);
  resolve_ref_types_.push_back(ResolveRefType(type, var));
}

void WastParser::TokenQueue::push_back(Token t) {
  assert(!tokens[!i]);
  tokens[!i] = t;
  if (empty()) {
    i = !i;
  }
}

void WastParser::TokenQueue::pop_front() {
  assert(tokens[i]);
  tokens[i].reset();
  i = !i;
}

void WastParser::TokenQueue::pop_both() {
  assert(tokens[0] && tokens[1]);
  tokens[0].reset();
  tokens[1].reset();
}

const Token& WastParser::TokenQueue::at(size_t n) const {
  assert(n <= 1);
  return tokens[i ^ static_cast<bool>(n)].value();
}

const Token& WastParser::TokenQueue::front() const {
  return at(0);
}

bool WastParser::TokenQueue::empty() const {
  return !tokens[i];
}

size_t WastParser::TokenQueue::size() const {
  return empty() ? 0 : 1 + tokens[!i].has_value();
}

Result ParseWatModule(WastLexer* lexer,
                      std::unique_ptr<Module>* out_module,
                      Errors* errors,
                      WastParseOptions* options) {
  assert(out_module != nullptr);
  assert(options != nullptr);
  WastParser parser(lexer, errors, options);
  CHECK_RESULT(parser.ParseModule(out_module));
  return Result::Ok;
}

Result ParseWastScript(WastLexer* lexer,
                       std::unique_ptr<Script>* out_script,
                       Errors* errors,
                       WastParseOptions* options) {
  assert(out_script != nullptr);
  assert(options != nullptr);
  WastParser parser(lexer, errors, options);
  CHECK_RESULT(parser.ParseScript(out_script));
  CHECK_RESULT(ResolveNamesScript(out_script->get(), errors));
  return Result::Ok;
}

}  // namespace wabt
