Skip to main content

xidl_parser/hir/
annotation.rs

1use super::*;
2use convert_case::{Case, Casing};
3use serde::{Deserialize, Serialize};
4use std::{collections::HashMap, str::FromStr};
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    Rename {
117        name: String,
118    },
119    RenameAll {
120        rule: RenameRule,
121    },
122    Skip,
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
126pub enum RenameRule {
127    None,
128    LowerCase,
129    UpperCase,
130    PascalCase,
131    CamelCase,
132    SnakeCase,
133    ScreamingSnakeCase,
134    KebabCase,
135    ScreamingKebabCase,
136}
137
138impl RenameRule {
139    pub fn as_str(&self) -> &'static str {
140        match self {
141            Self::None => "None",
142            Self::LowerCase => "lowercase",
143            Self::UpperCase => "UPPERCASE",
144            Self::PascalCase => "PascalCase",
145            Self::CamelCase => "camelCase",
146            Self::SnakeCase => "snake_case",
147            Self::ScreamingSnakeCase => "SCREAMING_SNAKE_CASE",
148            Self::KebabCase => "kebab-case",
149            Self::ScreamingKebabCase => "SCREAMING-KEBAB-CASE",
150        }
151    }
152}
153impl FromStr for RenameRule {
154    type Err = ();
155
156    fn from_str(s: &str) -> Result<Self, Self::Err> {
157        match s {
158            "lowercase" => Ok(Self::LowerCase),
159            "UPPERCASE" => Ok(Self::UpperCase),
160            "PascalCase" => Ok(Self::PascalCase),
161            "camelCase" => Ok(Self::CamelCase),
162            "snake_case" => Ok(Self::SnakeCase),
163            "SCREAMING_SNAKE_CASE" | "SCREAMINGSNAKECASE" => Ok(Self::ScreamingSnakeCase),
164            "kebab-case" => Ok(Self::KebabCase),
165            "SCREAMING-KEBAB-CASE" => Ok(Self::ScreamingKebabCase),
166            _ => Ok(Self::None),
167        }
168    }
169}
170
171#[derive(Debug, Serialize, Deserialize, Clone)]
172pub enum AnnotationParams {
173    ConstExpr(ConstExpr),
174    Positional(Vec<ConstExpr>),
175    Params(Vec<AnnotationParam>),
176    Raw(String),
177}
178
179#[derive(Debug, Serialize, Deserialize, Clone)]
180pub struct AnnotationParam {
181    pub ident: String,
182    pub value: Option<ConstExpr>,
183}
184
185pub fn annotation_name(annotation: &Annotation) -> Option<&str> {
186    match annotation {
187        Annotation::Builtin { name, .. } => Some(name.as_str()),
188        Annotation::ScopedName { name, .. } => name.name.last().map(|value| value.as_str()),
189        _ => None,
190    }
191}
192
193pub fn annotation_params(annotation: &Annotation) -> Option<&AnnotationParams> {
194    match annotation {
195        Annotation::Builtin { params, .. } => params.as_ref(),
196        Annotation::ScopedName { params, .. } => params.as_ref(),
197        _ => None,
198    }
199}
200
201pub fn normalize_annotation_params(params: &AnnotationParams) -> HashMap<String, String> {
202    let mut out = HashMap::new();
203    match params {
204        AnnotationParams::Raw(value) => {
205            let parsed = parse_raw_annotation_params(value);
206            if parsed.is_empty() {
207                out.insert(
208                    "value".to_string(),
209                    trim_annotation_quotes(value).unwrap_or_else(|| value.clone()),
210                );
211            }
212            for (key, value) in parsed {
213                out.insert(key.to_ascii_lowercase(), value);
214            }
215        }
216        AnnotationParams::Params(values) => {
217            for value in values {
218                let raw = value
219                    .value
220                    .as_ref()
221                    .map(render_annotation_const_expr)
222                    .unwrap_or_default();
223                out.insert(
224                    value.ident.to_ascii_lowercase(),
225                    trim_annotation_quotes(&raw).unwrap_or(raw),
226                );
227            }
228        }
229        AnnotationParams::ConstExpr(expr) => {
230            let rendered = render_annotation_const_expr(expr);
231            out.insert(
232                "value".to_string(),
233                trim_annotation_quotes(&rendered).unwrap_or(rendered),
234            );
235        }
236        AnnotationParams::Positional(values) => {
237            let rendered = values
238                .iter()
239                .map(render_annotation_const_expr)
240                .collect::<Vec<_>>()
241                .join(", ");
242            out.insert(
243                "value".to_string(),
244                trim_annotation_quotes(&rendered).unwrap_or(rendered),
245            );
246        }
247    }
248    out
249}
250
251pub fn is_skipped(annotations: &[Annotation]) -> bool {
252    annotations.iter().any(|a| matches!(a, Annotation::Skip))
253}
254
255pub fn field_rename(annotations: &[Annotation]) -> Option<String> {
256    annotations.iter().find_map(|a| {
257        if let Annotation::Rename { name } = a {
258            Some(name.clone())
259        } else {
260            None
261        }
262    })
263}
264
265pub fn rename_all(annotations: &[Annotation]) -> Option<RenameRule> {
266    annotations.iter().find_map(|a| {
267        if let Annotation::RenameAll { rule } = a {
268            Some(rule.clone())
269        } else {
270            None
271        }
272    })
273}
274
275pub fn effective_wire_name(
276    raw_name: &str,
277    annotations: &[Annotation],
278    container_annotations: &[Annotation],
279) -> String {
280    field_rename(annotations).unwrap_or_else(|| {
281        if let Some(rule) = rename_all(container_annotations) {
282            apply_rename_rule(raw_name, rule)
283        } else {
284            raw_name.to_string()
285        }
286    })
287}
288
289pub fn annotation_id_value(annotations: &[Annotation]) -> Option<u32> {
290    for annotation in annotations {
291        if let Annotation::Id { value } = annotation {
292            if let Ok(value) = value.parse::<u32>() {
293                return Some(value);
294            }
295        }
296    }
297    None
298}
299
300pub fn expand_annotations(values: Vec<crate::typed_ast::AnnotationAppl>) -> Vec<Annotation> {
301    let mut out = Vec::new();
302    for value in values {
303        push_annotation(&mut out, value);
304    }
305    out
306}
307
308fn push_annotation(out: &mut Vec<Annotation>, mut value: crate::typed_ast::AnnotationAppl) {
309    let extra = std::mem::take(&mut value.extra);
310    out.push(Annotation::from(value));
311    for item in extra {
312        push_annotation(out, item);
313    }
314}
315
316impl From<crate::typed_ast::AnnotationAppl> for Annotation {
317    fn from(value: crate::typed_ast::AnnotationAppl) -> Self {
318        let params = value.params.map(Into::into);
319
320        let name_ref = match &value.name {
321            crate::typed_ast::AnnotationName::ScopedName(name) => Some(name.identifier.0.as_str()),
322            crate::typed_ast::AnnotationName::Builtin(name) => Some(name.as_str()),
323        };
324
325        if let Some(name) = name_ref {
326            match name.to_ascii_lowercase().as_str() {
327                "rename" | "name" => {
328                    if let Some(p) = &params {
329                        let normalized = normalize_annotation_params(p);
330                        if let Some(val) =
331                            normalized.get("value").or_else(|| normalized.get("name"))
332                        {
333                            return Self::Rename { name: val.clone() };
334                        }
335                    }
336                }
337                "rename_all" => {
338                    if let Some(p) = &params {
339                        let normalized = normalize_annotation_params(p);
340                        if let Some(val) =
341                            normalized.get("rule").or_else(|| normalized.get("value"))
342                        {
343                            return Self::RenameAll {
344                                rule: val.parse().unwrap_or(RenameRule::None),
345                            };
346                        }
347                    }
348                }
349                "skip" => return Self::Skip,
350                _ => {}
351            }
352        }
353
354        match value.name {
355            crate::typed_ast::AnnotationName::ScopedName(name) => Self::ScopedName {
356                name: name.into(),
357                params,
358            },
359            crate::typed_ast::AnnotationName::Builtin(name) => match value.builtin {
360                Some(builtin) => super::annotation_builtin::from_builtin_annotation(builtin)
361                    .unwrap_or(Self::Builtin { name, params }),
362                None => Self::Builtin { name, params },
363            },
364        }
365    }
366}
367
368impl From<crate::typed_ast::AnnotationParams> for AnnotationParams {
369    fn from(value: crate::typed_ast::AnnotationParams) -> Self {
370        match value {
371            crate::typed_ast::AnnotationParams::Params(params) => {
372                let mut positional = Vec::new();
373                let mut named = Vec::new();
374                for param in params {
375                    match param {
376                        crate::typed_ast::AnnotationApplParam::Positional(expr) => {
377                            positional.push(expr.into());
378                        }
379                        crate::typed_ast::AnnotationApplParam::Named { ident, value } => {
380                            named.push(AnnotationParam {
381                                ident: ident.0,
382                                value: Some(value.into()),
383                            });
384                        }
385                    }
386                }
387                if !positional.is_empty() && named.is_empty() {
388                    if positional.len() == 1 {
389                        Self::ConstExpr(positional.remove(0))
390                    } else {
391                        Self::Positional(positional)
392                    }
393                } else {
394                    Self::Params(named)
395                }
396            }
397            crate::typed_ast::AnnotationParams::Raw(value) => Self::Raw(value),
398        }
399    }
400}
401
402fn apply_rename_rule(raw_name: &str, rule: RenameRule) -> String {
403    match rule {
404        RenameRule::None => raw_name.to_string(),
405        RenameRule::LowerCase => raw_name.to_case(Case::Flat),
406        RenameRule::UpperCase => raw_name.to_case(Case::UpperFlat),
407        RenameRule::PascalCase => raw_name.to_case(Case::Pascal),
408        RenameRule::CamelCase => raw_name.to_case(Case::Camel),
409        RenameRule::SnakeCase => raw_name.to_case(Case::Snake),
410        RenameRule::ScreamingSnakeCase => raw_name.to_case(Case::UpperSnake),
411        RenameRule::KebabCase => raw_name.to_case(Case::Kebab),
412        RenameRule::ScreamingKebabCase => raw_name.to_case(Case::Cobol),
413    }
414}
415
416fn parse_raw_annotation_params(raw: &str) -> Vec<(String, String)> {
417    let mut parts = Vec::new();
418    let mut buf = String::new();
419    let mut quote = None;
420    let mut escaped = false;
421
422    for ch in raw.chars() {
423        if escaped {
424            buf.push(ch);
425            escaped = false;
426            continue;
427        }
428        if ch == '\\' && quote.is_some() {
429            escaped = true;
430            buf.push(ch);
431            continue;
432        }
433        match ch {
434            '\'' | '"' => {
435                if quote == Some(ch) {
436                    quote = None;
437                } else if quote.is_none() {
438                    quote = Some(ch);
439                }
440                buf.push(ch);
441            }
442            ',' if quote.is_none() => {
443                let item = buf.trim();
444                if !item.is_empty() {
445                    parts.push(item.to_string());
446                }
447                buf.clear();
448            }
449            _ => buf.push(ch),
450        }
451    }
452
453    let item = buf.trim();
454    if !item.is_empty() {
455        parts.push(item.to_string());
456    }
457
458    parts
459        .into_iter()
460        .map(|part| {
461            if let Some((key, value)) = part.split_once('=') {
462                let value = trim_annotation_quotes(value.trim())
463                    .unwrap_or_else(|| value.trim().to_string());
464                (key.trim().to_string(), unescape_param_value(&value))
465            } else {
466                let value =
467                    trim_annotation_quotes(part.trim()).unwrap_or_else(|| part.trim().to_string());
468                ("value".to_string(), unescape_param_value(&value))
469            }
470        })
471        .collect()
472}
473
474fn unescape_param_value(value: &str) -> String {
475    let mut out = String::new();
476    let mut escaped = false;
477    for ch in value.chars() {
478        if escaped {
479            out.push(ch);
480            escaped = false;
481            continue;
482        }
483        if ch == '\\' {
484            escaped = true;
485            continue;
486        }
487        out.push(ch);
488    }
489    out
490}
491
492fn trim_annotation_quotes(value: &str) -> Option<String> {
493    let value = value.trim();
494    if value.len() < 2 {
495        return None;
496    }
497    let first = value.chars().next().unwrap();
498    let last = value.chars().last().unwrap();
499    if (first == '"' && last == '"') || (first == '\'' && last == '\'') {
500        Some(value[1..value.len() - 1].to_string())
501    } else {
502        None
503    }
504}
505
506fn render_annotation_const_expr(expr: &ConstExpr) -> String {
507    match expr {
508        ConstExpr::ScopedName(value) => {
509            let prefix = if value.is_root { "::" } else { "" };
510            format!("{prefix}{}", value.name.join("::"))
511        }
512        ConstExpr::Literal(value) => render_annotation_literal(value),
513        ConstExpr::UnaryExpr(op, value) => {
514            let op = match op {
515                UnaryOperator::Add => "+",
516                UnaryOperator::Sub => "-",
517                UnaryOperator::Not => "~",
518            };
519            format!("({op}{})", render_annotation_const_expr(value))
520        }
521        ConstExpr::BinaryExpr(op, left, right) => {
522            let op = match op {
523                BinaryOperator::Or => "|",
524                BinaryOperator::Xor => "^",
525                BinaryOperator::And => "&",
526                BinaryOperator::LeftShift => "<<",
527                BinaryOperator::RightShift => ">>",
528                BinaryOperator::Add => "+",
529                BinaryOperator::Sub => "-",
530                BinaryOperator::Mult => "*",
531                BinaryOperator::Div => "/",
532                BinaryOperator::Mod => "%",
533            };
534            format!(
535                "({} {op} {})",
536                render_annotation_const_expr(left),
537                render_annotation_const_expr(right)
538            )
539        }
540    }
541}
542
543fn render_annotation_literal(value: &Literal) -> String {
544    match value {
545        Literal::IntegerLiteral(IntegerLiteral(value)) => value.clone(),
546        Literal::FloatingPtLiteral(value) => {
547            let sign = value.sign.as_ref().map(IntegerSign::as_str).unwrap_or("");
548            format!("{}{}.{}", sign, value.integer.0, value.fraction.0)
549        }
550        Literal::CharLiteral(value)
551        | Literal::WideCharacterLiteral(value)
552        | Literal::StringLiteral(value)
553        | Literal::WideStringLiteral(value) => value.clone(),
554        Literal::BooleanLiteral(value) => value.to_string(),
555    }
556}