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

#include "wabt/wat-writer.h"

#include <algorithm>
#include <array>
#include <cassert>
#include <cinttypes>
#include <cstdarg>
#include <cstdio>
#include <iterator>
#include <map>
#include <string>
#include <vector>

#include "wabt/cast.h"
#include "wabt/common.h"
#include "wabt/expr-visitor.h"
#include "wabt/ir-util.h"
#include "wabt/ir.h"
#include "wabt/literal.h"
#include "wabt/stream.h"

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

#define INDENT_SIZE 2
#define NO_FORCE_NEWLINE 0
#define FORCE_NEWLINE 1

namespace wabt {

namespace {

static const uint8_t s_is_char_escaped[] = {
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};

// This table matches the characters allowed by wast-lexer.cc for `symbol`.
// The disallowed printable characters are: "(),;[]{} and <space>.
static const uint8_t s_valid_name_chars[256] = {
    //         0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
    /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    /* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    /* 0x20 */ 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1,
    /* 0x30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1,
    /* 0x40 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    /* 0x50 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1,
    /* 0x60 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    /* 0x70 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
};

enum class NextChar {
  None,
  Space,
  Newline,
  ForceNewline,
};

struct ExprTree {
  explicit ExprTree(const Expr* expr, Index result_count)
      : expr(expr), result_count(result_count) {}

  const Expr* expr;
  std::vector<ExprTree> children;
  Index result_count;
};

class WatWriter : ModuleContext {
 public:
  WatWriter(Stream* stream,
            const WriteWatOptions& options,
            const Module& module)
      : ModuleContext(module), options_(options), stream_(stream) {}

  Result WriteModule();

 private:
  void Indent();
  void Dedent();
  void WriteIndent();
  void WriteNextChar();
  void WriteDataWithNextChar(const void* src, size_t size);
  void Writef(const char* format, ...);
  void WritePutc(char c);
  void WritePuts(const char* s, NextChar next_char);
  void WritePutsSpace(const char* s);
  void WritePutsNewline(const char* s);
  void WriteNewline(bool force);
  void WriteOpen(const char* name, NextChar next_char);
  void WriteOpenNewline(const char* name);
  void WriteOpenSpace(const char* name);
  void WriteClose(NextChar next_char);
  void WriteCloseNewline();
  void WriteCloseSpace();
  void WriteString(const std::string& str, NextChar next_char);
  void WriteName(std::string_view str, NextChar next_char);
  void WriteNameOrIndex(std::string_view str, Index index, NextChar next_char);
  void WriteQuotedData(const void* data, size_t length);
  void WriteQuotedString(std::string_view str, NextChar next_char);
  void WriteVar(const Var& var, NextChar next_char);
  void WriteVarUnlessZero(const Var& var, NextChar next_char);
  void WriteMemoryVarUnlessZero(const Var& memidx, NextChar next_char);
  void WriteTwoMemoryVarsUnlessBothZero(const Var& srcmemidx,
                                        const Var& destmemidx,
                                        NextChar next_char);
  void WriteBrVar(const Var& var, NextChar next_char);
  void WriteRefKind(Type type, NextChar next_char);
  void WriteType(Type type, NextChar next_char);
  void WriteTypes(const TypeVector& types, const char* name);
  void WriteFuncSigSpace(const FuncSignature& func_sig);
  void WriteBeginBlock(LabelType label_type,
                       const Block& block,
                       const char* text);
  void WriteEndBlock();
  void WriteConst(const Const& const_);
  void WriteExpr(const Expr* expr);
  template <typename T>
  void WriteLoadStoreExpr(const Expr* expr);
  template <typename T>
  void WriteMemoryLoadStoreExpr(const Expr* expr);
  void WriteExprList(const ExprList& exprs);
  void WriteInitExpr(const ExprList& expr);
  template <typename T>
  void WriteTypeBindings(const char* prefix,
                         const T& types,
                         const std::vector<std::string>& index_to_name,
                         Index binding_index_offset = 0);
  void WriteBeginFunc(const Func& func);
  void WriteFunc(const Func& func);
  void WriteBeginGlobal(const Global& global);
  void WriteGlobal(const Global& global);
  void WriteTag(const Tag& tag);
  void WriteLimits(const Limits& limits);
  void WriteTable(const Table& table);
  void WriteElemSegment(const ElemSegment& segment);
  void WriteMemory(const Memory& memory);
  void WriteDataSegment(const DataSegment& segment);
  void WriteImport(const Import& import);
  void WriteExport(const Export& export_);
  void WriteTypeEntry(const TypeEntry& type);
  void WriteField(const Field& field);
  void WriteStartFunction(const Var& start);
  void WriteCustom(const Custom& custom);

  class ExprVisitorDelegate;

  void PushExpr(const Expr* expr, Index operand_count, Index result_count);
  void FlushExprTree(const ExprTree& expr_tree);
  void FlushExprTreeVector(const std::vector<ExprTree>&);
  void FlushExprTreeStack();
  void WriteFoldedExpr(const Expr*);
  void WriteFoldedExprList(const ExprList&);

  void BuildInlineExportMap();
  void WriteInlineExports(ExternalKind, Index);
  bool IsInlineExport(const Export& export_);
  void BuildInlineImportMap();
  void WriteInlineImport(ExternalKind, Index);

  const WriteWatOptions& options_;
  Stream* stream_ = nullptr;
  Result result_ = Result::Ok;
  int indent_ = 0;
  NextChar next_char_ = NextChar::None;
  std::vector<ExprTree> expr_tree_stack_;
  std::multimap<std::pair<ExternalKind, Index>, const Export*>
      inline_export_map_;
  std::vector<const Import*> inline_import_map_[kExternalKindCount];

  Index func_index_ = 0;
  Index global_index_ = 0;
  Index table_index_ = 0;
  Index memory_index_ = 0;
  Index type_index_ = 0;
  Index tag_index_ = 0;
  Index data_segment_index_ = 0;
  Index elem_segment_index_ = 0;
};

void WatWriter::Indent() {
  indent_ += INDENT_SIZE;
}

void WatWriter::Dedent() {
  indent_ -= INDENT_SIZE;
  assert(indent_ >= 0);
}

void WatWriter::WriteIndent() {
  static char s_indent[] =
      "                                                                       "
      "                                                                       ";
  static size_t s_indent_len = sizeof(s_indent) - 1;
  size_t to_write = indent_;
  while (to_write >= s_indent_len) {
    stream_->WriteData(s_indent, s_indent_len);
    to_write -= s_indent_len;
  }
  if (to_write > 0) {
    stream_->WriteData(s_indent, to_write);
  }
}

void WatWriter::WriteNextChar() {
  switch (next_char_) {
    case NextChar::Space:
      stream_->WriteChar(' ');
      break;
    case NextChar::Newline:
    case NextChar::ForceNewline:
      stream_->WriteChar('\n');
      WriteIndent();
      break;
    case NextChar::None:
      break;
  }
  next_char_ = NextChar::None;
}

void WatWriter::WriteDataWithNextChar(const void* src, size_t size) {
  WriteNextChar();
  stream_->WriteData(src, size);
}

void WABT_PRINTF_FORMAT(2, 3) WatWriter::Writef(const char* format, ...) {
  WABT_SNPRINTF_ALLOCA(buffer, length, format);
  /* default to following space */
  WriteDataWithNextChar(buffer, length);
  next_char_ = NextChar::Space;
}

void WatWriter::WritePutc(char c) {
  stream_->WriteChar(c);
}

void WatWriter::WritePuts(const char* s, NextChar next_char) {
  size_t len = strlen(s);
  WriteDataWithNextChar(s, len);
  next_char_ = next_char;
}

void WatWriter::WritePutsSpace(const char* s) {
  WritePuts(s, NextChar::Space);
}

void WatWriter::WritePutsNewline(const char* s) {
  WritePuts(s, NextChar::Newline);
}

void WatWriter::WriteNewline(bool force) {
  if (next_char_ == NextChar::ForceNewline) {
    WriteNextChar();
  }
  next_char_ = force ? NextChar::ForceNewline : NextChar::Newline;
}

void WatWriter::WriteOpen(const char* name, NextChar next_char) {
  WritePuts("(", NextChar::None);
  WritePuts(name, next_char);
  Indent();
}

void WatWriter::WriteOpenNewline(const char* name) {
  WriteOpen(name, NextChar::Newline);
}

void WatWriter::WriteOpenSpace(const char* name) {
  WriteOpen(name, NextChar::Space);
}

void WatWriter::WriteClose(NextChar next_char) {
  if (next_char_ != NextChar::ForceNewline) {
    next_char_ = NextChar::None;
  }
  Dedent();
  WritePuts(")", next_char);
}

void WatWriter::WriteCloseNewline() {
  WriteClose(NextChar::Newline);
}

void WatWriter::WriteCloseSpace() {
  WriteClose(NextChar::Space);
}

void WatWriter::WriteString(const std::string& str, NextChar next_char) {
  WritePuts(str.c_str(), next_char);
}

void WatWriter::WriteName(std::string_view str, NextChar next_char) {
  // Debug names must begin with a $ for for wast file to be valid
  assert(!str.empty() && str.front() == '$');
  bool has_invalid_chars = std::any_of(
      str.begin(), str.end(), [](uint8_t c) { return !s_valid_name_chars[c]; });

  if (has_invalid_chars) {
    std::string valid_str;
    std::transform(str.begin(), str.end(), std::back_inserter(valid_str),
                   [](uint8_t c) { return s_valid_name_chars[c] ? c : '_'; });
    WriteDataWithNextChar(valid_str.data(), valid_str.length());
  } else {
    WriteDataWithNextChar(str.data(), str.length());
  }

  next_char_ = next_char;
}

void WatWriter::WriteNameOrIndex(std::string_view str,
                                 Index index,
                                 NextChar next_char) {
  if (!str.empty()) {
    WriteName(str, next_char);
  } else {
    Writef("(;%u;)", index);
  }
}

void WatWriter::WriteQuotedData(const void* data, size_t length) {
  const uint8_t* u8_data = static_cast<const uint8_t*>(data);
  static const char s_hexdigits[] = "0123456789abcdef";
  WriteNextChar();
  WritePutc('\"');
  for (size_t i = 0; i < length; ++i) {
    uint8_t c = u8_data[i];
    if (s_is_char_escaped[c]) {
      WritePutc('\\');
      WritePutc(s_hexdigits[c >> 4]);
      WritePutc(s_hexdigits[c & 0xf]);
    } else {
      WritePutc(c);
    }
  }
  WritePutc('\"');
  next_char_ = NextChar::Space;
}

void WatWriter::WriteQuotedString(std::string_view str, NextChar next_char) {
  WriteQuotedData(str.data(), str.length());
  next_char_ = next_char;
}

void WatWriter::WriteVar(const Var& var, NextChar next_char) {
  if (var.is_index()) {
    Writef("%" PRIindex, var.index());
    next_char_ = next_char;
  } else {
    WriteName(var.name(), next_char);
  }
}

bool VarIsZero(const Var& var) {
  return var.is_index() && var.index() == 0;
}

void WatWriter::WriteVarUnlessZero(const Var& var, NextChar next_char) {
  if (!VarIsZero(var)) {
    WriteVar(var, next_char);
  }
}

void WatWriter::WriteMemoryVarUnlessZero(const Var& memidx,
                                         NextChar next_char) {
  if (module.GetMemoryIndex(memidx) != 0) {
    WriteVar(memidx, next_char);
  } else {
    next_char_ = next_char;
  }
}

void WatWriter::WriteTwoMemoryVarsUnlessBothZero(const Var& srcmemidx,
                                                 const Var& destmemidx,
                                                 NextChar next_char) {
  if (module.GetMemoryIndex(srcmemidx) != 0 ||
      module.GetMemoryIndex(destmemidx) != 0) {
    WriteVar(srcmemidx, NextChar::Space);
    WriteVar(destmemidx, next_char);
  } else {
    next_char_ = next_char;
  }
}

void WatWriter::WriteBrVar(const Var& var, NextChar next_char) {
  if (var.is_index()) {
    if (var.index() < GetLabelStackSize()) {
      Writef("%" PRIindex " (;@%" PRIindex ";)", var.index(),
             GetLabelStackSize() - var.index() - 1);
    } else {
      Writef("%" PRIindex " (; INVALID ;)", var.index());
    }
    next_char_ = next_char;
  } else {
    WriteString(var.name(), next_char);
  }
}

void WatWriter::WriteRefKind(Type type, NextChar next_char) {
  WritePuts(type.GetRefKindName(), next_char);
}

void WatWriter::WriteType(Type type, NextChar next_char) {
  WritePuts(type.GetName().c_str(), next_char);
}

void WatWriter::WriteTypes(const TypeVector& types, const char* name) {
  if (types.size()) {
    if (name) {
      WriteOpenSpace(name);
    }
    for (Type type : types) {
      WriteType(type, NextChar::Space);
    }
    if (name) {
      WriteCloseSpace();
    }
  }
}

void WatWriter::WriteFuncSigSpace(const FuncSignature& func_sig) {
  WriteTypes(func_sig.param_types, "param");
  WriteTypes(func_sig.result_types, "result");
}

void WatWriter::WriteBeginBlock(LabelType label_type,
                                const Block& block,
                                const char* text) {
  WritePutsSpace(text);
  bool has_label = !block.label.empty();
  if (has_label) {
    WriteString(block.label, NextChar::Space);
  }
  WriteTypes(block.decl.sig.param_types, "param");
  WriteTypes(block.decl.sig.result_types, "result");
  if (!has_label) {
    Writef(" ;; label = @%" PRIindex, GetLabelStackSize());
  }
  WriteNewline(FORCE_NEWLINE);
  BeginBlock(label_type, block);
  Indent();
}

void WatWriter::WriteEndBlock() {
  Dedent();
  EndBlock();
  WritePutsNewline(Opcode::End_Opcode.GetName());
}

void WatWriter::WriteConst(const Const& const_) {
  switch (const_.type()) {
    case Type::I32:
      WritePutsSpace(Opcode::I32Const_Opcode.GetName());
      Writef("%d", static_cast<int32_t>(const_.u32()));
      WriteNewline(NO_FORCE_NEWLINE);
      break;

    case Type::I64:
      WritePutsSpace(Opcode::I64Const_Opcode.GetName());
      Writef("%" PRId64, static_cast<int64_t>(const_.u64()));
      WriteNewline(NO_FORCE_NEWLINE);
      break;

    case Type::F32: {
      WritePutsSpace(Opcode::F32Const_Opcode.GetName());
      char buffer[128];
      WriteFloatHex(buffer, 128, const_.f32_bits());
      WritePutsSpace(buffer);
      Writef("(;=%g;)", Bitcast<float>(const_.f32_bits()));
      WriteNewline(NO_FORCE_NEWLINE);
      break;
    }

    case Type::F64: {
      WritePutsSpace(Opcode::F64Const_Opcode.GetName());
      char buffer[128];
      WriteDoubleHex(buffer, 128, const_.f64_bits());
      WritePutsSpace(buffer);
      Writef("(;=%g;)", Bitcast<double>(const_.f64_bits()));
      WriteNewline(NO_FORCE_NEWLINE);
      break;
    }

    case Type::V128: {
      WritePutsSpace(Opcode::V128Const_Opcode.GetName());
      auto vec = const_.vec128();
      Writef("i32x4 0x%08x 0x%08x 0x%08x 0x%08x", vec.u32(0), vec.u32(1),
             vec.u32(2), vec.u32(3));
      WriteNewline(NO_FORCE_NEWLINE);
      break;
    }

    default:
      assert(0);
      break;
  }
}

template <typename T>
void WatWriter::WriteLoadStoreExpr(const Expr* expr) {
  auto typed_expr = cast<T>(expr);
  WritePutsSpace(typed_expr->opcode.GetName());
  if (typed_expr->offset) {
    Writef("offset=%" PRIaddress, typed_expr->offset);
  }
  if (!typed_expr->opcode.IsNaturallyAligned(typed_expr->align)) {
    Writef("align=%" PRIaddress, typed_expr->align);
  }
  WriteNewline(NO_FORCE_NEWLINE);
}

template <typename T>
void WatWriter::WriteMemoryLoadStoreExpr(const Expr* expr) {
  auto typed_expr = cast<T>(expr);
  WritePutsSpace(typed_expr->opcode.GetName());
  WriteMemoryVarUnlessZero(typed_expr->memidx, NextChar::Space);
  if (typed_expr->offset) {
    Writef("offset=%" PRIaddress, typed_expr->offset);
  }
  if (!typed_expr->opcode.IsNaturallyAligned(typed_expr->align)) {
    Writef("align=%" PRIaddress, typed_expr->align);
  }
  WriteNewline(NO_FORCE_NEWLINE);
}

class WatWriter::ExprVisitorDelegate : public ExprVisitor::Delegate {
 public:
  explicit ExprVisitorDelegate(WatWriter* writer) : writer_(writer) {}

