text: /*
   * Copyright 2019 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 "src/decompiler.h"
  
  #include "src/decompiler-ast.h"
  #include "src/decompiler-ls.h"
  #include "src/decompiler-naming.h"
  
  #include "src/stream.h"
  
  #define WABT_TRACING 0
  #include "src/tracing.h"
  
  #include <inttypes.h>
  
  namespace wabt {
  
  struct Decompiler {
    Decompiler(const Module& module, const DecompileOptions& options)
        : mc(module), options(options) {}
  
    // Sorted such that "greater precedence" is also the bigger enum val.
    enum Precedence {
      // Low precedence.
      None,      // precedence doesn't matter, since never nested.
      Assign,    // =
      OtherBin,  // min
      Bit,       // & |
      Equal,     // == != < > >= <=
      Shift,     // << >>
      Add,       // + -
      Multiply,  // * / %
      If,        // if{}
      Indexing,  // []
      Atomic,    // (), a, 1, a()
      // High precedence.
    };
  
    // Anything besides these will get parentheses if used with equal precedence,
    // for clarity.
    bool Associative(Precedence p) {
      return p == Precedence::Add || p == Precedence::Multiply;
    }
  
    struct Value {
      std::vector<std::string> v;
      // Lazily add bracketing only if the parent requires it.
      // This is the precedence level of this value, for example, if this
      // precedence is Add, and the parent is Multiply, bracketing is needed,
      // but not if it is the reverse.
      Precedence precedence;
  
      Value(std::vector<std::string>&& v, Precedence p)
        : v(v), precedence(p) {}
  
      size_t width() {
        size_t w = 0;
        for (auto &s : v) {
          w = std::max(w, s.size());
        }
        return w;
      }
  
      // This value should really never be copied, only moved.
      Value(Value&& rhs) = default;
      Value(const Value& rhs) = delete;
      Value& operator=(Value&& rhs) = default;
      Value& operator=(const Value& rhs) = delete;
    };
  
    std::string to_string(double d) {
      auto s = std::to_string(d);
      // Remove redundant trailing '0's that to_string produces.
      while (s.size() > 2 && s.back() == '0' && s[s.size() - 2] != '.')
        s.pop_back();
      return s;
    }
  
    std::string Indent(size_t amount) {
      return std::string(amount, ' ');
    }
  
    std::string OpcodeToToken(Opcode opcode) {
      std::string s = opcode.GetDecomp();
      std::replace(s.begin(), s.end(), '.', '_');
      return s;
    }
  
    void IndentValue(Value &val, size_t amount, string_view first_indent) {
      auto indent = Indent(amount);
      for (auto& stat : val.v) {
        auto is = (&stat != &val.v[0] || first_indent.empty())
                      ? string_view(indent)
                      : first_indent;
        stat.insert(0, is.data(), is.size());
      }
    }
  
    Value WrapChild(Value &child, string_view prefix, string_view postfix,
                    Precedence precedence) {
      auto width = prefix.size() + postfix.size() + child.width();
      auto &v = child.v;
      if (width < target_exp_width ||
          (prefix.size() <= indent_amount && postfix.size() <= indent_amount)) {
        if (v.size() == 1) {
          // Fits in a single line.
          v[0].insert(0, prefix.data(), prefix.size());
          v[0].append(postfix.data(), postfix.size());
        } else {
          // Multiline, but with prefix on same line.
          IndentValue(child, prefix.size(), prefix);
          v.back().append(postfix.data(), postfix.size());
        }
      } else {
        // Multiline with prefix on its own line.
        IndentValue(child, indent_amount, {});
        v.insert(v.begin(), std::string(prefix));
        v.back().append(postfix.data(), postfix.size());
      }
      child.precedence = precedence;
      return std::move(child);
    }
  
    void BracketIfNeeded(Value &val, Precedence parent_precedence) {
      if (parent_precedence < val.precedence ||
          (parent_precedence == val.precedence &&
           Associative(parent_precedence))) return;
      val = WrapChild(val, "(", ")", Precedence::Atomic);
    }
  
    Value WrapBinary(std::vector<Value>& args, string_view infix,
                     bool indent_right, Precedence precedence) {
      assert(args.size() == 2);
      auto& left = args[0];
      auto& right = args[1];
      BracketIfNeeded(left, precedence);
      BracketIfNeeded(right, precedence);
      auto width = infix.size() + left.width() + right.width();
      if (width < target_exp_width && left.v.size() == 1 && right.v.size() == 1) {
        return Value{{left.v[0] + infix + right.v[0]}, precedence};
      } else {
        Value bin { {}, precedence };
        std::move(left.v.begin(), left.v.end(), std::back_inserter(bin.v));
        bin.v.back().append(infix.data(), infix.size());
        if (indent_right) IndentValue(right, indent_amount, {});
        std::move(right.v.begin(), right.v.end(), std::back_inserter(bin.v));
        return bin;
      }
    }
  
    Value WrapNAry(std::vector<Value>& args, string_view prefix,
                   string_view postfix, Precedence precedence) {
      size_t total_width = 0;
      size_t max_width = 0;
      bool multiline = false;
      for (auto& child : args) {
        auto w = child.width();
        max_width = std::max(max_width, w);
        total_width += w;
        multiline = multiline || child.v.size() > 1;
      }
      if (!multiline &&
          (total_width + prefix.size() + postfix.size() < target_exp_width ||
           args.empty())) {
        // Single line.
        auto s = std::string(prefix);
        for (auto& child : args) {
          if (&child != &args[0])
            s += ", ";
          s += child.v[0];
        }
        s += postfix;
        return Value{{std::move(s)}, precedence};
      } else {
        // Multi-line.
        Value ml { {}, precedence };
        auto ident_with_name = max_width + prefix.size() < target_exp_width;
        size_t i = 0;
        for (auto& child : args) {
          IndentValue(child, ident_with_name ? prefix.size() : indent_amount,
                      !i && ident_with_name ? prefix : string_view {});
          if (i < args.size() - 1) child.v.back() += ",";
          std::move(child.v.begin(), child.v.end(),
                    std::back_inserter(ml.v));
          i++;
        }
        if (!ident_with_name) ml.v.insert(ml.v.begin(), std::string(prefix));
        ml.v.back() += postfix;
        return ml;
      }
    }
  
    string_view VarName(string_view name) {
      assert(!name.empty());
      return name[0] == '$' ? name.substr(1) : name;
    }
  
    template<ExprType T> Value Get(const VarExpr<T>& ve) {
      return Value{{std::string(VarName(ve.var.name()))}, Precedence::Atomic};
    }
  
    template<ExprType T> Value Set(Value& child, const VarExpr<T>& ve) {
      return WrapChild(child, VarName(ve.var.name()) + " = ", "", Precedence::Assign);
    }
  
    std::string TempVarName(Index n) {
      // FIXME: this needs much better variable naming. Problem is, the code
      // in generate-names.cc has allready run, its dictionaries deleted, so it
      // is not easy to integrate with it.
      return "t" + std::to_string(n);
    }
  
    std::string LocalDecl(const std::string& name, Type t) {
      auto struc = lst.GenTypeDecl(name);
      return cat(VarName(name), ":",
                 struc.empty() ? GetDecompTypeName(t) : struc);
    }
  
    bool ConstIntVal(const Expr* e, uint64_t &dest) {
      dest = 0;
      if (!e || e->type() != ExprType::Const) return false;
      auto& c = cast<ConstExpr>(e)->const_;
      if (c.type() != Type::I32 && c.type() != Type::I64) return false;
      dest = c.type() == Type::I32 ? c.u32() : c.u64();
      return true;
    }
  
    void LoadStore(Value &val, const Node& addr_exp, uint64_t offset,
                    Opcode opc, Address align, Type op_type) {
      bool append_type = true;
      auto access = lst.GenAccess(offset, addr_exp);
      if (!access.empty()) {
        if (access == "*") {
          // The variable was declared as a typed pointer, so this access
          // doesn't need a type.
          append_type = false;
        } else {
          // We can do this load/store as a struct access.
          BracketIfNeeded(val, Precedence::Indexing);
          val.v.back() += "." + access;
          return;
        }
      }
      // Detect absolute addressing, which we try to turn into references to the
      // data section when possible.
      uint64_t abs_base;
      if (ConstIntVal(addr_exp.e, abs_base)) {
        // We don't care what part of the absolute address was stored where,
        // 1[0] and 0[1] are the same.
        abs_base += offset;
        // FIXME: make this less expensive with a binary search or whatever.
        for (auto dat : mc.module.data_segments) {
          uint64_t dat_base;
          if (dat->offset.size() == 1 &&
              ConstIntVal(&dat->offset.front(), dat_base) &&
              abs_base >= dat_base &&
              abs_base < dat_base + dat->data.size()) {
            // We are inside the range of this data segment!
            // Turn expression into data_name[index]
            val = Value { { dat->name }, Precedence::Atomic };
            // The new offset is from the start of the data segment, instead of
            // whatever it was.. this may be a different value from both the
            // original const and offset!
            offset = abs_base - dat_base;
          }
        }
      }
      // Do the load/store as a generalized indexing operation.
      // The offset is divisible by the alignment in 99.99% of
      // cases, but the spec doesn't guarantee it, so we must
      // have a backup syntax.
      auto index = offset % align == 0
                  ? std::to_string(offset / align)
                  : cat(std::to_string(offset), "@", std::to_string(align));
      // Detect the very common case of (base + (index << 2))[0]:int etc.
      // so we can instead do base[index]:int
      // TODO: (index << 2) on the left of + occurs also.
      // TODO: sadly this does not address cases where the shift amount > align.
      // (which happens for arrays of structs or arrays of long (with align=4)).
      // TODO: also very common is (v = base + (index << 2))[0]:int
      if (addr_exp.etype == ExprType::Binary) {
        auto& pe = *cast<BinaryExpr>(addr_exp.e);
        auto& shift_exp = addr_exp.children[1];
        if (pe.opcode == Opcode::I32Add &&
            shift_exp.etype == ExprType::Binary) {
          auto& se = *cast<BinaryExpr>(shift_exp.e);
          auto& const_exp = shift_exp.children[1];
          if (se.opcode == Opcode::I32Shl &&
              const_exp.etype == ExprType::Const) {
            auto& ce = *cast<ConstExpr>(const_exp.e);
            if (ce.const_.type() == Type::I32 &&
                (1U << ce.const_.u32()) == align) {
              // Pfew, case detected :( Lets re-write this in Haskell.
              // TODO: we're decompiling these twice.
              // The thing to the left of << is going to be part of the index.
              auto ival = DecompileExpr(shift_exp.children[0], &shift_exp);
              if (ival.v.size() == 1) {  // Don't bother if huge.
                if (offset == 0) {
                  index = ival.v[0];
                } else {
                  BracketIfNeeded(ival, Precedence::Add);
                  index = cat(ival.v[0], " + ", index);
                }
                // We're going to use the thing to the left of + as the new
                // base address:
                val = DecompileExpr(addr_exp.children[0], &addr_exp);
              }
            }
          }
        }
      }
      BracketIfNeeded(val, Precedence::Indexing);
      val.v.back() += cat("[", index, "]");
      if (append_type) {
        val.v.back() +=
          cat(":", GetDecompTypeName(GetMemoryType(op_type, opc)),
              lst.GenAlign(align, opc));
      }
      val.precedence = Precedence::Indexing;
    }
  
    Value DecompileExpr(const Node& n, const Node* parent) {
      std::vector<Value> args;
      for (auto& c : n.children) {
        args.push_back(DecompileExpr(c, &n));
      }
      // First deal with the specialized node types.
      switch (n.ntype) {
        case NodeType::FlushToVars: {
          std::string decls = "let ";
          for (Index i = 0; i < n.u.var_count; i++) {
            if (i) decls += ", ";
            decls += TempVarName(n.u.var_start + i);
          }
          decls += " = ";
          return WrapNAry(args, decls, "", Precedence::Assign);
        }
        case NodeType::FlushedVar: {
          return Value { { TempVarName(n.u.var_start) }, Precedence::Atomic };
        }
        case NodeType::Statements: {
          Value stats { {}, Precedence::None };
          for (size_t i = 0; i < n.children.size(); i++) {
            auto& s = args[i].v.back();
            if (s.back() != '}' && s.back() != ':') s += ';';
            std::move(args[i].v.begin(), args[i].v.end(),
                      std::back_inserter(stats.v));
          }
          return stats;
        }
        case NodeType::EndReturn: {
          return WrapNAry(args, "return ", "", Precedence::None);
        }
        case NodeType::Decl: {
          cur_ast->vars_defined[n.u.var->name()].defined = true;
          return Value{
              {"var " + LocalDecl(std::string(n.u.var->name()),
                                  cur_func->GetLocalType(*n.u.var))},
              Precedence::None};
        }
        case NodeType::DeclInit: {
          if (cur_ast->vars_defined[n.u.var->name()].defined) {
            // This has already been pre-declared, output as assign.
            return WrapChild(args[0], cat(VarName(n.u.var->name()), " = "), "",
                             Precedence::None);
          } else {
            return WrapChild(
                args[0],
                cat("var ",
                    LocalDecl(std::string(n.u.var->name()),
                              cur_func->GetLocalType(*n.u.var)),
                    " = "),
                "", Precedence::None);
          }
        }
        case NodeType::Expr:
          // We're going to fall thru to the second switch to deal with ExprType.
          break;
        case NodeType::Uninitialized:
          assert(false);
          break;
      }
      // Existing ExprTypes.
      switch (n.etype) {
        case ExprType::Const: {
          auto& c = cast<ConstExpr>(n.e)->const_;
          switch (c.type()) {
            case Type::I32:
              return Value{{std::to_string(static_cast<int32_t>(c.u32()))},
                           Precedence::Atomic};
            case Type::I64:
              return Value{{std::to_string(static_cast<int64_t>(c.u64())) + "L"},
                           Precedence::Atomic};
            case Type::F32: {
              float f = Bitcast<float>(c.f32_bits());
              return Value{{to_string(f) + "f"}, Precedence::Atomic};
            }
            case Type::F64: {
              double d = Bitcast<double>(c.f64_bits());
              return Value{{to_string(d)}, Precedence::Atomic};
            }
            case Type::V128:
              return Value{{"V128"}, Precedence::Atomic};  // FIXME
            default:
              WABT_UNREACHABLE;
          }
        }
        case ExprType::LocalGet: {
          return Get(*cast<LocalGetExpr>(n.e));
        }
        case ExprType::GlobalGet: {
          return Get(*cast<GlobalGetExpr>(n.e));
        }
        case ExprType::LocalSet: {
          return Set(args[0], *cast<LocalSetExpr>(n.e));
        }
        case ExprType::GlobalSet: {
          return Set(args[0], *cast<GlobalSetExpr>(n.e));
        }
        case ExprType::LocalTee: {
          auto& te = *cast<LocalTeeExpr>(n.e);
          return args.empty() ? Get(te) : Set(args[0], te);
        }
        case ExprType::Binary: {
          auto& be = *cast<BinaryExpr>(n.e);
          auto opcs = OpcodeToToken(be.opcode);
          // TODO: Is this selection better done on Opcode values directly?
          // What if new values get added and OtherBin doesn't make sense?
          auto prec = Precedence::OtherBin;
          if (opcs == "*" || opcs == "/" || opcs == "%") {
            prec = Precedence::Multiply;
          } else if (opcs == "+" || opcs == "-") {
            prec = Precedence::Add;
          } else if (opcs == "&" || opcs == "|" || opcs == "^") {
            prec = Precedence::Bit;
          } else if (opcs == "<<" || opcs == ">>") {
            prec = Precedence::Shift;
          }
          return WrapBinary(args, cat(" ", opcs, " "), false, prec);
        }
        case ExprType::Compare: {
          auto& ce = *cast<CompareExpr>(n.e);
           return WrapBinary(args, cat(" ", OpcodeToToken(ce.opcode), " "), false,
                             Precedence::Equal);
        }
        case ExprType::Unary: {
          auto& ue = *cast<UnaryExpr>(n.e);
          //BracketIfNeeded(stack.back());
          // TODO: also version without () depending on precedence.
          return WrapChild(args[0], OpcodeToToken(ue.opcode) + "(", ")",
                           Precedence::Atomic);
        }
        case ExprType::Load: {
          auto& le = *cast<LoadExpr>(n.e);
          LoadStore(args[0], n.children[0], le.offset, le.opcode, le.align,
                    le.opcode.GetResultType());
          return std::move(args[0]);
        }
        case ExprType::Store: {
          auto& se = *cast<StoreExpr>(n.e);
          LoadStore(args[0], n.children[0], se.offset, se.opcode, se.align,
                    se.opcode.GetParamType2());
          return WrapBinary(args, " = ", true, Precedence::Assign);
        }
        case ExprType::If: {
          auto ife = cast<IfExpr>(n.e);
          Value *elsep = nullptr;
          if (!ife->false_.empty()) {
            elsep = &args[2];
          }
          auto& thenp = args[1];
          auto& ifs = args[0];
          bool multiline = ifs.v.size() > 1 || thenp.v.size() > 1;
          size_t width = ifs.width() + thenp.width();
          if (elsep) {
            width += elsep->width();
            multiline = multiline || elsep->v.size() > 1;
          }
          multiline = multiline || width > target_exp_width;
          if (multiline) {
            auto if_start = string_view("if (");
            IndentValue(ifs, if_start.size(), if_start);
            ifs.v.back() += ") {";
            IndentValue(thenp, indent_amount, {});
            std::move(thenp.v.begin(), thenp.v.end(), std::back_inserter(ifs.v));
            if (elsep) {
              ifs.v.push_back("} else {");
              IndentValue(*elsep, indent_amount, {});
              std::move(elsep->v.begin(), elsep->v.end(), std::back_inserter(ifs.v));
            }
            ifs.v.push_back("}");
            ifs.precedence = Precedence::If;
            return std::move(ifs);
          } else {
            auto s = cat("if (", ifs.v[0], ") { ", thenp.v[0], " }");
            if (elsep)
              s += cat(" else { ", elsep->v[0], " }");
            return Value{{std::move(s)}, Precedence::If};
          }
        }
        case ExprType::Block: {
          auto& val = args[0];
          val.v.push_back(
                cat("label ", VarName(cast<BlockExpr>(n.e)->block.label), ":"));
          // If this block is part of a larger statement scope, it doesn't
          // need its own indenting, but if its part of an exp we wrap it in {}.
          if (parent && parent->ntype != NodeType::Statements
                     && parent->etype != ExprType::Block
                     && parent->etype != ExprType::Loop
                     && (parent->etype != ExprType::If ||
                         &parent->children[0] == &n)) {
            IndentValue(val, indent_amount, {});
            val.v.insert(val.v.begin(), "{");
            val.v.push_back("}");
          }
          val.precedence = Precedence::Atomic;
          return std::move(val);
        }
        case ExprType::Loop: {
          auto& val = args[0];
          auto& block = cast<LoopExpr>(n.e)->block;
          IndentValue(val, indent_amount, {});
          val.v.insert(val.v.begin(), cat("loop ", VarName(block.label), " {"));
          val.v.push_back("}");
          val.precedence = Precedence::Atomic;
          return std::move(val);
        }
        case ExprType::Br: {
          auto be = cast<BrExpr>(n.e);
          return Value{{(n.u.lt == LabelType::Loop ? "continue " : "goto ") +
                        VarName(be->var.name())},
                       Precedence::None};
        }
        case ExprType::BrIf: {
          auto bie = cast<BrIfExpr>(n.e);
          auto jmp = n.u.lt == LabelType::Loop ? "continue" : "goto";
          return WrapChild(args[0], "if (", cat(") ", jmp, " ",
                                                VarName(bie->var.name())),
                           Precedence::None);
        }
        case ExprType::Return: {
          return WrapNAry(args, "return ", "", Precedence::None);
        }
        case ExprType::Rethrow: {
          return WrapNAry(args, "rethrow ", "", Precedence::None);
        }
        case ExprType::Drop: {
          // Silent dropping of return values is very common, so currently
          // don't output this.
          return std::move(args[0]);
        }
        case ExprType::Nop: {
          return Value{{"nop"}, Precedence::None};
        }
        case ExprType::Unreachable: {
          return Value{{"unreachable"}, Precedence::None};
        }
        case ExprType::RefNull: {
          return Value{{"null"}, Precedence::Atomic};
        }
        case ExprType::BrTable: {
          auto bte = cast<BrTableExpr>(n.e);
          std::string ts = "br_table[";
          for (auto &v : bte->targets) {
            ts += VarName(v.name());
            ts += ", ";
          }
          ts += "..";
          ts += VarName(bte->default_target.name());
          ts += "](";
          return WrapChild(args[0], ts, ")", Precedence::Atomic);
        }
        default: {
          // Everything that looks like a function call.
          std::string name;
          auto precedence = Precedence::Atomic;
          switch (n.etype) {
            case ExprType::Call:
              name = cast<CallExpr>(n.e)->var.name();
              break;
            case ExprType::ReturnCall:
              name = "return_call " + cast<ReturnCallExpr>(n.e)->var.name();
              precedence = Precedence::None;
              break;
            case ExprType::Convert:
              name = std::string(OpcodeToToken(cast<ConvertExpr>(n.e)->opcode));
              break;
            case ExprType::Ternary:
              name = std::string(OpcodeToToken(cast<TernaryExpr>(n.e)->opcode));
              break;
            case ExprType::Select:
              // This one looks like it could be translated to "?:" style ternary,
              // but the arguments are NOT lazy, and side effects definitely do
              // occur in the branches. So it has no clear equivalent in C-syntax.
              // To emphasize that all args are being evaluated in order, we
              // leave it as a function call.
              name = "select_if";
              break;
            case ExprType::MemoryGrow:
              name = "memory_grow";
              break;
            case ExprType::MemorySize:
              name = "memory_size";
              break;
            case ExprType::MemoryCopy:
              name = "memory_copy";
              break;
            case ExprType::MemoryFill:
              name = "memory_fill";
              break;
            case ExprType::RefIsNull:
              name = "is_null";
              break;
            case ExprType::CallIndirect:
              name = "call_indirect";
              break;
            case ExprType::ReturnCallIndirect:
              name = "return_call call_indirect";
              break;
            default:
              name = GetExprTypeName(n.etype);
              break;
          }
          return WrapNAry(args, name + "(", ")", precedence);
        }
      }
    }
  
    bool CheckImportExport(std::string& s,
                           ExternalKind kind,
                           Index index,
                           string_view name) {
      // Figure out if this thing is imported, exported, or neither.
      auto is_import = mc.module.IsImport(kind, Var(index));
      // TODO: is this the best way to check for export?
      // FIXME: this doesn't work for functions that get renamed in some way,
      // as the export has the original name..
      auto xport = mc.module.GetExport(name);
      auto is_export = xport && xport->kind == kind;
      if (is_export)
        s += "export ";
      if (is_import)
        s += "import ";
      return is_import;
    }
  
    std::string InitExp(const ExprList &el) {
      assert(!el.empty());
      AST ast(mc, nullptr);
      ast.Construct(el, 1, 0, false);
      auto val = DecompileExpr(ast.exp_stack[0], nullptr);
      assert(ast.exp_stack.size() == 1 && val.v.size() == 1);
      return std::move(val.v[0]);
    }
  
    // FIXME: Merge with WatWriter::WriteQuotedData somehow.
    std::string BinaryToString(const std::vector<uint8_t> &in) {
      std::string s = "\"";
      size_t line_start = 0;
      static const char s_hexdigits[] = "0123456789abcdef";
      for (auto c : in) {
        if (c >= ' ' && c <= '~') {
          s += c;
        } else {
          s += '\\';
          s += s_hexdigits[c >> 4];
          s += s_hexdigits[c & 0xf];
        }
        if (s.size() - line_start > target_exp_width) {
          if (line_start == 0) {
            s = "  " + s;
          }
          s += "\"\n  ";
          line_start = s.size();
          s += "\"";
        }
      }
      s += '\"';
      return s;
    }
  
    std::string Decompile() {
      std::string s;
      // Memories.
      Index memory_index = 0;
      for (auto m : mc.module.memories) {
        auto is_import =
            CheckImportExport(s, ExternalKind::Memory, memory_index, m->name);
        s += cat("memory ", m->name);
        if (!is_import) {
          s += cat("(initial: ", std::to_string(m->page_limits.initial),
                   ", max: ", std::to_string(m->page_limits.max), ")");
        }
        s += ";\n";
        memory_index++;
      }
      if (!mc.module.memories.empty())
        s += "\n";
  
      // Globals.
      Index global_index = 0;
      for (auto g : mc.module.globals) {
        auto is_import =
            CheckImportExport(s, ExternalKind::Global, global_index, g->name);
        s += cat("global ", g->name, ":", GetDecompTypeName(g->type));
        if (!is_import) {
          s += cat(" = ", InitExp(g->init_expr));
        }
        s += ";\n";
        global_index++;
      }
      if (!mc.module.globals.empty())
        s += "\n";
  
      // Tables.
      Index table_index = 0;
      for (auto tab : mc.module.tables) {
        auto is_import =
            CheckImportExport(s, ExternalKind::Table, table_index, tab->name);
        s += cat("table ", tab->name, ":", GetDecompTypeName(tab->elem_type));
        if (!is_import) {
          s += cat("(min: ", std::to_string(tab->elem_limits.initial),
                   ", max: ", std::to_string(tab->elem_limits.max), ")");
        }
        s += ";\n";
        table_index++;
      }
      if (!mc.module.tables.empty())
        s += "\n";
  
      // Data.
      for (auto dat : mc.module.data_segments) {
        s += cat("data ", dat->name, "(offset: ", InitExp(dat->offset), ") = ");
        auto ds = BinaryToString(dat->data);
        if (ds.size() > target_exp_width / 2) {
          s += "\n";
        }
        s += ds;
        s += ";\n";
      }
      if (!mc.module.data_segments.empty())
        s += "\n";
  
      // Code.
      Index func_index = 0;
      for (auto f : mc.module.funcs) {
        cur_func = f;
        auto is_import =
            CheckImportExport(s, ExternalKind::Func, func_index, f->name);
        AST ast(mc, f);
        cur_ast = &ast;
        if (!is_import) {
          ast.Construct(f->exprs, f->GetNumResults(), 0, true);
          lst.Track(ast.exp_stack[0]);
          lst.CheckLayouts();
        }
        s += cat("function ", f->name, "(");
        for (Index i = 0; i < f->GetNumParams(); i++) {
          if (i)
            s += ", ";
          auto t = f->GetParamType(i);
          auto name = "$" + IndexToAlphaName(i);
          s += LocalDecl(name, t);
        }
        s += ")";
        if (f->GetNumResults()) {
          if (f->GetNumResults() == 1) {
            s += cat(":", GetDecompTypeName(f->GetResultType(0)));
          } else {
            s += ":(";
            for (Index i = 0; i < f->GetNumResults(); i++) {
              if (i)
                s += ", ";
              s += GetDecompTypeName(f->GetResultType(i));
            }
            s += ")";
          }
        }
        if (is_import) {
          s += ";";
        } else {
          s += " {\n";
          auto val = DecompileExpr(ast.exp_stack[0], nullptr);
          IndentValue(val, indent_amount, {});
          for (auto& stat : val.v) {
            s += stat;
            s += "\n";
          }
          s += "}";
        }
        s += "\n\n";
        mc.EndFunc();
        lst.Clear();
        func_index++;
        cur_ast = nullptr;
        cur_func = nullptr;
      }
      return s;
    }
  
    ModuleContext mc;
    const DecompileOptions& options;
    size_t indent_amount = 2;
    size_t target_exp_width = 70;
    const Func* cur_func = nullptr;
    AST* cur_ast = nullptr;
    LoadStoreTracking lst;
  };
  
  std::string Decompile(const Module& module, const DecompileOptions& options) {
    Decompiler decompiler(module, options);
    return decompiler.Decompile();
  }
  
  }  // namespace wabt
  