neo-decompiler 0.10.2

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::calls::{
    format_helper_with_casts, match_collection_constructor, match_numeric_helper,
    match_unary_pattern,
};
use super::literals::{match_big_byte_literal, match_big_integer_literal, match_map_literal};

/// Apply the C# expression-level rewrites (`cat` to `+`, NEO helper calls to
/// framework/.NET forms) to a fragment that's already known to be a single
/// expression.
pub(super) fn csharpize_expression(text: &str) -> String {
    rewrite_numeric_helpers(&rewrite_cat_operator(text))
}

/// Rewrite NEO arithmetic, buffer, collection, and literal helper forms into
/// compilable C# forms. The rewrite is identifier-boundary aware and
/// string-aware.
fn rewrite_numeric_helpers(line: &str) -> String {
    let bytes = line.as_bytes();
    let mut out = String::with_capacity(line.len());
    let mut i = 0;
    let mut in_string: Option<u8> = None;
    while i < bytes.len() {
        let b = bytes[i];
        if !b.is_ascii() {
            if line.is_char_boundary(i) {
                let ch = line[i..].chars().next().unwrap_or('\u{FFFD}');
                out.push(ch);
                i += ch.len_utf8();
            } else {
                i += 1;
            }
            continue;
        }
        if let Some(quote) = in_string {
            out.push(b as char);
            if b == b'\\' && i + 1 < bytes.len() {
                let esc = line[i + 1..].chars().next().unwrap_or('\u{FFFD}');
                out.push(esc);
                i += 1 + esc.len_utf8();
                continue;
            }
            if b == quote {
                in_string = None;
            }
            i += 1;
            continue;
        }
        if b == b'"' || b == b'\'' {
            in_string = Some(b);
            out.push(b as char);
            i += 1;
            continue;
        }
        let prev_is_ident_continuation = i > 0 && {
            let p = bytes[i - 1];
            p.is_ascii_alphanumeric() || p == b'_'
        };
        if !prev_is_ident_continuation {
            if let Some(rendered) = match_unary_pattern(&line[i..]) {
                out.push_str(&rendered.body);
                i += rendered.consumed;
                continue;
            }
            if let Some((replacement, consumed)) = match_collection_constructor(&line[i..]) {
                out.push_str(replacement);
                i += consumed;
                continue;
            }
            if let Some((rendered, consumed)) = match_map_literal(&line[i..]) {
                out.push_str(&rendered);
                i += consumed;
                continue;
            }
            if let Some((rendered, consumed)) = match_big_byte_literal(&line[i..]) {
                out.push_str(&rendered);
                i += consumed;
                continue;
            }
            if let Some((rendered, consumed)) = match_big_integer_literal(&line[i..]) {
                out.push_str(&rendered);
                i += consumed;
                continue;
            }
            if let Some(rule) = match_numeric_helper(&bytes[i..]) {
                if !rule.int_cast_args.is_empty() {
                    if let Some(rendered) = format_helper_with_casts(&line[i..], &rule) {
                        out.push_str(&rendered.body);
                        i += rendered.consumed;
                        continue;
                    }
                }
                out.push_str(rule.replacement);
                out.push('(');
                i += rule.needle_len;
                continue;
            }
        }
        out.push(b as char);
        i += 1;
    }
    out
}

/// Translate the high-level `cat` (CAT / string-concat) operator to C#'s `+`.
/// The replacement only fires for ` cat ` tokens outside string literals.
fn rewrite_cat_operator(line: &str) -> String {
    if !line.contains(" cat ") {
        return line.to_string();
    }
    let bytes = line.as_bytes();
    let mut out = String::with_capacity(line.len());
    let mut i = 0;
    let mut in_string: Option<u8> = None;
    while i < bytes.len() {
        let b = bytes[i];
        if !b.is_ascii() {
            if line.is_char_boundary(i) {
                let ch = line[i..].chars().next().unwrap_or('\u{FFFD}');
                out.push(ch);
                i += ch.len_utf8();
            } else {
                i += 1;
            }
            continue;
        }
        if let Some(quote) = in_string {
            out.push(b as char);
            if b == b'\\' && i + 1 < bytes.len() {
                let esc = line[i + 1..].chars().next().unwrap_or('\u{FFFD}');
                out.push(esc);
                i += 1 + esc.len_utf8();
                continue;
            }
            if b == quote {
                in_string = None;
            }
            i += 1;
            continue;
        }
        if b == b'"' || b == b'\'' {
            in_string = Some(b);
            out.push(b as char);
            i += 1;
            continue;
        }
        if b == b' ' && i + 4 < bytes.len() && &bytes[i..i + 5] == b" cat " && i > 0 {
            out.push_str(" + ");
            i += 5;
            continue;
        }
        out.push(b as char);
        i += 1;
    }
    out
}