Skip to main content

xidl_parser/hir/annotation/
convert.rs

1use super::super::*;
2use super::{Annotation, AnnotationParam, AnnotationParams, RenameRule};
3use convert_case::{Case, Casing};
4use std::collections::HashMap;
5
6pub fn annotation_name(annotation: &Annotation) -> Option<&str> {
7    match annotation {
8        Annotation::Builtin { name, .. } => Some(name.as_str()),
9        Annotation::ScopedName { name, .. } => name.name.last().map(|value| value.as_str()),
10        _ => None,
11    }
12}
13
14pub fn annotation_params(annotation: &Annotation) -> Option<&AnnotationParams> {
15    match annotation {
16        Annotation::Builtin { params, .. } => params.as_ref(),
17        Annotation::ScopedName { params, .. } => params.as_ref(),
18        _ => None,
19    }
20}
21
22pub fn normalize_annotation_params(params: &AnnotationParams) -> HashMap<String, String> {
23    let mut out = HashMap::new();
24    match params {
25        AnnotationParams::Raw(value) => {
26            let parsed = parse_raw_annotation_params(value);
27            if parsed.is_empty() {
28                out.insert(
29                    "value".to_string(),
30                    trim_annotation_quotes(value).unwrap_or_else(|| value.clone()),
31                );
32            }
33            for (key, value) in parsed {
34                out.insert(key.to_ascii_lowercase(), value);
35            }
36        }
37        AnnotationParams::Params(values) => {
38            for value in values {
39                let raw = value
40                    .value
41                    .as_ref()
42                    .map(render_annotation_const_expr)
43                    .unwrap_or_default();
44                out.insert(
45                    value.ident.to_ascii_lowercase(),
46                    trim_annotation_quotes(&raw).unwrap_or(raw),
47                );
48            }
49        }
50        AnnotationParams::ConstExpr(expr) => {
51            let rendered = render_annotation_const_expr(expr);
52            out.insert(
53                "value".to_string(),
54                trim_annotation_quotes(&rendered).unwrap_or(rendered),
55            );
56        }
57        AnnotationParams::Positional(values) => {
58            let rendered = values
59                .iter()
60                .map(render_annotation_const_expr)
61                .collect::<Vec<_>>()
62                .join(", ");
63            out.insert(
64                "value".to_string(),
65                trim_annotation_quotes(&rendered).unwrap_or(rendered),
66            );
67        }
68    }
69    out
70}
71
72pub fn is_skipped(annotations: &[Annotation]) -> bool {
73    annotations.iter().any(|a| matches!(a, Annotation::Skip))
74}
75
76pub fn field_rename(annotations: &[Annotation]) -> Option<String> {
77    annotations.iter().find_map(|a| {
78        if let Annotation::Rename { name } = a {
79            Some(name.clone())
80        } else {
81            None
82        }
83    })
84}
85
86pub fn rename_all(annotations: &[Annotation]) -> Option<RenameRule> {
87    annotations.iter().find_map(|a| {
88        if let Annotation::RenameAll { rule } = a {
89            Some(rule.clone())
90        } else {
91            None
92        }
93    })
94}
95
96pub fn effective_wire_name(
97    raw_name: &str,
98    annotations: &[Annotation],
99    container_annotations: &[Annotation],
100) -> String {
101    field_rename(annotations).unwrap_or_else(|| {
102        if let Some(rule) = rename_all(container_annotations) {
103            apply_rename_rule(raw_name, rule)
104        } else {
105            raw_name.to_string()
106        }
107    })
108}
109
110pub fn annotation_id_value(annotations: &[Annotation]) -> Option<u32> {
111    for annotation in annotations {
112        if let Annotation::Id { value } = annotation {
113            if let Ok(value) = value.parse::<u32>() {
114                return Some(value);
115            }
116        }
117    }
118    None
119}
120
121pub fn expand_annotations(values: Vec<crate::typed_ast::AnnotationAppl>) -> Vec<Annotation> {
122    let mut out = Vec::new();
123    for value in values {
124        push_annotation(&mut out, value);
125    }
126    out
127}
128
129fn push_annotation(out: &mut Vec<Annotation>, mut value: crate::typed_ast::AnnotationAppl) {
130    let extra = std::mem::take(&mut value.extra);
131    out.push(Annotation::from(value));
132    for item in extra {
133        push_annotation(out, item);
134    }
135}
136
137impl From<crate::typed_ast::AnnotationAppl> for Annotation {
138    fn from(value: crate::typed_ast::AnnotationAppl) -> Self {
139        let params = value.params.map(Into::into);
140
141        let name_ref = match &value.name {
142            crate::typed_ast::AnnotationName::ScopedName(name) => Some(name.identifier.0.as_str()),
143            crate::typed_ast::AnnotationName::Builtin(name) => Some(name.as_str()),
144        };
145
146        if let Some(name) = name_ref {
147            match name.to_ascii_lowercase().as_str() {
148                "rename" | "name" => {
149                    if let Some(p) = &params {
150                        let normalized = normalize_annotation_params(p);
151                        if let Some(val) =
152                            normalized.get("value").or_else(|| normalized.get("name"))
153                        {
154                            return Self::Rename { name: val.clone() };
155                        }
156                    }
157                }
158                "rename_all" => {
159                    if let Some(p) = &params {
160                        let normalized = normalize_annotation_params(p);
161                        if let Some(val) =
162                            normalized.get("rule").or_else(|| normalized.get("value"))
163                        {
164                            return Self::RenameAll {
165                                rule: val.parse().unwrap_or(RenameRule::None),
166                            };
167                        }
168                    }
169                }
170                "skip" => return Self::Skip,
171                "version" => {
172                    if params.is_none() {
173                        return Self::Version;
174                    }
175                }
176                "added" | "removed" => {
177                    if let Some(AnnotationParams::ConstExpr(ConstExpr::ScopedName(version))) =
178                        params.as_ref()
179                    {
180                        let version = version.clone();
181                        return match name.to_ascii_lowercase().as_str() {
182                            "added" => Self::Added { version },
183                            _ => Self::Removed { version },
184                        };
185                    }
186                }
187                _ => {}
188            }
189        }
190
191        match value.name {
192            crate::typed_ast::AnnotationName::ScopedName(name) => Self::ScopedName {
193                name: name.into(),
194                params,
195            },
196            crate::typed_ast::AnnotationName::Builtin(name) => match value.builtin {
197                Some(builtin) => super::super::annotation_builtin::from_builtin_annotation(builtin)
198                    .unwrap_or(Self::Builtin { name, params }),
199                None => Self::Builtin { name, params },
200            },
201        }
202    }
203}
204
205impl From<crate::typed_ast::AnnotationParams> for AnnotationParams {
206    fn from(value: crate::typed_ast::AnnotationParams) -> Self {
207        match value {
208            crate::typed_ast::AnnotationParams::Params(params) => {
209                let mut positional = Vec::new();
210                let mut named = Vec::new();
211                for param in params {
212                    match param {
213                        crate::typed_ast::AnnotationApplParam::Positional(expr) => {
214                            positional.push(expr.into());
215                        }
216                        crate::typed_ast::AnnotationApplParam::Named { ident, value } => {
217                            named.push(AnnotationParam {
218                                ident: ident.0,
219                                value: Some(value.into()),
220                            });
221                        }
222                    }
223                }
224                if !positional.is_empty() && named.is_empty() {
225                    if positional.len() == 1 {
226                        Self::ConstExpr(positional.remove(0))
227                    } else {
228                        Self::Positional(positional)
229                    }
230                } else {
231                    Self::Params(named)
232                }
233            }
234            crate::typed_ast::AnnotationParams::Raw(value) => Self::Raw(value),
235        }
236    }
237}
238
239pub(super) fn apply_rename_rule(raw_name: &str, rule: RenameRule) -> String {
240    match rule {
241        RenameRule::None => raw_name.to_string(),
242        RenameRule::LowerCase => raw_name.to_case(Case::Flat),
243        RenameRule::UpperCase => raw_name.to_case(Case::UpperFlat),
244        RenameRule::PascalCase => raw_name.to_case(Case::Pascal),
245        RenameRule::CamelCase => raw_name.to_case(Case::Camel),
246        RenameRule::SnakeCase => raw_name.to_case(Case::Snake),
247        RenameRule::ScreamingSnakeCase => raw_name.to_case(Case::UpperSnake),
248        RenameRule::KebabCase => raw_name.to_case(Case::Kebab),
249        RenameRule::ScreamingKebabCase => raw_name.to_case(Case::Cobol),
250    }
251}
252
253fn parse_raw_annotation_params(raw: &str) -> Vec<(String, String)> {
254    let mut parts = Vec::new();
255    let mut buf = String::new();
256    let mut quote = None;
257    let mut escaped = false;
258
259    for ch in raw.chars() {
260        if escaped {
261            buf.push(ch);
262            escaped = false;
263            continue;
264        }
265        if ch == '\\' && quote.is_some() {
266            escaped = true;
267            buf.push(ch);
268            continue;
269        }
270        match ch {
271            '\'' | '"' => {
272                if quote == Some(ch) {
273                    quote = None;
274                } else if quote.is_none() {
275                    quote = Some(ch);
276                }
277                buf.push(ch);
278            }
279            ',' if quote.is_none() => {
280                let item = buf.trim();
281                if !item.is_empty() {
282                    parts.push(item.to_string());
283                }
284                buf.clear();
285            }
286            _ => buf.push(ch),
287        }
288    }
289
290    let item = buf.trim();
291    if !item.is_empty() {
292        parts.push(item.to_string());
293    }
294
295    parts
296        .into_iter()
297        .map(|part| {
298            if let Some((key, value)) = part.split_once('=') {
299                let value = trim_annotation_quotes(value.trim())
300                    .unwrap_or_else(|| value.trim().to_string());
301                (key.trim().to_string(), unescape_param_value(&value))
302            } else {
303                let value =
304                    trim_annotation_quotes(part.trim()).unwrap_or_else(|| part.trim().to_string());
305                ("value".to_string(), unescape_param_value(&value))
306            }
307        })
308        .collect()
309}
310
311fn unescape_param_value(value: &str) -> String {
312    let mut out = String::new();
313    let mut escaped = false;
314    for ch in value.chars() {
315        if escaped {
316            out.push(ch);
317            escaped = false;
318            continue;
319        }
320        if ch == '\\' {
321            escaped = true;
322            continue;
323        }
324        out.push(ch);
325    }
326    out
327}
328
329fn trim_annotation_quotes(value: &str) -> Option<String> {
330    let value = value.trim();
331    if value.len() < 2 {
332        return None;
333    }
334    let first = value.chars().next().unwrap();
335    let last = value.chars().last().unwrap();
336    if (first == '"' && last == '"') || (first == '\'' && last == '\'') {
337        Some(value[1..value.len() - 1].to_string())
338    } else {
339        None
340    }
341}
342
343pub(super) fn render_annotation_const_expr(expr: &ConstExpr) -> String {
344    match expr {
345        ConstExpr::ScopedName(value) => {
346            let prefix = if value.is_root { "::" } else { "" };
347            format!("{prefix}{}", value.name.join("::"))
348        }
349        ConstExpr::Literal(value) => render_annotation_literal(value),
350        ConstExpr::UnaryExpr(op, value) => {
351            let op = match op {
352                UnaryOperator::Add => "+",
353                UnaryOperator::Sub => "-",
354                UnaryOperator::Not => "~",
355            };
356            format!("({op}{})", render_annotation_const_expr(value))
357        }
358        ConstExpr::BinaryExpr(op, left, right) => {
359            let op = match op {
360                BinaryOperator::Or => "|",
361                BinaryOperator::Xor => "^",
362                BinaryOperator::And => "&",
363                BinaryOperator::LeftShift => "<<",
364                BinaryOperator::RightShift => ">>",
365                BinaryOperator::Add => "+",
366                BinaryOperator::Sub => "-",
367                BinaryOperator::Mult => "*",
368                BinaryOperator::Div => "/",
369                BinaryOperator::Mod => "%",
370            };
371            format!(
372                "({} {op} {})",
373                render_annotation_const_expr(left),
374                render_annotation_const_expr(right)
375            )
376        }
377    }
378}
379
380fn render_annotation_literal(value: &Literal) -> String {
381    match value {
382        Literal::IntegerLiteral(IntegerLiteral(value)) => value.clone(),
383        Literal::FloatingPtLiteral(value) => {
384            let sign = value.sign.as_ref().map(IntegerSign::as_str).unwrap_or("");
385            format!("{}{}.{}", sign, value.integer.0, value.fraction.0)
386        }
387        Literal::CharLiteral(value)
388        | Literal::WideCharacterLiteral(value)
389        | Literal::StringLiteral(value)
390        | Literal::WideStringLiteral(value) => value.clone(),
391        Literal::BooleanLiteral(value) => value.to_string(),
392    }
393}