macho-header-syntax 0.5.6

Process-free typed C, C++, and Objective-C header syntax
Documentation
use crate::{CallingConvention, Decl, Language, RecordKind, Type};

use super::{
    ParseError, contains_record_body, find_top_level, linkage, matching_delimiter,
    parse_function_qualifiers, parse_identifier, parse_parameters, parse_path, parse_storage,
    parse_type, record_tag, split_top_level, split_type_and_declarators, split_type_and_name,
    starts_with_record, strip_attributes,
};

pub(super) fn lower_declaration(text: &str, language: Language) -> Result<Vec<Decl>, ParseError> {
    let trimmed = strip_attributes(text.trim().trim_end_matches(';').trim());
    if trimmed.is_empty() {
        return Ok(Vec::new());
    }
    if let Some(using) = trimmed.strip_prefix("using ") {
        let (name, target) = using
            .split_once('=')
            .ok_or_else(|| ParseError::InvalidDeclaration(trimmed.to_owned()))?;
        return Ok(vec![Decl::Alias {
            path: parse_path(name)?,
            target: parse_type(target, language)?,
        }]);
    }
    if let Some(typedef) = trimmed.strip_prefix("typedef ") {
        if contains_record_body(trimmed) {
            return lower_typedef_record(trimmed, language);
        }
        let rest = typedef.trim();
        let (target, name) = split_type_and_name(rest)?;
        return Ok(vec![Decl::Alias {
            path: parse_path(name)?,
            target: parse_type(target, language)?,
        }]);
    }
    if starts_with_record(trimmed) {
        return Ok(vec![lower_record(trimmed, language)?]);
    }
    if let Some(open) = find_top_level(trimmed, '(')
        && let Some(close) = matching_delimiter(trimmed, open, '(', ')')
    {
        let prefix = trimmed[..open].trim();
        let (return_text, name_text) = split_type_and_name(prefix)?;
        let name = parse_identifier(name_text)?;
        let (parameters, variadic, parameter_state) =
            parse_parameters(&trimmed[open + 1..close], language)?;
        let signature = Type::Function {
            return_type: Box::new(parse_type(return_text, language)?),
            parameters,
            parameter_state,
            variadic,
            calling_convention: CallingConvention::C,
            qualifiers: parse_function_qualifiers(&trimmed[close + 1..]),
        };
        return Ok(vec![Decl::Function {
            name,
            signature,
            storage: parse_storage(trimmed),
            linkage: linkage(language),
        }]);
    }
    let (ty, names) = split_type_and_declarators(trimmed)?;
    let storage = parse_storage(trimmed);
    let ty = parse_type(ty, language)?;
    names
        .into_iter()
        .map(|name| {
            Ok(Decl::Variable {
                name: parse_identifier(name.trim_start_matches('*').trim())?,
                ty: ty.clone(),
                storage,
                linkage: linkage(language),
            })
        })
        .collect()
}

fn lower_typedef_record(text: &str, language: Language) -> Result<Vec<Decl>, ParseError> {
    let open = text
        .find('{')
        .ok_or_else(|| ParseError::InvalidDeclaration(text.to_owned()))?;
    let close = matching_delimiter(text, open, '{', '}')
        .ok_or_else(|| ParseError::InvalidDeclaration(text.to_owned()))?;
    let before = text[..open].trim_start_matches("typedef").trim();
    let record = lower_record(&text[text.find(before).unwrap_or(0)..=close], language)?;
    let alias = text[close + 1..].trim();
    if alias.is_empty() {
        return Ok(vec![record]);
    }
    let target_path = match &record {
        Decl::Record { path, .. } => path.clone(),
        _ => unreachable!(),
    };
    let kind = match &record {
        Decl::Record { kind, .. } => record_tag(*kind),
        _ => unreachable!(),
    };
    Ok(vec![
        record,
        Decl::Alias {
            path: parse_path(alias)?,
            target: Type::Named {
                tag: kind,
                path: target_path,
                template_arguments: Vec::new(),
            },
        },
    ])
}

pub(super) fn lower_record(text: &str, language: Language) -> Result<Decl, ParseError> {
    let text = text.trim().trim_end_matches(';').trim();
    let (kind, keyword) = if text.starts_with("struct ") {
        (RecordKind::Struct, "struct")
    } else if text.starts_with("union ") {
        (RecordKind::Union, "union")
    } else if text.starts_with("class ") {
        (RecordKind::Class, "class")
    } else if text.starts_with("enum ") {
        (RecordKind::Enum, "enum")
    } else {
        return Err(ParseError::InvalidDeclaration(text.to_owned()));
    };
    let rest = text[keyword.len()..].trim();
    let name_end = rest
        .find(|ch: char| ch.is_whitespace() || ch == '{' || ch == ':')
        .unwrap_or(rest.len());
    let path = parse_path(&rest[..name_end])?;
    let Some(open) = text.find('{') else {
        return Ok(Decl::Forward { kind, path });
    };
    let close = matching_delimiter(text, open, '{', '}')
        .ok_or_else(|| ParseError::InvalidDeclaration(text.to_owned()))?;
    let mut fields = Vec::new();
    let mut members = Vec::new();
    let mut access = match kind {
        RecordKind::Class => crate::Access::Private,
        _ => crate::Access::Public,
    };
    for raw_statement in split_top_level(&text[open + 1..close], ';') {
        let (next_access, statement) = strip_access_specifier(raw_statement.trim(), access);
        access = next_access;
        if statement.is_empty() {
            continue;
        }
        for declaration in lower_declaration(statement, language)? {
            match declaration {
                Decl::Variable { name, ty, .. } => fields.push(crate::Field {
                    name,
                    ty,
                    offset: None,
                    bit_width: None,
                    access,
                }),
                other => members.push(other),
            }
        }
    }
    Ok(Decl::Record {
        kind,
        path,
        bases: Vec::new(),
        fields,
        members,
    })
}

fn strip_access_specifier(text: &str, current: crate::Access) -> (crate::Access, &str) {
    for (prefix, access) in [
        ("public:", crate::Access::Public),
        ("protected:", crate::Access::Protected),
        ("private:", crate::Access::Private),
    ] {
        if let Some(rest) = text.strip_prefix(prefix) {
            return (access, rest.trim());
        }
    }
    (current, text)
}