proc_strarray/
macro.rs

1// Procedural macros collection for string operations.
2//
3// Copyright 2024 Radim Kolar <hsn@sendmail.cz>
4// https://gitlab.com/hsn10/proc_strarray
5//
6// SPDX-License-Identifier: MIT OR Apache-2.0
7
8#![forbid(unsafe_code)]
9#![forbid(non_fmt_panics)]
10#![deny(missing_docs)]
11#![deny(rustdoc::missing_crate_level_docs)]
12#![warn(rustdoc::missing_doc_code_examples)]
13#![deny(rustdoc::invalid_codeblock_attributes)]
14#![warn(rustdoc::broken_intra_doc_links)]
15#![warn(rustdoc::private_intra_doc_links)]
16#![warn(rustdoc::unescaped_backticks)]
17#![allow(rustdoc::private_doc_tests)]
18#![allow(unused_parens)]
19#![allow(non_camel_case_types)]
20
21//! Collection of procedural macros for str and bytestr operations.
22//!
23//! 1. generate const u8 array from str or bytestr literal
24//! 1. repeat str or bytestr literal
25//! 1. repeat str or bytestr literal as byte slice
26//! 1. get length of str or bytestr literal
27//! 1. generate byte slice from str or bytestr literal
28//!
29//! Macros have *0* variant which terminates str or bytestr literal with \\0
30
31/* rust internal proc_macro API */
32extern crate proc_macro;
33use proc_macro::TokenStream;
34
35/* importing syn + quote */
36use syn::{parse_macro_input, LitByteStr, LitStr};
37use syn::parse::{Parse, ParseStream};
38use quote::quote;
39use std::fmt;
40
41
42//    str_array
43
44
45/**
46  Parsed arguments for str_array macro.
47*/
48#[derive(Debug)]
49struct MacroArgumentsAR {
50   /** name of generated array */
51   id: String,
52   /** source string or byte string */
53   content: StrOrByte,
54   /** length of source string, will be automatically determined if omitted */
55   len: Option<usize>
56}
57
58impl Parse for MacroArgumentsAR {
59   fn parse(input: ParseStream) -> syn::Result<Self> {
60      use syn::Ident;
61      use syn::Token;
62      use syn::LitInt;
63      let ident: Ident = input.parse()?;
64      let _comma: Token![,] = input.parse()?;
65      let payload: StrOrByte = input.parse()?;
66      /* check if there is an optional string size specified as ",50" */
67      let maybecomma: Result<Token![,],_> = input.parse();
68      let maybesize: Result<LitInt,_> = input.parse();
69      let maybeusizedlen =
70      if maybesize.is_ok() && maybecomma.is_ok() {
71         /* try to parse an optional length */
72         maybesize.unwrap().base10_parse::<usize>()
73      } else {
74         Err(syn::Error::new(input.span(), "Failed to parse size as usize"))
75      };
76      match( maybeusizedlen ) {
77         Err(e) =>
78            if (maybecomma.is_ok()) {
79              Err(e)
80            } else {
81               Ok(MacroArgumentsAR { id: ident.to_string(), content: payload, len: None })
82            },
83         Ok(expectedlen) => Ok(MacroArgumentsAR { id: ident.to_string(), content: payload, len: Some(expectedlen) })
84      }
85   }
86}
87
88/**
89   Procedural macro `proc_strarray::str_array!` creates const u8 array
90   from _str_ or _byte str_ literal.
91
92   ### Usage
93```rust
94   // without specified length
95   proc_strarray::str_array!(RESULT, "str literal");
96   // with specified length
97   proc_strarray::str_array!(RESULT2, "string", 6);
98```
99   ### Arguments
100   1. name of created array
101   1. str or byte str literal
102   1. (optional) expected length of str literal. Length is only used
103      for length check. It will not trim or extend an array.
104
105   ### Example
106```rust
107    // This code will create const array of u8 named STRU from
108    // content of "stru" str literal.
109    use proc_strarray::str_array;
110    // case 1: automatically determine array length.
111    str_array!(STRU, "stru");
112    // check if newly created array have length 4 and first character is 's'
113    assert_eq!(STRU.len(), 4);
114    assert_eq!(STRU[0], 's' as u8);
115    // case 2: manually specify expected array length
116    str_array!(STRU_len, "stru", 4);
117    assert_eq!(STRU_len.len(), 4);
118    // case 3: bytestr
119    str_array!(STRU_byte, b"stru", 4);
120    assert_eq!(STRU_byte.len(), 4);
121```
122*/
123#[proc_macro]
124pub fn str_array(tokens: TokenStream) -> TokenStream {
125   let input: MacroArgumentsAR = parse_macro_input!(tokens as MacroArgumentsAR);
126   let id = syn::Ident::new(&input.id, proc_macro2::Span::from(proc_macro::Span::call_site()));
127   let u8s = input.content.as_bytes();
128   let mut len = u8s.len();
129   if let Some(expectedlen) = input.len {
130      len = expectedlen;
131   };
132
133   proc_macro::TokenStream::from(
134   quote! {
135      const #id: [u8; #len] = [ #(#u8s),* ];
136   })
137}
138
139/**
140   Procedural macro `proc_strarray::str_array0!` creates *zero terminated*
141   const u8 array from _str_ or _byte str_ literal.
142
143   Macro is same as `str_array!` but it will append \\0 to result.
144   Optional array length *must* include terminating \\0.
145
146   ### See also
147   - [`str_array!`](macro.str_array.html): Creates a non-zero terminated const u8 array from a str literal.
148```rust
149    // Create const array of u8 named STRU from content of "stru" str literal.
150    use proc_strarray::str_array0;
151    // case 1: without specifying length
152    str_array0!(STRU, "stru");
153    // check if created array have length 4 + 1 \\0 and first character is 's'
154    assert_eq!(STRU.len(), 4+1);
155    assert_eq!(STRU[0], 's' as u8);
156    assert_eq!(STRU[4], 0);
157    // case 2: with specifying length
158    str_array0!(STRU2, "stru", 5);
159    // check if newly created array have length 5
160    assert_eq!(STRU.len(), 5);
161    // case 3: bytestr
162    str_array0!(STRU_byte, b"stru", 5);
163    assert_eq!(STRU_byte.len(), 4 + 1);
164```
165*/
166#[proc_macro]
167pub fn str_array0(tokens: TokenStream) -> TokenStream {
168   let input: MacroArgumentsAR = parse_macro_input!(tokens as MacroArgumentsAR);
169   let id = syn::Ident::new(&input.id, proc_macro::Span::call_site().into());
170   let u8s = input.content.add0().as_bytes();
171   let mut len = u8s.len();
172   if let Some(expectedlen) = input.len {
173      len = expectedlen;
174   };
175   proc_macro::TokenStream::from(
176   quote! {
177            const #id: [ u8; #len ] = [ #(#u8s),* ];
178   })
179}
180
181
182//    str_len
183
184
185/**
186   Procedural macro `proc_strarray::str_len!` returns length of str or
187   byte str literal.
188
189   ### See also
190   - [`str_len0!`](macro.str_len0.html): returns length of zero terminated
191   str or byte str literal.
192
193   ### Example
194```rust
195    // Get length of str or byte str.
196    use proc_strarray::str_len;
197    const PMD85: usize = str_len!("PMD-85-2");
198    assert_eq!(PMD85, 8);
199    const IQ151: usize = str_len!(b"IQ-151");
200    assert_eq!(IQ151, 6);
201```
202*/
203#[proc_macro]
204pub fn str_len(tokens: TokenStream) -> TokenStream {
205   let input = parse_macro_input!(tokens as StrOrByte);
206   let len = input.as_bytes().len();
207   proc_macro::TokenStream::from(
208   quote! {
209      #len
210   })
211}
212
213/**
214   Procedural macro `proc_strarray::str_len0!` returns length of
215   *zero terminated* str or byte str literal.
216
217   ### See also
218   - [`str_len!`](macro.str_len.html): returns length of str literal.
219
220   ### Example
221```rust
222    // This code will create const usize and assign length of supplied string to it.
223    use proc_strarray::str_len0;
224    const PMD85: usize = str_len0!("PMD-85");
225    assert_eq!(PMD85, 7);
226    const IQ151: usize = str_len0!(b"IQ-151");
227    assert_eq!(IQ151, 6+1);
228```
229*/
230#[proc_macro]
231pub fn str_len0(tokens: TokenStream) -> TokenStream {
232   let input = parse_macro_input!(tokens as StrOrByte);
233   let len = 1 + input.as_bytes().len();
234
235   proc_macro::TokenStream::from(
236   quote! {
237      #len
238   })
239}
240
241
242//    str_repeat
243
244
245/** holds LitStr or LitByteStr */
246enum StrOrByte {
247   str(LitStr),
248   byte(LitByteStr)
249}
250
251impl fmt::Debug for StrOrByte {
252   fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253      match self {
254         StrOrByte::str(lit_str) => write!(f, "Str({:?})", lit_str.value()),
255         StrOrByte::byte(lit_byte_str) => write!(f, "Byte({:?})", std::str::from_utf8(&lit_byte_str.value()).map_err(|_| fmt::Error)?),
256      }
257   }
258}
259
260impl PartialEq<StrOrByte> for StrOrByte {
261   fn eq(&self, other: &Self) -> bool {
262      match (self, other) {
263         (StrOrByte::str(a), StrOrByte::str(b)) => a.value() == b.value(),
264         (StrOrByte::byte(a), StrOrByte::byte(b)) => a.value() == b.value(),
265          _ => false,
266       }
267   }
268}
269
270impl PartialEq<&str> for StrOrByte {
271   fn eq(&self, other: &&str) -> bool {
272      match self {
273         StrOrByte::str(lit_str) => lit_str.value() == *other,
274         _ => false
275      }
276   }
277}
278
279impl PartialEq<&[u8]> for StrOrByte {
280   fn eq(&self, other: &&[u8]) -> bool {
281      match self {
282         StrOrByte::byte(lit_byte) => lit_byte.value() == *other,
283         _ => false
284      }
285   }
286}
287
288impl Parse for StrOrByte {
289   fn parse(input: ParseStream) -> syn::Result<Self> {
290      if input.peek(LitStr) {
291         let lit_str: LitStr = input.parse()?;
292         Ok(StrOrByte::str(lit_str))
293      } else {
294         let lit_bytestr: LitByteStr = input.parse()?;
295         Ok(StrOrByte::byte(lit_bytestr))
296      }
297   }
298}
299
300impl quote::ToTokens for StrOrByte {
301   fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
302      match self {
303         StrOrByte::str(lit_str) => lit_str.to_tokens(tokens),
304         StrOrByte::byte(lit_byte_str) => lit_byte_str.to_tokens(tokens),
305      }
306   }
307}
308
309impl StrOrByte {
310    fn repeat(&self, times: usize) -> Self {
311        match self {
312            StrOrByte::str(lit_str) => {
313                let repeated = lit_str.value().repeat(times);
314                StrOrByte::str(LitStr::new(&repeated, lit_str.span()))
315            },
316            StrOrByte::byte(lit_byte_str) => {
317                let repeated_bytes = lit_byte_str.value().repeat(times);
318                StrOrByte::byte(LitByteStr::new(&repeated_bytes, lit_byte_str.span()))
319            },
320        }
321    }
322    fn add0(&self) -> Self {
323        match self {
324            StrOrByte::str(lit_str) => {
325                let with_null = format!("{}\0", lit_str.value());
326                StrOrByte::str(LitStr::new(&with_null, lit_str.span()))
327            },
328            StrOrByte::byte(lit_byte_str) => {
329                let mut with_null = lit_byte_str.value().clone();
330                with_null.push(0 as u8);
331                StrOrByte::byte(LitByteStr::new(&with_null, lit_byte_str.span()))
332            },
333        }
334    }
335    fn as_bytes(&self) -> Vec<u8> {
336        match self {
337            StrOrByte::str(lit_str) => lit_str.value().as_bytes().to_vec(),
338            StrOrByte::byte(lit_byte_str) => lit_byte_str.value()
339      }
340   }
341}
342
343#[derive(Debug)]
344struct MacroArgumentsRP {
345   content: StrOrByte,
346   times: usize
347}
348
349impl Parse for MacroArgumentsRP {
350   fn parse(input: ParseStream) -> syn::Result<Self> {
351      use syn::Token;
352      use syn::LitInt;
353      let payload: StrOrByte = input.parse()?;
354      let _comma: Token![,] = input.parse()?;
355      let times: usize = input.parse::<LitInt>()?.base10_parse::<usize>()?;
356      Ok(MacroArgumentsRP { content: payload, times })
357   }
358}
359
360/**
361   Procedural macro `proc_strarray::str_repeat!` repeats str or byte str
362   literal n times.
363
364   ### Arguments
365   1. str or byte str literal
366   1. unsigned number of repetitions
367
368```rust
369    // This code will repeat string "AB" 4 times.
370    use proc_strarray::str_repeat;
371    const S: &str = str_repeat!("AB", 4);
372    assert_eq!(S.len(), 8);
373    assert_eq!(S, "ABABABAB");
374    // repeat byte str
375    const B: &[u8] = str_repeat!(b"zx", 4);
376    assert_eq!(B.len(), 8);
377    assert_eq!(B, b"zxzxzxzx");
378```
379*/
380#[proc_macro]
381pub fn str_repeat(tokens: TokenStream) -> TokenStream {
382   let input: MacroArgumentsRP = parse_macro_input!(tokens as MacroArgumentsRP);
383   let payload = input.content.repeat(input.times);
384
385   proc_macro::TokenStream::from(
386   quote! {
387     #payload
388   })
389}
390
391/**
392   Procedural macro `proc_strarray::str_repeat0!` repeats str or bytestr
393   literal n times and adds zero termination.
394
395```rust
396    // This code will repeat string "AB" 2 times.
397    use proc_strarray::str_repeat0;
398    const S: &str = str_repeat0!("AB", 2);
399    assert_eq!(S.len(), 5);
400    assert_eq!(S.chars().take(4).collect::<String>(), "ABAB");
401    assert_eq!(S.chars().nth(4).unwrap(), 0 as char);
402    // repeat byte str
403    const B: &[u8] = str_repeat0!(b"zx", 2);
404    assert_eq!(B.len(), 5);
405    assert_eq!(B, b"zxzx\0");
406```
407*/
408#[proc_macro]
409pub fn str_repeat0(tokens: TokenStream) -> TokenStream {
410   let input: MacroArgumentsRP = parse_macro_input!(tokens as MacroArgumentsRP);
411   let payload = input.content.repeat(input.times).add0();
412
413   proc_macro::TokenStream::from(
414   quote! {
415     #payload
416   })
417}
418
419//    str_repeat_bytes
420
421/**
422   Procedural macro `proc_strarray::str_repeat_bytes!` repeats str or byte str
423   literal as byte slice.
424
425   Arguments are same as `str_repeat!` only returned type is byte slice.
426
427   ### See also
428   - [`str_repeat!`](macro.str_repeat.html): repeat str or bytestr literal.
429
430```rust
431    // This code will repeat string "AB" 2 times.
432    use proc_strarray::str_repeat_bytes;
433    const S: &[u8] = str_repeat_bytes!("AB", 2);
434    assert_eq!(S.len(), 4);
435    assert_eq!(S, b"ABAB");
436```
437*/
438#[proc_macro]
439pub fn str_repeat_bytes(tokens: TokenStream) -> TokenStream {
440   let input: MacroArgumentsRP = parse_macro_input!(tokens as MacroArgumentsRP);
441   let bytes = input.content.repeat(input.times).as_bytes();
442
443   proc_macro::TokenStream::from(
444   quote! { &[#(#bytes),*] }
445   )
446}
447
448/**
449   Procedural macro `proc_strarray::str_repeat_bytes0!` repeats str or byte str
450   literal as byte slice with added \\0.
451
452   Arguments are same as `str_repeat0!` only returned type is byte slice.
453
454   ### See also
455   - [`str_repeat!`](macro.str_repeat.html): repeat str or bytestr literal.
456   - [`str_repeat0!`](macro.str_repeat0.html): repeat str or bytestr literal
457     with added \\0
458
459```rust
460    // This code will repeat string "AB" 2 times.
461    use proc_strarray::str_repeat_bytes0;
462    const S: &[u8] = str_repeat_bytes0!("AB", 2);
463    assert_eq!(S.len(), 5);
464    assert_eq!(S, b"ABAB\0");
465```
466*/
467#[proc_macro]
468pub fn str_repeat_bytes0(tokens: TokenStream) -> TokenStream {
469   let input: MacroArgumentsRP = parse_macro_input!(tokens as MacroArgumentsRP);
470   let bytes = input.content.repeat(input.times).add0().as_bytes();
471
472   proc_macro::TokenStream::from(
473   quote! { &[#(#bytes),*] }
474   )
475}
476
477/**
478Convert string or bytestring literals to byte slice.
479
480```rust
481use proc_strarray::str_bytes;
482const A: &[u8] = str_bytes!("A");
483const B: &[u8] = str_bytes!(b"A");
484assert_eq!(A.len(),1);
485assert_eq!(B.len(),1);
486```
487*/
488#[proc_macro]
489pub fn str_bytes(tokens: TokenStream) -> TokenStream {
490   let input = parse_macro_input!(tokens as StrOrByte);
491   let bytes = input.as_bytes();
492   proc_macro::TokenStream::from(
493   quote! { &[#(#bytes),*] }
494   )
495}
496
497/**
498Convert string or byte string literals to zero terminated byte slice.
499
500```rust
501use proc_strarray::str_bytes0;
502const A: &[u8] = str_bytes0!("A");
503const B: &[u8] = str_bytes0!(b"A");
504assert_eq!(A.len(),2);
505assert_eq!(B.len(),2);
506```
507*/
508#[proc_macro]
509pub fn str_bytes0(tokens: TokenStream) -> TokenStream {
510   let input = parse_macro_input!(tokens as StrOrByte);
511   let bytes = input.add0().as_bytes();
512   proc_macro::TokenStream::from(
513   quote! { &[#(#bytes),*] }
514   )
515}
516
517#[cfg(test)]
518#[path= "./macro_tests.rs"]
519mod tests;
520
521#[cfg(test)]
522#[path= "./macro_parseAR_tests.rs"]
523mod ARparsetests;
524
525#[cfg(test)]
526#[path= "./macro_parseRP_tests.rs"]
527mod RPparsetests;
528
529#[cfg(test)]
530#[path= "./macro_strorbyte_tests.rs"]
531mod SoBparsetests;
532
533#[cfg(doctest)]
534#[path= "./macro_sized_tests.rs"]
535mod sizedtests;
536
537#[cfg(doctest)]
538#[path= "./macro_0sized_tests.rs"]
539mod zerosizedtests;