// 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 { log_level: Expr, format_and_args: FormatAndArgsFlavor, } impl Parse for PwLogArgs { fn parse(input: ParseStream) -> syn::parse::Result { let log_level: Expr = input.parse()?; input.parse::()?; 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, } 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 PrintfFormatMacroGenerator for LogfGenerator<'_> { fn finalize( self, format_string_fragments: &[PrintfFormatStringFragment], ) -> Result { 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::>>()?; 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> { 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> { // 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> { self.args.push(quote! { let args = >::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); 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); 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(), } }