1use std::borrow::Cow;
2
3pub use crate::config::{Case, IdentFormat};
4use crate::{
5 svd::{Access, Device, Field, RegisterInfo, RegisterProperties},
6 Config,
7};
8use inflections::Inflect;
9use proc_macro2::{Ident, Span, TokenStream};
10use quote::quote;
11use std::collections::HashSet;
12use svd_rs::{MaybeArray, Peripheral, PeripheralInfo};
13
14use syn::{
15 punctuated::Punctuated, token::PathSep, Lit, LitInt, PathArguments, PathSegment, Type, TypePath,
16};
17
18use anyhow::{anyhow, Result};
19
20pub const BITS_PER_BYTE: u32 = 8;
21
22const BLACKLIST_CHARS: &[char] = &['(', ')', '[', ']', '/', ' ', '-'];
25
26fn to_pascal_case(s: &str) -> String {
27 if !s.contains('_') {
28 s.to_pascal_case()
29 } else {
30 let mut string = String::new();
31 let mut parts = s.split('_').peekable();
32 if let Some(&"") = parts.peek() {
33 string.push('_');
34 }
35 while let Some(p) = parts.next() {
36 if p.is_empty() {
37 continue;
38 }
39 string.push_str(&p.to_pascal_case());
40 match parts.peek() {
41 Some(nxt)
42 if p.ends_with(|s: char| s.is_numeric())
43 && nxt.starts_with(|s: char| s.is_numeric()) =>
44 {
45 string.push('_');
46 }
47 Some(&"") => {
48 string.push('_');
49 }
50 _ => {}
51 }
52 }
53 string
54 }
55}
56
57impl Case {
58 pub fn cow_to_case<'a>(&self, cow: Cow<'a, str>) -> Cow<'a, str> {
59 match self {
60 Self::Constant => match cow {
61 Cow::Borrowed(s) if s.is_constant_case() => cow,
62 _ => cow.to_constant_case().into(),
63 },
64 Self::Pascal => match cow {
65 Cow::Borrowed(s) if s.is_pascal_case() => cow,
66 _ => to_pascal_case(&cow).into(),
67 },
68 Self::Snake => match cow {
69 Cow::Borrowed(s) if s.is_snake_case() => cow,
70 _ => cow.to_snake_case().into(),
71 },
72 }
73 }
74
75 pub fn sanitize<'a>(&self, s: &'a str) -> Cow<'a, str> {
76 let s = sanitize(s);
77 self.cow_to_case(s)
78 }
79}
80
81fn sanitize(s: &str) -> Cow<'_, str> {
82 if s.contains(BLACKLIST_CHARS) {
83 Cow::Owned(s.replace(BLACKLIST_CHARS, ""))
84 } else {
85 s.into()
86 }
87}
88
89pub fn ident(name: &str, config: &Config, fmt: &str, span: Span) -> Ident {
90 Ident::new(
91 &config
92 .ident_formats
93 .get(fmt)
94 .expect("Missing {fmt} entry")
95 .sanitize(name),
96 span,
97 )
98}
99
100impl IdentFormat {
101 pub fn apply<'a>(&self, name: &'a str) -> Cow<'a, str> {
102 let name = match &self.case {
103 Some(case) => case.sanitize(name),
104 _ => sanitize(name),
105 };
106 if self.prefix.is_empty() && self.suffix.is_empty() {
107 name
108 } else {
109 format!("{}{}{}", self.prefix, name, self.suffix).into()
110 }
111 }
112 pub fn sanitize<'a>(&self, name: &'a str) -> Cow<'a, str> {
113 let s = self.apply(name);
114 let s = if s.as_bytes().first().unwrap_or(&0).is_ascii_digit() {
115 Cow::from(format!("_{}", s))
116 } else {
117 s
118 };
119 match self.case {
120 Some(Case::Snake) | None => sanitize_keyword(s),
121 _ => s,
122 }
123 }
124}
125
126pub fn ident_str(name: &str, fmt: &IdentFormat) -> String {
127 let name = match &fmt.case {
128 Some(case) => case.sanitize(name),
129 _ => sanitize(name),
130 };
131 format!("{}{}{}", fmt.prefix, name, fmt.suffix)
132}
133
134pub fn sanitize_keyword(sc: Cow<str>) -> Cow<str> {
135 const KEYWORDS: [&str; 56] = [
136 "abstract", "alignof", "as", "async", "await", "become", "box", "break", "const",
137 "continue", "crate", "do", "dyn", "else", "enum", "extern", "false", "final", "fn", "for",
138 "gen", "if", "impl", "in", "let", "loop", "macro", "match", "mod", "move", "mut",
139 "offsetof", "override", "priv", "proc", "pub", "pure", "ref", "return", "self", "sizeof",
140 "static", "struct", "super", "trait", "true", "try", "type", "typeof", "unsafe", "unsized",
141 "use", "virtual", "where", "while", "yield",
142 ];
143 if KEYWORDS.contains(&sc.as_ref()) {
144 sc + "_"
145 } else {
146 sc
147 }
148}
149
150pub fn respace(s: &str) -> String {
151 s.split_whitespace()
152 .collect::<Vec<_>>()
153 .join(" ")
154 .replace(r"\n", "\n")
155}
156
157pub fn escape_brackets(s: &str) -> String {
158 s.split('[')
159 .fold(String::new(), |acc, x| {
160 if acc.is_empty() {
161 x.to_string()
162 } else if acc.ends_with('\\') {
163 acc + "[" + x
164 } else {
165 acc + "\\[" + x
166 }
167 })
168 .split(']')
169 .fold(String::new(), |acc, x| {
170 if acc.is_empty() {
171 x.to_string()
172 } else if acc.ends_with('\\') {
173 acc + "]" + x
174 } else {
175 acc + "\\]" + x
176 }
177 })
178}
179
180pub fn escape_special_chars(s: &str) -> Cow<'_, str> {
182 if s.contains('[') {
183 escape_brackets(s).into()
184 } else {
185 s.into()
186 }
187}
188
189pub fn name_of<T: FullName>(maybe_array: &MaybeArray<T>, ignore_group: bool) -> String {
190 let fullname = maybe_array.fullname(ignore_group);
191 if maybe_array.is_array() {
192 fullname.remove_dim().into()
193 } else {
194 fullname.into()
195 }
196}
197
198pub fn access_of(properties: &RegisterProperties, fields: Option<&[Field]>) -> Access {
199 properties.access.unwrap_or_else(|| {
200 if let Some(fields) = fields {
201 if fields.iter().all(|f| f.access == Some(Access::ReadOnly)) {
202 Access::ReadOnly
203 } else if fields.iter().all(|f| f.access == Some(Access::WriteOnce)) {
204 Access::WriteOnce
205 } else if fields
206 .iter()
207 .all(|f| f.access == Some(Access::ReadWriteOnce))
208 {
209 Access::ReadWriteOnce
210 } else if fields
211 .iter()
212 .all(|f| f.access == Some(Access::WriteOnly) || f.access == Some(Access::WriteOnce))
213 {
214 Access::WriteOnly
215 } else {
216 Access::ReadWrite
217 }
218 } else {
219 Access::ReadWrite
220 }
221 })
222}
223
224pub fn digit_or_hex(n: u64) -> LitInt {
225 if n < 10 {
226 unsuffixed(n)
227 } else {
228 hex(n)
229 }
230}
231
232pub fn hex(n: u64) -> LitInt {
234 let (h4, h3, h2, h1) = (
235 (n >> 48) & 0xffff,
236 (n >> 32) & 0xffff,
237 (n >> 16) & 0xffff,
238 n & 0xffff,
239 );
240 LitInt::new(
241 &(if h4 != 0 {
242 format!("0x{h4:04x}_{h3:04x}_{h2:04x}_{h1:04x}")
243 } else if h3 != 0 {
244 format!("0x{h3:04x}_{h2:04x}_{h1:04x}")
245 } else if h2 != 0 {
246 format!("0x{h2:04x}_{h1:04x}")
247 } else if h1 & 0xff00 != 0 {
248 format!("0x{h1:04x}")
249 } else if h1 != 0 {
250 format!("0x{:02x}", h1 & 0xff)
251 } else {
252 "0".to_string()
253 }),
254 Span::call_site(),
255 )
256}
257
258pub fn hex_nonzero(n: u64) -> Option<LitInt> {
260 (n != 0).then(|| hex(n))
261}
262
263pub fn unsuffixed(n: impl Into<u64>) -> LitInt {
265 LitInt::new(&n.into().to_string(), Span::call_site())
266}
267
268pub fn unsuffixed_or_bool(n: u64, width: u32) -> Lit {
269 if width == 1 {
270 Lit::Bool(syn::LitBool::new(n != 0, Span::call_site()))
271 } else {
272 Lit::Int(unsuffixed(n))
273 }
274}
275
276pub fn new_syn_u32(len: u32, span: Span) -> syn::Expr {
277 syn::Expr::Lit(syn::ExprLit {
278 attrs: Vec::new(),
279 lit: syn::Lit::Int(syn::LitInt::new(&len.to_string(), span)),
280 })
281}
282
283pub fn zst_type() -> Type {
284 Type::Tuple(syn::TypeTuple {
285 paren_token: syn::token::Paren::default(),
286 elems: Punctuated::new(),
287 })
288}
289
290pub fn name_to_ty(name: Ident) -> Type {
291 let mut segments = Punctuated::new();
292 segments.push(path_segment(name));
293 syn::Type::Path(type_path(segments))
294}
295
296pub fn block_path_to_ty(
297 bpath: &svd_parser::expand::BlockPath,
298 config: &Config,
299 span: Span,
300) -> TypePath {
301 let mut path = config.settings.crate_path.clone().unwrap_or_default().0;
302 path.segments.push(path_segment(ident(
303 &bpath.peripheral.remove_dim(),
304 config,
305 "peripheral_mod",
306 span,
307 )));
308 for ps in &bpath.path {
309 path.segments.push(path_segment(ident(
310 &ps.remove_dim(),
311 config,
312 "cluster_mod",
313 span,
314 )));
315 }
316 TypePath { qself: None, path }
317}
318
319pub fn register_path_to_ty(
320 rpath: &svd_parser::expand::RegisterPath,
321 config: &Config,
322 span: Span,
323) -> TypePath {
324 let mut p = block_path_to_ty(&rpath.block, config, span);
325 p.path.segments.push(path_segment(ident(
326 &rpath.name.remove_dim(),
327 config,
328 "register_mod",
329 span,
330 )));
331 p
332}
333
334pub fn ident_to_path(ident: Ident) -> TypePath {
335 let mut segments = Punctuated::new();
336 segments.push(path_segment(ident));
337 type_path(segments)
338}
339
340pub fn type_path(segments: Punctuated<PathSegment, PathSep>) -> TypePath {
341 TypePath {
342 qself: None,
343 path: syn::Path {
344 leading_colon: None,
345 segments,
346 },
347 }
348}
349
350pub fn path_segment(ident: Ident) -> PathSegment {
351 PathSegment {
352 ident,
353 arguments: PathArguments::None,
354 }
355}
356
357pub trait U32Ext {
358 fn size_to_str(&self) -> Result<&str>;
359 fn to_ty(&self) -> Result<Ident>;
360 fn to_ty_width(&self) -> Result<u32>;
361}
362
363impl U32Ext for u32 {
364 fn size_to_str(&self) -> Result<&str> {
365 Ok(match *self {
366 8 => "u8",
367 16 => "u16",
368 32 => "u32",
369 64 => "u64",
370 _ => return Err(anyhow!("can't convert {self} bits into register size type")),
371 })
372 }
373 fn to_ty(&self) -> Result<Ident> {
374 Ok(Ident::new(
375 match *self {
376 1 => "bool",
377 2..=8 => "u8",
378 9..=16 => "u16",
379 17..=32 => "u32",
380 33..=64 => "u64",
381 _ => {
382 return Err(anyhow!(
383 "can't convert {self} bits into a Rust integral type"
384 ))
385 }
386 },
387 Span::call_site(),
388 ))
389 }
390
391 fn to_ty_width(&self) -> Result<u32> {
392 Ok(match *self {
393 1 => 1,
394 2..=8 => 8,
395 9..=16 => 16,
396 17..=32 => 32,
397 33..=64 => 64,
398 _ => {
399 return Err(anyhow!(
400 "can't convert {self} bits into a Rust integral type width"
401 ))
402 }
403 })
404 }
405}
406
407pub fn build_rs(config: &Config) -> TokenStream {
408 let extra_build = config.extra_build();
409
410 quote! {
411 use std::env;
414 use std::fs::File;
415 use std::io::Write;
416 use std::path::PathBuf;
417
418 fn main() {
419 if env::var_os("CARGO_FEATURE_RT").is_some() {
420 let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
422 File::create(out.join("device.x"))
423 .unwrap()
424 .write_all(include_bytes!("device.x"))
425 .unwrap();
426 println!("cargo:rustc-link-search={}", out.display());
427
428 println!("cargo:rerun-if-changed=device.x");
429
430 #extra_build
431 }
432
433 println!("cargo:rerun-if-changed=build.rs");
434 }
435 }
436}
437
438pub trait DimSuffix {
439 fn expand_dim(&self, suffix: &str) -> Cow<'_, str>;
440 fn remove_dim(&self) -> Cow<'_, str> {
441 self.expand_dim("")
442 }
443}
444
445impl DimSuffix for str {
446 fn expand_dim(&self, suffix: &str) -> Cow<'_, str> {
447 if self.contains("%s") {
448 self.replace(if self.contains("[%s]") { "[%s]" } else { "%s" }, suffix)
449 .into()
450 } else {
451 self.into()
452 }
453 }
454}
455
456pub trait FullName {
457 fn fullname(&self, ignore_group: bool) -> Cow<'_, str>;
458}
459
460impl FullName for RegisterInfo {
461 fn fullname(&self, ignore_group: bool) -> Cow<'_, str> {
462 fullname(&self.name, &self.alternate_group, ignore_group)
463 }
464}
465
466pub fn fullname<'a>(name: &'a str, group: &Option<String>, ignore_group: bool) -> Cow<'a, str> {
467 match &group {
468 Some(group) if !ignore_group => format!("{group}_{}", name).into(),
469 _ => name.into(),
470 }
471}
472
473impl FullName for PeripheralInfo {
474 fn fullname(&self, _ignore_group: bool) -> Cow<'_, str> {
475 self.name.as_str().into()
476 }
477}
478
479pub fn group_names<'a>(d: &'a Device, feature_format: &'a IdentFormat) -> Vec<Cow<'a, str>> {
480 let set: HashSet<_> = d
481 .peripherals
482 .iter()
483 .filter_map(|p| p.group_name.as_ref())
484 .map(|name| feature_format.apply(name))
485 .collect();
486 let mut v: Vec<_> = set.into_iter().collect();
487 v.sort();
488 v
489}
490
491pub fn peripheral_names(d: &Device, feature_format: &IdentFormat) -> Vec<String> {
492 let mut v = Vec::new();
493 for p in &d.peripherals {
494 match p {
495 Peripheral::Single(info) => {
496 v.push(feature_format.apply(&info.name).remove_dim().into());
497 }
498 Peripheral::Array(info, dim) => {
499 v.extend(svd_rs::array::names(info, dim).map(|n| feature_format.apply(&n).into()));
500 }
501 }
502 }
503 v.sort();
504 v
505}
506
507#[test]
508fn pascalcase() {
509 assert_eq!(to_pascal_case("_reserved"), "_Reserved");
510 assert_eq!(to_pascal_case("_FOO_BAR_"), "_FooBar_");
511 assert_eq!(to_pascal_case("FOO_BAR1"), "FooBar1");
512 assert_eq!(to_pascal_case("FOO_BAR_1"), "FooBar1");
513 assert_eq!(to_pascal_case("FOO_BAR_1_2"), "FooBar1_2");
514 assert_eq!(to_pascal_case("FOO_BAR_1_2_"), "FooBar1_2_");
515}