xidl_parser/hir/annotation/
convert.rs1use 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) = ¶ms {
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) = ¶ms {
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 _ => {}
172 }
173 }
174
175 match value.name {
176 crate::typed_ast::AnnotationName::ScopedName(name) => Self::ScopedName {
177 name: name.into(),
178 params,
179 },
180 crate::typed_ast::AnnotationName::Builtin(name) => match value.builtin {
181 Some(builtin) => super::super::annotation_builtin::from_builtin_annotation(builtin)
182 .unwrap_or(Self::Builtin { name, params }),
183 None => Self::Builtin { name, params },
184 },
185 }
186 }
187}
188
189impl From<crate::typed_ast::AnnotationParams> for AnnotationParams {
190 fn from(value: crate::typed_ast::AnnotationParams) -> Self {
191 match value {
192 crate::typed_ast::AnnotationParams::Params(params) => {
193 let mut positional = Vec::new();
194 let mut named = Vec::new();
195 for param in params {
196 match param {
197 crate::typed_ast::AnnotationApplParam::Positional(expr) => {
198 positional.push(expr.into());
199 }
200 crate::typed_ast::AnnotationApplParam::Named { ident, value } => {
201 named.push(AnnotationParam {
202 ident: ident.0,
203 value: Some(value.into()),
204 });
205 }
206 }
207 }
208 if !positional.is_empty() && named.is_empty() {
209 if positional.len() == 1 {
210 Self::ConstExpr(positional.remove(0))
211 } else {
212 Self::Positional(positional)
213 }
214 } else {
215 Self::Params(named)
216 }
217 }
218 crate::typed_ast::AnnotationParams::Raw(value) => Self::Raw(value),
219 }
220 }
221}
222
223pub(super) fn apply_rename_rule(raw_name: &str, rule: RenameRule) -> String {
224 match rule {
225 RenameRule::None => raw_name.to_string(),
226 RenameRule::LowerCase => raw_name.to_case(Case::Flat),
227 RenameRule::UpperCase => raw_name.to_case(Case::UpperFlat),
228 RenameRule::PascalCase => raw_name.to_case(Case::Pascal),
229 RenameRule::CamelCase => raw_name.to_case(Case::Camel),
230 RenameRule::SnakeCase => raw_name.to_case(Case::Snake),
231 RenameRule::ScreamingSnakeCase => raw_name.to_case(Case::UpperSnake),
232 RenameRule::KebabCase => raw_name.to_case(Case::Kebab),
233 RenameRule::ScreamingKebabCase => raw_name.to_case(Case::Cobol),
234 }
235}
236
237fn parse_raw_annotation_params(raw: &str) -> Vec<(String, String)> {
238 let mut parts = Vec::new();
239 let mut buf = String::new();
240 let mut quote = None;
241 let mut escaped = false;
242
243 for ch in raw.chars() {
244 if escaped {
245 buf.push(ch);
246 escaped = false;
247 continue;
248 }
249 if ch == '\\' && quote.is_some() {
250 escaped = true;
251 buf.push(ch);
252 continue;
253 }
254 match ch {
255 '\'' | '"' => {
256 if quote == Some(ch) {
257 quote = None;
258 } else if quote.is_none() {
259 quote = Some(ch);
260 }
261 buf.push(ch);
262 }
263 ',' if quote.is_none() => {
264 let item = buf.trim();
265 if !item.is_empty() {
266 parts.push(item.to_string());
267 }
268 buf.clear();
269 }
270 _ => buf.push(ch),
271 }
272 }
273
274 let item = buf.trim();
275 if !item.is_empty() {
276 parts.push(item.to_string());
277 }
278
279 parts
280 .into_iter()
281 .map(|part| {
282 if let Some((key, value)) = part.split_once('=') {
283 let value = trim_annotation_quotes(value.trim())
284 .unwrap_or_else(|| value.trim().to_string());
285 (key.trim().to_string(), unescape_param_value(&value))
286 } else {
287 let value =
288 trim_annotation_quotes(part.trim()).unwrap_or_else(|| part.trim().to_string());
289 ("value".to_string(), unescape_param_value(&value))
290 }
291 })
292 .collect()
293}
294
295fn unescape_param_value(value: &str) -> String {
296 let mut out = String::new();
297 let mut escaped = false;
298 for ch in value.chars() {
299 if escaped {
300 out.push(ch);
301 escaped = false;
302 continue;
303 }
304 if ch == '\\' {
305 escaped = true;
306 continue;
307 }
308 out.push(ch);
309 }
310 out
311}
312
313fn trim_annotation_quotes(value: &str) -> Option<String> {
314 let value = value.trim();
315 if value.len() < 2 {
316 return None;
317 }
318 let first = value.chars().next().unwrap();
319 let last = value.chars().last().unwrap();
320 if (first == '"' && last == '"') || (first == '\'' && last == '\'') {
321 Some(value[1..value.len() - 1].to_string())
322 } else {
323 None
324 }
325}
326
327pub(super) fn render_annotation_const_expr(expr: &ConstExpr) -> String {
328 match expr {
329 ConstExpr::ScopedName(value) => {
330 let prefix = if value.is_root { "::" } else { "" };
331 format!("{prefix}{}", value.name.join("::"))
332 }
333 ConstExpr::Literal(value) => render_annotation_literal(value),
334 ConstExpr::UnaryExpr(op, value) => {
335 let op = match op {
336 UnaryOperator::Add => "+",
337 UnaryOperator::Sub => "-",
338 UnaryOperator::Not => "~",
339 };
340 format!("({op}{})", render_annotation_const_expr(value))
341 }
342 ConstExpr::BinaryExpr(op, left, right) => {
343 let op = match op {
344 BinaryOperator::Or => "|",
345 BinaryOperator::Xor => "^",
346 BinaryOperator::And => "&",
347 BinaryOperator::LeftShift => "<<",
348 BinaryOperator::RightShift => ">>",
349 BinaryOperator::Add => "+",
350 BinaryOperator::Sub => "-",
351 BinaryOperator::Mult => "*",
352 BinaryOperator::Div => "/",
353 BinaryOperator::Mod => "%",
354 };
355 format!(
356 "({} {op} {})",
357 render_annotation_const_expr(left),
358 render_annotation_const_expr(right)
359 )
360 }
361 }
362}
363
364fn render_annotation_literal(value: &Literal) -> String {
365 match value {
366 Literal::IntegerLiteral(IntegerLiteral(value)) => value.clone(),
367 Literal::FloatingPtLiteral(value) => {
368 let sign = value.sign.as_ref().map(IntegerSign::as_str).unwrap_or("");
369 format!("{}{}.{}", sign, value.integer.0, value.fraction.0)
370 }
371 Literal::CharLiteral(value)
372 | Literal::WideCharacterLiteral(value)
373 | Literal::StringLiteral(value)
374 | Literal::WideStringLiteral(value) => value.clone(),
375 Literal::BooleanLiteral(value) => value.to_string(),
376 }
377}