  Result OnBinaryExpr(BinaryExpr*) override;
  Result BeginBlockExpr(BlockExpr*) override;
  Result EndBlockExpr(BlockExpr*) override;
  Result OnBrExpr(BrExpr*) override;
  Result OnBrIfExpr(BrIfExpr*) override;
  Result OnBrTableExpr(BrTableExpr*) override;
  Result OnCallExpr(CallExpr*) override;
  Result OnCallIndirectExpr(CallIndirectExpr*) override;
  Result OnCallRefExpr(CallRefExpr*) override;
  Result OnCodeMetadataExpr(CodeMetadataExpr*) override;
  Result OnCompareExpr(CompareExpr*) override;
  Result OnConstExpr(ConstExpr*) override;
  Result OnConvertExpr(ConvertExpr*) override;
  Result OnDropExpr(DropExpr*) override;
  Result OnGlobalGetExpr(GlobalGetExpr*) override;
  Result OnGlobalSetExpr(GlobalSetExpr*) override;
  Result BeginIfExpr(IfExpr*) override;
  Result AfterIfTrueExpr(IfExpr*) override;
  Result EndIfExpr(IfExpr*) override;
  Result OnLoadExpr(LoadExpr*) override;
  Result OnLocalGetExpr(LocalGetExpr*) override;
  Result OnLocalSetExpr(LocalSetExpr*) override;
  Result OnLocalTeeExpr(LocalTeeExpr*) override;
  Result BeginLoopExpr(LoopExpr*) override;
  Result EndLoopExpr(LoopExpr*) override;
  Result OnMemoryCopyExpr(MemoryCopyExpr*) override;
  Result OnDataDropExpr(DataDropExpr*) override;
  Result OnMemoryFillExpr(MemoryFillExpr*) override;
  Result OnMemoryGrowExpr(MemoryGrowExpr*) override;
  Result OnMemoryInitExpr(MemoryInitExpr*) override;
  Result OnMemorySizeExpr(MemorySizeExpr*) override;
  Result OnTableCopyExpr(TableCopyExpr*) override;
  Result OnElemDropExpr(ElemDropExpr*) override;
  Result OnTableInitExpr(TableInitExpr*) override;
  Result OnTableGetExpr(TableGetExpr*) override;
  Result OnTableSetExpr(TableSetExpr*) override;
  Result OnTableGrowExpr(TableGrowExpr*) override;
  Result OnTableSizeExpr(TableSizeExpr*) override;
  Result OnTableFillExpr(TableFillExpr*) override;
  Result OnRefFuncExpr(RefFuncExpr*) override;
  Result OnRefNullExpr(RefNullExpr*) override;
  Result OnRefIsNullExpr(RefIsNullExpr*) override;
  Result OnNopExpr(NopExpr*) override;
  Result OnReturnExpr(ReturnExpr*) override;
  Result OnReturnCallExpr(ReturnCallExpr*) override;
  Result OnReturnCallIndirectExpr(ReturnCallIndirectExpr*) override;
  Result OnSelectExpr(SelectExpr*) override;
  Result OnStoreExpr(StoreExpr*) override;
  Result OnUnaryExpr(UnaryExpr*) override;
  Result OnUnreachableExpr(UnreachableExpr*) override;
  Result BeginTryExpr(TryExpr*) override;
  Result BeginTryTableExpr(TryTableExpr*) override;
  Result EndTryTableExpr(TryTableExpr*) override;
  Result OnCatchExpr(TryExpr*, Catch*) override;
  Result OnDelegateExpr(TryExpr*) override;
  Result EndTryExpr(TryExpr*) override;
  Result OnThrowExpr(ThrowExpr*) override;
  Result OnThrowRefExpr(ThrowRefExpr*) override;
  Result OnRethrowExpr(RethrowExpr*) override;
  Result OnAtomicWaitExpr(AtomicWaitExpr*) override;
  Result OnAtomicFenceExpr(AtomicFenceExpr*) override;
  Result OnAtomicNotifyExpr(AtomicNotifyExpr*) override;
  Result OnAtomicLoadExpr(AtomicLoadExpr*) override;
  Result OnAtomicStoreExpr(AtomicStoreExpr*) override;
  Result OnAtomicRmwExpr(AtomicRmwExpr*) override;
  Result OnAtomicRmwCmpxchgExpr(AtomicRmwCmpxchgExpr*) override;
  Result OnTernaryExpr(TernaryExpr*) override;
  Result OnSimdLaneOpExpr(SimdLaneOpExpr*) override;
  Result OnSimdLoadLaneExpr(SimdLoadLaneExpr*) override;
  Result OnSimdStoreLaneExpr(SimdStoreLaneExpr*) override;
  Result OnSimdShuffleOpExpr(SimdShuffleOpExpr*) override;
  Result OnLoadSplatExpr(LoadSplatExpr*) override;
  Result OnLoadZeroExpr(LoadZeroExpr*) override;

