xidl-parser 0.63.0

A IDL codegen.
Documentation
use crate::hir;
use convert_case::{Case, Casing};

#[cfg(test)]
mod tests;

pub(super) fn parse_string_array(raw: &str) -> Vec<String> {
    let trimmed = raw.trim();
    let unquoted = trim_quotes(trimmed).unwrap_or_else(|| trimmed.to_string());
    split_top_level(&unquoted, ',')
        .into_iter()
        .map(|value| value.trim().to_string())
        .filter(|value| !value.is_empty())
        .collect()
}

pub(super) fn parse_raw_params(raw: &str) -> Vec<(String, String)> {
    let trimmed = raw.trim();
    if trimmed.is_empty() {
        return Vec::new();
    }
    if !trimmed.contains('=') {
        return vec![(
            "value".to_string(),
            trim_quotes(trimmed).unwrap_or_else(|| trimmed.to_string()),
        )];
    }
    split_top_level(trimmed, ',')
        .into_iter()
        .filter_map(|part| {
            let (key, value) = part.split_once('=')?;
            let key = key.trim();
            if key.is_empty() {
                return None;
            }
            Some((
                key.to_string(),
                trim_quotes(value.trim()).unwrap_or_else(|| value.trim().to_string()),
            ))
        })
        .collect()
}

pub(super) fn trim_quotes(value: &str) -> Option<String> {
    let value = value.trim();
    if value.len() >= 2 {
        let first = value.chars().next().unwrap();
        let last = value.chars().last().unwrap();
        if (first == '"' && last == '"') || (first == '\'' && last == '\'') {
            return Some(value[1..value.len() - 1].to_string());
        }
    }
    None
}

pub(super) fn render_const_expr(expr: &hir::ConstExpr) -> String {
    fn scoped_name(value: &hir::ScopedName) -> String {
        let parts = value
            .name
            .iter()
            .map(|part| part.to_case(Case::Snake))
            .collect::<Vec<_>>()
            .join("::");
        if value.is_root {
            format!("::{parts}")
        } else {
            parts
        }
    }

    fn literal(value: &hir::Literal) -> String {
        match value {
            hir::Literal::IntegerLiteral(value) => value.0.clone(),
            hir::Literal::FloatingPtLiteral(value) => {
                let sign = value
                    .sign
                    .as_ref()
                    .map(hir::IntegerSign::as_str)
                    .unwrap_or("");
                format!("{}{}.{}", sign, value.integer.0, value.fraction.0)
            }
            hir::Literal::CharLiteral(value)
            | hir::Literal::WideCharacterLiteral(value)
            | hir::Literal::StringLiteral(value)
            | hir::Literal::WideStringLiteral(value) => value.clone(),
            hir::Literal::BooleanLiteral(value) => value.to_string(),
        }
    }

    fn render(expr: &hir::ConstExpr) -> String {
        match expr {
            hir::ConstExpr::ScopedName(value) => scoped_name(value),
            hir::ConstExpr::Literal(value) => literal(value),
            hir::ConstExpr::UnaryExpr(op, value) => {
                let op = match op {
                    hir::UnaryOperator::Add => "+",
                    hir::UnaryOperator::Sub => "-",
                    hir::UnaryOperator::Not => "~",
                };
                format!("({op}{})", render(value))
            }
            hir::ConstExpr::BinaryExpr(op, left, right) => {
                let op = match op {
                    hir::BinaryOperator::Or => "|",
                    hir::BinaryOperator::Xor => "^",
                    hir::BinaryOperator::And => "&",
                    hir::BinaryOperator::LeftShift => "<<",
                    hir::BinaryOperator::RightShift => ">>",
                    hir::BinaryOperator::Add => "+",
                    hir::BinaryOperator::Sub => "-",
                    hir::BinaryOperator::Mult => "*",
                    hir::BinaryOperator::Div => "/",
                    hir::BinaryOperator::Mod => "%",
                };
                format!("({} {op} {})", render(left), render(right))
            }
        }
    }

    render(expr)
}

fn split_top_level(raw: &str, separator: char) -> Vec<String> {
    let mut parts = Vec::new();
    let mut current = String::new();
    let mut quote = None;
    let mut bracket_depth = 0usize;
    let mut paren_depth = 0usize;
    let mut escaped = false;
    for ch in raw.chars() {
        if let Some(q) = quote {
            current.push(ch);
            if escaped {
                escaped = false;
                continue;
            }
            if ch == '\\' {
                escaped = true;
                continue;
            }
            if ch == q {
                quote = None;
            }
            continue;
        }
        match ch {
            '"' | '\'' => {
                quote = Some(ch);
                current.push(ch);
            }
            '[' => {
                bracket_depth += 1;
                current.push(ch);
            }
            ']' => {
                bracket_depth = bracket_depth.saturating_sub(1);
                current.push(ch);
            }
            '(' => {
                paren_depth += 1;
                current.push(ch);
            }
            ')' => {
                paren_depth = paren_depth.saturating_sub(1);
                current.push(ch);
            }
            _ if ch == separator && bracket_depth == 0 && paren_depth == 0 => {
                parts.push(current.trim().to_string());
                current.clear();
            }
            _ => current.push(ch),
        }
    }
    if !current.trim().is_empty() {
        parts.push(current.trim().to_string());
    }
    parts
}