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) = ¶ms {
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) = ¶ms {
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}