Skip to main content

xidl_parser/hir/
pragma.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Serialize, Deserialize, Clone)]
4pub enum Pragma {
5    Custom(CustomPragma),
6    XidlcPackage(String),
7    XidlcOpenApiVersion(String),
8    XidlcOpenApiService {
9        base_url: String,
10        description: Option<String>,
11    },
12}
13
14#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
15pub struct CustomPragma {
16    pub directive: String,
17    pub argument: Option<String>,
18}
19
20pub(crate) fn parse_xidlc_pragma(call: &crate::typed_ast::PreprocCall) -> Option<Pragma> {
21    let directive = call.directive.0.as_str();
22    if !directive.eq_ignore_ascii_case("#pragma") {
23        return None;
24    }
25
26    let Some(arg) = call.argument.as_ref().map(|arg| arg.0.as_str()) else {
27        return Some(Pragma::Custom(CustomPragma::from(call)));
28    };
29    let mut parts = arg.split_whitespace();
30    let Some(namespace) = parts.next() else {
31        return Some(Pragma::Custom(CustomPragma::from(call)));
32    };
33    if !namespace.eq_ignore_ascii_case("xidlc") {
34        return Some(Pragma::Custom(CustomPragma::from(call)));
35    }
36
37    let Some(token) = parts.next() else {
38        return Some(Pragma::Custom(CustomPragma::from(call)));
39    };
40    let rest = parts.collect::<Vec<_>>().join(" ");
41    if token.eq_ignore_ascii_case("package") {
42        return Some(if rest.is_empty() {
43            Pragma::Custom(CustomPragma::from(call))
44        } else {
45            Pragma::XidlcPackage(trim_pragma_value(&rest))
46        });
47    }
48    if token.eq_ignore_ascii_case("version") {
49        return Some(if rest.is_empty() {
50            Pragma::Custom(CustomPragma::from(call))
51        } else {
52            Pragma::XidlcOpenApiVersion(trim_pragma_value(&rest))
53        });
54    }
55    if token.eq_ignore_ascii_case("service") {
56        return Some(
57            parse_pragma_service(&rest)
58                .map(|(base_url, description)| Pragma::XidlcOpenApiService {
59                    base_url,
60                    description,
61                })
62                .unwrap_or_else(|| Pragma::Custom(CustomPragma::from(call))),
63        );
64    }
65    if token.eq_ignore_ascii_case("openapi") {
66        return Some(
67            parse_nested_openapi_pragma(&rest)
68                .unwrap_or_else(|| Pragma::Custom(CustomPragma::from(call))),
69        );
70    }
71
72    Some(Pragma::Custom(CustomPragma::from(call)))
73}
74
75pub(crate) fn trim_pragma_value(value: &str) -> String {
76    let value = value.trim();
77    if value.len() >= 2 {
78        let first = value.chars().next().unwrap();
79        let last = value.chars().last().unwrap();
80        if (first == '"' && last == '"') || (first == '\'' && last == '\'') {
81            return value[1..value.len() - 1].to_string();
82        }
83    }
84    value.to_string()
85}
86
87fn parse_nested_openapi_pragma(rest: &str) -> Option<Pragma> {
88    let mut nested = rest.split_whitespace();
89    let token = nested.next()?;
90    let nested_rest = nested.collect::<Vec<_>>().join(" ");
91    if token.eq_ignore_ascii_case("version") && !nested_rest.is_empty() {
92        return Some(Pragma::XidlcOpenApiVersion(trim_pragma_value(&nested_rest)));
93    }
94    if token.eq_ignore_ascii_case("service") {
95        return parse_pragma_service(&nested_rest).map(|(base_url, description)| {
96            Pragma::XidlcOpenApiService {
97                base_url,
98                description,
99            }
100        });
101    }
102    None
103}
104
105fn parse_pragma_service(value: &str) -> Option<(String, Option<String>)> {
106    let value = value.trim();
107    if value.is_empty() {
108        return None;
109    }
110
111    let (base_url, remainder) = if value.starts_with('"') || value.starts_with('\'') {
112        let quote = value.chars().next().unwrap();
113        let end = value.char_indices().skip(1).find(|(_, ch)| *ch == quote)?.0;
114        (value[1..end].to_string(), value[end + 1..].trim())
115    } else {
116        let mut parts = value.splitn(2, char::is_whitespace);
117        (parts.next()?.to_string(), parts.next().unwrap_or("").trim())
118    };
119
120    let description = (!remainder.is_empty()).then(|| trim_pragma_value(remainder));
121    Some((base_url, description))
122}
123
124impl From<&crate::typed_ast::PreprocCall> for CustomPragma {
125    fn from(value: &crate::typed_ast::PreprocCall) -> Self {
126        Self {
127            directive: value.directive.0.clone(),
128            argument: value.argument.as_ref().map(|arg| arg.0.clone()),
129        }
130    }
131}
132
133#[cfg(test)]
134mod tests;