Skip to main content

xidl_parser/hir/
annotation.rs

1use super::*;
2use convert_case::{Case, Casing};
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[cfg(test)]
7mod tests;
8#[derive(Debug, Serialize, Deserialize, Clone)]
9pub enum Annotation {
10    Id {
11        value: String,
12    },
13    Key {
14        value: Option<String>,
15    },
16    AutoId {
17        value: Option<String>,
18    },
19    Optional {
20        value: Option<String>,
21    },
22    Position {
23        value: String,
24    },
25    Value {
26        value: String,
27    },
28    Extensibility {
29        kind: String,
30    },
31    Final,
32    Appendable,
33    Mutable,
34    MustUnderstand {
35        value: Option<String>,
36    },
37    Default {
38        value: String,
39    },
40    Range {
41        min: String,
42        max: String,
43    },
44    Min {
45        value: String,
46    },
47    Max {
48        value: String,
49    },
50    Unit {
51        value: String,
52    },
53    BitBound {
54        value: String,
55    },
56    External {
57        value: Option<String>,
58    },
59    Nested {
60        value: Option<String>,
61    },
62    Verbatim {
63        language: Option<String>,
64        placement: Option<String>,
65        text: String,
66    },
67    Service {
68        platform: Option<String>,
69    },
70    Oneway {
71        value: Option<String>,
72    },
73    Ami {
74        value: Option<String>,
75    },
76    HashId {
77        value: Option<String>,
78    },
79    DefaultNested {
80        value: Option<String>,
81    },
82    IgnoreLiteralNames {
83        value: Option<String>,
84    },
85    TryConstruct {
86        value: Option<String>,
87    },
88    NonSerialized {
89        value: Option<String>,
90    },
91    DataRepresentation {
92        kinds: Vec<String>,
93    },
94    Topic {
95        name: Option<String>,
96        platform: Option<String>,
97    },
98    Choice,
99    Empty,
100    DdsService,
101    DdsRequestTopic {
102        name: String,
103    },
104    DdsReplyTopic {
105        name: String,
106    },
107    Builtin {
108        name: String,
109        params: Option<AnnotationParams>,
110    },
111    ScopedName {
112        name: ScopedName,
113        params: Option<AnnotationParams>,
114    },
115    DefaultLiteral,
116}
117
118#[derive(Debug, Serialize, Deserialize, Clone)]
119pub enum AnnotationParams {
120    ConstExpr(ConstExpr),
121    Positional(Vec<ConstExpr>),
122    Params(Vec<AnnotationParam>),
123    Raw(String),
124}
125
126#[derive(Debug, Serialize, Deserialize, Clone)]
127pub struct AnnotationParam {
128    pub ident: String,
129    pub value: Option<ConstExpr>,
130}
131
132pub fn annotation_name(annotation: &Annotation) -> Option<&str> {
133    match annotation {
134        Annotation::Builtin { name, .. } => Some(name.as_str()),
135        Annotation::ScopedName { name, .. } => name.name.last().map(|value| value.as_str()),
136        _ => None,
137    }
138}
139
140pub fn annotation_params(annotation: &Annotation) -> Option<&AnnotationParams> {
141    match annotation {
142        Annotation::Builtin { params, .. } => params.as_ref(),
143        Annotation::ScopedName { params, .. } => params.as_ref(),
144        _ => None,
145    }
146}
147
148pub fn normalize_annotation_params(params: &AnnotationParams) -> HashMap<String, String> {
149    let mut out = HashMap::new();
150    match params {
151        AnnotationParams::Raw(value) => {
152            let parsed = parse_raw_annotation_params(value);
153            if parsed.is_empty() {
154                out.insert(
155                    "value".to_string(),
156                    trim_annotation_quotes(value).unwrap_or_else(|| value.clone()),
157                );
158            }
159            for (key, value) in parsed {
160                out.insert(key.to_ascii_lowercase(), value);
161            }
162        }
163        AnnotationParams::Params(values) => {
164            for value in values {
165                let raw = value
166                    .value
167                    .as_ref()
168                    .map(render_annotation_const_expr)
169                    .unwrap_or_default();
170                out.insert(
171                    value.ident.to_ascii_lowercase(),
172                    trim_annotation_quotes(&raw).unwrap_or(raw),
173                );
174            }
175        }
176        AnnotationParams::ConstExpr(expr) => {
177            let rendered = render_annotation_const_expr(expr);
178            out.insert(
179                "value".to_string(),
180                trim_annotation_quotes(&rendered).unwrap_or(rendered),
181            );
182        }
183        AnnotationParams::Positional(values) => {
184            let rendered = values
185                .iter()
186                .map(render_annotation_const_expr)
187                .collect::<Vec<_>>()
188                .join(", ");
189            out.insert(
190                "value".to_string(),
191                trim_annotation_quotes(&rendered).unwrap_or(rendered),
192            );
193        }
194    }
195    out
196}
197
198pub fn field_rename(annotations: &[Annotation]) -> Option<String> {
199    annotation_string_param(annotations, "rename", &["value", "name"])
200        .or_else(|| annotation_string_param(annotations, "name", &["value", "name"]))
201}
202
203pub fn serialize_name(annotations: &[Annotation]) -> Option<String> {
204    annotation_string_param(annotations, "serialize_name", &["serialize", "value"])
205}
206
207pub fn deserialize_name(annotations: &[Annotation]) -> Option<String> {
208    deserialize_names(annotations).into_iter().next()
209}
210
211pub fn deserialize_aliases(annotations: &[Annotation]) -> Vec<String> {
212    let mut names = deserialize_names(annotations);
213    if names.len() > 1 {
214        names.drain(1..).collect()
215    } else {
216        Vec::new()
217    }
218}
219
220pub fn rename_all(annotations: &[Annotation]) -> Option<String> {
221    annotation_string_param(annotations, "rename_all", &["rule", "value"])
222}
223
224pub fn effective_wire_name(
225    raw_name: &str,
226    annotations: &[Annotation],
227    container_annotations: &[Annotation],
228) -> String {
229    field_rename(annotations)
230        .or_else(|| serialize_name(annotations))
231        .or_else(|| deserialize_name(annotations))
232        .unwrap_or_else(|| apply_rename_rule(raw_name, rename_all(container_annotations)))
233}
234
235pub fn annotation_id_value(annotations: &[Annotation]) -> Option<u32> {
236    for annotation in annotations {
237        if let Annotation::Id { value } = annotation {
238            if let Ok(value) = value.parse::<u32>() {
239                return Some(value);
240            }
241        }
242    }
243    None
244}
245
246pub fn expand_annotations(values: Vec<crate::typed_ast::AnnotationAppl>) -> Vec<Annotation> {
247    let mut out = Vec::new();
248    for value in values {
249        push_annotation(&mut out, value);
250    }
251    out
252}
253
254fn push_annotation(out: &mut Vec<Annotation>, mut value: crate::typed_ast::AnnotationAppl) {
255    let extra = std::mem::take(&mut value.extra);
256    out.push(Annotation::from(value));
257    for item in extra {
258        push_annotation(out, item);
259    }
260}
261
262impl From<crate::typed_ast::AnnotationAppl> for Annotation {
263    fn from(value: crate::typed_ast::AnnotationAppl) -> Self {
264        let params = value.params.map(Into::into);
265        match value.name {
266            crate::typed_ast::AnnotationName::ScopedName(name) => Self::ScopedName {
267                name: name.into(),
268                params,
269            },
270            crate::typed_ast::AnnotationName::Builtin(name) => match value.builtin {
271                Some(builtin) => super::annotation_builtin::from_builtin_annotation(builtin)
272                    .unwrap_or(Self::Builtin { name, params }),
273                None => Self::Builtin { name, params },
274            },
275        }
276    }
277}
278
279impl From<crate::typed_ast::AnnotationParams> for AnnotationParams {
280    fn from(value: crate::typed_ast::AnnotationParams) -> Self {
281        match value {
282            crate::typed_ast::AnnotationParams::Params(params) => {
283                let mut positional = Vec::new();
284                let mut named = Vec::new();
285                for param in params {
286                    match param {
287                        crate::typed_ast::AnnotationApplParam::Positional(expr) => {
288                            positional.push(expr.into());
289                        }
290                        crate::typed_ast::AnnotationApplParam::Named { ident, value } => {
291                            named.push(AnnotationParam {
292                                ident: ident.0,
293                                value: Some(value.into()),
294                            });
295                        }
296                    }
297                }
298                if !positional.is_empty() && named.is_empty() {
299                    if positional.len() == 1 {
300                        Self::ConstExpr(positional.remove(0))
301                    } else {
302                        Self::Positional(positional)
303                    }
304                } else {
305                    Self::Params(named)
306                }
307            }
308            crate::typed_ast::AnnotationParams::Raw(value) => Self::Raw(value),
309        }
310    }
311}
312
313fn deserialize_names(annotations: &[Annotation]) -> Vec<String> {
314    let mut names = Vec::new();
315    for annotation in annotations {
316        let Some(name) = annotation_name(annotation) else {
317            continue;
318        };
319        if !name.eq_ignore_ascii_case("deserialize_name") {
320            continue;
321        }
322        let Some(value) = annotation_params(annotation)
323            .map(normalize_annotation_params)
324            .and_then(|params| {
325                params
326                    .get("deserialize")
327                    .cloned()
328                    .or_else(|| params.get("value").cloned())
329            })
330        else {
331            continue;
332        };
333        names.extend(parse_string_list(&value));
334    }
335    names
336}
337
338fn annotation_string_param(
339    annotations: &[Annotation],
340    target: &str,
341    keys: &[&str],
342) -> Option<String> {
343    annotations.iter().find_map(|annotation| {
344        let name = annotation_name(annotation)?;
345        if !name.eq_ignore_ascii_case(target) {
346            return None;
347        }
348        let params = annotation_params(annotation)?;
349        let normalized = normalize_annotation_params(params);
350        keys.iter()
351            .find_map(|key| normalized.get(&key.to_ascii_lowercase()).cloned())
352    })
353}
354
355fn parse_string_list(value: &str) -> Vec<String> {
356    let value = value.trim();
357    if !(value.starts_with('[') && value.ends_with(']')) {
358        if value.contains(',') {
359            return value
360                .split(',')
361                .map(|part| part.trim().trim_matches('"'))
362                .filter(|part| !part.is_empty())
363                .map(ToString::to_string)
364                .collect();
365        }
366        if !value.is_empty() {
367            return vec![value.trim_matches('"').to_string()];
368        } else {
369            return vec![];
370        }
371    }
372    let inner = &value[1..value.len() - 1];
373    inner
374        .split(',')
375        .map(|part| part.trim().trim_matches('"'))
376        .filter(|part| !part.is_empty())
377        .map(ToString::to_string)
378        .collect()
379}
380
381fn apply_rename_rule(raw_name: &str, rule: Option<String>) -> String {
382    match rule.as_deref() {
383        Some("None") | None => raw_name.to_string(),
384        Some("lowercase") => raw_name.to_case(Case::Flat),
385        Some("UPPERCASE") => raw_name.to_case(Case::UpperFlat),
386        Some("PascalCase") => raw_name.to_case(Case::Pascal),
387        Some("camelCase") => raw_name.to_case(Case::Camel),
388        Some("snake_case") => raw_name.to_case(Case::Snake),
389        Some("SCREAMING_SNAKE_CASE") | Some("SCREAMINGSNAKECASE") => {
390            raw_name.to_case(Case::UpperSnake)
391        }
392        Some("kebab-case") => raw_name.to_case(Case::Kebab),
393        Some("SCREAMING-KEBAB-CASE") => raw_name.to_case(Case::Cobol),
394        Some(_) => raw_name.to_string(),
395    }
396}
397
398fn parse_raw_annotation_params(raw: &str) -> Vec<(String, String)> {
399    let mut parts = Vec::new();
400    let mut buf = String::new();
401    let mut quote = None;
402    let mut escaped = false;
403
404    for ch in raw.chars() {
405        if escaped {
406            buf.push(ch);
407            escaped = false;
408            continue;
409        }
410        if ch == '\\' && quote.is_some() {
411            escaped = true;
412            buf.push(ch);
413            continue;
414        }
415        match ch {
416            '\'' | '"' => {
417                if quote == Some(ch) {
418                    quote = None;
419                } else if quote.is_none() {
420                    quote = Some(ch);
421                }
422                buf.push(ch);
423            }
424            ',' if quote.is_none() => {
425                let item = buf.trim();
426                if !item.is_empty() {
427                    parts.push(item.to_string());
428                }
429                buf.clear();
430            }
431            _ => buf.push(ch),
432        }
433    }
434
435    let item = buf.trim();
436    if !item.is_empty() {
437        parts.push(item.to_string());
438    }
439
440    parts
441        .into_iter()
442        .map(|part| {
443            if let Some((key, value)) = part.split_once('=') {
444                let value = trim_annotation_quotes(value.trim())
445                    .unwrap_or_else(|| value.trim().to_string());
446                (key.trim().to_string(), unescape_param_value(&value))
447            } else {
448                let value =
449                    trim_annotation_quotes(part.trim()).unwrap_or_else(|| part.trim().to_string());
450                ("value".to_string(), unescape_param_value(&value))
451            }
452        })
453        .collect()
454}
455
456fn unescape_param_value(value: &str) -> String {
457    let mut out = String::new();
458    let mut escaped = false;
459    for ch in value.chars() {
460        if escaped {
461            out.push(ch);
462            escaped = false;
463            continue;
464        }
465        if ch == '\\' {
466            escaped = true;
467            continue;
468        }
469        out.push(ch);
470    }
471    out
472}
473
474fn trim_annotation_quotes(value: &str) -> Option<String> {
475    let value = value.trim();
476    if value.len() < 2 {
477        return None;
478    }
479    let first = value.chars().next().unwrap();
480    let last = value.chars().last().unwrap();
481    if (first == '"' && last == '"') || (first == '\'' && last == '\'') {
482        Some(value[1..value.len() - 1].to_string())
483    } else {
484        None
485    }
486}
487
488fn render_annotation_const_expr(expr: &ConstExpr) -> String {
489    match expr {
490        ConstExpr::ScopedName(value) => {
491            let prefix = if value.is_root { "::" } else { "" };
492            format!("{prefix}{}", value.name.join("::"))
493        }
494        ConstExpr::Literal(value) => render_annotation_literal(value),
495        ConstExpr::UnaryExpr(op, value) => {
496            let op = match op {
497                UnaryOperator::Add => "+",
498                UnaryOperator::Sub => "-",
499                UnaryOperator::Not => "~",
500            };
501            format!("({op}{})", render_annotation_const_expr(value))
502        }
503        ConstExpr::BinaryExpr(op, left, right) => {
504            let op = match op {
505                BinaryOperator::Or => "|",
506                BinaryOperator::Xor => "^",
507                BinaryOperator::And => "&",
508                BinaryOperator::LeftShift => "<<",
509                BinaryOperator::RightShift => ">>",
510                BinaryOperator::Add => "+",
511                BinaryOperator::Sub => "-",
512                BinaryOperator::Mult => "*",
513                BinaryOperator::Div => "/",
514                BinaryOperator::Mod => "%",
515            };
516            format!(
517                "({} {op} {})",
518                render_annotation_const_expr(left),
519                render_annotation_const_expr(right)
520            )
521        }
522    }
523}
524
525fn render_annotation_literal(value: &Literal) -> String {
526    match value {
527        Literal::IntegerLiteral(IntegerLiteral(value)) => value.clone(),
528        Literal::FloatingPtLiteral(value) => {
529            let sign = value.sign.as_ref().map(IntegerSign::as_str).unwrap_or("");
530            format!("{}{}.{}", sign, value.integer.0, value.fraction.0)
531        }
532        Literal::CharLiteral(value)
533        | Literal::WideCharacterLiteral(value)
534        | Literal::StringLiteral(value)
535        | Literal::WideStringLiteral(value) => value.clone(),
536        Literal::BooleanLiteral(value) => value.to_string(),
537    }
538}