blob: 75f58ccbf4755a16e8e07d56496fb276672ed3e4 [file] [log] [blame]
// 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(),
}
}