embedded_c_sdk_bind_print_macros/
lib.rs

1extern crate proc_macro;
2
3use core::mem;
4use proc_macro::TokenStream;
5use std::cmp::Ordering;
6use std::borrow::Cow;
7
8use proc_macro2::Span;
9use quote::quote;
10use syn::{
11    parse::{self, Parse, ParseStream},
12    parse_macro_input,
13    punctuated::Punctuated,
14    spanned::Spanned,
15    Expr, ExprLit, LitStr, Token, Ident,
16};
17
18#[proc_macro]
19pub fn print(input: TokenStream) -> TokenStream {
20    write(input, false)
21}
22
23#[proc_macro]
24pub fn println(input: TokenStream) -> TokenStream {
25    write(input, true)
26}
27
28fn write(input: TokenStream, newline: bool) -> TokenStream {
29    let input = parse_macro_input!(input as Input);
30    let literal = input.literal;
31
32    let mut format = literal.value();
33    if newline {
34        format.push('\n');
35    }
36    let pieces = match parse(&format, literal.span()) {
37        Err(e) => return e.to_compile_error().into(),
38        Ok(pieces) => pieces,
39    };
40
41    let required_args = pieces.iter().filter(|piece| !piece.is_str()).count();
42    let supplied_args = input.args.len();
43    match supplied_args.cmp(&required_args) {
44        Ordering::Less => {
45            return parse::Error::new(
46                literal.span(),
47                &format!(
48                    "format string requires {} arguments but {} {} supplied",
49                    required_args,
50                    supplied_args,
51                    if supplied_args == 1 { "was" } else { "were" }
52                ),
53            )
54            .to_compile_error()
55            .into();
56        }
57        Ordering::Greater => {
58            return parse::Error::new(
59                input.args[required_args].span(),
60                //"argument never used".to_string(),
61                &format!(
62                    "format string requires {} arguments but {} {} supplied",
63                    required_args,
64                    supplied_args,
65                    if supplied_args == 1 { "was" } else { "were" }
66                ),
67            )
68            .to_compile_error()
69            .into();
70        }
71        Ordering::Equal => {}
72    }
73
74    let mut args = vec![];
75    let mut exprs = vec![];
76    let mut dbgs = vec![];
77    let mut i = 0;
78    let mut format = String::new();
79    for piece in pieces {
80        if let Piece::Str(s) = piece {
81            format.push_str(&s.to_string());
82        } else {
83            let arg = &input.args[i];
84            i += 1;
85            args.push(quote!(&(#arg)));
86
87            match piece {
88                Piece::Percent(pct) => {
89                    match pct {
90                        PCT::Int => {
91                            exprs.push(quote!( #arg ));
92                        }
93                        PCT::Ch => {
94                            exprs.push(quote!( #arg ));
95                        }
96                        PCT::Str => {
97                            exprs.push(quote!(  (*#arg).as_ptr() as *const _ as InvokeParam, (*#arg).len() ));
98                        }
99                        PCT::Array => {
100                            exprs.push(quote!(  (*#arg).as_ptr() as *const _ as InvokeParam, (*#arg).len() ));
101                        }
102                        PCT::Hex => {
103                            exprs.push(quote!(  #arg ));
104                        }
105                    }
106                }
107                Piece::Display => {
108                    let mut ok = false;
109                    let mut err_msg = "".to_string();
110                    let mut lable = "";
111                    match arg {
112                        Expr::Array(_) => {
113                            lable = "%y";
114                            exprs.push(
115                                quote!(  #arg.as_ptr() as *const _ as InvokeParam, (#arg.len() * core::mem::size_of_val(&#arg[0])) ),
116                            );
117                            ok = true;
118                        }
119                        Expr::Reference(ref_exp) => {
120                            let ref_expr = &*ref_exp.expr;
121                            match ref_expr {
122                                Expr::Lit(lit) => match fn_match_lit(lit, arg) {
123                                    Ok((l, e)) => {
124                                        lable = l;
125                                        exprs.push(e);
126                                        ok = true;
127                                    }
128                                    Err(msg) => err_msg = msg.to_string(),
129                                },
130                                Expr::Array(_) => {
131                                    lable = "%y";
132                                    exprs.push(quote!(  #arg.as_ptr() as *const _ as InvokeParam, (#arg.len() * core::mem::size_of_val(&#arg[0])) ));
133                                    ok = true;
134                                }
135                                Expr::Path(path_expr) => {
136                                    let first = path_expr.path.segments.first();
137                                    if let Some(segment) = first {
138                                        lable = "%y";
139                                        exprs.push(quote!( #segment.as_ptr() as *const _ as InvokeParam, (#segment.len() * core::mem::size_of_val(&#segment[0])) ));
140                                        ok = true;
141                                    } else {
142                                        err_msg = "Path has no segments".to_string();
143                                    }
144                                }
145                                _ => {
146                                    err_msg = map_expr_error(ref_expr);
147                                    err_msg.push_str(" -> in match Expr::Reference");
148                                }
149                            }
150                        }
151                        Expr::Path(path_expr) => {
152                            let first = path_expr.path.segments.first();
153                            if let Some(segment) = first {
154                                lable = "%d";
155                                exprs.push(quote!( #segment ));
156                                ok = true;
157                            } else {
158                                err_msg = "Path has no segments".to_string();
159                            }
160                        }
161                        Expr::Lit(lit) => match fn_match_lit(lit, arg) {
162                            Ok((l, e)) => {
163                                lable = l;
164                                exprs.push(e);
165                                ok = true;
166                            }
167                            Err(msg) => err_msg = msg.to_string(),
168                        },
169                        Expr::Call(_) => {
170                            lable = "%d";
171                            exprs.push(quote!( #arg ));
172                            ok = true;
173                        }
174                        Expr::MethodCall(_) => {
175                            lable = "%d";
176                            exprs.push(quote!( #arg ));
177                            ok = true;
178                        }
179                        _ => {
180                            err_msg = map_expr_error(arg);
181                            err_msg.push_str(" -> in match arg");
182                        }
183                    }
184                    if !ok {
185                        return parse::Error::new(arg.span(), err_msg.to_string())
186                            .to_compile_error()
187                            .into();
188                    }
189
190                    format.push_str(lable);
191                }
192                Piece::Debug => {
193                    let buf_x = mk_name_with_idx("buf", i);
194                    let str_x = mk_name_with_idx("str", i);
195
196                    dbgs.push(quote!(
197                        let mut #buf_x = [0u8; 256];
198                        let #str_x = format::fmt_to_buf(
199                            &mut #buf_x,
200                            format_args!("{:?}", #arg),
201                        ).unwrap_or("format error");
202                    ));
203                    format.push_str("%S");
204                    exprs.push(quote!(  #str_x.as_ptr() as *const _ as InvokeParam, #str_x.len() ));
205                }
206                Piece::Hex {
207                    upper_case,
208                    // pad_char,
209                    // pad_length,
210                    // prefix,
211                } => {
212                    format.push_str(if upper_case { "%X" } else { "%x" });
213                    exprs.push(quote!( #arg ));
214                }
215                Piece::Str(_) => unreachable!(),
216            }
217        }
218    }
219
220    quote!(
221        unsafe {
222            use embedded_c_sdk_bind_hal::*;
223            
224            #(#dbgs )*
225            ll_invoke( INVOKE_ID_LOG_PRINT, concat!(#format, "\0").as_ptr() as *const _ as InvokeParam, #(#exprs as InvokeParam, )* )
226        }
227    )
228    .into()
229}
230
231fn fn_match_lit<'a>(
232    lit: &ExprLit,
233    arg: &Expr,
234) -> Result<(&'a str, proc_macro2::TokenStream), &'a str> {
235    let mut ok = false;
236    let mut err_msg = "";
237    let mut lable = "";
238    let mut expr = proc_macro2::TokenStream::new();
239    match &lit.lit {
240        syn::Lit::Str(_) | syn::Lit::ByteStr(_) => {
241            lable = "%S";
242            expr = quote!(  #arg.as_ptr() as *const _ as InvokeParam, #arg.len() );
243            ok = true;
244        }
245        syn::Lit::CStr(_) => {
246            lable = "%s";
247            expr = quote!(  #arg.as_ptr() as *const _ );
248            ok = true;
249        }
250        syn::Lit::Byte(_) => {
251            lable = "%d";
252            expr = quote!(  #arg );
253            ok = true;
254        }
255        syn::Lit::Char(_) => {
256            lable = "%c";
257            expr = quote!(  #arg );
258            ok = true;
259        }
260        syn::Lit::Int(_) => {
261            lable = "%d";
262            expr = quote!(  #arg );
263            ok = true;
264        }
265        syn::Lit::Float(_) => {
266            err_msg = "Float types not supported yet";
267        }
268        syn::Lit::Bool(lit_bool) => {
269            lable = "%s";
270            if lit_bool.value() {
271                expr = quote!("true\0".as_ptr() as *const _).into();
272            } else {
273                expr = quote!("false\0".as_ptr() as *const _).into();
274            }
275            ok = true;
276        }
277        _ => {
278            err_msg = "Unsupported Lit parameter types";
279        }
280    }
281    if ok {
282        Ok((lable, expr))
283    } else {
284        Err(err_msg)
285    }
286}
287
288struct Input {
289    literal: LitStr,
290    _comma: Option<Token![,]>,
291    args: Punctuated<Expr, Token![,]>,
292}
293
294impl Parse for Input {
295    fn parse(input: ParseStream) -> parse::Result<Self> {
296        let literal = input.parse()?;
297
298        if input.is_empty() {
299            Ok(Input {
300                literal,
301                _comma: None,
302                args: Punctuated::new(),
303            })
304        } else {
305            Ok(Input {
306                literal,
307                _comma: input.parse()?,
308                args: Punctuated::parse_terminated(input)?,
309            })
310        }
311    }
312}
313
314#[derive(Debug, PartialEq)]
315enum PCT {
316    Int,
317    Ch,
318    Str,
319    Hex,
320    Array,
321}
322
323#[derive(Debug, PartialEq)]
324enum Piece<'a> {
325    Debug,
326    Display,
327    Str(Cow<'a, str>),
328    Percent(PCT),
329    Hex {
330        upper_case: bool,
331        // pad_char: u8,
332        // pad_length: usize,
333        // prefix: bool,
334    },
335}
336
337impl Piece<'_> {
338    fn is_str(&self) -> bool {
339        matches!(self, Piece::Str(_))
340    }
341}
342
343fn mk_name_with_idx(prefix: &str, i: usize) -> Ident {
344    Ident::new(&format!("{}_{}", prefix, i), Span::call_site())
345}
346
347// `}}` -> `}`
348fn unescape(mut literal: &str, span: Span) -> parse::Result<Cow<str>> {
349    if literal.contains('}') {
350        let mut buf = String::new();
351
352        while literal.contains('}') {
353            const ERR: &str = "format string contains an unmatched right brace";
354            let mut parts = literal.splitn(2, '}');
355
356            match (parts.next(), parts.next()) {
357                (Some(left), Some(right)) => {
358                    const ESCAPED_BRACE: &str = "}";
359
360                    if let Some(tail) = right.strip_prefix(ESCAPED_BRACE) {
361                        buf.push_str(left);
362                        buf.push('}');
363
364                        literal = tail;
365                    } else {
366                        return Err(parse::Error::new(span, ERR));
367                    }
368                }
369
370                _ => unreachable!(),
371            }
372        }
373
374        buf.push_str(literal);
375
376        Ok(buf.into())
377    } else {
378        Ok(Cow::Borrowed(literal))
379    }
380}
381
382fn parse_c_format(literal: &str, span: Span) -> parse::Result<Vec<Piece>> {
383    let mut pieces = vec![];
384
385    let chars = &mut literal.chars();
386    while let Some(ch) = chars.next() {
387        if ch != '%' {
388            continue;
389        }
390        if let Some(lb) = chars.next() {
391            match lb {
392                'd' => {
393                    pieces.push(Piece::Percent(PCT::Int));
394                }
395                'c' => {
396                    pieces.push(Piece::Percent(PCT::Ch));
397                }
398                's' | 'S' => {
399                    pieces.push(Piece::Percent(PCT::Str));
400                }
401                'x' | 'X' => {
402                    pieces.push(Piece::Percent(PCT::Hex));
403                }
404                'y' | 'Y' => {
405                    pieces.push(Piece::Percent(PCT::Array));
406                }
407                '%' => {}
408                _ => {
409                    return Err(parse::Error::new(
410                        span,
411                        "invalid format string: expected `%d`, `%c`, `%s/S`, `%x/X` or `%y`",
412                    ));
413                }
414            }
415        }
416    }
417    if literal.contains("%s") {
418        let literal = literal.replace("%s", "%S");
419        pieces.push(Piece::Str(literal.into()));
420    } else {
421        pieces.push(Piece::Str(literal.into()));
422    }
423
424    Ok(pieces)
425}
426fn parse(mut literal: &str, span: Span) -> parse::Result<Vec<Piece>> {
427    let mut pieces = vec![];
428
429    let mut buf = String::new();
430    loop {
431        let mut parts = literal.splitn(2, '{');
432        match (parts.next(), parts.next()) {
433            // empty string literal
434            (None, None) => break,
435
436            // end of the string literal
437            (Some(s), None) => {
438                if pieces.is_empty() {
439                    return parse_c_format(literal, span);
440                } else if buf.is_empty() {
441                    if !s.is_empty() {
442                        pieces.push(Piece::Str(unescape(s, span)?));
443                    }
444                } else {
445                    buf.push_str(&unescape(s, span)?);
446
447                    pieces.push(Piece::Str(Cow::Owned(buf)));
448                }
449
450                break;
451            }
452
453            (head, Some(tail)) => {
454                const DEBUG: &str = ":?}";
455                const DEBUG_PRETTY: &str = ":#?}";
456                const DISPLAY: &str = "}";
457                const ESCAPED_BRACE: &str = "{";
458
459                let head = head.unwrap_or("");
460                if tail.starts_with(DEBUG)
461                    || tail.starts_with(DEBUG_PRETTY)
462                    || tail.starts_with(DISPLAY)
463                    || tail.starts_with(':')
464                {
465                    if buf.is_empty() {
466                        if !head.is_empty() {
467                            pieces.push(Piece::Str(unescape(head, span)?));
468                        }
469                    } else {
470                        buf.push_str(&unescape(head, span)?);
471
472                        pieces.push(Piece::Str(Cow::Owned(mem::take(&mut buf))));
473                    }
474
475                    if let Some(tail_tail) = tail.strip_prefix(DEBUG) {
476                        pieces.push(Piece::Debug);
477
478                        literal = tail_tail;
479                    } else if let Some(tail_tail) = tail.strip_prefix(DEBUG_PRETTY) {
480                        pieces.push(Piece::Debug);
481
482                        literal = tail_tail;
483                    } else if let Some(tail2) = tail.strip_prefix(':') {
484                        let (piece, remainder) = parse_colon(tail2, span)?;
485                        pieces.push(piece);
486                        literal = remainder;
487                    } else {
488                        pieces.push(Piece::Display);
489
490                        literal = &tail[DISPLAY.len()..];
491                    }
492                } else if let Some(tail_tail) = tail.strip_prefix(ESCAPED_BRACE) {
493                    buf.push_str(&unescape(head, span)?);
494                    buf.push('{');
495
496                    literal = tail_tail;
497                } else {
498                    return Err(parse::Error::new(
499                        span,
500                        "invalid format string: expected `{{`, `{}`, `{:?}` or `{:#?}`",
501                    ));
502                }
503            }
504        }
505    }
506
507    Ok(pieces)
508}
509
510/// given a string src that begins with a text decimal number, return the tail (characters after the number) and the value of the decimal number
511fn split_number(src: &str) -> (&str, usize) {
512    let mut rval = 0;
513    let mut cursor = 0;
514
515    let chars = src.chars();
516    for (i, ch) in chars.enumerate() {
517        match ch.to_digit(10) {
518            Some(val) => {
519                rval = rval * 10 + val as usize;
520                cursor = i + 1;
521            }
522            None => break,
523        }
524    }
525
526    (&src[cursor..], rval)
527}
528
529/// parses the stuff after a `{:` into a [Piece] and the trailing `&str` (what comes after the `}`)
530fn parse_colon(format: &str, span: Span) -> parse::Result<(Piece, &str)> {
531    let (format, _prefix) = if let Some(tail) = format.strip_prefix('#') {
532        (tail, true)
533    } else {
534        (format, false)
535    };
536    let (format, _pad_char) = if let Some(tail) = format.strip_prefix('0') {
537        (tail, b'0')
538    } else {
539        (format, b' ')
540    };
541    let (format, _pad_length) = if !format.is_empty()
542        && if let Some(ch) = format.chars().next() {
543            ch.is_ascii_digit()
544        } else {
545            false
546        } {
547        split_number(format)
548    } else {
549        (format, 0)
550    };
551    if let Some(tail) = format.strip_prefix("x}") {
552        Ok((
553            Piece::Hex {
554                upper_case: false,
555                // pad_char,
556                // pad_length,
557                // prefix,
558            },
559            tail,
560        ))
561    } else if let Some(tail) = format.strip_prefix("X}") {
562        Ok((
563            Piece::Hex {
564                upper_case: true,
565                // pad_char,
566                // pad_length,
567                // prefix,
568            },
569            tail,
570        ))
571    } else {
572        Err(parse::Error::new(
573            span,
574            "invalid format string: expected `{{`, `{}`, `{:?}`, `{:#?}` or '{:x}'",
575        ))
576    }
577}
578
579fn map_expr_error(expr: &Expr) -> String {
580    let expr_type = match expr {
581        Expr::Assign(_) => "Assign",
582        Expr::Async(_) => "Async",
583        Expr::Await(_) => "Await",
584        Expr::Binary(_) => "Binary",
585        Expr::Block(_) => "Block",
586        Expr::Break(_) => "Break",
587        Expr::Call(_) => "Call",
588        Expr::Cast(_) => "Cast",
589        Expr::Closure(_) => "Closure",
590        Expr::Const(_) => "Const",
591        Expr::Continue(_) => "Continue",
592        Expr::Field(_) => "Field",
593        Expr::ForLoop(_) => "ForLoop",
594        Expr::Group(_) => "Group",
595        Expr::If(_) => "If",
596        Expr::Index(_) => "Index",
597        Expr::Infer(_) => "Infer",
598        Expr::Let(_) => "Let",
599        Expr::Loop(_) => "Loop",
600        Expr::Macro(_) => "Macro",
601        Expr::Match(_) => "Match",
602        Expr::MethodCall(_) => "MethodCall",
603        Expr::Paren(_) => "Paren",
604        Expr::Range(_) => "Range",
605        Expr::Repeat(_) => "Repeat",
606        Expr::Return(_) => "Return",
607        Expr::Struct(_) => "Struct",
608        Expr::Try(_) => "Try",
609        Expr::TryBlock(_) => "TryBlock",
610        Expr::Tuple(_) => "Tuple",
611        Expr::Unary(_) => "Unary",
612        Expr::Unsafe(_) => "Unsafe",
613        Expr::Verbatim(_) => "Verbatim",
614        Expr::While(_) => "While",
615        Expr::Yield(_) => "Yield",
616        Expr::Array(_) => "Array",
617        Expr::Lit(_) => "Lit",
618        Expr::Path(_) => "Path",
619        Expr::Reference(_) => "Reference",
620        _ => "Unknow",
621    };
622
623    let mut msg = "Expr not implement: ".to_string();
624    msg.push_str(expr_type);
625
626    return msg;
627}