| // Copyright 2023 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| use proc_macro::TokenStream; |
| use proc_macro2::Ident; |
| use quote::{quote, ToTokens}; |
| use syn::{ |
| parse::{Parse, ParseStream}, |
| parse_macro_input, Expr, Token, |
| }; |
| |
| use pw_format::macros::{ |
| generate_printf, Arg, CoreFmtFormatStringParser, FormatAndArgsFlavor, FormatStringParser, |
| PrintfFormatMacroGenerator, PrintfFormatStringFragment, PrintfFormatStringParser, Result, |
| }; |
| |
| type TokenStream2 = proc_macro2::TokenStream; |
| |
| // Arguments to `pw_log[f]_backend`. A log level followed by a [`pw_format`] |
| // format string. |
| #[derive(Debug)] |
| struct PwLogArgs<T: FormatStringParser> { |
| log_level: Expr, |
| format_and_args: FormatAndArgsFlavor<T>, |
| } |
| |
| impl<T: FormatStringParser> Parse for PwLogArgs<T> { |
| fn parse(input: ParseStream) -> syn::parse::Result<Self> { |
| let log_level: Expr = input.parse()?; |
| input.parse::<Token![,]>()?; |
| let format_and_args: FormatAndArgsFlavor<_> = input.parse()?; |
| |
| Ok(PwLogArgs { |
| log_level, |
| format_and_args, |
| }) |
| } |
| } |
| |
| // Generator that implements [`pw_format::PrintfFormatMacroGenerator`] to take |
| // a log line and turn it into `printf` calls; |
| struct LogfGenerator<'a> { |
| log_level: &'a Expr, |
| args: Vec<TokenStream2>, |
| } |
| |
| impl<'a> LogfGenerator<'a> { |
| fn new(log_level: &'a Expr) -> Self { |
| Self { |
| log_level, |
| args: Vec::new(), |
| } |
| } |
| } |
| |
| // Use a [`pw_format::PrintfFormatMacroGenerator`] to prepare arguments to call |
| // `printf`. |
| impl<'a> PrintfFormatMacroGenerator for LogfGenerator<'a> { |
| fn finalize( |
| self, |
| format_string_fragments: &[PrintfFormatStringFragment], |
| ) -> Result<TokenStream2> { |
| let log_level = self.log_level; |
| let args = &self.args; |
| let format_string_pieces: Vec<_> = format_string_fragments |
| .iter() |
| .map(|fragment| fragment.as_token_stream("__pw_log_backend_crate")) |
| .collect::<Result<Vec<_>>>()?; |
| Ok(quote! { |
| { |
| use core::ffi::{c_int, c_uchar}; |
| use __pw_log_backend_crate::{Arguments, VarArgs}; |
| // Prepend log level tag and append newline and null terminator for |
| // C string validity. |
| let format_string = __pw_log_backend_crate::concat_static_strs!( |
| "[%s] ", #(#format_string_pieces),*, "\n\0" |
| ); |
| unsafe { |
| // Build up the argument type/value. |
| let args = (); |
| #(#args)* |
| |
| // Call printf through the argument type/value. |
| args.call_printf(format_string.as_ptr(), |
| __pw_log_backend_crate::log_level_tag(#log_level).as_ptr()); |
| } |
| } |
| }) |
| } |
| |
| fn string_fragment(&mut self, _string: &str) -> Result<()> { |
| // String fragments are encoded directly into the format string. |
| Ok(()) |
| } |
| |
| fn integer_conversion(&mut self, ty: Ident, expression: Arg) -> Result<Option<String>> { |
| self.args.push(quote! { |
| let args = <#ty as Arguments<#ty>>::push_arg(args, &((#expression) as #ty)); |
| }); |
| Ok(None) |
| } |
| |
| fn string_conversion(&mut self, expression: Arg) -> Result<Option<String>> { |
| // In order to not convert Rust Strings to CStrings at runtime, we use |
| // the "%.*s" specifier to explicitly bound the length of the |
| // non-null-terminated Rust String. |
| self.args.push(quote! { |
| let args = <&str as Arguments<&str>>::push_arg(args, &((#expression) as &str)); |
| }); |
| Ok(Some("%.*s".into())) |
| } |
| |
| fn char_conversion(&mut self, expression: Arg) -> Result<Option<String>> { |
| self.args.push(quote! { |
| let args = <char as Arguments<char>>::push_arg(args, &((#expression) as char)); |
| }); |
| Ok(None) |
| } |
| |
| fn untyped_conversion(&mut self, expression: Arg) -> Result<()> { |
| match &expression { |
| Arg::ExprCast(cast) => { |
| let ty = &cast.ty; |
| self.args.push(quote! { |
| let args = <#ty as Arguments<#ty>>::push_arg(args, &(#expression)); |
| }); |
| } |
| Arg::Expr(_) => { |
| return Err(pw_format::macros::Error::new(&format!( |
| "Expected argument to untyped format (%v) to be a cast expression (e.g. x as i32), but found {}.", |
| expression.to_token_stream() |
| ))); |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| #[proc_macro] |
| pub fn _pw_log_backend(tokens: TokenStream) -> TokenStream { |
| let input = parse_macro_input!(tokens as PwLogArgs<CoreFmtFormatStringParser>); |
| |
| let generator = LogfGenerator::new(&input.log_level); |
| |
| match generate_printf(generator, input.format_and_args.into()) { |
| Ok(token_stream) => token_stream.into(), |
| Err(e) => e.to_compile_error().into(), |
| } |
| } |
| |
| #[proc_macro] |
| pub fn _pw_logf_backend(tokens: TokenStream) -> TokenStream { |
| let input = parse_macro_input!(tokens as PwLogArgs<PrintfFormatStringParser>); |
| |
| let generator = LogfGenerator::new(&input.log_level); |
| |
| match generate_printf(generator, input.format_and_args.into()) { |
| Ok(token_stream) => token_stream.into(), |
| Err(e) => e.to_compile_error().into(), |
| } |
| } |