neo-decompiler 0.10.1

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;
use super::super::util::{extract_any_if_condition, is_else_if_open, is_else_open, is_if_open};
use super::parse::{
    is_literal, parse_case_sides, resolve_case_value, resolve_condition_expression,
};
use super::scan::{
    case_body_is_switch_safe, collect_trivia, end_of_if_chain, extract_block_body,
    find_first_if_in_range, find_next_if_after_case_prelude,
};

pub(super) fn try_build_switch(
    statements: &[String],
    start: usize,
) -> Option<(Vec<String>, usize)> {
    let header = statements.get(start)?.trim();
    if !is_if_open(header) {
        return None;
    }

    let mut cases: Vec<(String, Vec<String>)> = Vec::new();
    let mut default_body: Option<Vec<String>> = None;
    let mut overall_end = start;
    let mut has_else_link = false;

    let mut current_header = start;
    let mut scrutinee: Option<String> = None;

    loop {
        let header_line = statements.get(current_header)?.trim();
        let condition = extract_any_if_condition(header_line)?;
        let resolved = resolve_condition_expression(statements, current_header, condition)?;
        let (next_scrutinee, case_token) = parse_case_sides(resolved.as_str())?;

        let case_value = resolve_case_value(statements, current_header, case_token)?;
        if !is_literal(case_value.as_str()) {
            return None;
        }

        if let Some(existing) = &scrutinee {
            if existing != &next_scrutinee {
                return None;
            }
        } else {
            scrutinee = Some(next_scrutinee);
        }

        let (body, if_end) = extract_block_body(statements, current_header)?;
        overall_end = overall_end.max(if_end);
        cases.push((case_value, body));

        let (trivia, next_header) = collect_trivia(statements, if_end + 1);
        if next_header >= statements.len() {
            break;
        }

        let next_line = statements[next_header].trim();
        if is_else_if_open(next_line) {
            has_else_link = true;
            if let Some((_, last_body)) = cases.last_mut() {
                last_body.extend(trivia);
            }
            current_header = next_header;
            continue;
        }

        if is_else_open(next_line) {
            has_else_link = true;
            let else_end = HighLevelEmitter::find_block_end(statements, next_header)?;
            overall_end = overall_end.max(else_end);

            if let Some(inner_start) = find_first_if_in_range(statements, next_header + 1, else_end)
            {
                let inner_chain_end = end_of_if_chain(statements, inner_start)?;
                if inner_chain_end < else_end {
                    let (inner_trivia, after_inner) =
                        collect_trivia(statements, inner_chain_end + 1);
                    let only_trivia_left = after_inner == else_end;
                    if only_trivia_left {
                        if let Some((_, last_body)) = cases.last_mut() {
                            last_body.extend(trivia);
                            last_body.extend(inner_trivia);
                        }
                        current_header = inner_start;
                        overall_end = overall_end.max(else_end);
                        continue;
                    }
                }
            }

            if let Some((_, last_body)) = cases.last_mut() {
                last_body.extend(trivia);
            }

            default_body = Some(
                statements
                    .get(next_header + 1..else_end)
                    .unwrap_or_default()
                    .to_vec(),
            );
            break;
        }

        if let Some(next_if_header) = find_next_if_after_case_prelude(statements, if_end + 1) {
            let next_if_line = statements[next_if_header].trim();
            if let Some(cond) = extract_any_if_condition(next_if_line) {
                if let Some(resolved) =
                    resolve_condition_expression(statements, next_if_header, cond)
                {
                    if let Some((peek_scrutinee, _)) = parse_case_sides(resolved.as_str()) {
                        if scrutinee.as_deref() == Some(peek_scrutinee.as_str()) {
                            current_header = next_if_header;
                            continue;
                        }
                    }
                }
            }
        }

        break;
    }

    let scrutinee = scrutinee?;

    let min_cases = if has_else_link { 2 } else { 3 };
    if cases.len() < min_cases {
        return None;
    }

    let mut seen = std::collections::BTreeSet::new();
    if !cases.iter().all(|(value, _)| seen.insert(value.clone())) {
        return None;
    }

    if !has_else_link
        && !cases
            .iter()
            .all(|(_, body)| case_body_is_switch_safe(body, &scrutinee))
    {
        return None;
    }

    let mut output = Vec::new();
    output.push(format!("switch {scrutinee} {{"));
    for (value, body) in &cases {
        output.push(format!("case {value} {{"));
        output.extend(body.iter().cloned());
        output.push("}".into());
    }
    if let Some(body) = default_body {
        output.push("default {".into());
        output.extend(body);
        output.push("}".into());
    }
    output.push("}".into());

    Some((output, overall_end))
}