blob: e72ca54cadd9160b5db149addfc8c3aac64c5f71 [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;
use syn::{
parse::{Parse, ParseStream},
parse_macro_input, Expr, Token,
};
use pw_format::macros::{generate_printf, FormatAndArgs, PrintfFormatMacroGenerator, Result};
type TokenStream2 = proc_macro2::TokenStream;
// Arguments to `pw_logf_backend`. A log level followed by a [`pw_format`]
// format string.
#[derive(Debug)]
struct PwLogfArgs {
log_level: Expr,
format_and_args: FormatAndArgs,
}
impl Parse for PwLogfArgs {
fn parse(input: ParseStream) -> syn::parse::Result<Self> {
let log_level: Expr = input.parse()?;
input.parse::<Token![,]>()?;
let format_and_args: FormatAndArgs = input.parse()?;
Ok(PwLogfArgs {
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: String) -> Result<TokenStream2> {
let log_level = self.log_level;
let args = &self.args;
let format_string = format!("[%s] {format_string}\n\0");
Ok(quote! {
{
use core::ffi::{c_int, c_uchar};
unsafe {
extern "C" {
fn printf(fmt: *const c_uchar, ...) -> c_int;
}
printf(#format_string.as_ptr(),
__pw_log_backend_crate::log_level_tag(#log_level).as_ptr(),
#(#args),*);
}
}
})
}
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: Expr) -> Result<Option<String>> {
self.args.push(quote! {((#expression) as #ty)});
Ok(None)
}
fn string_conversion(&mut self, expression: Expr) -> 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! {((#expression) as &str).len()});
self.args.push(quote! {((#expression) as &str).as_ptr()});
Ok(Some("%.*s".into()))
}
fn char_conversion(&mut self, expression: Expr) -> Result<Option<String>> {
self.args.push(quote! {((#expression) as char)});
Ok(None)
}
}
#[proc_macro]
pub fn _pw_logf_backend(tokens: TokenStream) -> TokenStream {
let input = parse_macro_input!(tokens as PwLogfArgs);
let generator = LogfGenerator::new(&input.log_level);
match generate_printf(generator, input.format_and_args) {
Ok(token_stream) => token_stream.into(),
Err(e) => e.to_compile_error().into(),
}
}