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}