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
use super::super::super::HighLevelEmitter;

pub(super) fn resolve_condition_expression(
    statements: &[String],
    header_index: usize,
    condition: &str,
) -> Option<String> {
    if condition.contains("==") {
        return Some(condition.trim().to_string());
    }
    let condition = condition.trim();
    let condition = condition
        .strip_prefix('!')
        .map(str::trim)
        .unwrap_or(condition);
    let prev = HighLevelEmitter::previous_code_line(statements, header_index)?;
    let assign = HighLevelEmitter::parse_assignment(statements[prev].as_str())?;
    (assign.lhs == condition).then_some(assign.rhs)
}

pub(super) fn parse_case_sides(condition: &str) -> Option<(String, &str)> {
    let (left, right) = split_equals(condition)?;
    let left = left.trim();
    let right = right.trim();

    if is_literal(left) && !is_literal(right) {
        return Some((right.to_string(), left));
    }
    if !is_literal(left) && is_literal(right) {
        return Some((left.to_string(), right));
    }

    if is_temp(left) && !is_temp(right) {
        return Some((right.to_string(), left));
    }
    if is_temp(right) && !is_temp(left) {
        return Some((left.to_string(), right));
    }

    None
}

pub(super) fn resolve_case_value(
    statements: &[String],
    header_index: usize,
    token: &str,
) -> Option<String> {
    if is_literal(token) {
        return Some(token.trim().to_string());
    }
    if !is_temp(token) {
        return None;
    }

    let mut cursor = header_index;
    while let Some(prev) = HighLevelEmitter::previous_code_line(statements, cursor) {
        cursor = prev;
        let Some(assign) = HighLevelEmitter::parse_assignment(statements[prev].as_str()) else {
            continue;
        };
        if assign.lhs != token {
            continue;
        }
        let rhs = assign.rhs.trim().to_string();
        return is_literal(rhs.as_str()).then_some(rhs);
    }
    None
}

pub(super) fn is_literal(value: &str) -> bool {
    let value = value.trim();
    if value.is_empty() {
        return false;
    }
    if value.starts_with('"') && value.ends_with('"') && value.len() >= 2 {
        return true;
    }
    if value.starts_with('\'') && value.ends_with('\'') && value.len() >= 3 {
        return true;
    }
    if matches!(value, "true" | "false" | "null") {
        return true;
    }
    if value.starts_with("0x") && value.len() > 2 {
        return value[2..].chars().all(|ch| ch.is_ascii_hexdigit());
    }
    value.parse::<i64>().is_ok()
}

pub(super) fn is_temp(value: &str) -> bool {
    let value = value.trim();
    value.len() > 1 && value.starts_with('t') && value[1..].bytes().all(|b| b.is_ascii_digit())
}

pub(super) fn parse_inline_if_goto(line: &str) -> Option<(String, String)> {
    let line = line.trim();
    let rest = line.strip_prefix("if ")?;
    let (condition, suffix) = rest.split_once(" { goto ")?;
    let label = suffix.strip_suffix("; }")?.trim();
    if label.is_empty() {
        return None;
    }
    Some((condition.trim().to_string(), label.to_string()))
}

pub(super) fn parse_plain_goto_label(line: &str) -> Option<String> {
    let line = line.trim();
    let label = line.strip_prefix("goto ")?.strip_suffix(';')?.trim();
    if label.is_empty() {
        return None;
    }
    Some(label.to_string())
}

pub(super) fn parse_label_line(line: &str) -> Option<String> {
    let line = line.trim();
    let label = line.strip_suffix(':')?.trim();
    if !label.starts_with("label_") {
        return None;
    }
    Some(label.to_string())
}

fn split_equals(condition: &str) -> Option<(&str, &str)> {
    let pos = condition.find("==")?;
    let (left, rest) = condition.split_at(pos);
    let right = rest.strip_prefix("==")?;
    Some((left, right))
}