razgad 0.1.0

A library for decoding, classifying, normalizing, and re-emitting mangled, decorated, and runtime symbol names across a wide spread of compiler, platform, and language ecosystems.
Documentation
use crate::{Confidence, Name, Scheme, SpecialKind, Symbol, SymbolKind};

pub fn decode(scheme: Scheme, input: &str) -> Option<Symbol> {
    if let Some(class_name) = input.strip_prefix("_OBJC_CLASS_$_") {
        let mut symbol = Symbol::special(scheme, SpecialKind::ObjectiveCClass, [class_name]);
        symbol.concrete_family = Scheme::ObjectiveC;
        return Some(
            symbol
                .with_display(format!("Objective-C class {class_name}"))
                .with_verbatim(input),
        );
    }

    if input == "v@:" {
        return Some(
            Symbol::new(scheme, SymbolKind::TypeEncoding)
                .with_display("void self selector")
                .with_verbatim(input),
        );
    }

    if let Some(method) = parse_block_invoke(input) {
        let mut symbol = Symbol::new(scheme, SymbolKind::Runtime);
        if let Some((class_name, selector)) = parse_method(method) {
            symbol.path = vec![Name::identifier(class_name), Name::identifier(selector)];
        }
        return Some(
            symbol
                .with_display(format!("block invoke for {method}"))
                .with_verbatim(input),
        );
    }

    if let Some(method) = parse_cold_clone(input) {
        let mut symbol = Symbol::new(scheme, SymbolKind::Runtime);
        if let Some((class_name, selector)) = parse_method(method) {
            symbol.path = vec![Name::identifier(class_name), Name::identifier(selector)];
        }
        return Some(
            symbol
                .with_display(format!("cold clone of {method}"))
                .with_verbatim(input),
        );
    }

    if let Some(method) = parse_numeric_clone(input) {
        let mut symbol = Symbol::new(scheme, SymbolKind::Runtime);
        if let Some((class_name, selector)) = parse_method(method) {
            symbol.path = vec![Name::identifier(class_name), Name::identifier(selector)];
        }
        return Some(
            symbol
                .with_display(format!("clone of {method}"))
                .with_verbatim(input),
        );
    }

    if let Some((class_name, selector)) = parse_method(input) {
        let mut symbol = Symbol::new(scheme, SymbolKind::Method);
        symbol.path = vec![Name::identifier(class_name), Name::identifier(selector)];
        return Some(symbol.with_display(input).with_verbatim(input));
    }

    None
}

pub fn detect(input: &str) -> Option<(Scheme, Confidence)> {
    if input.starts_with("_OBJC_")
        || input.starts_with("OBJC_")
        || input.starts_with("-[")
        || input.starts_with("+[")
    {
        return Some((Scheme::ObjectiveC, Confidence::Certain));
    }
    if (input.contains("-[") || input.contains("+["))
        && (input.contains("_block_invoke") || input.contains(".cold"))
    {
        return Some((Scheme::ObjectiveC, Confidence::High));
    }
    if input == "v@:" {
        return Some((Scheme::ObjectiveC, Confidence::Medium));
    }
    None
}

fn parse_method(input: &str) -> Option<(&str, &str)> {
    let body = input
        .strip_prefix("-[")
        .or_else(|| input.strip_prefix("+["))?;
    let body = body.strip_suffix(']')?;
    let (class_name, selector) = body.split_once(' ')?;
    Some((class_name, selector))
}

fn parse_block_invoke(input: &str) -> Option<&str> {
    let start = input.find("-[").or_else(|| input.find("+["))?;
    let suffix = &input[start..];
    let end = suffix.find(']')?;
    suffix[end + 1..]
        .contains("_block_invoke")
        .then_some(&input[start..start + end + 1])
}

fn parse_cold_clone(input: &str) -> Option<&str> {
    if let Some((method, _)) = input.split_once(".cold") {
        if method.starts_with("-[") || method.starts_with("+[") {
            return Some(method);
        }
    }
    None
}

fn parse_numeric_clone(input: &str) -> Option<&str> {
    let (method, suffix) = input.rsplit_once('_')?;
    if !suffix.is_empty()
        && suffix.chars().all(|ch| ch.is_ascii_digit())
        && (method.starts_with("-[") || method.starts_with("+["))
    {
        Some(method)
    } else {
        None
    }
}