xidl-parser 0.50.2

A IDL codegen.
Documentation
use super::{SerializeKind, SerializeVersion};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Pragma {
    Custom(CustomPragma),
    XidlcSerialize(SerializeKind),
    XidlcVersion(SerializeVersion),
    XidlcPackage(String),
    XidlcOpenApiVersion(String),
    XidlcOpenApiService {
        base_url: String,
        description: Option<String>,
    },
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct CustomPragma {
    pub directive: String,
    pub argument: Option<String>,
}

pub(crate) fn parse_xidlc_pragma(call: &crate::typed_ast::PreprocCall) -> Option<Pragma> {
    let directive = call.directive.0.as_str();
    if !directive.eq_ignore_ascii_case("#pragma") {
        return None;
    }

    let Some(arg) = call.argument.as_ref().map(|arg| arg.0.as_str()) else {
        return Some(Pragma::Custom(CustomPragma::from(call)));
    };
    let mut parts = arg.split_whitespace();
    let Some(namespace) = parts.next() else {
        return Some(Pragma::Custom(CustomPragma::from(call)));
    };
    if !namespace.eq_ignore_ascii_case("xidlc") {
        return Some(Pragma::Custom(CustomPragma::from(call)));
    }

    let Some(token) = parts.next() else {
        return Some(Pragma::Custom(CustomPragma::from(call)));
    };
    let rest = parts.collect::<Vec<_>>().join(" ");
    if token.eq_ignore_ascii_case("XCDR1") {
        return Some(Pragma::XidlcVersion(SerializeVersion::Xcdr1));
    }
    if token.eq_ignore_ascii_case("XCDR2") {
        return Some(Pragma::XidlcVersion(SerializeVersion::Xcdr2));
    }
    if token.eq_ignore_ascii_case("package") {
        return Some(if rest.is_empty() {
            Pragma::Custom(CustomPragma::from(call))
        } else {
            Pragma::XidlcPackage(trim_pragma_value(&rest))
        });
    }
    if token.eq_ignore_ascii_case("version") {
        return Some(if rest.is_empty() {
            Pragma::Custom(CustomPragma::from(call))
        } else {
            Pragma::XidlcOpenApiVersion(trim_pragma_value(&rest))
        });
    }
    if token.eq_ignore_ascii_case("service") {
        return Some(
            parse_pragma_service(&rest)
                .map(|(base_url, description)| Pragma::XidlcOpenApiService {
                    base_url,
                    description,
                })
                .unwrap_or_else(|| Pragma::Custom(CustomPragma::from(call))),
        );
    }
    if token.eq_ignore_ascii_case("openapi") {
        return Some(
            parse_nested_openapi_pragma(&rest)
                .unwrap_or_else(|| Pragma::Custom(CustomPragma::from(call))),
        );
    }

    token
        .strip_prefix("serialize(")
        .and_then(|value| value.strip_suffix(')'))
        .and_then(parse_serialize_pragma)
        .or_else(|| Some(Pragma::Custom(CustomPragma::from(call))))
}

pub(crate) fn trim_pragma_value(value: &str) -> 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 value[1..value.len() - 1].to_string();
        }
    }
    value.to_string()
}

fn parse_nested_openapi_pragma(rest: &str) -> Option<Pragma> {
    let mut nested = rest.split_whitespace();
    let token = nested.next()?;
    let nested_rest = nested.collect::<Vec<_>>().join(" ");
    if token.eq_ignore_ascii_case("version") && !nested_rest.is_empty() {
        return Some(Pragma::XidlcOpenApiVersion(trim_pragma_value(&nested_rest)));
    }
    if token.eq_ignore_ascii_case("service") {
        return parse_pragma_service(&nested_rest).map(|(base_url, description)| {
            Pragma::XidlcOpenApiService {
                base_url,
                description,
            }
        });
    }
    None
}

fn parse_serialize_pragma(value: &str) -> Option<Pragma> {
    let value = value.trim();
    if value.eq_ignore_ascii_case("XCDR1") {
        return Some(Pragma::XidlcVersion(SerializeVersion::Xcdr1));
    }
    if value.eq_ignore_ascii_case("XCDR2") {
        return Some(Pragma::XidlcVersion(SerializeVersion::Xcdr2));
    }
    parse_serialize_kind(value).map(Pragma::XidlcSerialize)
}

fn parse_pragma_service(value: &str) -> Option<(String, Option<String>)> {
    let value = value.trim();
    if value.is_empty() {
        return None;
    }

    let (base_url, remainder) = if value.starts_with('"') || value.starts_with('\'') {
        let quote = value.chars().next().unwrap();
        let end = value.char_indices().skip(1).find(|(_, ch)| *ch == quote)?.0;
        (value[1..end].to_string(), value[end + 1..].trim())
    } else {
        let mut parts = value.splitn(2, char::is_whitespace);
        (parts.next()?.to_string(), parts.next().unwrap_or("").trim())
    };

    let description = (!remainder.is_empty()).then(|| trim_pragma_value(remainder));
    Some((base_url, description))
}

fn parse_serialize_kind(value: &str) -> Option<SerializeKind> {
    if value.eq_ignore_ascii_case("CDR") {
        Some(SerializeKind::Cdr)
    } else if value.eq_ignore_ascii_case("PLAIN_CDR") {
        Some(SerializeKind::PlainCdr)
    } else if value.eq_ignore_ascii_case("PL_CDR") {
        Some(SerializeKind::PlCdr)
    } else if value.eq_ignore_ascii_case("PLAIN_CDR2") {
        Some(SerializeKind::PlainCdr2)
    } else if value.eq_ignore_ascii_case("DELIMITED_CDR") {
        Some(SerializeKind::DelimitedCdr)
    } else if value.eq_ignore_ascii_case("PL_CDR2") {
        Some(SerializeKind::PlCdr2)
    } else {
        None
    }
}

impl From<&crate::typed_ast::PreprocCall> for CustomPragma {
    fn from(value: &crate::typed_ast::PreprocCall) -> Self {
        Self {
            directive: value.directive.0.clone(),
            argument: value.argument.as_ref().map(|arg| arg.0.clone()),
        }
    }
}

#[cfg(test)]
mod tests;