neo-decompiler 0.11.0

Neo N3 NEF decompiler: parse, disassemble, lift bytecode to high-level pseudocode and C# skeletons, with a CLI, JSON reports, and optional WebAssembly bindings.
Documentation
import {
  extractIfCondition,
  findBlockEnd,
  findMatchingClose,
  isBlank,
  isElseOpen,
  isIfOpen,
  leadingWhitespace,
  negateCondition,
} from "./postprocess-shared.js";

export function rewriteElseIfChains(statements) {
  let i = 0;
  while (i + 1 < statements.length) {
    if (isElseOpen(statements[i]) && isIfOpen(statements[i + 1])) {
      const condition = extractIfCondition(statements[i + 1]);
      if (condition !== null) {
        statements[i] = `} else if ${condition} {`;
        statements.splice(i + 1, 1);
        const closeIdx = findMatchingClose(statements, i);
        if (closeIdx >= 0) {
          removeOneCloser(statements, closeIdx);
        }
        continue;
      }
    }
    i++;
  }
}

function removeOneCloser(statements, closeIdx) {
  if (closeIdx + 1 < statements.length) {
    if (statements[closeIdx].trim() === "}" && statements[closeIdx + 1].trim() === "}") {
      statements.splice(closeIdx + 1, 1);
      return;
    }
  }
  if (statements[closeIdx].trim() === "}" && closeIdx > 0) {
    for (let i = closeIdx - 1; i >= 0; i--) {
      const prev = statements[i].trim();
      if (prev !== "") {
        if (prev === "}") statements.splice(closeIdx, 1);
        break;
      }
    }
  }
}

export function collapseIfTrue(statements) {
  let index = 0;
  while (index < statements.length) {
    if (statements[index].trim() !== "if true {") {
      index++;
      continue;
    }
    const end = findBlockEnd(statements, index);
    if (end < 0 || statements[end].trim() !== "}") {
      index++;
      continue;
    }
    statements.splice(end, 1);
    statements.splice(index, 1);
  }
}

export function invertEmptyIfElse(statements) {
  let index = 0;
  while (index < statements.length) {
    const trimmed = statements[index].trim();
    if (!isIfOpen(trimmed)) {
      index++;
      continue;
    }

    // Check if body is empty (only comments between if and }).
    let j = index + 1;
    while (j < statements.length) {
      if (!isBlank(statements[j])) break;
      j++;
    }
    if (j >= statements.length || statements[j].trim() !== "}") {
      index++;
      continue;
    }

    const closeIf = j;
    if (closeIf + 1 >= statements.length || statements[closeIf + 1].trim() !== "else {") {
      index++;
      continue;
    }

    const elseLine = closeIf + 1;
    const elseEnd = findBlockEnd(statements, elseLine);
    if (elseEnd < 0) {
      index++;
      continue;
    }

    const cond = trimmed.slice(3, -2);
    const negated = negateCondition(cond);
    const indent = leadingWhitespace(statements[index]);
    statements[index] = `${indent}if ${negated} {`;
    // Drop the empty if-body `}` and the `else {` opener. The else block's own
    // closing `}` (at elseEnd) is intentionally retained; it becomes the closer
    // for the inverted `if`, keeping brace balance intact.
    statements.splice(closeIf, 2);
  }
}

export function removeEmptyIf(statements) {
  let index = 0;
  while (index < statements.length) {
    const trimmed = statements[index].trim();
    if (!isIfOpen(trimmed)) {
      index++;
      continue;
    }

    let j = index + 1;
    while (j < statements.length) {
      if (!isBlank(statements[j])) break;
      j++;
    }
    if (j >= statements.length || statements[j].trim() !== "}") {
      index++;
      continue;
    }

    // Must not be followed by else.
    if (j + 1 < statements.length && statements[j + 1].trim().startsWith("else")) {
      index++;
      continue;
    }
    statements.splice(index, j - index + 1);
  }
}