/* Copyright 2016 Mozilla Foundation
 *
 * 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.
 */
import {
  BinaryReader,
  BinaryReaderState,
  bytesToString,
  ElementMode,
  ExternalKind,
  IDataSegmentBody,
  IElementSegment,
  IElementSegmentBody,
  IEventNameEntry,
  IEventType,
  IExportEntry,
  IFieldNameEntry,
  IFunctionEntry,
  IFunctionInformation,
  IFunctionNameEntry,
  IGlobalNameEntry,
  IGlobalType,
  IGlobalVariable,
  IImportEntry,
  ILocalNameEntry,
  IMemoryAddress,
  IMemoryNameEntry,
  IMemoryType,
  INameEntry,
  Int64,
  IOperatorInformation,
  IResizableLimits,
  ISectionInformation,
  IStartEntry,
  ITableNameEntry,
  ITableType,
  ITypeEntry,
  ITypeNameEntry,
  NameType,
  OperatorCode,
  OperatorCodeNames,
  SectionCode,
  Type,
  TypeKind,
} from "./WasmParser.js";

const NAME_SECTION_NAME = "name";
const INVALID_NAME_SYMBOLS_REGEX = /[^0-9A-Za-z!#$%&'*+.:<=>?@^_`|~\/\-]/;
const INVALID_NAME_SYMBOLS_REGEX_GLOBAL = new RegExp(
  INVALID_NAME_SYMBOLS_REGEX.source,
  "g"
);

function formatFloat32(n: number): string {
  if (n === 0) return 1 / n < 0 ? "-0.0" : "0.0";
  if (isFinite(n)) return n.toString();
  if (!isNaN(n)) return n < 0 ? "-inf" : "inf";
  var view = new DataView(new ArrayBuffer(8));
  view.setFloat32(0, n, true);
  var data = view.getInt32(0, true);
  var payload = data & 0x7fffff;
  const canonicalBits = 4194304; // 0x800..0
  if (data > 0 && payload === canonicalBits) return "nan";
  // canonical NaN;
  else if (payload === canonicalBits) return "-nan";
  return (data < 0 ? "-" : "+") + "nan:0x" + payload.toString(16);
}

function formatFloat64(n: number): string {
  if (n === 0) return 1 / n < 0 ? "-0.0" : "0.0";
  if (isFinite(n)) return n.toString();
  if (!isNaN(n)) return n < 0 ? "-inf" : "inf";
  var view = new DataView(new ArrayBuffer(8));
  view.setFloat64(0, n, true);
  var data1 = view.getUint32(0, true);
  var data2 = view.getInt32(4, true);
  var payload = data1 + (data2 & 0xfffff) * 4294967296;
  const canonicalBits = 524288 * 4294967296; // 0x800..0
  if (data2 > 0 && payload === canonicalBits) return "nan";
  // canonical NaN;
  else if (payload === canonicalBits) return "-nan";
  return (data2 < 0 ? "-" : "+") + "nan:0x" + payload.toString(16);
}

function formatI32Array(bytes, count) {
  var dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
  var result = [];
  for (var i = 0; i < count; i++)
    result.push(`0x${formatHex(dv.getInt32(i << 2, true), 8)}`);
  return result.join(" ");
}

function formatI8Array(bytes, count) {
  var dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
  var result = [];
  for (var i = 0; i < count; i++) result.push(`${dv.getInt8(i)}`);
  return result.join(" ");
}

function memoryAddressToString(
  address: IMemoryAddress,
  code: OperatorCode
): string {
  var defaultAlignFlags;
  switch (code) {
    case OperatorCode.v128_load:
    case OperatorCode.i16x8_load8x8_s:
    case OperatorCode.i16x8_load8x8_u:
    case OperatorCode.i32x4_load16x4_s:
    case OperatorCode.i32x4_load16x4_u:
    case OperatorCode.i64x2_load32x2_s:
    case OperatorCode.i64x2_load32x2_u:
    case OperatorCode.v8x16_load_splat:
    case OperatorCode.v16x8_load_splat:
    case OperatorCode.v32x4_load_splat:
    case OperatorCode.v64x2_load_splat:
    case OperatorCode.v128_store:
      defaultAlignFlags = 4;
      break;
    case OperatorCode.i64_load:
    case OperatorCode.i64_store:
    case OperatorCode.f64_load:
    case OperatorCode.f64_store:
    case OperatorCode.i64_atomic_wait:
    case OperatorCode.i64_atomic_load:
    case OperatorCode.i64_atomic_store:
    case OperatorCode.i64_atomic_rmw_add:
    case OperatorCode.i64_atomic_rmw_sub:
    case OperatorCode.i64_atomic_rmw_and:
    case OperatorCode.i64_atomic_rmw_or:
    case OperatorCode.i64_atomic_rmw_xor:
    case OperatorCode.i64_atomic_rmw_xchg:
    case OperatorCode.i64_atomic_rmw_cmpxchg:
    case OperatorCode.v128_load64_zero:
      defaultAlignFlags = 3;
      break;
    case OperatorCode.i32_load:
    case OperatorCode.i64_load32_s:
    case OperatorCode.i64_load32_u:
    case OperatorCode.i32_store:
    case OperatorCode.i64_store32:
    case OperatorCode.f32_load:
    case OperatorCode.f32_store:
    case OperatorCode.atomic_notify:
    case OperatorCode.i32_atomic_wait:
    case OperatorCode.i32_atomic_load:
    case OperatorCode.i64_atomic_load32_u:
    case OperatorCode.i32_atomic_store:
    case OperatorCode.i64_atomic_store32:
    case OperatorCode.i32_atomic_rmw_add:
    case OperatorCode.i64_atomic_rmw32_add_u:
    case OperatorCode.i32_atomic_rmw_sub:
    case OperatorCode.i64_atomic_rmw32_sub_u:
    case OperatorCode.i32_atomic_rmw_and:
    case OperatorCode.i64_atomic_rmw32_and_u:
    case OperatorCode.i32_atomic_rmw_or:
    case OperatorCode.i64_atomic_rmw32_or_u:
    case OperatorCode.i32_atomic_rmw_xor:
    case OperatorCode.i64_atomic_rmw32_xor_u:
    case OperatorCode.i32_atomic_rmw_xchg:
    case OperatorCode.i64_atomic_rmw32_xchg_u:
    case OperatorCode.i32_atomic_rmw_cmpxchg:
    case OperatorCode.i64_atomic_rmw32_cmpxchg_u:
    case OperatorCode.v128_load32_zero:
      defaultAlignFlags = 2;
      break;
    case OperatorCode.i32_load16_s:
    case OperatorCode.i32_load16_u:
    case OperatorCode.i64_load16_s:
    case OperatorCode.i64_load16_u:
    case OperatorCode.i32_store16:
    case OperatorCode.i64_store16:
    case OperatorCode.i32_atomic_load16_u:
    case OperatorCode.i64_atomic_load16_u:
    case OperatorCode.i32_atomic_store16:
    case OperatorCode.i64_atomic_store16:
    case OperatorCode.i32_atomic_rmw16_add_u:
    case OperatorCode.i64_atomic_rmw16_add_u:
    case OperatorCode.i32_atomic_rmw16_sub_u:
    case OperatorCode.i64_atomic_rmw16_sub_u:
    case OperatorCode.i32_atomic_rmw16_and_u:
    case OperatorCode.i64_atomic_rmw16_and_u:
    case OperatorCode.i32_atomic_rmw16_or_u:
    case OperatorCode.i64_atomic_rmw16_or_u:
    case OperatorCode.i32_atomic_rmw16_xor_u:
    case OperatorCode.i64_atomic_rmw16_xor_u:
    case OperatorCode.i32_atomic_rmw16_xchg_u:
    case OperatorCode.i64_atomic_rmw16_xchg_u:
    case OperatorCode.i32_atomic_rmw16_cmpxchg_u:
    case OperatorCode.i64_atomic_rmw16_cmpxchg_u:
      defaultAlignFlags = 1;
      break;
    case OperatorCode.i32_load8_s:
    case OperatorCode.i32_load8_u:
    case OperatorCode.i64_load8_s:
    case OperatorCode.i64_load8_u:
    case OperatorCode.i32_store8:
    case OperatorCode.i64_store8:
    case OperatorCode.i32_atomic_load8_u:
    case OperatorCode.i64_atomic_load8_u:
    case OperatorCode.i32_atomic_store8:
    case OperatorCode.i64_atomic_store8:
    case OperatorCode.i32_atomic_rmw8_add_u:
    case OperatorCode.i64_atomic_rmw8_add_u:
    case OperatorCode.i32_atomic_rmw8_sub_u:
    case OperatorCode.i64_atomic_rmw8_sub_u:
    case OperatorCode.i32_atomic_rmw8_and_u:
    case OperatorCode.i64_atomic_rmw8_and_u:
    case OperatorCode.i32_atomic_rmw8_or_u:
    case OperatorCode.i64_atomic_rmw8_or_u:
    case OperatorCode.i32_atomic_rmw8_xor_u:
    case OperatorCode.i64_atomic_rmw8_xor_u:
    case OperatorCode.i32_atomic_rmw8_xchg_u:
    case OperatorCode.i64_atomic_rmw8_xchg_u:
    case OperatorCode.i32_atomic_rmw8_cmpxchg_u:
    case OperatorCode.i64_atomic_rmw8_cmpxchg_u:
      defaultAlignFlags = 0;
      break;
  }
  if (address.flags == defaultAlignFlags)
    // hide default flags
    return !address.offset ? null : `offset=${address.offset}`;
  if (!address.offset)
    // hide default offset
    return `align=${1 << address.flags}`;
  return `offset=${address.offset | 0} align=${1 << address.flags}`;
}
function limitsToString(limits: IResizableLimits): string {
  return (
    limits.initial + (limits.maximum !== undefined ? " " + limits.maximum : "")
  );
}
var paddingCache = ["0", "00", "000"];
function formatHex(n: number, width?: number): string {
  var s = (n >>> 0).toString(16).toUpperCase();
  if (width === undefined || s.length >= width) return s;
  var paddingIndex = width - s.length - 1;
  while (paddingIndex >= paddingCache.length)
    paddingCache.push(paddingCache[paddingCache.length - 1] + "0");
  return paddingCache[paddingIndex] + s;
}
const IndentIncrement = "  ";

function isValidName(name: string) {
  return !INVALID_NAME_SYMBOLS_REGEX.test(name);
}

export interface IExportMetadata {
  getFunctionExportNames(index: number): string[];
  getGlobalExportNames(index: number): string[];
  getMemoryExportNames(index: number): string[];
  getTableExportNames(index: number): string[];
  getEventExportNames(index: number): string[];
}

export interface INameResolver {
  getTypeName(index: number, isRef: boolean): string;
  getTableName(index: number, isRef: boolean): string;
  getMemoryName(index: number, isRef: boolean): string;
  getGlobalName(index: number, isRef: boolean): string;
  getElementName(index: number, isRef: boolean): string;
  getEventName(index: number, isRef: boolean): string;
  getFunctionName(index: number, isImport: boolean, isRef: boolean): string;
  getVariableName(funcIndex: number, index: number, isRef: boolean): string;
  getFieldName(typeIndex: number, index: number, isRef: boolean): string;
  getLabel(index: number): string;
}
export class DefaultNameResolver implements INameResolver {
  public getTypeName(index: number, isRef: boolean): string {
    return "$type" + index;
  }
  public getTableName(index: number, isRef: boolean): string {
    return "$table" + index;
  }
  public getMemoryName(index: number, isRef: boolean): string {
    return "$memory" + index;
  }
  public getGlobalName(index: number, isRef: boolean): string {
    return "$global" + index;
  }
  public getElementName(index: number, isRef: boolean): string {
    return `$elem${index}`;
  }
  public getEventName(index: number, isRef: boolean): string {
    return `$event${index}`;
  }
  public getFunctionName(
    index: number,
    isImport: boolean,
    isRef: boolean
  ): string {
    return (isImport ? "$import" : "$func") + index;
  }
  public getVariableName(
    funcIndex: number,
    index: number,
    isRef: boolean
  ): string {
    return "$var" + index;
  }
  public getFieldName(
    typeIndex: number,
    index: number,
    isRef: boolean
  ): string {
    return "$field" + index;
  }
  public getLabel(index: number): string {
    return "$label" + index;
  }
}

const EMPTY_STRING_ARRAY: string[] = [];

class DevToolsExportMetadata implements IExportMetadata {
  private readonly _functionExportNames: string[][];
  private readonly _globalExportNames: string[][];
  private readonly _memoryExportNames: string[][];
  private readonly _tableExportNames: string[][];
  private readonly _eventExportNames: string[][];

  constructor(
    functionExportNames: string[][],
    globalExportNames: string[][],
    memoryExportNames: string[][],
    tableExportNames: string[][],
    eventExportNames: string[][]
  ) {
    this._functionExportNames = functionExportNames;
    this._globalExportNames = globalExportNames;
    this._memoryExportNames = memoryExportNames;
    this._tableExportNames = tableExportNames;
    this._eventExportNames = eventExportNames;
  }

  public getFunctionExportNames(index: number) {
    return this._functionExportNames[index] ?? EMPTY_STRING_ARRAY;
  }

  public getGlobalExportNames(index: number) {
    return this._globalExportNames[index] ?? EMPTY_STRING_ARRAY;
  }

  public getMemoryExportNames(index: number) {
    return this._memoryExportNames[index] ?? EMPTY_STRING_ARRAY;
  }

  public getTableExportNames(index: number) {
    return this._tableExportNames[index] ?? EMPTY_STRING_ARRAY;
  }

  public getEventExportNames(index: number) {
    return this._eventExportNames[index] ?? EMPTY_STRING_ARRAY;
  }
}

export class NumericNameResolver implements INameResolver {
  public getTypeName(index: number, isRef: boolean): string {
    return isRef ? "" + index : `(;${index};)`;
  }
  public getTableName(index: number, isRef: boolean): string {
    return isRef ? "" + index : `(;${index};)`;
  }
  public getMemoryName(index: number, isRef: boolean): string {
    return isRef ? "" + index : `(;${index};)`;
  }
  public getGlobalName(index: number, isRef: boolean): string {
    return isRef ? "" + index : `(;${index};)`;
  }
  public getElementName(index: number, isRef: boolean): string {
    return isRef ? "" + index : `(;${index};)`;
  }
  public getEventName(index: number, isRef: boolean): string {
    return isRef ? "" + index : `(;${index};)`;
  }
  public getFunctionName(
    index: number,
    isImport: boolean,
    isRef: boolean
  ): string {
    return isRef ? "" + index : `(;${index};)`;
  }
  public getVariableName(
    funcIndex: number,
    index: number,
    isRef: boolean
  ): string {
    return isRef ? "" + index : `(;${index};)`;
  }
  public getFieldName(
    typeIndex: number,
    index: number,
    isRef: boolean
  ): string {
    return isRef ? "" : index + `(;${index};)`;
  }
  public getLabel(index: number): string {
    return null;
  }
}

export enum LabelMode {
  Depth,
  WhenUsed,
  Always,
}

// The breakable range is [start, end).
export interface IFunctionBodyOffset {
  start: number;
  end: number;
}

export interface IDisassemblerResult {
  lines: Array<string>;
  offsets?: Array<number>;
  done: boolean;
  functionBodyOffsets?: Array<IFunctionBodyOffset>;
}

export class WasmDisassembler {
  private _lines: Array<string>;
  private _offsets: Array<number>;
  private _buffer: string;
  private _types: Array<ITypeEntry>;
  private _funcIndex: number;
  private _funcTypes: Array<number>;
  private _importCount: number;
  private _globalCount: number;
  private _memoryCount: number;
  private _eventCount: number;
  private _tableCount: number;
  private _elementCount: number;
  private _expression: Array<IOperatorInformation>;
  private _backrefLabels: Array<{
    line: number;
    position: number;
    useLabel: boolean;
    label: string;
  }>;
  private _labelIndex: number;
  private _indent: string;
  private _indentLevel: number;
  private _addOffsets: boolean;
  private _skipTypes = true;
  private _done: boolean;
  private _currentPosition: number;
  private _nameResolver: INameResolver;
  private _exportMetadata: IExportMetadata = null;
  private _labelMode: LabelMode;
  private _functionBodyOffsets: Array<IFunctionBodyOffset>;
  private _currentFunctionBodyOffset: number;
  private _currentSectionId: SectionCode;
  private _logFirstInstruction: boolean;
  constructor() {
    this._lines = [];
    this._offsets = [];
    this._buffer = "";
    this._indent = null;
    this._indentLevel = 0;
    this._addOffsets = false;
    this._done = false;
    this._currentPosition = 0;
    this._nameResolver = new DefaultNameResolver();
    this._labelMode = LabelMode.WhenUsed;
    this._functionBodyOffsets = [];
    this._currentFunctionBodyOffset = 0;
    this._currentSectionId = SectionCode.Unknown;
    this._logFirstInstruction = false;

    this._reset();
  }
  private _reset(): void {
    this._types = [];
    this._funcIndex = 0;
    this._funcTypes = [];
    this._importCount = 0;
    this._globalCount = 0;
    this._memoryCount = 0;
    this._eventCount = 0;
    this._tableCount = 0;
    this._elementCount = 0;
    this._expression = [];
    this._backrefLabels = null;
    this._labelIndex = 0;
  }
  public get addOffsets(): boolean {
    return this._addOffsets;
  }
  public set addOffsets(value: boolean) {
    if (this._currentPosition)
      throw new Error("Cannot switch addOffsets during processing.");
    this._addOffsets = value;
  }
  public get skipTypes(): boolean {
    return this._skipTypes;
  }
  public set skipTypes(skipTypes: boolean) {
    if (this._currentPosition)
      throw new Error("Cannot switch skipTypes during processing.");
    this._skipTypes = skipTypes;
  }
  public get labelMode(): LabelMode {
    return this._labelMode;
  }
  public set labelMode(value: LabelMode) {
    if (this._currentPosition)
      throw new Error("Cannot switch labelMode during processing.");
    this._labelMode = value;
  }
  public get exportMetadata(): IExportMetadata {
    return this._exportMetadata;
  }
  public set exportMetadata(exportMetadata: IExportMetadata) {
    if (this._currentPosition)
      throw new Error("Cannot switch exportMetadata during processing.");
    this._exportMetadata = exportMetadata;
  }
  public get nameResolver(): INameResolver {
    return this._nameResolver;
  }
  public set nameResolver(resolver: INameResolver) {
    if (this._currentPosition)
      throw new Error("Cannot switch nameResolver during processing.");
    this._nameResolver = resolver;
  }
  private appendBuffer(s: string) {
    this._buffer += s;
  }
  private newLine() {
    if (this.addOffsets) this._offsets.push(this._currentPosition);
    this._lines.push(this._buffer);
    this._buffer = "";
  }
  private logStartOfFunctionBodyOffset() {
    if (this.addOffsets) {
      this._currentFunctionBodyOffset = this._currentPosition;
    }
  }
  private logEndOfFunctionBodyOffset() {
    if (this.addOffsets) {
      this._functionBodyOffsets.push({
        start: this._currentFunctionBodyOffset,
        end: this._currentPosition,
      });
    }
  }
  private typeIndexToString(typeIndex: number): string {
    if (typeIndex >= 0) return this._nameResolver.getTypeName(typeIndex, true);
    switch (typeIndex) {
      case TypeKind.funcref:
        return "func";
      case TypeKind.externref:
        return "extern";
      case TypeKind.anyref:
        return "any";
      case TypeKind.eqref:
        return "eq";
      case TypeKind.i31ref:
        return "i31";
      case TypeKind.dataref:
        return "data";
    }
  }
  private typeToString(type: Type): string {
    switch (type.kind) {
      case TypeKind.i32:
        return "i32";
      case TypeKind.i64:
        return "i64";
      case TypeKind.f32:
        return "f32";
      case TypeKind.f64:
        return "f64";
      case TypeKind.v128:
        return "v128";
      case TypeKind.i8:
        return "i8";
      case TypeKind.i16:
        return "i16";
      case TypeKind.funcref:
        return "funcref";
      case TypeKind.externref:
        return "externref";
      case TypeKind.anyref:
        return "anyref";
      case TypeKind.eqref:
        return "eqref";
      case TypeKind.i31ref:
        return "i31ref";
      case TypeKind.dataref:
        return "dataref";
      case TypeKind.ref:
        return `(ref ${this.typeIndexToString(type.index)})`;
      case TypeKind.optref:
        return `(ref null ${this.typeIndexToString(type.index)})`;
      case TypeKind.rtt:
        return `(rtt ${this.typeIndexToString(type.index)})`;
      case TypeKind.rtt_d:
        return `(rtt ${type.depth} ${this.typeIndexToString(type.index)})`;
      default:
        throw new Error(`Unexpected type ${JSON.stringify(type)}`);
    }
  }
  private maybeMut(type: string, mutability: boolean): string {
    return mutability ? `(mut ${type})` : type;
  }
  private globalTypeToString(type: IGlobalType): string {
    const typeStr = this.typeToString(type.contentType);
    return this.maybeMut(typeStr, !!type.mutability);
  }
  private printFuncType(typeIndex: number): void {
    var type = this._types[typeIndex];
    if (type.params.length > 0) {
      this.appendBuffer(" (param");
      for (var i = 0; i < type.params.length; i++) {
        this.appendBuffer(" ");
        this.appendBuffer(this.typeToString(type.params[i]));
      }
      this.appendBuffer(")");
    }
    if (type.returns.length > 0) {
      this.appendBuffer(" (result");
      for (var i = 0; i < type.returns.length; i++) {
        this.appendBuffer(" ");
        this.appendBuffer(this.typeToString(type.returns[i]));
      }
      this.appendBuffer(")");
    }
  }
  private printStructType(typeIndex: number): void {
    var type = this._types[typeIndex];
    if (type.fields.length === 0) return;
    for (var i = 0; i < type.fields.length; i++) {
      const fieldType = this.maybeMut(
        this.typeToString(type.fields[i]),
        type.mutabilities[i]
      );
      const fieldName = this._nameResolver.getFieldName(typeIndex, i, false);
      this.appendBuffer(` (field ${fieldName} ${fieldType})`);
    }
  }
  private printArrayType(typeIndex: number): void {
    var type = this._types[typeIndex];
    this.appendBuffer(" (field ");
    this.appendBuffer(
      this.maybeMut(this.typeToString(type.elementType), type.mutability)
    );
  }
  private printBlockType(type: Type): void {
    if (type.kind === TypeKind.empty_block_type) {
      return;
    }
    if (type.kind === TypeKind.unspecified) {
      return this.printFuncType(type.index);
    }
    this.appendBuffer(" (result ");
    this.appendBuffer(this.typeToString(type));
    this.appendBuffer(")");
  }
  private printString(b: Uint8Array): void {
    this.appendBuffer('"');
    for (var i = 0; i < b.length; i++) {
      var byte = b[i];
      if (
        byte < 0x20 ||
        byte >= 0x7f ||
        byte == /* " */ 0x22 ||
        byte == /* \ */ 0x5c
      ) {
        this.appendBuffer(
          "\\" + (byte >> 4).toString(16) + (byte & 15).toString(16)
        );
      } else {
        this.appendBuffer(String.fromCharCode(byte));
      }
    }
    this.appendBuffer('"');
  }
  private printExpression(expression: IOperatorInformation[]): void {
    for (const operator of expression) {
      this.appendBuffer("(");
      this.printOperator(operator);
      this.appendBuffer(")");
    }
  }
  // extraDepthOffset is used by "delegate" instructions.
  private useLabel(depth: number, extraDepthOffset = 0): string {
    if (!this._backrefLabels) {
      return "" + depth;
    }
    var i = this._backrefLabels.length - depth - 1 - extraDepthOffset;
    if (i < 0) {
      return "" + depth;
    }
    var backrefLabel = this._backrefLabels[i];
    if (!backrefLabel.useLabel) {
      backrefLabel.useLabel = true;
      backrefLabel.label = this._nameResolver.getLabel(this._labelIndex);
      var line = this._lines[backrefLabel.line];
      this._lines[backrefLabel.line] =
        line.substring(0, backrefLabel.position) +
        " " +
        backrefLabel.label +
        line.substring(backrefLabel.position);
      this._labelIndex++;
    }
    return backrefLabel.label || "" + depth;
  }
  private printOperator(operator: IOperatorInformation): void {
    var code = operator.code;
    this.appendBuffer(OperatorCodeNames[code]);
    switch (code) {
      case OperatorCode.block:
      case OperatorCode.loop:
      case OperatorCode.if:
      case OperatorCode.try:
        if (this._labelMode !== LabelMode.Depth) {
          const backrefLabel = {
            line: this._lines.length,
            position: this._buffer.length,
            useLabel: false,
            label: null,
          };
          if (this._labelMode === LabelMode.Always) {
            backrefLabel.useLabel = true;
            backrefLabel.label = this._nameResolver.getLabel(
              this._labelIndex++
            );
            if (backrefLabel.label) {
              this.appendBuffer(" ");
              this.appendBuffer(backrefLabel.label);
            }
          }
          this._backrefLabels.push(backrefLabel);
        }
        this.printBlockType(operator.blockType);
        break;
      case OperatorCode.end:
        if (this._labelMode === LabelMode.Depth) {
          break;
        }
        const backrefLabel = this._backrefLabels.pop();
        if (backrefLabel.label) {
          this.appendBuffer(" ");
          this.appendBuffer(backrefLabel.label);
        }
        break;
      case OperatorCode.br:
      case OperatorCode.br_if:
      case OperatorCode.br_on_null:
      case OperatorCode.br_on_non_null:
      case OperatorCode.br_on_cast:
      case OperatorCode.br_on_cast_fail:
      case OperatorCode.br_on_func:
      case OperatorCode.br_on_non_func:
      case OperatorCode.br_on_data:
      case OperatorCode.br_on_non_data:
      case OperatorCode.br_on_i31:
      case OperatorCode.br_on_non_i31:
        this.appendBuffer(" ");
        this.appendBuffer(this.useLabel(operator.brDepth));
        break;
      case OperatorCode.br_on_cast_static:
      case OperatorCode.br_on_cast_static_fail: {
        const label = this.useLabel(operator.brDepth);
        const refType = this._nameResolver.getTypeName(operator.refType, true);
        this.appendBuffer(` ${label} ${refType}`);
        break;
      }
      case OperatorCode.br_table:
        for (var i = 0; i < operator.brTable.length; i++) {
          this.appendBuffer(" ");
          this.appendBuffer(this.useLabel(operator.brTable[i]));
        }
        break;
      case OperatorCode.rethrow:
        this.appendBuffer(" ");
        this.appendBuffer(this.useLabel(operator.relativeDepth));
        break;
      case OperatorCode.delegate:
        this.appendBuffer(" ");
        this.appendBuffer(this.useLabel(operator.relativeDepth, 1));
        break;
      case OperatorCode.catch:
      case OperatorCode.throw:
        var eventName = this._nameResolver.getEventName(
          operator.eventIndex,
          true
        );
        this.appendBuffer(` ${eventName}`);
        break;
      case OperatorCode.ref_null:
        this.appendBuffer(" ");
        this.appendBuffer(this.typeIndexToString(operator.refType));
        break;
      case OperatorCode.call:
      case OperatorCode.return_call:
      case OperatorCode.ref_func:
        var funcName = this._nameResolver.getFunctionName(
          operator.funcIndex,
          operator.funcIndex < this._importCount,
          true
        );
        this.appendBuffer(` ${funcName}`);
        break;
      case OperatorCode.call_indirect:
      case OperatorCode.return_call_indirect:
        this.printFuncType(operator.typeIndex);
        break;
      case OperatorCode.select_with_type: {
        const selectType = this.typeToString(operator.selectType);
        this.appendBuffer(` ${selectType}`);
        break;
      }
      case OperatorCode.local_get:
      case OperatorCode.local_set:
      case OperatorCode.local_tee:
        var paramName = this._nameResolver.getVariableName(
          this._funcIndex,
          operator.localIndex,
          true
        );
        this.appendBuffer(` ${paramName}`);
        break;
      case OperatorCode.global_get:
      case OperatorCode.global_set:
        var globalName = this._nameResolver.getGlobalName(
          operator.globalIndex,
          true
        );
        this.appendBuffer(` ${globalName}`);
        break;
      case OperatorCode.i32_load:
      case OperatorCode.i64_load:
      case OperatorCode.f32_load:
      case OperatorCode.f64_load:
      case OperatorCode.i32_load8_s:
      case OperatorCode.i32_load8_u:
      case OperatorCode.i32_load16_s:
      case OperatorCode.i32_load16_u:
      case OperatorCode.i64_load8_s:
      case OperatorCode.i64_load8_u:
      case OperatorCode.i64_load16_s:
      case OperatorCode.i64_load16_u:
      case OperatorCode.i64_load32_s:
      case OperatorCode.i64_load32_u:
      case OperatorCode.i32_store:
      case OperatorCode.i64_store:
      case OperatorCode.f32_store:
      case OperatorCode.f64_store:
      case OperatorCode.i32_store8:
      case OperatorCode.i32_store16:
      case OperatorCode.i64_store8:
      case OperatorCode.i64_store16:
      case OperatorCode.i64_store32:
      case OperatorCode.atomic_notify:
      case OperatorCode.i32_atomic_wait:
      case OperatorCode.i64_atomic_wait:
      case OperatorCode.i32_atomic_load:
      case OperatorCode.i64_atomic_load:
      case OperatorCode.i32_atomic_load8_u:
      case OperatorCode.i32_atomic_load16_u:
      case OperatorCode.i64_atomic_load8_u:
      case OperatorCode.i64_atomic_load16_u:
      case OperatorCode.i64_atomic_load32_u:
      case OperatorCode.i32_atomic_store:
      case OperatorCode.i64_atomic_store:
      case OperatorCode.i32_atomic_store8:
      case OperatorCode.i32_atomic_store16:
      case OperatorCode.i64_atomic_store8:
      case OperatorCode.i64_atomic_store16:
      case OperatorCode.i64_atomic_store32:
      case OperatorCode.i32_atomic_rmw_add:
      case OperatorCode.i64_atomic_rmw_add:
      case OperatorCode.i32_atomic_rmw8_add_u:
      case OperatorCode.i32_atomic_rmw16_add_u:
      case OperatorCode.i64_atomic_rmw8_add_u:
      case OperatorCode.i64_atomic_rmw16_add_u:
      case OperatorCode.i64_atomic_rmw32_add_u:
      case OperatorCode.i32_atomic_rmw_sub:
      case OperatorCode.i64_atomic_rmw_sub:
      case OperatorCode.i32_atomic_rmw8_sub_u:
      case OperatorCode.i32_atomic_rmw16_sub_u:
      case OperatorCode.i64_atomic_rmw8_sub_u:
      case OperatorCode.i64_atomic_rmw16_sub_u:
      case OperatorCode.i64_atomic_rmw32_sub_u:
      case OperatorCode.i32_atomic_rmw_and:
      case OperatorCode.i64_atomic_rmw_and:
      case OperatorCode.i32_atomic_rmw8_and_u:
      case OperatorCode.i32_atomic_rmw16_and_u:
      case OperatorCode.i64_atomic_rmw8_and_u:
      case OperatorCode.i64_atomic_rmw16_and_u:
      case OperatorCode.i64_atomic_rmw32_and_u:
      case OperatorCode.i32_atomic_rmw_or:
      case OperatorCode.i64_atomic_rmw_or:
      case OperatorCode.i32_atomic_rmw8_or_u:
      case OperatorCode.i32_atomic_rmw16_or_u:
      case OperatorCode.i64_atomic_rmw8_or_u:
      case OperatorCode.i64_atomic_rmw16_or_u:
      case OperatorCode.i64_atomic_rmw32_or_u:
      case OperatorCode.i32_atomic_rmw_xor:
      case OperatorCode.i64_atomic_rmw_xor:
      case OperatorCode.i32_atomic_rmw8_xor_u:
      case OperatorCode.i32_atomic_rmw16_xor_u:
      case OperatorCode.i64_atomic_rmw8_xor_u:
      case OperatorCode.i64_atomic_rmw16_xor_u:
      case OperatorCode.i64_atomic_rmw32_xor_u:
      case OperatorCode.i32_atomic_rmw_xchg:
      case OperatorCode.i64_atomic_rmw_xchg:
      case OperatorCode.i32_atomic_rmw8_xchg_u:
      case OperatorCode.i32_atomic_rmw16_xchg_u:
      case OperatorCode.i64_atomic_rmw8_xchg_u:
      case OperatorCode.i64_atomic_rmw16_xchg_u:
      case OperatorCode.i64_atomic_rmw32_xchg_u:
      case OperatorCode.i32_atomic_rmw_cmpxchg:
      case OperatorCode.i64_atomic_rmw_cmpxchg:
      case OperatorCode.i32_atomic_rmw8_cmpxchg_u:
      case OperatorCode.i32_atomic_rmw16_cmpxchg_u:
      case OperatorCode.i64_atomic_rmw8_cmpxchg_u:
      case OperatorCode.i64_atomic_rmw16_cmpxchg_u:
      case OperatorCode.i64_atomic_rmw32_cmpxchg_u:
      case OperatorCode.v128_load:
      case OperatorCode.i16x8_load8x8_s:
      case OperatorCode.i16x8_load8x8_u:
      case OperatorCode.i32x4_load16x4_s:
      case OperatorCode.i32x4_load16x4_u:
      case OperatorCode.i64x2_load32x2_s:
      case OperatorCode.i64x2_load32x2_u:
      case OperatorCode.v8x16_load_splat:
      case OperatorCode.v16x8_load_splat:
      case OperatorCode.v32x4_load_splat:
      case OperatorCode.v64x2_load_splat:
      case OperatorCode.v128_store:
      case OperatorCode.v128_load32_zero:
      case OperatorCode.v128_load64_zero:
        var memoryAddress = memoryAddressToString(
          operator.memoryAddress,
          operator.code
        );
        if (memoryAddress !== null) {
          this.appendBuffer(" ");
          this.appendBuffer(memoryAddress);
        }
        break;
      case OperatorCode.current_memory:
      case OperatorCode.grow_memory:
        break;
      case OperatorCode.i32_const:
        this.appendBuffer(` ${(<number>operator.literal).toString()}`);
        break;
      case OperatorCode.i64_const:
        this.appendBuffer(` ${(<Int64>operator.literal).toString()}`);
        break;
      case OperatorCode.f32_const:
        this.appendBuffer(` ${formatFloat32(<number>operator.literal)}`);
        break;
      case OperatorCode.f64_const:
        this.appendBuffer(` ${formatFloat64(<number>operator.literal)}`);
        break;
      case OperatorCode.v128_const:
        this.appendBuffer(` i32x4 ${formatI32Array(operator.literal, 4)}`);
        break;
      case OperatorCode.i8x16_shuffle:
        this.appendBuffer(` ${formatI8Array(operator.lines, 16)}`);
        break;
      case OperatorCode.i8x16_extract_lane_s:
      case OperatorCode.i8x16_extract_lane_u:
      case OperatorCode.i8x16_replace_lane:
      case OperatorCode.i16x8_extract_lane_s:
      case OperatorCode.i16x8_extract_lane_u:
      case OperatorCode.i16x8_replace_lane:
      case OperatorCode.i32x4_extract_lane:
      case OperatorCode.i32x4_replace_lane:
      case OperatorCode.f32x4_extract_lane:
      case OperatorCode.f32x4_replace_lane:
      case OperatorCode.i64x2_extract_lane:
      case OperatorCode.i64x2_replace_lane:
      case OperatorCode.f64x2_extract_lane:
      case OperatorCode.f64x2_replace_lane:
        this.appendBuffer(` ${operator.lineIndex}`);
        break;
      case OperatorCode.memory_init:
      case OperatorCode.data_drop:
        this.appendBuffer(` ${operator.segmentIndex}`);
        break;
      case OperatorCode.elem_drop:
        const elementName = this._nameResolver.getElementName(
          operator.segmentIndex,
          true
        );
        this.appendBuffer(` ${elementName}`);
        break;
      case OperatorCode.table_set:
      case OperatorCode.table_get:
      case OperatorCode.table_fill: {
        const tableName = this._nameResolver.getTableName(
          operator.tableIndex,
          true
        );
        this.appendBuffer(` ${tableName}`);
        break;
      }
      case OperatorCode.table_copy: {
        // Table index might be omitted and defaults to 0.
        if (operator.tableIndex !== 0 || operator.destinationIndex !== 0) {
          const tableName = this._nameResolver.getTableName(
            operator.tableIndex,
            true
          );
          const destinationName = this._nameResolver.getTableName(
            operator.destinationIndex,
            true
          );
          this.appendBuffer(` ${destinationName} ${tableName}`);
        }
        break;
      }
      case OperatorCode.table_init: {
        // Table index might be omitted and defaults to 0.
        if (operator.tableIndex !== 0) {
          const tableName = this._nameResolver.getTableName(
            operator.tableIndex,
            true
          );
          this.appendBuffer(` ${tableName}`);
        }
        const elementName = this._nameResolver.getElementName(
          operator.segmentIndex,
          true
        );
        this.appendBuffer(` ${elementName}`);
        break;
      }
      case OperatorCode.struct_get:
      case OperatorCode.struct_get_s:
      case OperatorCode.struct_get_u:
      case OperatorCode.struct_set: {
        const refType = this._nameResolver.getTypeName(operator.refType, true);
        const fieldName = this._nameResolver.getFieldName(
          operator.refType,
          operator.fieldIndex,
          true
        );
        this.appendBuffer(` ${refType} ${fieldName}`);
        break;
      }
      case OperatorCode.rtt_canon:
      case OperatorCode.rtt_sub:
      case OperatorCode.rtt_fresh_sub:
      case OperatorCode.ref_test_static:
      case OperatorCode.ref_cast_static:
      case OperatorCode.struct_new_default:
      case OperatorCode.struct_new_default_with_rtt:
      case OperatorCode.struct_new:
      case OperatorCode.struct_new_with_rtt:
      case OperatorCode.array_new_default:
      case OperatorCode.array_new_default_with_rtt:
      case OperatorCode.array_new:
      case OperatorCode.array_new_with_rtt:
      case OperatorCode.array_get:
      case OperatorCode.array_get_s:
      case OperatorCode.array_get_u:
      case OperatorCode.array_set:
      case OperatorCode.array_len: {
        const refType = this._nameResolver.getTypeName(operator.refType, true);
        this.appendBuffer(` ${refType}`);
        break;
      }
      case OperatorCode.array_copy: {
        const dstType = this._nameResolver.getTypeName(operator.refType, true);
        const srcType = this._nameResolver.getTypeName(operator.srcType, true);
        this.appendBuffer(` ${dstType} ${srcType}`);
        break;
      }
      case OperatorCode.array_init:
      case OperatorCode.array_init_static: {
        const refType = this._nameResolver.getTypeName(operator.refType, true);
        const length = operator.brDepth; // Overloaded field.
        this.appendBuffer(` ${refType} ${length}`);
        break;
      }
    }
  }
  private printImportSource(info: IImportEntry): void {
    this.printString(info.module);
    this.appendBuffer(" ");
    this.printString(info.field);
  }
  private increaseIndent(): void {
    this._indent += IndentIncrement;
    this._indentLevel++;
  }
  private decreaseIndent(): void {
    this._indent = this._indent.slice(0, -IndentIncrement.length);
    this._indentLevel--;
  }
  public disassemble(reader: BinaryReader): string {
    const done = this.disassembleChunk(reader);
    if (!done) return null;
    let lines = this._lines;
    if (this._addOffsets) {
      lines = lines.map((line, index) => {
        var position = formatHex(this._offsets[index], 4);
        return line + " ;; @" + position;
      });
    }
    lines.push(""); // we need '\n' after last line
    const result = lines.join("\n");
    this._lines.length = 0;
    this._offsets.length = 0;
    this._functionBodyOffsets.length = 0;
    return result;
  }
  public getResult(): IDisassemblerResult {
    let linesReady = this._lines.length;
    if (this._backrefLabels && this._labelMode === LabelMode.WhenUsed) {
      this._backrefLabels.some((backrefLabel) => {
        if (backrefLabel.useLabel) return false;
        linesReady = backrefLabel.line;
        return true;
      });
    }
    if (linesReady === 0) {
      return {
        lines: [],
        offsets: this._addOffsets ? [] : undefined,
        done: this._done,
        functionBodyOffsets: this._addOffsets ? [] : undefined,
      };
    }
    if (linesReady === this._lines.length) {
      const result = {
        lines: this._lines,
        offsets: this._addOffsets ? this._offsets : undefined,
        done: this._done,
        functionBodyOffsets: this._addOffsets
          ? this._functionBodyOffsets
          : undefined,
      };
      this._lines = [];
      if (this._addOffsets) {
        this._offsets = [];
        this._functionBodyOffsets = [];
      }
      return result;
    }
    const result = {
      lines: this._lines.splice(0, linesReady),
      offsets: this._addOffsets
        ? this._offsets.splice(0, linesReady)
        : undefined,
      done: false,
      functionBodyOffsets: this._addOffsets
        ? this._functionBodyOffsets
        : undefined,
    };
    if (this._backrefLabels) {
      this._backrefLabels.forEach((backrefLabel) => {
        backrefLabel.line -= linesReady;
      });
    }
    return result;
  }
  public disassembleChunk(reader: BinaryReader, offsetInModule = 0): boolean {
    if (this._done)
      throw new Error(
        "Invalid state: disassembly process was already finished."
      );
    while (true) {
      this._currentPosition = reader.position + offsetInModule;
      if (!reader.read()) return false;
      switch (reader.state) {
        case BinaryReaderState.END_WASM:
          this.appendBuffer(")");
          this.newLine();
          this._reset();
          if (!reader.hasMoreBytes()) {
            this._done = true;
            return true;
          }
          break;
        case BinaryReaderState.ERROR:
          throw reader.error;
        case BinaryReaderState.BEGIN_WASM:
          this.appendBuffer("(module");
          this.newLine();
          break;
        case BinaryReaderState.END_SECTION:
          this._currentSectionId = SectionCode.Unknown;
          break;
        case BinaryReaderState.BEGIN_SECTION:
          var sectionInfo = <ISectionInformation>reader.result;
          switch (sectionInfo.id) {
            case SectionCode.Type:
            case SectionCode.Import:
            case SectionCode.Export:
            case SectionCode.Global:
            case SectionCode.Function:
            case SectionCode.Start:
            case SectionCode.Code:
            case SectionCode.Memory:
            case SectionCode.Data:
            case SectionCode.Table:
            case SectionCode.Element:
            case SectionCode.Event:
              this._currentSectionId = sectionInfo.id;
              break; // reading known section;
            default:
              reader.skipSection();
              break;
          }
          break;
        case BinaryReaderState.MEMORY_SECTION_ENTRY:
          var memoryInfo = <IMemoryType>reader.result;
          var memoryIndex = this._memoryCount++;
          var memoryName = this._nameResolver.getMemoryName(memoryIndex, false);
          this.appendBuffer(`  (memory ${memoryName}`);
          if (this._exportMetadata !== null) {
            for (const exportName of this._exportMetadata.getMemoryExportNames(
              memoryIndex
            )) {
              this.appendBuffer(` (export ${JSON.stringify(exportName)})`);
            }
          }
          this.appendBuffer(` ${limitsToString(memoryInfo.limits)}`);
          if (memoryInfo.shared) {
            this.appendBuffer(` shared`);
          }
          this.appendBuffer(")");
          this.newLine();
          break;
        case BinaryReaderState.EVENT_SECTION_ENTRY:
          var eventInfo = <IEventType>reader.result;
          var eventIndex = this._eventCount++;
          var eventName = this._nameResolver.getEventName(eventIndex, false);
          this.appendBuffer(`  (event ${eventName}`);
          if (this._exportMetadata !== null) {
            for (const exportName of this._exportMetadata.getEventExportNames(
              eventIndex
            )) {
              this.appendBuffer(` (export ${JSON.stringify(exportName)})`);
            }
          }
          this.printFuncType(eventInfo.typeIndex);
          this.appendBuffer(")");
          this.newLine();
          break;
        case BinaryReaderState.TABLE_SECTION_ENTRY:
          var tableInfo = <ITableType>reader.result;
          var tableIndex = this._tableCount++;
          var tableName = this._nameResolver.getTableName(tableIndex, false);
          this.appendBuffer(`  (table ${tableName}`);
          if (this._exportMetadata !== null) {
            for (const exportName of this._exportMetadata.getTableExportNames(
              tableIndex
            )) {
              this.appendBuffer(` (export ${JSON.stringify(exportName)})`);
            }
          }
          this.appendBuffer(
            ` ${limitsToString(tableInfo.limits)} ${this.typeToString(
              tableInfo.elementType
            )})`
          );
          this.newLine();
          break;
        case BinaryReaderState.EXPORT_SECTION_ENTRY:
          // Skip printing exports here when we have export metadata
          // which we can use to print export information inline.
          if (this._exportMetadata === null) {
            var exportInfo = <IExportEntry>reader.result;
            this.appendBuffer("  (export ");
            this.printString(exportInfo.field);
            this.appendBuffer(" ");
            switch (exportInfo.kind) {
              case ExternalKind.Function:
                var funcName = this._nameResolver.getFunctionName(
                  exportInfo.index,
                  exportInfo.index < this._importCount,
                  true
                );
                this.appendBuffer(`(func ${funcName})`);
                break;
              case ExternalKind.Table:
                var tableName = this._nameResolver.getTableName(
                  exportInfo.index,
                  true
                );
                this.appendBuffer(`(table ${tableName})`);
                break;
              case ExternalKind.Memory:
                var memoryName = this._nameResolver.getMemoryName(
                  exportInfo.index,
                  true
                );
                this.appendBuffer(`(memory ${memoryName})`);
                break;
              case ExternalKind.Global:
                var globalName = this._nameResolver.getGlobalName(
                  exportInfo.index,
                  true
                );
                this.appendBuffer(`(global ${globalName})`);
                break;
              case ExternalKind.Event:
                var eventName = this._nameResolver.getEventName(
                  exportInfo.index,
                  true
                );
                this.appendBuffer(`(event ${eventName})`);
                break;
              default:
                throw new Error(`Unsupported export ${exportInfo.kind}`);
            }
            this.appendBuffer(")");
            this.newLine();
          }
          break;
        case BinaryReaderState.IMPORT_SECTION_ENTRY:
          var importInfo = <IImportEntry>reader.result;
          switch (importInfo.kind) {
            case ExternalKind.Function:
              this._importCount++;
              var funcIndex = this._funcIndex++;
              var funcName = this._nameResolver.getFunctionName(
                funcIndex,
                true,
                false
              );
              this.appendBuffer(`  (func ${funcName}`);
              if (this._exportMetadata !== null) {
                for (const exportName of this._exportMetadata.getFunctionExportNames(
                  funcIndex
                )) {
                  this.appendBuffer(` (export ${JSON.stringify(exportName)})`);
                }
              }
              this.appendBuffer(` (import `);
              this.printImportSource(importInfo);
              this.appendBuffer(")");
              this.printFuncType(importInfo.funcTypeIndex);
              this.appendBuffer(")");
              break;
            case ExternalKind.Global:
              var globalImportInfo = <IGlobalType>importInfo.type;
              var globalIndex = this._globalCount++;
              var globalName = this._nameResolver.getGlobalName(
                globalIndex,
                false
              );
              this.appendBuffer(`  (global ${globalName}`);
              if (this._exportMetadata !== null) {
                for (const exportName of this._exportMetadata.getGlobalExportNames(
                  globalIndex
                )) {
                  this.appendBuffer(` (export ${JSON.stringify(exportName)})`);
                }
              }
              this.appendBuffer(` (import `);
              this.printImportSource(importInfo);
              this.appendBuffer(
                `) ${this.globalTypeToString(globalImportInfo)})`
              );
              break;
            case ExternalKind.Memory:
              var memoryImportInfo = <IMemoryType>importInfo.type;
              var memoryIndex = this._memoryCount++;
              var memoryName = this._nameResolver.getMemoryName(
                memoryIndex,
                false
              );
              this.appendBuffer(`  (memory ${memoryName}`);
              if (this._exportMetadata !== null) {
                for (const exportName of this._exportMetadata.getMemoryExportNames(
                  memoryIndex
                )) {
                  this.appendBuffer(` (export ${JSON.stringify(exportName)})`);
                }
              }
              this.appendBuffer(` (import `);
              this.printImportSource(importInfo);
              this.appendBuffer(`) ${limitsToString(memoryImportInfo.limits)}`);
              if (memoryImportInfo.shared) {
                this.appendBuffer(` shared`);
              }
              this.appendBuffer(")");
              break;
            case ExternalKind.Table:
              var tableImportInfo = <ITableType>importInfo.type;
              var tableIndex = this._tableCount++;
              var tableName = this._nameResolver.getTableName(
                tableIndex,
                false
              );
              this.appendBuffer(`  (table ${tableName}`);
              if (this._exportMetadata !== null) {
                for (const exportName of this._exportMetadata.getTableExportNames(
                  tableIndex
                )) {
                  this.appendBuffer(` (export ${JSON.stringify(exportName)})`);
                }
              }
              this.appendBuffer(` (import `);
              this.printImportSource(importInfo);
              this.appendBuffer(
                `) ${limitsToString(
                  tableImportInfo.limits
                )} ${this.typeToString(tableImportInfo.elementType)})`
              );
              break;
            case ExternalKind.Event:
              var eventImportInfo = <IEventType>importInfo.type;
              var eventIndex = this._eventCount++;
              var eventName = this._nameResolver.getEventName(
                eventIndex,
                false
              );
              this.appendBuffer(`  (event ${eventName}`);
              if (this._exportMetadata !== null) {
                for (const exportName of this._exportMetadata.getEventExportNames(
                  eventIndex
                )) {
                  this.appendBuffer(` (export ${JSON.stringify(exportName)})`);
                }
              }
              this.appendBuffer(` (import `);
              this.printImportSource(importInfo);
              this.appendBuffer(")");
              this.printFuncType(eventImportInfo.typeIndex);
              this.appendBuffer(")");
              break;
            default:
              throw new Error(`NYI other import types: ${importInfo.kind}`);
          }
          this.newLine();
          break;
        case BinaryReaderState.BEGIN_ELEMENT_SECTION_ENTRY:
          var elementSegment = <IElementSegment>reader.result;
          var elementIndex = this._elementCount++;
          var elementName = this._nameResolver.getElementName(
            elementIndex,
            false
          );
          this.appendBuffer(`  (elem ${elementName}`);
          switch (elementSegment.mode) {
            case ElementMode.Active:
              if (elementSegment.tableIndex !== 0) {
                const tableName = this._nameResolver.getTableName(
                  elementSegment.tableIndex,
                  false
                );
                this.appendBuffer(` (table ${tableName})`);
              }
              break;
            case ElementMode.Passive:
              break;
            case ElementMode.Declarative:
              this.appendBuffer(" declare");
              break;
          }
          break;
        case BinaryReaderState.END_ELEMENT_SECTION_ENTRY:
          this.appendBuffer(")");
          this.newLine();
          break;
        case BinaryReaderState.ELEMENT_SECTION_ENTRY_BODY:
          const elementSegmentBody = <IElementSegmentBody>reader.result;
          this.appendBuffer(
            ` ${this.typeToString(elementSegmentBody.elementType)}`
          );
          break;
        case BinaryReaderState.BEGIN_GLOBAL_SECTION_ENTRY:
          var globalInfo = <IGlobalVariable>reader.result;
          var globalIndex = this._globalCount++;
          var globalName = this._nameResolver.getGlobalName(globalIndex, false);
          this.appendBuffer(`  (global ${globalName}`);
          if (this._exportMetadata !== null) {
            for (const exportName of this._exportMetadata.getGlobalExportNames(
              globalIndex
            )) {
              this.appendBuffer(` (export ${JSON.stringify(exportName)})`);
            }
          }
          this.appendBuffer(` ${this.globalTypeToString(globalInfo.type)}`);
          break;
        case BinaryReaderState.END_GLOBAL_SECTION_ENTRY:
          this.appendBuffer(")");
          this.newLine();
          break;
        case BinaryReaderState.TYPE_SECTION_ENTRY:
          var typeEntry = <ITypeEntry>reader.result;
          var typeIndex = this._types.length;
          this._types.push(typeEntry);
          if (!this._skipTypes) {
            var typeName = this._nameResolver.getTypeName(typeIndex, false);
            var superTypeName = undefined;
            if (typeEntry.supertype !== undefined) {
              superTypeName = this.typeIndexToString(typeEntry.supertype);
            }
            if (typeEntry.form === TypeKind.func) {
              this.appendBuffer(`  (type ${typeName} (func`);
              this.printFuncType(typeIndex);
              this.appendBuffer("))");
            } else if (typeEntry.form === TypeKind.func_subtype) {
              this.appendBuffer(`  (type ${typeName} (func_subtype`);
              this.printFuncType(typeIndex);
              this.appendBuffer(` (supertype ${superTypeName})))`);
            } else if (typeEntry.form === TypeKind.struct) {
              this.appendBuffer(`  (type ${typeName} (struct`);
              this.printStructType(typeIndex);
              this.appendBuffer("))");
            } else if (typeEntry.form === TypeKind.struct_subtype) {
              this.appendBuffer(`  (type ${typeName} (struct_subtype`);
              this.printStructType(typeIndex);
              this.appendBuffer(` (supertype ${superTypeName})))`);
            } else if (typeEntry.form === TypeKind.array) {
              this.appendBuffer(`  (type ${typeName} (array`);
              this.printArrayType(typeIndex);
              this.appendBuffer("))");
            } else if (typeEntry.form === TypeKind.array_subtype) {
              this.appendBuffer(`  (type ${typeName} (array_subtype`);
              this.printArrayType(typeIndex);
              this.appendBuffer(`) (supertype ${superTypeName})))`);
            } else {
              throw new Error(`Unknown type form: ${typeEntry.form}`);
            }
            this.newLine();
          }
          break;
        case BinaryReaderState.START_SECTION_ENTRY:
          var startEntry = <IStartEntry>reader.result;
          var funcName = this._nameResolver.getFunctionName(
            startEntry.index,
            startEntry.index < this._importCount,
            true
          );
          this.appendBuffer(`  (start ${funcName})`);
          this.newLine();
          break;
        case BinaryReaderState.BEGIN_DATA_SECTION_ENTRY:
          this.appendBuffer("  (data");
          break;
        case BinaryReaderState.DATA_SECTION_ENTRY_BODY:
          var body = <IDataSegmentBody>reader.result;
          this.appendBuffer(" ");
          this.printString(body.data);
          break;
        case BinaryReaderState.END_DATA_SECTION_ENTRY:
          this.appendBuffer(")");
          this.newLine();
          break;
        case BinaryReaderState.BEGIN_INIT_EXPRESSION_BODY:
        case BinaryReaderState.BEGIN_OFFSET_EXPRESSION_BODY:
          this._expression = [];
          break;
        case BinaryReaderState.INIT_EXPRESSION_OPERATOR:
        case BinaryReaderState.OFFSET_EXPRESSION_OPERATOR:
          var operator = <IOperatorInformation>reader.result;
          if (operator.code !== OperatorCode.end) {
            this._expression.push(operator);
          }
          break;
        case BinaryReaderState.END_OFFSET_EXPRESSION_BODY:
          if (this._expression.length > 1) {
            this.appendBuffer(" (offset ");
            this.printExpression(this._expression);
            this.appendBuffer(")");
          } else {
            this.appendBuffer(" ");
            this.printExpression(this._expression);
          }
          this._expression = [];
          break;
        case BinaryReaderState.END_INIT_EXPRESSION_BODY:
          if (
            this._expression.length > 1 &&
            this._currentSectionId === SectionCode.Element
          ) {
            this.appendBuffer(" (item ");
            this.printExpression(this._expression);
            this.appendBuffer(")");
          } else {
            this.appendBuffer(" ");
            this.printExpression(this._expression);
          }
          this._expression = [];
          break;
        case BinaryReaderState.FUNCTION_SECTION_ENTRY:
          this._funcTypes.push((<IFunctionEntry>reader.result).typeIndex);
          break;
        case BinaryReaderState.BEGIN_FUNCTION_BODY:
          var func = <IFunctionInformation>reader.result;
          var type = this._types[
            this._funcTypes[this._funcIndex - this._importCount]
          ];
          this.appendBuffer("  (func ");
          this.appendBuffer(
            this._nameResolver.getFunctionName(this._funcIndex, false, false)
          );
          if (this._exportMetadata !== null) {
            for (const exportName of this._exportMetadata.getFunctionExportNames(
              this._funcIndex
            )) {
              this.appendBuffer(` (export ${JSON.stringify(exportName)})`);
            }
          }
          for (var i = 0; i < type.params.length; i++) {
            var paramName = this._nameResolver.getVariableName(
              this._funcIndex,
              i,
              false
            );
            this.appendBuffer(
              ` (param ${paramName} ${this.typeToString(type.params[i])})`
            );
          }
          for (var i = 0; i < type.returns.length; i++) {
            this.appendBuffer(
              ` (result ${this.typeToString(type.returns[i])})`
            );
          }
          this.newLine();
          var localIndex = type.params.length;
          if (func.locals.length > 0) {
            this.appendBuffer("   ");
            for (var l of func.locals) {
              for (var i = 0; i < l.count; i++) {
                var paramName = this._nameResolver.getVariableName(
                  this._funcIndex,
                  localIndex++,
                  false
                );
                this.appendBuffer(
                  ` (local ${paramName} ${this.typeToString(l.type)})`
                );
              }
            }
            this.newLine();
          }
          this._indent = "    ";
          this._indentLevel = 0;
          this._labelIndex = 0;
          this._backrefLabels = this._labelMode === LabelMode.Depth ? null : [];
          this._logFirstInstruction = true;
          break;
        case BinaryReaderState.CODE_OPERATOR:
          if (this._logFirstInstruction) {
            this.logStartOfFunctionBodyOffset();
            this._logFirstInstruction = false;
          }
          var operator = <IOperatorInformation>reader.result;
          if (operator.code == OperatorCode.end && this._indentLevel == 0) {
            // reached of the function, closing function body
            this.appendBuffer(`  )`);
            this.newLine();
            break;
          }
          switch (operator.code) {
            case OperatorCode.end:
            case OperatorCode.else:
            case OperatorCode.catch:
            case OperatorCode.catch_all:
            case OperatorCode.unwind:
            case OperatorCode.delegate:
              this.decreaseIndent();
              break;
          }
          this.appendBuffer(this._indent);
          this.printOperator(operator);
          this.newLine();
          switch (operator.code) {
            case OperatorCode.if:
            case OperatorCode.block:
            case OperatorCode.loop:
            case OperatorCode.else:
            case OperatorCode.try:
            case OperatorCode.catch:
            case OperatorCode.catch_all:
            case OperatorCode.unwind:
              this.increaseIndent();
              break;
          }
          break;
        case BinaryReaderState.END_FUNCTION_BODY:
          this._funcIndex++;
          this._backrefLabels = null;
          this.logEndOfFunctionBodyOffset();
          // See case BinaryReaderState.CODE_OPERATOR for closing of body
          break;
        default:
          throw new Error(`Expectected state: ${reader.state}`);
      }
    }
  }
}

const UNKNOWN_FUNCTION_PREFIX = "unknown";

class NameSectionNameResolver extends DefaultNameResolver {
  protected readonly _functionNames: string[];
  protected readonly _localNames: string[][];
  protected readonly _eventNames: string[];
  protected readonly _typeNames: string[];
  protected readonly _tableNames: string[];
  protected readonly _memoryNames: string[];
  protected readonly _globalNames: string[];
  protected readonly _fieldNames: string[][];

  constructor(
    functionNames: string[],
    localNames: string[][],
    eventNames: string[],
    typeNames: string[],
    tableNames: string[],
    memoryNames: string[],
    globalNames: string[],
    fieldNames: string[][]
  ) {
    super();
    this._functionNames = functionNames;
    this._localNames = localNames;
    this._eventNames = eventNames;
    this._typeNames = typeNames;
    this._tableNames = tableNames;
    this._memoryNames = memoryNames;
    this._globalNames = globalNames;
    this._fieldNames = fieldNames;
  }

  public override getTypeName(index: number, isRef: boolean): string {
    const name = this._typeNames[index];
    if (!name) return super.getTypeName(index, isRef);
    return isRef ? `$${name}` : `$${name} (;${index};)`;
  }

  public override getTableName(index: number, isRef: boolean): string {
    const name = this._tableNames[index];
    if (!name) return super.getTableName(index, isRef);
    return isRef ? `$${name}` : `$${name} (;${index};)`;
  }

  public override getMemoryName(index: number, isRef: boolean): string {
    const name = this._memoryNames[index];
    if (!name) return super.getMemoryName(index, isRef);
    return isRef ? `$${name}` : `$${name} (;${index};)`;
  }

  public override getGlobalName(index: number, isRef: boolean): string {
    const name = this._globalNames[index];
    if (!name) return super.getGlobalName(index, isRef);
    return isRef ? `$${name}` : `$${name} (;${index};)`;
  }

  public override getEventName(index: number, isRef: boolean): string {
    const name = this._eventNames[index];
    if (!name) return super.getEventName(index, isRef);
    return isRef ? `$${name}` : `$${name} (;${index};)`;
  }

  public override getFunctionName(
    index: number,
    isImport: boolean,
    isRef: boolean
  ): string {
    const name = this._functionNames[index];
    if (!name) return `$${UNKNOWN_FUNCTION_PREFIX}${index}`;
    return isRef ? `$${name}` : `$${name} (;${index};)`;
  }

  public override getVariableName(
    funcIndex: number,
    index: number,
    isRef: boolean
  ): string {
    const name =
      this._localNames[funcIndex] && this._localNames[funcIndex][index];
    if (!name) return super.getVariableName(funcIndex, index, isRef);
    return isRef ? `$${name}` : `$${name} (;${index};)`;
  }

  public override getFieldName(
    typeIndex: number,
    index: number,
    isRef: boolean
  ): string {
    const name =
      this._fieldNames[typeIndex] && this._fieldNames[typeIndex][index];
    if (!name) return super.getFieldName(typeIndex, index, isRef);
    return isRef ? `$${name}` : `$${name} (;${index};)`;
  }
}

export class NameSectionReader {
  private _done = false;
  private _functionsCount = 0;
  private _functionImportsCount = 0;
  private _functionNames: string[] = null;
  private _functionLocalNames: string[][] = null;
  private _eventNames: string[] = null;
  private _typeNames: string[] = null;
  private _tableNames: string[] = null;
  private _memoryNames: string[] = null;
  private _globalNames: string[] = null;
  private _fieldNames: string[][] = null;
  private _hasNames = false;

  public read(reader: BinaryReader): boolean {
    if (this._done)
      throw new Error(
        "Invalid state: disassembly process was already finished."
      );
    while (true) {
      if (!reader.read()) return false;
      switch (reader.state) {
        case BinaryReaderState.END_WASM:
          if (!reader.hasMoreBytes()) {
            this._done = true;
            return true;
          }
          break;
        case BinaryReaderState.ERROR:
          throw reader.error;
        case BinaryReaderState.BEGIN_WASM:
          this._functionsCount = 0;
          this._functionImportsCount = 0;
          this._functionNames = [];
          this._functionLocalNames = [];
          this._eventNames = [];
          this._typeNames = [];
          this._tableNames = [];
          this._memoryNames = [];
          this._globalNames = [];
          this._fieldNames = [];
          this._hasNames = false;
          break;
        case BinaryReaderState.END_SECTION:
          break;
        case BinaryReaderState.BEGIN_SECTION:
          var sectionInfo = <ISectionInformation>reader.result;
          if (
            sectionInfo.id === SectionCode.Custom &&
            bytesToString(sectionInfo.name) === NAME_SECTION_NAME
          ) {
            break;
          }
          if (
            sectionInfo.id === SectionCode.Function ||
            sectionInfo.id === SectionCode.Import
          ) {
            break;
          }
          reader.skipSection();
          break;
        case BinaryReaderState.IMPORT_SECTION_ENTRY:
          var importInfo = <IImportEntry>reader.result;
          if (importInfo.kind === ExternalKind.Function)
            this._functionImportsCount++;
          break;
        case BinaryReaderState.FUNCTION_SECTION_ENTRY:
          this._functionsCount++;
          break;
        case BinaryReaderState.NAME_SECTION_ENTRY:
          const nameInfo = <INameEntry>reader.result;
          if (nameInfo.type === NameType.Function) {
            const { names } = <IFunctionNameEntry>nameInfo;
            names.forEach(({ index, name }) => {
              this._functionNames[index] = bytesToString(name);
            });
            this._hasNames = true;
          } else if (nameInfo.type === NameType.Local) {
            const { funcs } = <ILocalNameEntry>nameInfo;
            funcs.forEach(({ index, locals }) => {
              const localNames = (this._functionLocalNames[index] = []);
              locals.forEach(({ index, name }) => {
                localNames[index] = bytesToString(name);
              });
            });
            this._hasNames = true;
          } else if (nameInfo.type === NameType.Event) {
            const { names } = <IEventNameEntry>nameInfo;
            names.forEach(({ index, name }) => {
              this._eventNames[index] = bytesToString(name);
            });
            this._hasNames = true;
          } else if (nameInfo.type === NameType.Type) {
            const { names } = <ITypeNameEntry>nameInfo;
            names.forEach(({ index, name }) => {
              this._typeNames[index] = bytesToString(name);
            });
            this._hasNames = true;
          } else if (nameInfo.type === NameType.Table) {
            const { names } = <ITableNameEntry>nameInfo;
            names.forEach(({ index, name }) => {
              this._tableNames[index] = bytesToString(name);
            });
            this._hasNames = true;
          } else if (nameInfo.type === NameType.Memory) {
            const { names } = <IMemoryNameEntry>nameInfo;
            names.forEach(({ index, name }) => {
              this._memoryNames[index] = bytesToString(name);
            });
            this._hasNames = true;
          } else if (nameInfo.type === NameType.Global) {
            const { names } = <IGlobalNameEntry>nameInfo;
            names.forEach(({ index, name }) => {
              this._globalNames[index] = bytesToString(name);
            });
            this._hasNames = true;
          } else if (nameInfo.type === NameType.Field) {
            const { types } = <IFieldNameEntry>nameInfo;
            types.forEach(({ index, fields }) => {
              const fieldNames = (this._fieldNames[index] = []);
              fields.forEach(({ index, name }) => {
                fieldNames[index] = bytesToString(name);
              });
            });
          }
          break;
        default:
          throw new Error(`Expectected state: ${reader.state}`);
      }
    }
  }

  public hasValidNames(): boolean {
    return this._hasNames;
  }

  public getNameResolver(): INameResolver {
    if (!this.hasValidNames()) throw new Error("Has no valid name section");

    // Fix bad names.
    const functionNamesLength =
      this._functionImportsCount + this._functionsCount;
    const functionNames = this._functionNames.slice(0, functionNamesLength);
    const usedNameAt = Object.create(null);
    for (let i = 0; i < functionNames.length; i++) {
      const name = functionNames[i];
      if (!name) continue;
      const goodName =
        !(name in usedNameAt) &&
        isValidName(name) &&
        name.indexOf(UNKNOWN_FUNCTION_PREFIX) !== 0;
      if (!goodName) {
        if (usedNameAt[name] >= 0) {
          // Remove all non-unique names.
          functionNames[usedNameAt[name]] = null;
          usedNameAt[name] = -1;
        }
        functionNames[i] = null;
        continue;
      }
      usedNameAt[name] = i;
    }

    return new NameSectionNameResolver(
      functionNames,
      this._functionLocalNames,
      this._eventNames,
      this._typeNames,
      this._tableNames,
      this._memoryNames,
      this._globalNames,
      this._fieldNames
    );
  }
}

export class DevToolsNameResolver extends NameSectionNameResolver {
  constructor(
    functionNames: string[],
    localNames: string[][],
    eventNames: string[],
    typeNames: string[],
    tableNames: string[],
    memoryNames: string[],
    globalNames: string[],
    fieldNames: string[][]
  ) {
    super(
      functionNames,
      localNames,
      eventNames,
      typeNames,
      tableNames,
      memoryNames,
      globalNames,
      fieldNames
    );
  }

  public override getFunctionName(
    index: number,
    isImport: boolean,
    isRef: boolean
  ): string {
    const name = this._functionNames[index];
    if (!name) return isImport ? `$import${index}` : `$func${index}`;
    return isRef ? `$${name}` : `$${name} (;${index};)`;
  }
}

export class DevToolsNameGenerator {
  private _done = false;
  private _functionImportsCount = 0;
  private _memoryImportsCount = 0;
  private _tableImportsCount = 0;
  private _globalImportsCount = 0;
  private _eventImportsCount = 0;

  private _functionNames: string[] = null;
  private _functionLocalNames: string[][] = null;
  private _eventNames: string[] = null;
  private _memoryNames: string[] = null;
  private _typeNames: string[] = null;
  private _tableNames: string[] = null;
  private _globalNames: string[] = null;
  private _fieldNames: string[][] = null;

  private _functionExportNames: string[][] = null;
  private _globalExportNames: string[][] = null;
  private _memoryExportNames: string[][] = null;
  private _tableExportNames: string[][] = null;
  private _eventExportNames: string[][] = null;

  private _addExportName(exportNames: string[][], index: number, name: string) {
    const names = exportNames[index];
    if (names) {
      names.push(name);
    } else {
      exportNames[index] = [name];
    }
  }

  private _setName(
    names: string[],
    index: number,
    name: string,
    isNameSectionName: boolean
  ) {
    if (!name) return;
    if (isNameSectionName) {
      if (!isValidName(name)) return;
      names[index] = name;
    } else if (!names[index]) {
      names[index] = name.replace(INVALID_NAME_SYMBOLS_REGEX_GLOBAL, "_");
    }
  }

  public read(reader: BinaryReader): boolean {
    if (this._done)
      throw new Error(
        "Invalid state: disassembly process was already finished."
      );
    while (true) {
      if (!reader.read()) return false;
      switch (reader.state) {
        case BinaryReaderState.END_WASM:
          if (!reader.hasMoreBytes()) {
            this._done = true;
            return true;
          }
          break;
        case BinaryReaderState.ERROR:
          throw reader.error;
        case BinaryReaderState.BEGIN_WASM:
          this._functionImportsCount = 0;
          this._memoryImportsCount = 0;
          this._tableImportsCount = 0;
          this._globalImportsCount = 0;
          this._eventImportsCount = 0;

          this._functionNames = [];
          this._functionLocalNames = [];
          this._eventNames = [];
          this._memoryNames = [];
          this._typeNames = [];
          this._tableNames = [];
          this._globalNames = [];
          this._fieldNames = [];
          this._functionExportNames = [];
          this._globalExportNames = [];
          this._memoryExportNames = [];
          this._tableExportNames = [];
          this._eventExportNames = [];
          break;
        case BinaryReaderState.END_SECTION:
          break;
        case BinaryReaderState.BEGIN_SECTION:
          var sectionInfo = <ISectionInformation>reader.result;

          if (
            sectionInfo.id === SectionCode.Custom &&
            bytesToString(sectionInfo.name) === NAME_SECTION_NAME
          ) {
            break;
          }
          switch (sectionInfo.id) {
            case SectionCode.Import:
            case SectionCode.Export:
              break; // reading known section;
            default:
              reader.skipSection();
              break;
          }
          break;
        case BinaryReaderState.IMPORT_SECTION_ENTRY:
          var importInfo = <IImportEntry>reader.result;
          const importName = `${bytesToString(
            importInfo.module
          )}.${bytesToString(importInfo.field)}`;
          switch (importInfo.kind) {
            case ExternalKind.Function:
              this._setName(
                this._functionNames,
                this._functionImportsCount++,
                importName,
                false
              );
              break;
            case ExternalKind.Table:
              this._setName(
                this._tableNames,
                this._tableImportsCount++,
                importName,
                false
              );
              break;
            case ExternalKind.Memory:
              this._setName(
                this._memoryNames,
                this._memoryImportsCount++,
                importName,
                false
              );
              break;
            case ExternalKind.Global:
              this._setName(
                this._globalNames,
                this._globalImportsCount++,
                importName,
                false
              );
              break;
            case ExternalKind.Event:
              this._setName(
                this._eventNames,
                this._eventImportsCount++,
                importName,
                false
              );
            default:
              throw new Error(`Unsupported export ${importInfo.kind}`);
          }
          break;
        case BinaryReaderState.NAME_SECTION_ENTRY:
          const nameInfo = <INameEntry>reader.result;
          if (nameInfo.type === NameType.Function) {
            const { names } = <IFunctionNameEntry>nameInfo;
            names.forEach(({ index, name }) => {
              this._setName(
                this._functionNames,
                index,
                bytesToString(name),
                true
              );
            });
          } else if (nameInfo.type === NameType.Local) {
            const { funcs } = <ILocalNameEntry>nameInfo;
            funcs.forEach(({ index, locals }) => {
              const localNames = (this._functionLocalNames[index] = []);
              locals.forEach(({ index, name }) => {
                localNames[index] = bytesToString(name);
              });
            });
          } else if (nameInfo.type === NameType.Event) {
            const { names } = <IEventNameEntry>nameInfo;
            names.forEach(({ index, name }) => {
              this._setName(this._eventNames, index, bytesToString(name), true);
            });
          } else if (nameInfo.type === NameType.Type) {
            const { names } = <ITypeNameEntry>nameInfo;
            names.forEach(({ index, name }) => {
              this._setName(this._typeNames, index, bytesToString(name), true);
            });
          } else if (nameInfo.type === NameType.Table) {
            const { names } = <ITableNameEntry>nameInfo;
            names.forEach(({ index, name }) => {
              this._setName(this._tableNames, index, bytesToString(name), true);
            });
          } else if (nameInfo.type === NameType.Memory) {
            const { names } = <IMemoryNameEntry>nameInfo;
            names.forEach(({ index, name }) => {
              this._setName(
                this._memoryNames,
                index,
                bytesToString(name),
                true
              );
            });
          } else if (nameInfo.type === NameType.Global) {
            const { names } = <IGlobalNameEntry>nameInfo;
            names.forEach(({ index, name }) => {
              this._setName(
                this._globalNames,
                index,
                bytesToString(name),
                true
              );
            });
          } else if (nameInfo.type === NameType.Field) {
            const { types } = <IFieldNameEntry>nameInfo;
            types.forEach(({ index, fields }) => {
              const fieldNames = (this._fieldNames[index] = []);
              fields.forEach(({ index, name }) => {
                fieldNames[index] = bytesToString(name);
              });
            });
          }
          break;
        case BinaryReaderState.EXPORT_SECTION_ENTRY:
          var exportInfo = <IExportEntry>reader.result;
          const exportName = bytesToString(exportInfo.field);
          switch (exportInfo.kind) {
            case ExternalKind.Function:
              this._addExportName(
                this._functionExportNames,
                exportInfo.index,
                exportName
              );
              this._setName(
                this._functionNames,
                exportInfo.index,
                exportName,
                false
              );
              break;
            case ExternalKind.Global:
              this._addExportName(
                this._globalExportNames,
                exportInfo.index,
                exportName
              );
              this._setName(
                this._globalNames,
                exportInfo.index,
                exportName,
                false
              );
              break;
            case ExternalKind.Memory:
              this._addExportName(
                this._memoryExportNames,
                exportInfo.index,
                exportName
              );
              this._setName(
                this._memoryNames,
                exportInfo.index,
                exportName,
                false
              );
              break;
            case ExternalKind.Table:
              this._addExportName(
                this._tableExportNames,
                exportInfo.index,
                exportName
              );
              this._setName(
                this._tableNames,
                exportInfo.index,
                exportName,
                false
              );
              break;
            case ExternalKind.Event:
              this._addExportName(
                this._eventExportNames,
                exportInfo.index,
                exportName
              );
              this._setName(
                this._eventNames,
                exportInfo.index,
                exportName,
                false
              );
              break;
            default:
              throw new Error(`Unsupported export ${exportInfo.kind}`);
          }
          break;
        default:
          throw new Error(`Expectected state: ${reader.state}`);
      }
    }
  }

  public getExportMetadata(): IExportMetadata {
    return new DevToolsExportMetadata(
      this._functionExportNames,
      this._globalExportNames,
      this._memoryExportNames,
      this._tableExportNames,
      this._eventExportNames
    );
  }

  public getNameResolver(): INameResolver {
    return new DevToolsNameResolver(
      this._functionNames,
      this._functionLocalNames,
      this._eventNames,
      this._typeNames,
      this._tableNames,
      this._memoryNames,
      this._globalNames,
      this._fieldNames
    );
  }
}