 private:
  WatWriter* writer_;
};

Result WatWriter::ExprVisitorDelegate::OnBinaryExpr(BinaryExpr* expr) {
  writer_->WritePutsNewline(expr->opcode.GetName());
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::BeginBlockExpr(BlockExpr* expr) {
  writer_->WriteBeginBlock(LabelType::Block, expr->block,
                           Opcode::Block_Opcode.GetName());
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::EndBlockExpr(BlockExpr* expr) {
  writer_->WriteEndBlock();
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnBrExpr(BrExpr* expr) {
  writer_->WritePutsSpace(Opcode::Br_Opcode.GetName());
  writer_->WriteBrVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnBrIfExpr(BrIfExpr* expr) {
  writer_->WritePutsSpace(Opcode::BrIf_Opcode.GetName());
  writer_->WriteBrVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnBrTableExpr(BrTableExpr* expr) {
  writer_->WritePutsSpace(Opcode::BrTable_Opcode.GetName());
  for (const Var& var : expr->targets) {
    writer_->WriteBrVar(var, NextChar::Space);
  }
  writer_->WriteBrVar(expr->default_target, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnCallExpr(CallExpr* expr) {
  writer_->WritePutsSpace(Opcode::Call_Opcode.GetName());
  writer_->WriteVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnCallIndirectExpr(
    CallIndirectExpr* expr) {
  writer_->WritePutsSpace(Opcode::CallIndirect_Opcode.GetName());
  writer_->WriteVarUnlessZero(expr->table, NextChar::Space);
  writer_->WriteOpenSpace("type");
  const auto type_var =
      expr->decl.has_func_type
          ? expr->decl.type_var
          : Var{writer_->module.GetFuncTypeIndex(expr->decl), expr->loc};
  writer_->WriteVar(type_var, NextChar::Newline);
  writer_->WriteCloseNewline();
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnCallRefExpr(CallRefExpr* expr) {
  writer_->WritePutsSpace(Opcode::CallRef_Opcode.GetName());
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnCodeMetadataExpr(
    CodeMetadataExpr* expr) {
  writer_->WriteOpen("@metadata.code.", NextChar::None);
  writer_->WriteDataWithNextChar(expr->name.data(), expr->name.size());
  writer_->WritePutc(' ');
  writer_->WriteQuotedData(expr->data.data(), expr->data.size());
  writer_->WriteCloseSpace();
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnCompareExpr(CompareExpr* expr) {
  writer_->WritePutsNewline(expr->opcode.GetName());
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnConstExpr(ConstExpr* expr) {
  writer_->WriteConst(expr->const_);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnConvertExpr(ConvertExpr* expr) {
  writer_->WritePutsNewline(expr->opcode.GetName());
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnDropExpr(DropExpr* expr) {
  writer_->WritePutsNewline(Opcode::Drop_Opcode.GetName());
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnGlobalGetExpr(GlobalGetExpr* expr) {
  writer_->WritePutsSpace(Opcode::GlobalGet_Opcode.GetName());
  writer_->WriteVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnGlobalSetExpr(GlobalSetExpr* expr) {
  writer_->WritePutsSpace(Opcode::GlobalSet_Opcode.GetName());
  writer_->WriteVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::BeginIfExpr(IfExpr* expr) {
  writer_->WriteBeginBlock(LabelType::If, expr->true_,
                           Opcode::If_Opcode.GetName());
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::AfterIfTrueExpr(IfExpr* expr) {
  if (!expr->false_.empty()) {
    writer_->Dedent();
    writer_->WritePutsSpace(Opcode::Else_Opcode.GetName());
    writer_->Indent();
    writer_->WriteNewline(FORCE_NEWLINE);
  }
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::EndIfExpr(IfExpr* expr) {
  writer_->WriteEndBlock();
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnLoadExpr(LoadExpr* expr) {
  writer_->WriteMemoryLoadStoreExpr<LoadExpr>(expr);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnLocalGetExpr(LocalGetExpr* expr) {
  writer_->WritePutsSpace(Opcode::LocalGet_Opcode.GetName());
  writer_->WriteVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnLocalSetExpr(LocalSetExpr* expr) {
  writer_->WritePutsSpace(Opcode::LocalSet_Opcode.GetName());
  writer_->WriteVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnLocalTeeExpr(LocalTeeExpr* expr) {
  writer_->WritePutsSpace(Opcode::LocalTee_Opcode.GetName());
  writer_->WriteVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::BeginLoopExpr(LoopExpr* expr) {
  writer_->WriteBeginBlock(LabelType::Loop, expr->block,
                           Opcode::Loop_Opcode.GetName());
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::EndLoopExpr(LoopExpr* expr) {
  writer_->WriteEndBlock();
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnMemoryCopyExpr(MemoryCopyExpr* expr) {
  writer_->WritePutsSpace(Opcode::MemoryCopy_Opcode.GetName());
  writer_->WriteTwoMemoryVarsUnlessBothZero(expr->destmemidx, expr->srcmemidx,
                                            NextChar::Space);
  writer_->WriteNewline(NO_FORCE_NEWLINE);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnDataDropExpr(DataDropExpr* expr) {
  writer_->WritePutsSpace(Opcode::DataDrop_Opcode.GetName());
  writer_->WriteVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnMemoryFillExpr(MemoryFillExpr* expr) {
  writer_->WritePutsSpace(Opcode::MemoryFill_Opcode.GetName());
  writer_->WriteMemoryVarUnlessZero(expr->memidx, NextChar::Space);
  writer_->WriteNewline(NO_FORCE_NEWLINE);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnMemoryGrowExpr(MemoryGrowExpr* expr) {
  writer_->WritePutsSpace(Opcode::MemoryGrow_Opcode.GetName());
  writer_->WriteMemoryVarUnlessZero(expr->memidx, NextChar::Space);
  writer_->WriteNewline(NO_FORCE_NEWLINE);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnMemorySizeExpr(MemorySizeExpr* expr) {
  writer_->WritePutsSpace(Opcode::MemorySize_Opcode.GetName());
  writer_->WriteMemoryVarUnlessZero(expr->memidx, NextChar::Space);
  writer_->WriteNewline(NO_FORCE_NEWLINE);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnMemoryInitExpr(MemoryInitExpr* expr) {
  writer_->WritePutsSpace(Opcode::MemoryInit_Opcode.GetName());
  writer_->WriteVar(expr->var, NextChar::Space);
  writer_->WriteMemoryVarUnlessZero(expr->memidx, NextChar::Space);
  writer_->WriteNewline(NO_FORCE_NEWLINE);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnTableCopyExpr(TableCopyExpr* expr) {
  writer_->WritePutsSpace(Opcode::TableCopy_Opcode.GetName());
  if (!(VarIsZero(expr->dst_table) && VarIsZero(expr->src_table))) {
    writer_->WriteVar(expr->dst_table, NextChar::Space);
    writer_->WriteVar(expr->src_table, NextChar::Space);
  }
  writer_->WriteNewline(NO_FORCE_NEWLINE);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnElemDropExpr(ElemDropExpr* expr) {
  writer_->WritePutsSpace(Opcode::ElemDrop_Opcode.GetName());
  writer_->WriteVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnTableInitExpr(TableInitExpr* expr) {
  writer_->WritePutsSpace(Opcode::TableInit_Opcode.GetName());
  writer_->WriteVarUnlessZero(expr->table_index, NextChar::Space);
  writer_->WriteVar(expr->segment_index, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnTableGetExpr(TableGetExpr* expr) {
  writer_->WritePutsSpace(Opcode::TableGet_Opcode.GetName());
  writer_->WriteVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnTableSetExpr(TableSetExpr* expr) {
  writer_->WritePutsSpace(Opcode::TableSet_Opcode.GetName());
  writer_->WriteVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnTableGrowExpr(TableGrowExpr* expr) {
  writer_->WritePutsSpace(Opcode::TableGrow_Opcode.GetName());
  writer_->WriteVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnTableSizeExpr(TableSizeExpr* expr) {
  writer_->WritePutsSpace(Opcode::TableSize_Opcode.GetName());
  writer_->WriteVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnTableFillExpr(TableFillExpr* expr) {
  writer_->WritePutsSpace(Opcode::TableFill_Opcode.GetName());
  writer_->WriteVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnRefFuncExpr(RefFuncExpr* expr) {
  writer_->WritePutsSpace(Opcode::RefFunc_Opcode.GetName());
  writer_->WriteVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnRefNullExpr(RefNullExpr* expr) {
  writer_->WritePutsSpace(Opcode::RefNull_Opcode.GetName());
  writer_->WriteRefKind(expr->type, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnRefIsNullExpr(RefIsNullExpr* expr) {
  writer_->WritePutsNewline(Opcode::RefIsNull_Opcode.GetName());
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnNopExpr(NopExpr* expr) {
  writer_->WritePutsNewline(Opcode::Nop_Opcode.GetName());
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnReturnExpr(ReturnExpr* expr) {
  writer_->WritePutsNewline(Opcode::Return_Opcode.GetName());
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnReturnCallExpr(ReturnCallExpr* expr) {
  writer_->WritePutsSpace(Opcode::ReturnCall_Opcode.GetName());
  writer_->WriteVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnReturnCallIndirectExpr(
    ReturnCallIndirectExpr* expr) {
  writer_->WritePutsSpace(Opcode::ReturnCallIndirect_Opcode.GetName());
  writer_->WriteOpenSpace("type");
  const auto type_var =
      expr->decl.has_func_type
          ? expr->decl.type_var
          : Var{writer_->module.GetFuncTypeIndex(expr->decl), expr->loc};
  writer_->WriteVar(type_var, NextChar::Space);
  writer_->WriteCloseNewline();
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnSelectExpr(SelectExpr* expr) {
  writer_->WritePutsSpace(Opcode::Select_Opcode.GetName());
  if (!expr->result_type.empty()) {
    writer_->WriteTypes(expr->result_type, "result");
  }
  writer_->WriteNewline(NO_FORCE_NEWLINE);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnStoreExpr(StoreExpr* expr) {
  writer_->WriteMemoryLoadStoreExpr<StoreExpr>(expr);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnUnaryExpr(UnaryExpr* expr) {
  writer_->WritePutsNewline(expr->opcode.GetName());
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnUnreachableExpr(
    UnreachableExpr* expr) {
  writer_->WritePutsNewline(Opcode::Unreachable_Opcode.GetName());
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::BeginTryTableExpr(TryTableExpr* expr) {
  // copied from WriteBeginBlock, try_table needs to push label *after*
  // writing catches
  writer_->WritePutsSpace(Opcode::TryTable_Opcode.GetName());
  bool has_label = !expr->block.label.empty();
  if (has_label) {
    writer_->WriteString(expr->block.label, NextChar::Space);
  }
  writer_->WriteTypes(expr->block.decl.sig.param_types, "param");
  writer_->WriteTypes(expr->block.decl.sig.result_types, "result");
  if (!has_label) {
    writer_->Writef(" ;; label = @%" PRIindex, writer_->GetLabelStackSize());
  }
  writer_->WriteNewline(FORCE_NEWLINE);
  writer_->Indent();
  for (const auto& catch_ : expr->catches) {
    writer_->WritePuts("(", NextChar::None);
    switch (catch_.kind) {
      case CatchKind::Catch:
        writer_->WritePutsSpace("catch");
        break;
      case CatchKind::CatchRef:
        writer_->WritePutsSpace("catch_ref");
        break;
      case CatchKind::CatchAll:
        writer_->WritePutsSpace("catch_all");
        break;
      case CatchKind::CatchAllRef:
        writer_->WritePutsSpace("catch_all_ref");
        break;
    }
    if (catch_.kind == CatchKind::Catch || catch_.kind == CatchKind::CatchRef) {
      writer_->WriteVar(catch_.tag, NextChar::Space);
    }
    writer_->WriteBrVar(catch_.target, NextChar::None);
    writer_->WritePuts(")", NextChar::Newline);
  }
  writer_->BeginBlock(LabelType::TryTable, expr->block);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::EndTryTableExpr(TryTableExpr* expr) {
  writer_->WriteEndBlock();
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::BeginTryExpr(TryExpr* expr) {
  writer_->WriteBeginBlock(LabelType::Try, expr->block,
                           Opcode::Try_Opcode.GetName());
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnCatchExpr(TryExpr* expr,
                                                   Catch* catch_) {
  writer_->Dedent();
  if (catch_->IsCatchAll()) {
    writer_->WritePutsNewline(Opcode::CatchAll_Opcode.GetName());
  } else {
    writer_->WritePutsSpace(Opcode::Catch_Opcode.GetName());
    writer_->WriteVar(catch_->var, NextChar::Newline);
  }
  writer_->Indent();
  writer_->SetTopLabelType(LabelType::Catch);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnDelegateExpr(TryExpr* expr) {
  writer_->Dedent();
  writer_->EndBlock();
  writer_->WritePutsSpace(Opcode::Delegate_Opcode.GetName());
  writer_->WriteVar(expr->delegate_target, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::EndTryExpr(TryExpr* expr) {
  writer_->WriteEndBlock();
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnThrowExpr(ThrowExpr* expr) {
  writer_->WritePutsSpace(Opcode::Throw_Opcode.GetName());
  writer_->WriteVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnThrowRefExpr(ThrowRefExpr* expr) {
  writer_->WritePutsNewline(Opcode::ThrowRef_Opcode.GetName());
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnRethrowExpr(RethrowExpr* expr) {
  writer_->WritePutsSpace(Opcode::Rethrow_Opcode.GetName());
  writer_->WriteBrVar(expr->var, NextChar::Newline);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnAtomicWaitExpr(AtomicWaitExpr* expr) {
  writer_->WriteLoadStoreExpr<AtomicWaitExpr>(expr);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnAtomicFenceExpr(
    AtomicFenceExpr* expr) {
  assert(expr->consistency_model == 0);
  writer_->WritePutsNewline(Opcode::AtomicFence_Opcode.GetName());
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnAtomicNotifyExpr(
    AtomicNotifyExpr* expr) {
  writer_->WriteLoadStoreExpr<AtomicNotifyExpr>(expr);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnAtomicLoadExpr(AtomicLoadExpr* expr) {
  writer_->WriteLoadStoreExpr<AtomicLoadExpr>(expr);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnAtomicStoreExpr(
    AtomicStoreExpr* expr) {
  writer_->WriteLoadStoreExpr<AtomicStoreExpr>(expr);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnAtomicRmwExpr(AtomicRmwExpr* expr) {
  writer_->WriteLoadStoreExpr<AtomicRmwExpr>(expr);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnAtomicRmwCmpxchgExpr(
    AtomicRmwCmpxchgExpr* expr) {
  writer_->WriteLoadStoreExpr<AtomicRmwCmpxchgExpr>(expr);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnTernaryExpr(TernaryExpr* expr) {
  writer_->WritePutsNewline(expr->opcode.GetName());
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnSimdLaneOpExpr(SimdLaneOpExpr* expr) {
  writer_->WritePutsSpace(expr->opcode.GetName());
  writer_->Writef("%" PRIu64, (expr->val));
  writer_->WriteNewline(NO_FORCE_NEWLINE);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnSimdLoadLaneExpr(
    SimdLoadLaneExpr* expr) {
  writer_->WritePutsSpace(expr->opcode.GetName());
  writer_->WriteMemoryVarUnlessZero(expr->memidx, NextChar::Space);
  if (expr->offset) {
    writer_->Writef("offset=%" PRIaddress, expr->offset);
  }
  if (!expr->opcode.IsNaturallyAligned(expr->align)) {
    writer_->Writef("align=%" PRIaddress, expr->align);
  }
  writer_->Writef("%" PRIu64, (expr->val));
  writer_->WriteNewline(NO_FORCE_NEWLINE);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnSimdStoreLaneExpr(
    SimdStoreLaneExpr* expr) {
  writer_->WritePutsSpace(expr->opcode.GetName());
  writer_->WriteMemoryVarUnlessZero(expr->memidx, NextChar::Space);
  if (expr->offset) {
    writer_->Writef("offset=%" PRIaddress, expr->offset);
  }
  if (!expr->opcode.IsNaturallyAligned(expr->align)) {
    writer_->Writef("align=%" PRIaddress, expr->align);
  }
  writer_->Writef("%" PRIu64, (expr->val));
  writer_->WriteNewline(NO_FORCE_NEWLINE);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnSimdShuffleOpExpr(
    SimdShuffleOpExpr* expr) {
  writer_->WritePutsSpace(expr->opcode.GetName());
  std::array<uint8_t, 16> values = Bitcast<std::array<uint8_t, 16>>(expr->val);
  for (int32_t lane = 0; lane < 16; ++lane) {
#if WABT_BIG_ENDIAN
    writer_->Writef("%u", values[15 - lane]);
#else
    writer_->Writef("%u", values[lane]);
#endif
  }
  writer_->WriteNewline(NO_FORCE_NEWLINE);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnLoadSplatExpr(LoadSplatExpr* expr) {
  writer_->WriteLoadStoreExpr<LoadSplatExpr>(expr);
  return Result::Ok;
}

Result WatWriter::ExprVisitorDelegate::OnLoadZeroExpr(LoadZeroExpr* expr) {
  writer_->WriteLoadStoreExpr<LoadZeroExpr>(expr);
  return Result::Ok;
}

void WatWriter::WriteExpr(const Expr* expr) {
  WABT_TRACE(WriteExprList);
  ExprVisitorDelegate delegate(this);
  ExprVisitor visitor(&delegate);
  visitor.VisitExpr(const_cast<Expr*>(expr));
}

void WatWriter::WriteExprList(const ExprList& exprs) {
  WABT_TRACE(WriteExprList);
  ExprVisitorDelegate delegate(this);
  ExprVisitor visitor(&delegate);
  visitor.VisitExprList(const_cast<ExprList&>(exprs));
}

void WatWriter::WriteFoldedExpr(const Expr* expr) {
  WABT_TRACE_ARGS(WriteFoldedExpr, "%s", GetExprTypeName(*expr));
  auto arity = GetExprArity(*expr);
  PushExpr(expr, arity.nargs, arity.nreturns);
}

void WatWriter::WriteFoldedExprList(const ExprList& exprs) {
  WABT_TRACE(WriteFoldedExprList);
  for (const Expr& expr : exprs) {
    WriteFoldedExpr(&expr);
  }
}

void WatWriter::PushExpr(const Expr* expr,
                         Index operand_count,
                         Index result_count) {
  WABT_TRACE_ARGS(PushExpr, "%s, %" PRIindex ", %" PRIindex "",
                  GetExprTypeName(*expr), operand_count, result_count);

  // Try to pop operand off the expr stack to use as this expr's children. One
  // expr can have multiple return values (with the multi-value extension), so
  // we have to iterate over each in reverse.

  auto first_operand = expr_tree_stack_.end();

  Index current_count = 0;
  if (operand_count > 0) {
    for (auto iter = expr_tree_stack_.rbegin(); iter != expr_tree_stack_.rend();
         ++iter) {
      assert(iter->result_count > 0);
      current_count += iter->result_count;

      if (current_count == operand_count) {
        first_operand = iter.base() - 1;
        break;
      } else if (current_count > operand_count) {
        // We went over the number of operands this instruction wants; this can
        // only happen when there are expressions on the stack with a
        // result_count > 1. When this happens we can't fold, since any result
        // we produce will not make sense.
        break;
      }
    }
  }

  ExprTree tree(expr, result_count);

  if (current_count == operand_count && operand_count > 0) {
    auto last_operand = expr_tree_stack_.end();
    std::move(first_operand, last_operand, std::back_inserter(tree.children));
    expr_tree_stack_.erase(first_operand, last_operand);
  }

  expr_tree_stack_.emplace_back(std::move(tree));
  if (current_count > operand_count || result_count == 0) {
    FlushExprTreeStack();
  }
}

void WatWriter::FlushExprTree(const ExprTree& expr_tree) {
  WABT_TRACE_ARGS(FlushExprTree, "%s", GetExprTypeName(*expr_tree.expr));
  switch (expr_tree.expr->type()) {
    case ExprType::Block:
      WritePuts("(", NextChar::None);
      WriteBeginBlock(LabelType::Block, cast<BlockExpr>(expr_tree.expr)->block,
                      Opcode::Block_Opcode.GetName());
      WriteFoldedExprList(cast<BlockExpr>(expr_tree.expr)->block.exprs);
      FlushExprTreeStack();
      WriteCloseNewline();
      EndBlock();
      break;

    case ExprType::Loop:
      WritePuts("(", NextChar::None);
      WriteBeginBlock(LabelType::Loop, cast<LoopExpr>(expr_tree.expr)->block,
                      Opcode::Loop_Opcode.GetName());
      WriteFoldedExprList(cast<LoopExpr>(expr_tree.expr)->block.exprs);
      FlushExprTreeStack();
      WriteCloseNewline();
      EndBlock();
      break;

    case ExprType::If: {
      auto if_expr = cast<IfExpr>(expr_tree.expr);
      WritePuts("(", NextChar::None);
      WriteBeginBlock(LabelType::If, if_expr->true_,
                      Opcode::If_Opcode.GetName());
      FlushExprTreeVector(expr_tree.children);
      WriteOpenNewline("then");
      WriteFoldedExprList(if_expr->true_.exprs);
      FlushExprTreeStack();
      WriteCloseNewline();
      if (!if_expr->false_.empty()) {
        WriteOpenNewline("else");
        WriteFoldedExprList(if_expr->false_);
        FlushExprTreeStack();
        WriteCloseNewline();
      }
      WriteCloseNewline();
      EndBlock();
      break;
    }

    case ExprType::Try: {
      auto try_expr = cast<TryExpr>(expr_tree.expr);
      WritePuts("(", NextChar::None);
      WriteBeginBlock(LabelType::Try, try_expr->block,
                      Opcode::Try_Opcode.GetName());
      WriteOpenNewline("do");
      FlushExprTreeVector(expr_tree.children);
      WriteFoldedExprList(try_expr->block.exprs);
      FlushExprTreeStack();
      WriteCloseNewline();
      switch (try_expr->kind) {
        case TryKind::Catch:
          for (const Catch& catch_ : try_expr->catches) {
            WritePuts("(", NextChar::None);
            if (catch_.IsCatchAll()) {
              WritePutsNewline("catch_all");
            } else {
              WritePutsSpace(Opcode::Catch_Opcode.GetName());
              WriteVar(catch_.var, NextChar::Newline);
            }
            Indent();
            WriteFoldedExprList(catch_.exprs);
            FlushExprTreeStack();
            WriteCloseNewline();
          }
          break;
        case TryKind::Delegate:
          WritePuts("(", NextChar::None);
          WritePutsSpace(Opcode::Delegate_Opcode.GetName());
          WriteVar(try_expr->delegate_target, NextChar::None);
          WritePuts(")", NextChar::Newline);
          break;
        case TryKind::Plain:
          // Nothing to do.
          break;
      }
      WriteCloseNewline();
      EndBlock();
      break;
    }

    case ExprType::TryTable: {
      auto try_table_expr = cast<TryTableExpr>(expr_tree.expr);

      WritePuts("(", NextChar::None);
      // copied from WriteBeginBlock, try_table needs to push label *after*
      // writing catches
      WritePutsSpace(Opcode::TryTable_Opcode.GetName());
      bool has_label = !try_table_expr->block.label.empty();
      if (has_label) {
        WriteString(try_table_expr->block.label, NextChar::Space);
      }
      WriteTypes(try_table_expr->block.decl.sig.param_types, "param");
      WriteTypes(try_table_expr->block.decl.sig.result_types, "result");
      if (!has_label) {
        Writef(" ;; label = @%" PRIindex, GetLabelStackSize());
      }
      WriteNewline(FORCE_NEWLINE);
      Indent();

      for (const auto& catch_ : try_table_expr->catches) {
        WritePuts("(", NextChar::None);
        switch (catch_.kind) {
          case CatchKind::Catch:
            WritePutsSpace("catch");
            break;
          case CatchKind::CatchRef:
            WritePutsSpace("catch_ref");
            break;
          case CatchKind::CatchAll:
            WritePutsSpace("catch_all");
            break;
          case CatchKind::CatchAllRef:
            WritePutsSpace("catch_all_ref");
            break;
        }
        if (catch_.kind == CatchKind::Catch ||
            catch_.kind == CatchKind::CatchRef) {
          WriteVar(catch_.tag, NextChar::Space);
        }
        WriteBrVar(catch_.target, NextChar::None);
        WritePuts(")", NextChar::Newline);
      }

      BeginBlock(LabelType::TryTable, try_table_expr->block);

      WriteFoldedExprList(try_table_expr->block.exprs);
      FlushExprTreeStack();
      WriteCloseNewline();
      EndBlock();
      break;
    }

    default: {
      WritePuts("(", NextChar::None);
      WriteExpr(expr_tree.expr);
      Indent();
      FlushExprTreeVector(expr_tree.children);
      WriteCloseNewline();
      break;
    }
  }
}

void WatWriter::FlushExprTreeVector(const std::vector<ExprTree>& expr_trees) {
  WABT_TRACE_ARGS(FlushExprTreeVector, "%zu", expr_trees.size());
  for (auto expr_tree : expr_trees) {
    FlushExprTree(expr_tree);
  }
}

void WatWriter::FlushExprTreeStack() {
  std::vector<ExprTree> stack_copy(std::move(expr_tree_stack_));
  expr_tree_stack_.clear();
  FlushExprTreeVector(stack_copy);
}

void WatWriter::WriteInitExpr(const ExprList& expr) {
  if (!expr.empty()) {
    WritePuts("(", NextChar::None);
    WriteExprList(expr);
    /* clear the next char, so we don't write a newline after the expr */
    next_char_ = NextChar::None;
    WritePuts(")", NextChar::Space);
  }
}

template <typename T>
void WatWriter::WriteTypeBindings(const char* prefix,
                                  const T& types,
                                  const std::vector<std::string>& index_to_name,
                                  Index binding_index_offset) {
  /* named params/locals must be specified by themselves, but nameless
   * params/locals can be compressed, e.g.:
   *   (param $foo i32)
   *   (param i32 i64 f32)
   */
  bool first = true;
  bool prev_has_name = false;
  size_t index = 0;
  for (Type type : types) {
    const std::string& name = index_to_name[binding_index_offset + index];
    bool has_name = !name.empty();
    if ((has_name || prev_has_name) && !first) {
      WriteCloseSpace();
    }
    if (has_name || prev_has_name || first) {
      WriteOpenSpace(prefix);
    }
    if (has_name) {
      WriteString(name, NextChar::Space);
    }
    WriteType(type, NextChar::Space);
    prev_has_name = has_name;
    first = false;
    ++index;
  }
  if (types.size() != 0) {
    WriteCloseSpace();
  }
}

void WatWriter::WriteBeginFunc(const Func& func) {
  WriteOpenSpace("func");
  WriteNameOrIndex(func.name, func_index_, NextChar::Space);
  WriteInlineExports(ExternalKind::Func, func_index_);
  WriteInlineImport(ExternalKind::Func, func_index_);
  if (func.decl.has_func_type) {
    WriteOpenSpace("type");
    WriteVar(func.decl.type_var, NextChar::None);
    WriteCloseSpace();
  }

  if (module.IsImport(ExternalKind::Func, Var(func_index_, Location()))) {
    // Imported functions can be written a few ways:
    //
    //   1. (import "module" "field" (func (type 0)))
    //   2. (import "module" "field" (func (param i32) (result i32)))
    //   3. (func (import "module" "field") (type 0))
    //   4. (func (import "module" "field") (param i32) (result i32))
    //   5. (func (import "module" "field") (type 0) (param i32) (result i32))
    //
    // Note that the text format does not allow including the param/result
    // explicitly when using the "(import..." syntax (#1 and #2).
    if (options_.inline_import || !func.decl.has_func_type) {
      WriteFuncSigSpace(func.decl.sig);
    }
  }
  func_index_++;
}

void WatWriter::WriteFunc(const Func& func) {
  WriteBeginFunc(func);
  std::vector<std::string> index_to_name;
  MakeTypeBindingReverseMapping(func.GetNumParamsAndLocals(), func.bindings,
                                &index_to_name);
  WriteTypeBindings("param", func.decl.sig.param_types, index_to_name);
  WriteTypes(func.decl.sig.result_types, "result");
  WriteNewline(NO_FORCE_NEWLINE);
  if (func.local_types.size()) {
    WriteTypeBindings("local", func.local_types, index_to_name,
                      func.GetNumParams());
  }
  WriteNewline(NO_FORCE_NEWLINE);
  BeginFunc(func);
  if (options_.fold_exprs) {
    WriteFoldedExprList(func.exprs);
    FlushExprTreeStack();
  } else {
    WriteExprList(func.exprs);
  }
  EndFunc();
  WriteCloseNewline();
}

void WatWriter::WriteBeginGlobal(const Global& global) {
  WriteOpenSpace("global");
  WriteNameOrIndex(global.name, global_index_, NextChar::Space);
  WriteInlineExports(ExternalKind::Global, global_index_);
  WriteInlineImport(ExternalKind::Global, global_index_);
  if (global.mutable_) {
    WriteOpenSpace("mut");
    WriteType(global.type, NextChar::Space);
    WriteCloseSpace();
  } else {
    WriteType(global.type, NextChar::Space);
  }
  global_index_++;
}

void WatWriter::WriteGlobal(const Global& global) {
  WriteBeginGlobal(global);
  WriteInitExpr(global.init_expr);
  WriteCloseNewline();
}

void WatWriter::WriteTag(const Tag& tag) {
  WriteOpenSpace("tag");
  WriteNameOrIndex(tag.name, tag_index_, NextChar::Space);
  WriteInlineExports(ExternalKind::Tag, tag_index_);
  WriteInlineImport(ExternalKind::Tag, tag_index_);
  if (tag.decl.has_func_type) {
    WriteOpenSpace("type");
    WriteVar(tag.decl.type_var, NextChar::None);
    WriteCloseSpace();
  }
  WriteTypes(tag.decl.sig.param_types, "param");
  ++tag_index_;
  WriteCloseNewline();
}

void WatWriter::WriteLimits(const Limits& limits) {
  if (limits.is_64) {
    Writef("i64");
  }
  Writef("%" PRIu64, limits.initial);
  if (limits.has_max) {
    Writef("%" PRIu64, limits.max);
  }
  if (limits.is_shared) {
    Writef("shared");
  }
}

void WatWriter::WriteTable(const Table& table) {
  WriteOpenSpace("table");
  WriteNameOrIndex(table.name, table_index_, NextChar::Space);
  WriteInlineExports(ExternalKind::Table, table_index_);
  WriteInlineImport(ExternalKind::Table, table_index_);
  WriteLimits(table.elem_limits);
  WriteType(table.elem_type, NextChar::None);
  WriteCloseNewline();
  table_index_++;
}

void WatWriter::WriteElemSegment(const ElemSegment& segment) {
  WriteOpenSpace("elem");
  // The first name we encounter here, pre-bulk-memory, was intended to refer to
  // the table while segment names were not supported at all.  For this reason
  // we cannot emit a segment name here without bulk-memory enabled, otherwise
  // the name will be assumed to be the name of a table and parsing will fail.
  if (options_.features.bulk_memory_enabled()) {
    WriteNameOrIndex(segment.name, elem_segment_index_, NextChar::Space);
  } else {
    Writef("(;%u;)", elem_segment_index_);
  }

  uint8_t flags = segment.GetFlags(&module);

  if ((flags & (SegPassive | SegExplicitIndex)) == SegExplicitIndex) {
    WriteOpenSpace("table");
    WriteVar(segment.table_var, NextChar::Space);
    WriteCloseSpace();
  }

  if (!(flags & SegPassive)) {
    WriteInitExpr(segment.offset);
  }

  if ((flags & SegDeclared) == SegDeclared) {
    WritePuts("declare", NextChar::Space);
  }

  if (flags & SegUseElemExprs) {
    WriteType(segment.elem_type, NextChar::Space);
  } else {
    assert(segment.elem_type == Type::FuncRef);
    WritePuts("func", NextChar::Space);
  }

  for (const ExprList& expr : segment.elem_exprs) {
    if (flags & SegUseElemExprs) {
      WriteInitExpr(expr);
    } else {
      assert(expr.size() == 1);
      assert(expr.front().type() == ExprType::RefFunc);
      WriteVar(cast<const RefFuncExpr>(&expr.front())->var, NextChar::Space);
    }
  }
  WriteCloseNewline();
  elem_segment_index_++;
}

void WatWriter::WriteMemory(const Memory& memory) {
  WriteOpenSpace("memory");
  WriteNameOrIndex(memory.name, memory_index_, NextChar::Space);
  WriteInlineExports(ExternalKind::Memory, memory_index_);
  WriteInlineImport(ExternalKind::Memory, memory_index_);
  WriteLimits(memory.page_limits);
  if (memory.page_size != WABT_DEFAULT_PAGE_SIZE) {
    WriteOpenSpace("pagesize");
    Writef("%u", memory.page_size);
    WriteCloseSpace();
  }
  WriteCloseNewline();
  memory_index_++;
}

void WatWriter::WriteDataSegment(const DataSegment& segment) {
  WriteOpenSpace("data");
  WriteNameOrIndex(segment.name, data_segment_index_, NextChar::Space);
  if (segment.kind != SegmentKind::Passive) {
    if (module.GetMemoryIndex(segment.memory_var) != 0) {
      WriteOpenSpace("memory");
      WriteVar(segment.memory_var, NextChar::Space);
      WriteCloseSpace();
    }
    WriteInitExpr(segment.offset);
  }
  WriteQuotedData(segment.data.data(), segment.data.size());
  WriteCloseNewline();
  data_segment_index_++;
}

void WatWriter::WriteImport(const Import& import) {
  if (!options_.inline_import) {
    WriteOpenSpace("import");
    WriteQuotedString(import.module_name, NextChar::Space);
    WriteQuotedString(import.field_name, NextChar::Space);
  }

  switch (import.kind()) {
    case ExternalKind::Func:
      WriteBeginFunc(cast<FuncImport>(&import)->func);
      WriteCloseSpace();
      break;

    case ExternalKind::Table:
      WriteTable(cast<TableImport>(&import)->table);
      break;

    case ExternalKind::Memory:
      WriteMemory(cast<MemoryImport>(&import)->memory);
      break;

    case ExternalKind::Global:
      WriteBeginGlobal(cast<GlobalImport>(&import)->global);
      WriteCloseSpace();
      break;

    case ExternalKind::Tag:
      WriteTag(cast<TagImport>(&import)->tag);
      break;
  }

  if (options_.inline_import) {
    WriteNewline(NO_FORCE_NEWLINE);
  } else {
    WriteCloseNewline();
  }
}

void WatWriter::WriteExport(const Export& export_) {
  if (options_.inline_export && IsInlineExport(export_)) {
    return;
  }
  WriteOpenSpace("export");
  WriteQuotedString(export_.name, NextChar::Space);
  WriteOpenSpace(GetKindName(export_.kind));
  WriteVar(export_.var, NextChar::Space);
  WriteCloseSpace();
  WriteCloseNewline();
}

void WatWriter::WriteTypeEntry(const TypeEntry& type) {
  WriteOpenSpace("type");
  WriteNameOrIndex(type.name, type_index_++, NextChar::Space);
  switch (type.kind()) {
    case TypeEntryKind::Func:
      WriteOpenSpace("func");
      WriteFuncSigSpace(cast<FuncType>(&type)->sig);
      WriteCloseSpace();
      break;

    case TypeEntryKind::Struct: {
      auto* struct_type = cast<StructType>(&type);
      WriteOpenSpace("struct");
      Index field_index = 0;
      for (auto&& field : struct_type->fields) {
        // TODO: Write shorthand if there is no name.
        WriteOpenSpace("field");
        WriteNameOrIndex(field.name, field_index++, NextChar::Space);
        WriteField(field);
        WriteCloseSpace();
      }
      WriteCloseSpace();
      break;
    }

    case TypeEntryKind::Array: {
      auto* array_type = cast<ArrayType>(&type);
      WriteOpenSpace("array");
      WriteField(array_type->field);
      WriteCloseSpace();
      break;
    }
  }
  WriteCloseNewline();
}

void WatWriter::WriteField(const Field& field) {
  if (field.mutable_) {
    WriteOpenSpace("mut");
  }
  WriteType(field.type, NextChar::Space);
  if (field.mutable_) {
    WriteCloseSpace();
  }
}

void WatWriter::WriteStartFunction(const Var& start) {
  WriteOpenSpace("start");
  WriteVar(start, NextChar::None);
  WriteCloseNewline();
}

void WatWriter::WriteCustom(const Custom& custom) {
  WriteOpenSpace("@custom");
  WriteQuotedString(custom.name, NextChar::Space);
  WriteQuotedData(custom.data.data(), custom.data.size());
  WriteCloseNewline();
}

Result WatWriter::WriteModule() {
  BuildInlineExportMap();
  BuildInlineImportMap();
  WriteOpenSpace("module");
  if (module.name.empty()) {
    WriteNewline(NO_FORCE_NEWLINE);
  } else {
    WriteName(module.name, NextChar::Newline);
  }
  for (const ModuleField& field : module.fields) {
    switch (field.type()) {
      case ModuleFieldType::Func:
        WriteFunc(cast<FuncModuleField>(&field)->func);
        break;
      case ModuleFieldType::Global:
        WriteGlobal(cast<GlobalModuleField>(&field)->global);
        break;
      case ModuleFieldType::Import:
        WriteImport(*cast<ImportModuleField>(&field)->import);
        break;
      case ModuleFieldType::Tag:
        WriteTag(cast<TagModuleField>(&field)->tag);
        break;
      case ModuleFieldType::Export:
        WriteExport(cast<ExportModuleField>(&field)->export_);
        break;
      case ModuleFieldType::Table:
        WriteTable(cast<TableModuleField>(&field)->table);
        break;
      case ModuleFieldType::ElemSegment:
        WriteElemSegment(cast<ElemSegmentModuleField>(&field)->elem_segment);
        break;
      case ModuleFieldType::Memory:
        WriteMemory(cast<MemoryModuleField>(&field)->memory);
        break;
      case ModuleFieldType::DataSegment:
        WriteDataSegment(cast<DataSegmentModuleField>(&field)->data_segment);
        break;
      case ModuleFieldType::Type:
        WriteTypeEntry(*cast<TypeModuleField>(&field)->type);
        break;
      case ModuleFieldType::Start:
        WriteStartFunction(cast<StartModuleField>(&field)->start);
        break;
    }
  }
  if (options_.features.annotations_enabled()) {
    for (const Custom& custom : module.customs) {
      WriteCustom(custom);
    }
  }
  WriteCloseNewline();
  /* force the newline to be written */
  WriteNextChar();
  return result_;
}

void WatWriter::BuildInlineExportMap() {
  if (!options_.inline_export) {
    return;
  }

  for (Export* export_ : module.exports) {
    Index index = kInvalidIndex;

    // Exported imports can't be written with inline exports, unless the
    // imports are also inline. For example, the following is invalid:
    //
    //   (import "module" "field" (func (export "e")))
    //
    // But this is valid:
    //
    //   (func (export "e") (import "module" "field"))
    //
    if (!options_.inline_import && module.IsImport(*export_)) {
      continue;
    }

    switch (export_->kind) {
      case ExternalKind::Func:
        index = module.GetFuncIndex(export_->var);
        break;

      case ExternalKind::Table:
        index = module.GetTableIndex(export_->var);
        break;

      case ExternalKind::Memory:
        index = module.GetMemoryIndex(export_->var);
        break;

      case ExternalKind::Global:
        index = module.GetGlobalIndex(export_->var);
        break;

      case ExternalKind::Tag:
        index = module.GetTagIndex(export_->var);
        break;
    }

    if (index != kInvalidIndex) {
      auto key = std::make_pair(export_->kind, index);
      inline_export_map_.insert(std::make_pair(key, export_));
    }
  }
}

void WatWriter::WriteInlineExports(ExternalKind kind, Index index) {
  if (!options_.inline_export) {
    return;
  }

  auto iter_pair = inline_export_map_.equal_range(std::make_pair(kind, index));
  for (auto iter = iter_pair.first; iter != iter_pair.second; ++iter) {
    const Export* export_ = iter->second;
    WriteOpenSpace("export");
    WriteQuotedString(export_->name, NextChar::None);
    WriteCloseSpace();
  }
}

bool WatWriter::IsInlineExport(const Export& export_) {
  Index index{};
  switch (export_.kind) {
    case ExternalKind::Func:
      index = module.GetFuncIndex(export_.var);
      break;

    case ExternalKind::Table:
      index = module.GetTableIndex(export_.var);
      break;

    case ExternalKind::Memory:
      index = module.GetMemoryIndex(export_.var);
      break;

    case ExternalKind::Global:
      index = module.GetGlobalIndex(export_.var);
      break;

    case ExternalKind::Tag:
      index = module.GetTagIndex(export_.var);
      break;
  }

  return inline_export_map_.find(std::make_pair(export_.kind, index)) !=
         inline_export_map_.end();
}

void WatWriter::BuildInlineImportMap() {
  if (!options_.inline_import) {
    return;
  }

  for (const Import* import : module.imports) {
    inline_import_map_[static_cast<size_t>(import->kind())].push_back(import);
  }
}

void WatWriter::WriteInlineImport(ExternalKind kind, Index index) {
  if (!options_.inline_import) {
    return;
  }

  size_t kind_index = static_cast<size_t>(kind);

  if (index >= inline_import_map_[kind_index].size()) {
    return;
  }

  const Import* import = inline_import_map_[kind_index][index];
  WriteOpenSpace("import");
  WriteQuotedString(import->module_name, NextChar::Space);
  WriteQuotedString(import->field_name, NextChar::Space);
  WriteCloseSpace();
}

}  // end anonymous namespace

Result WriteWat(Stream* stream,
                const Module* module,
                const WriteWatOptions& options) {
  WatWriter wat_writer(stream, options, *module);
  return wat_writer.WriteModule();
}

}  // namespace wabt