Skip to main content

xidl_parser/hir/annotation/
convert.rs

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