Skip to main content

xidl_parser/hir/
pragma.rs

1use super::{SerializeKind, SerializeVersion};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Serialize, Deserialize, Clone)]
5pub enum Pragma {
6    Custom(CustomPragma),
7    XidlcSerialize(SerializeKind),
8    XidlcVersion(SerializeVersion),
9    XidlcPackage(String),
10    XidlcOpenApiVersion(String),
11    XidlcOpenApiService {
12        base_url: String,
13        description: Option<String>,
14    },
15}
16
17#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
18pub struct CustomPragma {
19    pub directive: String,
20    pub argument: Option<String>,
21}
22
23pub(crate) fn parse_xidlc_pragma(call: &crate::typed_ast::PreprocCall) -> Option<Pragma> {
24    let directive = call.directive.0.as_str();
25    if !directive.eq_ignore_ascii_case("#pragma") {
26        return None;
27    }
28
29    let Some(arg) = call.argument.as_ref().map(|arg| arg.0.as_str()) else {
30        return Some(Pragma::Custom(CustomPragma::from(call)));
31    };
32    let mut parts = arg.split_whitespace();
33    let Some(namespace) = parts.next() else {
34        return Some(Pragma::Custom(CustomPragma::from(call)));
35    };
36    if !namespace.eq_ignore_ascii_case("xidlc") {
37        return Some(Pragma::Custom(CustomPragma::from(call)));
38    }
39
40    let Some(token) = parts.next() else {
41        return Some(Pragma::Custom(CustomPragma::from(call)));
42    };
43    let rest = parts.collect::<Vec<_>>().join(" ");
44    if token.eq_ignore_ascii_case("XCDR1") {
45        return Some(Pragma::XidlcVersion(SerializeVersion::Xcdr1));
46    }
47    if token.eq_ignore_ascii_case("XCDR2") {
48        return Some(Pragma::XidlcVersion(SerializeVersion::Xcdr2));
49    }
50    if token.eq_ignore_ascii_case("package") {
51        return Some(if rest.is_empty() {
52            Pragma::Custom(CustomPragma::from(call))
53        } else {
54            Pragma::XidlcPackage(trim_pragma_value(&rest))
55        });
56    }
57    if token.eq_ignore_ascii_case("version") {
58        return Some(if rest.is_empty() {
59            Pragma::Custom(CustomPragma::from(call))
60        } else {
61            Pragma::XidlcOpenApiVersion(trim_pragma_value(&rest))
62        });
63    }
64    if token.eq_ignore_ascii_case("service") {
65        return Some(
66            parse_pragma_service(&rest)
67                .map(|(base_url, description)| Pragma::XidlcOpenApiService {
68                    base_url,
69                    description,
70                })
71                .unwrap_or_else(|| Pragma::Custom(CustomPragma::from(call))),
72        );
73    }
74    if token.eq_ignore_ascii_case("openapi") {
75        return Some(
76            parse_nested_openapi_pragma(&rest)
77                .unwrap_or_else(|| Pragma::Custom(CustomPragma::from(call))),
78        );
79    }
80
81    token
82        .strip_prefix("serialize(")
83        .and_then(|value| value.strip_suffix(')'))
84        .and_then(parse_serialize_pragma)
85        .or_else(|| Some(Pragma::Custom(CustomPragma::from(call))))
86}
87
88pub(crate) fn trim_pragma_value(value: &str) -> String {
89    let value = value.trim();
90    if value.len() >= 2 {
91        let first = value.chars().next().unwrap();
92        let last = value.chars().last().unwrap();
93        if (first == '"' && last == '"') || (first == '\'' && last == '\'') {
94            return value[1..value.len() - 1].to_string();
95        }
96    }
97    value.to_string()
98}
99
100fn parse_nested_openapi_pragma(rest: &str) -> Option<Pragma> {
101    let mut nested = rest.split_whitespace();
102    let token = nested.next()?;
103    let nested_rest = nested.collect::<Vec<_>>().join(" ");
104    if token.eq_ignore_ascii_case("version") && !nested_rest.is_empty() {
105        return Some(Pragma::XidlcOpenApiVersion(trim_pragma_value(&nested_rest)));
106    }
107    if token.eq_ignore_ascii_case("service") {
108        return parse_pragma_service(&nested_rest).map(|(base_url, description)| {
109            Pragma::XidlcOpenApiService {
110                base_url,
111                description,
112            }
113        });
114    }
115    None
116}
117
118fn parse_serialize_pragma(value: &str) -> Option<Pragma> {
119    let value = value.trim();
120    if value.eq_ignore_ascii_case("XCDR1") {
121        return Some(Pragma::XidlcVersion(SerializeVersion::Xcdr1));
122    }
123    if value.eq_ignore_ascii_case("XCDR2") {
124        return Some(Pragma::XidlcVersion(SerializeVersion::Xcdr2));
125    }
126    parse_serialize_kind(value).map(Pragma::XidlcSerialize)
127}
128
129fn parse_pragma_service(value: &str) -> Option<(String, Option<String>)> {
130    let value = value.trim();
131    if value.is_empty() {
132        return None;
133    }
134
135    let (base_url, remainder) = if value.starts_with('"') || value.starts_with('\'') {
136        let quote = value.chars().next().unwrap();
137        let end = value.char_indices().skip(1).find(|(_, ch)| *ch == quote)?.0;
138        (value[1..end].to_string(), value[end + 1..].trim())
139    } else {
140        let mut parts = value.splitn(2, char::is_whitespace);
141        (parts.next()?.to_string(), parts.next().unwrap_or("").trim())
142    };
143
144    let description = (!remainder.is_empty()).then(|| trim_pragma_value(remainder));
145    Some((base_url, description))
146}
147
148fn parse_serialize_kind(value: &str) -> Option<SerializeKind> {
149    if value.eq_ignore_ascii_case("CDR") {
150        Some(SerializeKind::Cdr)
151    } else if value.eq_ignore_ascii_case("PLAIN_CDR") {
152        Some(SerializeKind::PlainCdr)
153    } else if value.eq_ignore_ascii_case("PL_CDR") {
154        Some(SerializeKind::PlCdr)
155    } else if value.eq_ignore_ascii_case("PLAIN_CDR2") {
156        Some(SerializeKind::PlainCdr2)
157    } else if value.eq_ignore_ascii_case("DELIMITED_CDR") {
158        Some(SerializeKind::DelimitedCdr)
159    } else if value.eq_ignore_ascii_case("PL_CDR2") {
160        Some(SerializeKind::PlCdr2)
161    } else {
162        None
163    }
164}
165
166impl From<&crate::typed_ast::PreprocCall> for CustomPragma {
167    fn from(value: &crate::typed_ast::PreprocCall) -> Self {
168        Self {
169            directive: value.directive.0.clone(),
170            argument: value.argument.as_ref().map(|arg| arg.0.clone()),
171        }
172    }
173}
174
175#[cfg(test)]
176mod tests;