defmt-macros-0.3.6/.cargo_vcs_info.json0000644000000001440000000000100134220ustar { "git": { "sha1": "e1413dafb86f3bbe7377d44bee52f97abaaedcfd" }, "path_in_vcs": "macros" }defmt-macros-0.3.6/Cargo.toml0000644000000023230000000000100114210ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "defmt-macros" version = "0.3.6" authors = ["The Knurling-rs developers"] description = "defmt macros" readme = "README.md" keywords = [ "knurling", "defmt", ] license = "MIT OR Apache-2.0" repository = "https://github.com/knurling-rs/defmt" resolver = "1" [lib] proc-macro = true [dependencies.defmt-parser] version = "=0.3.3" features = ["unstable"] [dependencies.proc-macro-error] version = "1" [dependencies.proc-macro2] version = "1" [dependencies.quote] version = "1" [dependencies.syn] version = "2" features = ["full"] [dev-dependencies.maplit] version = "1" [dev-dependencies.pretty_assertions] version = "1" [dev-dependencies.rstest] version = "0.17" default-features = false [features] unstable-test = [] defmt-macros-0.3.6/Cargo.toml.orig000064400000000000000000000013051046102023000151010ustar 00000000000000[package] authors = ["The Knurling-rs developers"] description = "defmt macros" edition = "2021" keywords = ["knurling", "defmt"] license = "MIT OR Apache-2.0" name = "defmt-macros" readme = "../README.md" repository = "https://github.com/knurling-rs/defmt" version = "0.3.6" [lib] proc-macro = true [features] # WARNING: for internal use only, not covered by semver guarantees unstable-test = [] [dependencies] defmt-parser = { version = "=0.3.3", path = "../parser", features = ["unstable"] } proc-macro-error = "1" proc-macro2 = "1" quote = "1" syn = { version = "2", features = ["full"] } [dev-dependencies] maplit = "1" pretty_assertions = "1" rstest = { version = "0.17", default-features = false } defmt-macros-0.3.6/README.md000064400000000000000000000047471046102023000135060ustar 00000000000000# `defmt` `defmt` ("de format", short for "deferred formatting") is a highly efficient logging framework that targets resource-constrained devices, like microcontrollers. For more details about the framework check the book at . The git version of the defmt book can be viewed at . ## Setup ### New project The fastest way to get started with `defmt` is to use our [app-template] to set up a new Cortex-M embedded project. [app-template]: https://github.com/knurling-rs/app-template ### Existing project To include `defmt` in your existing project, follow our [Application Setup guide]. [Application Setup guide]: https://defmt.ferrous-systems.com/setup.html ## MSRV `defmt` always compiles on the [latest `stable` rust release](https://github.com/rust-lang/rust/releases/latest). This is enforced by our CI building and testing against this version. It still might work on older rust versions, but this isn't ensured. ## defmt ecosystem The following diagram illustrates the user-facing and internal crates of the defmt framework. ![defmt crates structure](assets/defmt.png) ## Developer Information ### Running Tests Tests are run using `cargo xtask` -- although this is simply an alias (defined in `.cargo/config.toml`) for `cargo run --package xtask --`. To see a list of options, see [`xtask/src/main.rs`](xtask/src/main.rs), or run: ```console $ cargo xtask help ``` For example, to run all the tests, run: ```console $ cargo xtask test-all ``` You will need `qemu-system-arm` installed and in your `$PATH` for some of the tests (e.g. `test-snapshot`). ## Support `defmt` is part of the [Knurling] project, [Ferrous Systems]' effort at improving tooling used to develop for embedded systems. If you think that our work is useful, consider sponsoring it via [GitHub Sponsors]. ## License Licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions. [Knurling]: https://knurling.ferrous-systems.com/ [Ferrous Systems]: https://ferrous-systems.com/ [GitHub Sponsors]: https://github.com/sponsors/knurling-rs defmt-macros-0.3.6/build.rs000064400000000000000000000001041046102023000136530ustar 00000000000000fn main() { println!("cargo:rerun-if-env-changed=DEFMT_LOG"); } defmt-macros-0.3.6/src/attributes/global_logger.rs000064400000000000000000000030731046102023000203400ustar 00000000000000use proc_macro::TokenStream; use proc_macro_error::{abort, abort_call_site}; use quote::quote; use syn::{parse_macro_input, Fields, ItemStruct}; pub(crate) fn expand(args: TokenStream, item: TokenStream) -> TokenStream { if !args.is_empty() { abort_call_site!("`#[global_logger]` attribute takes no arguments") } let strukt = parse_macro_input!(item as ItemStruct); validate(&strukt); codegen(&strukt) } fn validate(strukt: &ItemStruct) { let is_unit_struct = matches!(strukt.fields, Fields::Unit); if !strukt.generics.params.is_empty() || strukt.generics.where_clause.is_some() || !is_unit_struct { abort!( strukt, "struct must be a non-generic unit struct (e.g. `struct S;`)" ); } } fn codegen(strukt: &ItemStruct) -> TokenStream { let attrs = &strukt.attrs; let ident = &strukt.ident; let vis = &strukt.vis; quote!( #(#attrs)* #vis struct #ident; #[inline(never)] #[no_mangle] unsafe fn _defmt_acquire() { <#ident as defmt::Logger>::acquire() } #[inline(never)] #[no_mangle] unsafe fn _defmt_flush() { <#ident as defmt::Logger>::flush() } #[inline(never)] #[no_mangle] unsafe fn _defmt_release() { <#ident as defmt::Logger>::release() } #[inline(never)] #[no_mangle] unsafe fn _defmt_write(bytes: &[u8]) { <#ident as defmt::Logger>::write(bytes) } ) .into() } defmt-macros-0.3.6/src/attributes/panic_handler.rs000064400000000000000000000042501046102023000203260ustar 00000000000000use proc_macro::TokenStream; use proc_macro_error::{abort, abort_call_site}; use quote::quote; use syn::{parse_macro_input, Attribute, ItemFn, ReturnType, Type}; pub(crate) fn expand(args: TokenStream, item: TokenStream) -> TokenStream { if !args.is_empty() { abort_call_site!("`#[defmt::panic_handler]` attribute takes no arguments"); } let fun = parse_macro_input!(item as ItemFn); validate(&fun); codegen(&fun) } fn validate(fun: &ItemFn) { let is_divergent = match &fun.sig.output { ReturnType::Default => false, ReturnType::Type(_, ty) => matches!(&**ty, Type::Never(_)), }; if fun.sig.constness.is_some() || fun.sig.asyncness.is_some() || fun.sig.unsafety.is_some() || fun.sig.abi.is_some() || !fun.sig.generics.params.is_empty() || fun.sig.generics.where_clause.is_some() || fun.sig.variadic.is_some() || !fun.sig.inputs.is_empty() || !is_divergent { abort!(fun.sig.ident, "function must have signature `fn() -> !`"); } check_for_attribute_conflicts("panic_handler", &fun.attrs, &["export_name", "no_mangle"]); } /// Checks if any attribute in `attrs_to_check` is in `reject_list` and returns a compiler error if there's a match /// /// The compiler error will indicate that the attribute conflicts with `attr_name` fn check_for_attribute_conflicts( attr_name: &str, attrs_to_check: &[Attribute], reject_list: &[&str], ) { for attr in attrs_to_check { if let Some(ident) = attr.path().get_ident() { let ident = ident.to_string(); if reject_list.contains(&ident.as_str()) { abort!( attr, "`#[{}]` attribute cannot be used together with `#[{}]`", attr_name, ident ) } } } } fn codegen(fun: &ItemFn) -> TokenStream { let attrs = &fun.attrs; let block = &fun.block; let ident = &fun.sig.ident; quote!( #(#attrs)* #[export_name = "_defmt_panic"] #[inline(never)] fn #ident() -> ! { #block } ) .into() } defmt-macros-0.3.6/src/attributes.rs000064400000000000000000000001471046102023000155400ustar 00000000000000//! Procedural macros that are attributes pub(crate) mod global_logger; pub(crate) mod panic_handler; defmt-macros-0.3.6/src/cargo.rs000064400000000000000000000003741046102023000144470ustar 00000000000000use std::env; pub(crate) fn package_name() -> String { env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "".to_string()) } pub(crate) fn crate_name() -> String { env::var("CARGO_CRATE_NAME").unwrap_or_else(|_| "".to_string()) } defmt-macros-0.3.6/src/construct/symbol.rs000064400000000000000000000060331046102023000167030ustar 00000000000000use std::fmt::Write; use crate::cargo; pub(crate) fn mangled(defmt_tag: &str, data: &str) -> String { Symbol::new(defmt_tag, data).mangle() } struct Symbol<'a> { /// Name of the Cargo package in which the symbol is being instantiated. Used for avoiding /// symbol name collisions. package: String, /// Unique identifier that disambiguates otherwise equivalent invocations in the same crate. disambiguator: u64, /// Symbol categorization. Known values: /// * `defmt_prim` for primitive formatting strings that are placed at the start of the `.defmt` /// section. /// * `defmt_fmt`, `defmt_str` for interned format strings and string literals. /// * `defmt_println` for logging messages that are always displayed. /// * `defmt_trace`, `defmt_debug`, `defmt_info`, `defmt_warn`, `defmt_error` for logging /// messages used at the different log levels. /// * `defmt_bitflags` indicates that a format string was generated by a `defmt::bitflags!` /// invocation, and that the decoder should look up possible flags in the binary. /// The data string is of the format `NAME@REPR#NUM`, where `NAME` is the name of the bitflags /// struct, `REPR` is the raw integer type representing the bitflags value (and also the /// wire format), and `NUM` is the number of defined bitflag values. /// * `defmt_bitflags_value` marks a `static` that holds the value of a bitflags `const`, its /// data field is `STRUCT_NAME::FLAG_NAME`. /// * Anything starting with `defmt_` is reserved for use by defmt, other prefixes are free for /// use by third-party apps (but they all should use a prefix!). tag: String, /// Symbol data for use by the host tooling. Interpretation depends on `tag`. data: &'a str, /// Crate name obtained via CARGO_CRATE_NAME (added since a Cargo package can contain many crates). crate_name: String, } impl<'a> Symbol<'a> { fn new(tag: &'a str, data: &'a str) -> Self { Self { // `CARGO_PKG_NAME` is set to the invoking package's name. package: cargo::package_name(), disambiguator: super::crate_local_disambiguator(), tag: format!("defmt_{tag}"), data, crate_name: cargo::crate_name(), } } fn mangle(&self) -> String { format!( r#"{{"package":"{}","tag":"{}","data":"{}","disambiguator":"{}","crate_name":"{}"}}"#, json_escape(&self.package), json_escape(&self.tag), json_escape(self.data), self.disambiguator, json_escape(&self.crate_name), ) } } fn json_escape(string: &str) -> String { let mut escaped = String::new(); for c in string.chars() { match c { '\\' => escaped.push_str("\\\\"), '\"' => escaped.push_str("\\\""), '\n' => escaped.push_str("\\n"), c if c.is_control() || c == '@' => write!(escaped, "\\u{:04x}", c as u32).unwrap(), c => escaped.push(c), } } escaped } defmt-macros-0.3.6/src/construct.rs000064400000000000000000000061041046102023000153750ustar 00000000000000use std::{ collections::hash_map::DefaultHasher, hash::{Hash as _, Hasher as _}, }; use proc_macro::Span; use proc_macro2::{Ident as Ident2, Span as Span2, TokenStream as TokenStream2}; use quote::{format_ident, quote}; use syn::{parse_quote, Expr, Ident, LitStr}; pub(crate) use symbol::mangled as mangled_symbol_name; mod symbol; pub(crate) fn crate_local_disambiguator() -> u64 { // We want a deterministic, but unique-per-macro-invocation identifier. For that we // hash the call site `Span`'s debug representation, which contains a counter that // should disambiguate macro invocations within a crate. hash(&format!("{:?}", Span::call_site())) } pub(crate) fn escaped_expr_string(expr: &Expr) -> String { quote!(#expr) .to_string() .replace('{', "{{") .replace('}', "}}") } pub(crate) fn interned_string(string: &str, tag: &str, is_log_statement: bool) -> TokenStream2 { // NOTE we rely on this variable name when extracting file location information from the DWARF // without it we have no other mean to differentiate static variables produced by `info!` vs // produced by `intern!` (or `internp`) let var_name = if is_log_statement { format_ident!("DEFMT_LOG_STATEMENT") } else { format_ident!("S") }; let var_addr = if cfg!(feature = "unstable-test") { quote!({ defmt::export::fetch_add_string_index() }) } else { let var_item = static_variable(&var_name, string, tag); quote!({ #var_item &#var_name as *const u8 as u16 }) }; quote!({ defmt::export::make_istr(#var_addr) }) } /// work around restrictions on length and allowed characters imposed by macos linker /// returns (note the comma character for macos): /// under macos: ".defmt," + 16 character hex digest of symbol's hash /// otherwise: ".defmt." + prefix + symbol pub(crate) fn linker_section(for_macos: bool, prefix: Option<&str>, symbol: &str) -> String { let mut sub_section = if let Some(prefix) = prefix { format!(".{prefix}.{symbol}") } else { format!(".{symbol}") }; if for_macos { sub_section = format!(",{:x}", hash(&sub_section)); } format!(".defmt{sub_section}") } pub(crate) fn static_variable(name: &Ident2, data: &str, tag: &str) -> TokenStream2 { let sym_name = mangled_symbol_name(tag, data); let section = linker_section(false, None, &sym_name); let section_for_macos = linker_section(true, None, &sym_name); quote!( #[cfg_attr(target_os = "macos", link_section = #section_for_macos)] #[cfg_attr(not(target_os = "macos"), link_section = #section)] #[export_name = #sym_name] static #name: u8 = 0; ) } pub(crate) fn string_literal(content: &str) -> LitStr { LitStr::new(content, Span2::call_site()) } pub(crate) fn variable(name: &str) -> Expr { let ident = Ident::new(name, Span2::call_site()); parse_quote!(#ident) } fn hash(string: &str) -> u64 { let mut hasher = DefaultHasher::new(); string.hash(&mut hasher); hasher.finish() } defmt-macros-0.3.6/src/consts.rs000064400000000000000000000001611046102023000146570ustar 00000000000000/// The (erased) "Format" type as it appears in `defmt` format strings pub(crate) const TYPE_FORMAT: &str = "?"; defmt-macros-0.3.6/src/derives/format/codegen/enum_data.rs000064400000000000000000000065351046102023000216530ustar 00000000000000use proc_macro2::TokenStream as TokenStream2; use proc_macro_error::abort_call_site; use quote::quote; use syn::{DataEnum, Ident}; use crate::construct; use super::EncodeData; pub(crate) fn encode(ident: &Ident, data: &DataEnum) -> syn::Result { if data.variants.is_empty() { return Ok(EncodeData { stmts: vec![quote!(match *self {})], format_tag: construct::interned_string("!", "derived", false), }); } let mut format_string = String::new(); let mut match_arms = vec![]; let mut is_first_variant = true; let discriminant_encoder = DiscriminantEncoder::new(data.variants.len()); let enum_ident = ident; for (index, variant) in data.variants.iter().enumerate() { let variant_ident = &variant.ident; if is_first_variant { is_first_variant = false; } else { format_string.push('|'); } format_string.push_str(&variant_ident.to_string()); let mut field_patterns = vec![]; let encode_fields_stmts = super::fields::codegen(&variant.fields, &mut format_string, &mut field_patterns)?; let pattern = quote!( { #(#field_patterns),* } ); let encode_discriminant_stmt = discriminant_encoder.encode(index); match_arms.push(quote!( #enum_ident::#variant_ident #pattern => { #encode_discriminant_stmt #(#encode_fields_stmts;)* } )) } let format_tag = construct::interned_string(&format_string, "derived", false); let stmts = vec![quote!(match self { #(#match_arms)* })]; Ok(EncodeData { format_tag, stmts }) } enum DiscriminantEncoder { Nop, U8, U16, U32, U64, } impl DiscriminantEncoder { fn new(number_of_variants: usize) -> Self { if number_of_variants == 1 { Self::Nop } else if number_of_variants <= usize::from(u8::MAX) { Self::U8 } else if number_of_variants <= usize::from(u16::MAX) { Self::U16 } else if number_of_variants as u128 > u128::from(u64::MAX) { // unreachable on existing hardware? abort_call_site!( "`#[derive(Format)]` does not support enums with more than {} variants", number_of_variants ) } else if number_of_variants as u64 <= u64::from(u32::MAX) { Self::U32 } else { Self::U64 } } // NOTE this assumes `index` < `number_of_variants` used to construct `self` fn encode(&self, index: usize) -> TokenStream2 { match self { // For single-variant enums, there is no need to encode the discriminant. DiscriminantEncoder::Nop => quote!(), DiscriminantEncoder::U8 => { let index = index as u8; quote!(defmt::export::u8(&#index);) } DiscriminantEncoder::U16 => { let index = index as u16; quote!(defmt::export::u16(&#index);) } DiscriminantEncoder::U32 => { let index = index as u32; quote!(defmt::export::u32(&#index);) } DiscriminantEncoder::U64 => { let index = index as u64; quote!(defmt::export::u64(&#index);) } } } } defmt-macros-0.3.6/src/derives/format/codegen/fields.rs000064400000000000000000000114731046102023000211610ustar 00000000000000use std::fmt::Write as _; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; use syn::{Field, Fields, Index, Type}; use crate::consts; pub(crate) fn codegen( fields: &Fields, format_string: &mut String, patterns: &mut Vec, ) -> syn::Result> { let (fields, fields_are_named) = match fields { Fields::Named(named) => (&named.named, true), Fields::Unit => return Ok(vec![]), Fields::Unnamed(unnamed) => (&unnamed.unnamed, false), }; if fields.is_empty() { return Ok(vec![]); } if fields_are_named { format_string.push_str(" {{ "); } else { format_string.push('('); } let mut stmts = vec![]; let mut is_first = true; for (index, field) in fields.iter().enumerate() { if is_first { is_first = false; } else { format_string.push_str(", "); } let format_opt = get_defmt_format_option(field)?; let ty = as_native_type(&field.ty).unwrap_or_else(|| consts::TYPE_FORMAT.to_string()); let ident = field .ident .clone() .unwrap_or_else(|| format_ident!("arg{}", index)); if let Some(FormatOption::Debug2Format) = format_opt { stmts.push(quote!(defmt::export::fmt(&defmt::Debug2Format(&#ident)))); } else if let Some(FormatOption::Display2Format) = format_opt { stmts.push(quote!(defmt::export::fmt(&defmt::Display2Format(&#ident)))); } else if ty == consts::TYPE_FORMAT { stmts.push(quote!(defmt::export::fmt(#ident))); } else { let method = format_ident!("{}", ty); stmts.push(quote!(defmt::export::#method(#ident))); } if field.ident.is_some() { // Named field. write!(format_string, "{ident}: {{={ty}:?}}").ok(); patterns.push(quote!( #ident )); } else { // Unnamed (tuple) field. write!(format_string, "{{={ty}}}").ok(); let index = Index::from(index); patterns.push(quote!( #index: #ident )); } } if fields_are_named { format_string.push_str(" }}"); } else { format_string.push(')'); } Ok(stmts) } #[derive(Copy, Clone, Eq, PartialEq, Debug)] enum FormatOption { Debug2Format, Display2Format, } /// If the field has a valid defmt attribute (e.g. `#[defmt(Debug2Format)]`), returns `Ok(Some(FormatOption))`. /// Returns `Err` if we can't parse a valid defmt attribute. /// Returns `Ok(None)` if there are no `defmt` attributes on the field. fn get_defmt_format_option(field: &Field) -> syn::Result> { let mut format_option = None; for attr in &field.attrs { if attr.path().is_ident("defmt") { if format_option.is_some() { return Err(syn::Error::new_spanned( field, "multiple `defmt` attributes not supported", )); } let mut parsed_format = None; attr.parse_nested_meta(|meta| { // #[defmt(Debug2Format)] if meta.path.is_ident("Debug2Format") { parsed_format = Some(FormatOption::Debug2Format); return Ok(()); } // #[defmt(Display2Format)] if meta.path.is_ident("Display2Format") { parsed_format = Some(FormatOption::Display2Format); return Ok(()); } Err(meta.error("expected `Debug2Format` or `Display2Format`")) })?; if parsed_format.is_none() { return Err(syn::Error::new_spanned( &attr.meta, "expected 1 attribute argument", )); } format_option = parsed_format; } } Ok(format_option) } /// Returns `Some` if `ty` refers to a builtin Rust type that has native support from defmt and does /// not have to go through the `Format` trait. /// /// This should return `Some` for all types that can be used as `{=TYPE}`. /// /// Note: This is technically incorrect, since builtin types can be shadowed. However the efficiency /// gains are too big to pass up, so we expect user code to not do that. fn as_native_type(ty: &Type) -> Option { match ty { Type::Path(path) => { let ident = path.path.get_ident()?; let ty_name = ident.to_string(); match &*ty_name { "u8" | "u16" | "u32" | "usize" | "i8" | "i16" | "i32" | "isize" | "f32" | "f64" | "bool" | "str" => Some(ty_name), _ => None, } } Type::Reference(ty_ref) => as_native_type(&ty_ref.elem), _ => None, } } defmt-macros-0.3.6/src/derives/format/codegen.rs000064400000000000000000000034711046102023000177120ustar 00000000000000use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::{parse_quote, DataStruct, GenericParam, Ident, ImplGenerics, TypeGenerics, WhereClause}; pub(crate) use enum_data::encode as encode_enum_data; use crate::construct; mod enum_data; mod fields; pub(crate) struct EncodeData { pub(crate) format_tag: TokenStream2, pub(crate) stmts: Vec, } pub(crate) fn encode_struct_data(ident: &Ident, data: &DataStruct) -> syn::Result { let mut format_string = ident.to_string(); let mut stmts = vec![]; let mut field_patterns = vec![]; let encode_fields_stmts = fields::codegen(&data.fields, &mut format_string, &mut field_patterns)?; stmts.push(quote!(match self { Self { #(#field_patterns),* } => { #(#encode_fields_stmts;)* } })); let format_tag = construct::interned_string(&format_string, "derived", false); Ok(EncodeData { format_tag, stmts }) } pub(crate) struct Generics<'a> { pub(crate) impl_generics: ImplGenerics<'a>, pub(crate) type_generics: TypeGenerics<'a>, pub(crate) where_clause: WhereClause, } impl<'a> Generics<'a> { pub(crate) fn codegen(generics: &'a mut syn::Generics) -> Self { let mut where_clause = generics.make_where_clause().clone(); let (impl_generics, type_generics, _) = generics.split_for_impl(); // Extend where-clause with `Format` bounds for type parameters. for param in &generics.params { if let GenericParam::Type(ty) = param { let ident = &ty.ident; where_clause .predicates .push(parse_quote!(#ident: defmt::Format)); } } Self { impl_generics, type_generics, where_clause, } } } defmt-macros-0.3.6/src/derives/format.rs000064400000000000000000000024171046102023000163050ustar 00000000000000use proc_macro::TokenStream; use proc_macro_error::abort_call_site; use quote::quote; use syn::{parse_macro_input, Data, DeriveInput}; mod codegen; pub(crate) fn expand(input: TokenStream) -> TokenStream { let mut input = parse_macro_input!(input as DeriveInput); let ident = &input.ident; let encode_data = match &input.data { Data::Enum(data) => codegen::encode_enum_data(ident, data), Data::Struct(data) => codegen::encode_struct_data(ident, data), Data::Union(_) => abort_call_site!("`#[derive(Format)]` does not support unions"), }; let codegen::EncodeData { format_tag, stmts } = match encode_data { Ok(data) => data, Err(e) => return e.into_compile_error().into(), }; let codegen::Generics { impl_generics, type_generics, where_clause, } = codegen::Generics::codegen(&mut input.generics); quote!( impl #impl_generics defmt::Format for #ident #type_generics #where_clause { fn format(&self, f: defmt::Formatter) { defmt::unreachable!() } fn _format_tag() -> defmt::Str { #format_tag } fn _format_data(&self) { #(#stmts)* } } ) .into() } defmt-macros-0.3.6/src/derives.rs000064400000000000000000000001211046102023000150030ustar 00000000000000//! Procedural macros that are `#[derive(*)]` attributes pub(crate) mod format; defmt-macros-0.3.6/src/function_like/assert_binop/args.rs000064400000000000000000000020401046102023000216210ustar 00000000000000use syn::{ parse::{self, Parse, ParseStream}, Expr, Token, }; use crate::function_like::log; pub(crate) struct Args { pub(crate) left: Expr, pub(crate) right: Expr, pub(crate) log_args: Option, } impl Parse for Args { fn parse(input: ParseStream) -> parse::Result { let left = input.parse()?; let _comma: Token![,] = input.parse()?; let right = input.parse()?; if input.is_empty() { // assert_eq!(a, b) return Ok(Args { left, right, log_args: None, }); } let _comma: Token![,] = input.parse()?; if input.is_empty() { // assert_eq!(a, b,) Ok(Args { left, right, log_args: None, }) } else { // assert_eq!(a, b, "c", d) Ok(Args { left, right, log_args: Some(input.parse()?), }) } } } defmt-macros-0.3.6/src/function_like/assert_binop.rs000064400000000000000000000041531046102023000206740ustar 00000000000000use defmt_parser::Level; use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, punctuated::Punctuated}; use crate::{construct, function_like::log}; use self::args::Args; mod args; pub(crate) fn eq(args: TokenStream) -> TokenStream { expand(args, BinOp::Eq) } pub(crate) fn ne(args: TokenStream) -> TokenStream { expand(args, BinOp::Ne) } fn expand(args: TokenStream, binop: BinOp) -> TokenStream { let args = parse_macro_input!(args as Args); let left = args.left; let right = args.right; let mut formatting_args = Punctuated::new(); let extra_string = if let Some(log_args) = args.log_args { if let Some(args) = log_args.formatting_args { formatting_args.extend(args); } format!(": {}", log_args.format_string.value()) } else { String::new() }; let vals = match binop { BinOp::Eq => &["left_val", "right_val"][..], BinOp::Ne => &["left_val"][..], }; for val in vals { formatting_args.push(construct::variable(val)); } let panic_msg = match binop { BinOp::Eq => format!( "panicked at 'assertion failed: `(left == right)`{}' left: `{{:?}}` right: `{{:?}}`", extra_string ), BinOp::Ne => format!( "panicked at 'assertion failed: `(left != right)`{}' left/right: `{{:?}}`", extra_string ), }; let log_args = log::Args { format_string: construct::string_literal(&panic_msg), formatting_args: Some(formatting_args), }; let log_stmt = log::expand_parsed(Level::Error, log_args); let mut cond = quote!(*left_val == *right_val); if binop == BinOp::Eq { cond = quote!(!(#cond)); } quote!( // evaluate arguments first match (&(#left), &(#right)) { (left_val, right_val) => { // following `core::assert_eq!` if #cond { #log_stmt; defmt::export::panic() } } } ) .into() } #[derive(PartialEq)] enum BinOp { Eq, Ne, } defmt-macros-0.3.6/src/function_like/assert_like/assert.rs000064400000000000000000000020601046102023000220050ustar 00000000000000use defmt_parser::Level; use proc_macro::TokenStream; use quote::quote; use syn::parse_macro_input; use crate::{construct, function_like::log}; pub(crate) fn expand(args: TokenStream) -> TokenStream { let args = parse_macro_input!(args as super::Args); let condition = args.condition; let (format_string, formatting_args) = if let Some(log_args) = args.log_args { let format_string = format!("panicked at '{}'", log_args.format_string.value()); (format_string, log_args.formatting_args) } else { let format_string = format!( "panicked at 'assertion failed: {}'", construct::escaped_expr_string(&condition) ); (format_string, None) }; let format_string = construct::string_literal(&format_string); let log_stmt = log::expand_parsed( Level::Error, log::Args { format_string, formatting_args, }, ); quote!( if !(#condition) { #log_stmt; defmt::export::panic() } ) .into() } defmt-macros-0.3.6/src/function_like/assert_like/unwrap.rs000064400000000000000000000026021046102023000220220ustar 00000000000000use defmt_parser::Level; use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, punctuated::Punctuated}; use crate::{construct, function_like::log}; pub(crate) fn expand(args: TokenStream) -> TokenStream { let args = parse_macro_input!(args as super::Args); let condition = args.condition; let (format_string, formatting_args) = if let Some(log_args) = args.log_args { let format_string = format!("panicked at '{}'", log_args.format_string.value()); (format_string, log_args.formatting_args) } else { let format_string = format!( "panicked at 'unwrap failed: {}'\nerror: `{{:?}}`", construct::escaped_expr_string(&condition) ); let mut formatting_args = Punctuated::new(); formatting_args.push(construct::variable("_unwrap_err")); (format_string, Some(formatting_args)) }; let format_string = construct::string_literal(&format_string); let log_stmt = log::expand_parsed( Level::Error, log::Args { format_string, formatting_args, }, ); quote!( match defmt::export::into_result(#condition) { ::core::result::Result::Ok(res) => res, ::core::result::Result::Err(_unwrap_err) => { #log_stmt; defmt::export::panic() } } ) .into() } defmt-macros-0.3.6/src/function_like/assert_like.rs000064400000000000000000000015601046102023000205100ustar 00000000000000use syn::{ parse::{self, Parse, ParseStream}, Expr, Token, }; use super::log; pub(crate) mod assert; pub(crate) mod unwrap; struct Args { condition: Expr, log_args: Option, } impl Parse for Args { fn parse(input: ParseStream) -> parse::Result { let condition = input.parse()?; if input.is_empty() { // assert!(a) return Ok(Args { log_args: None, condition, }); } let _comma: Token![,] = input.parse()?; if input.is_empty() { // assert!(a,) Ok(Args { log_args: None, condition, }) } else { // assert!(a, "b", c) Ok(Args { log_args: Some(input.parse()?), condition, }) } } } defmt-macros-0.3.6/src/function_like/dbg/args.rs000064400000000000000000000005371046102023000176760ustar 00000000000000use syn::{ parse::{Parse, ParseStream}, punctuated::Punctuated, Expr, Token, }; pub(crate) struct Args { pub(crate) exprs: Punctuated, } impl Parse for Args { fn parse(input: ParseStream) -> Result { Ok(Self { exprs: Punctuated::parse_terminated(input)?, }) } } defmt-macros-0.3.6/src/function_like/dbg.rs000064400000000000000000000016701046102023000167410ustar 00000000000000use proc_macro::TokenStream; use quote::quote; use syn::parse_macro_input; use crate::construct; use self::args::Args; mod args; pub(crate) fn expand(args: TokenStream) -> TokenStream { let args = parse_macro_input!(args as Args); codegen(&args) } fn codegen(args: &Args) -> TokenStream { let tuple_exprs = args .exprs .iter() .map(|expr| { let escaped_expr = construct::escaped_expr_string(expr); let format_string = format!("{escaped_expr} = {{}}"); quote!(match #expr { tmp => { defmt::trace!(#format_string, tmp); tmp } }) }) .collect::>(); if tuple_exprs.is_empty() { // for compatibility with `std::dbg!` we also emit a TRACE log in this case quote!(defmt::trace!("")) } else { quote![ (#(#tuple_exprs),*) ] } .into() } defmt-macros-0.3.6/src/function_like/intern.rs000064400000000000000000000004221046102023000174760ustar 00000000000000use proc_macro::TokenStream; use syn::{parse_macro_input, LitStr}; use crate::construct; pub(crate) fn expand(args: TokenStream) -> TokenStream { let literal = parse_macro_input!(args as LitStr); construct::interned_string(&literal.value(), "str", false).into() } defmt-macros-0.3.6/src/function_like/internp.rs000064400000000000000000000017361046102023000176670ustar 00000000000000use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, LitStr}; use crate::construct; pub(crate) fn expand(args: TokenStream) -> TokenStream { let literal = parse_macro_input!(args as LitStr); let sym_name = construct::mangled_symbol_name("prim", &literal.value()); let prefix = Some("prim"); let section = construct::linker_section(false, prefix, &sym_name); let section_for_macos = construct::linker_section(true, prefix, &sym_name); let var_addr = if cfg!(feature = "unstable-test") { quote!({ defmt::export::fetch_add_string_index() as u16 }) } else { quote!({ #[cfg_attr(target_os = "macos", link_section = #section_for_macos)] #[cfg_attr(not(target_os = "macos"), link_section = #section)] #[export_name = #sym_name] static S: u8 = 0; &S as *const u8 as u16 }) }; quote!({ defmt::export::make_istr(#var_addr) }) .into() } defmt-macros-0.3.6/src/function_like/log/args.rs000064400000000000000000000011611046102023000177150ustar 00000000000000use syn::{ parse::{self, Parse, ParseStream}, punctuated::Punctuated, Expr, LitStr, Token, }; pub(crate) struct Args { pub(crate) format_string: LitStr, pub(crate) formatting_args: Option>, } impl Parse for Args { fn parse(input: ParseStream) -> parse::Result { Ok(Self { format_string: input.parse()?, formatting_args: if input.is_empty() { None } else { let _comma: Token![,] = input.parse()?; Some(Punctuated::parse_terminated(input)?) }, }) } } defmt-macros-0.3.6/src/function_like/log/codegen.rs000064400000000000000000000121531046102023000203700ustar 00000000000000use defmt_parser::{Fragment, Parameter, Type}; use proc_macro2::{Ident as Ident2, Span as Span2, TokenStream as TokenStream2}; use proc_macro_error::abort; use quote::{format_ident, quote}; pub(crate) struct Codegen { pub(crate) exprs: Vec, pub(crate) patterns: Vec, } impl Codegen { pub(crate) fn new(fragments: &[Fragment<'_>], given_arg_count: usize, span: Span2) -> Self { let params = fragments .iter() .filter_map(|frag| match frag { Fragment::Parameter(param) => Some(param.clone()), Fragment::Literal(_) => None, }) .collect::>(); let expected_arg_count = params .iter() .map(|param| param.index + 1) .max() .unwrap_or(0); if given_arg_count != expected_arg_count { let mut only = ""; if given_arg_count < expected_arg_count { only = "only "; } abort!( span, "format string requires {} arguments but {}{} were provided", expected_arg_count, only, given_arg_count ) } let mut exprs = vec![]; let mut patterns = vec![]; for arg_index in 0..expected_arg_count { let arg_ident = format_ident!("arg{}", arg_index); let matching_param = params .iter() .find(|param| param.index == arg_index) .unwrap(); let expr = encode_arg(&matching_param.ty, ¶ms, arg_index, &arg_ident); exprs.push(expr); patterns.push(arg_ident); } Codegen { exprs, patterns } } } fn encode_arg(ty: &Type, params: &[Parameter], arg_index: usize, arg: &Ident2) -> TokenStream2 { match ty { Type::I8 => quote!(defmt::export::i8(#arg)), Type::I16 => quote!(defmt::export::i16(#arg)), Type::I32 => quote!(defmt::export::i32(#arg)), Type::I64 => quote!(defmt::export::i64(#arg)), Type::I128 => quote!(defmt::export::i128(#arg)), Type::Isize => quote!(defmt::export::isize(#arg)), Type::U8 => quote!(defmt::export::u8(#arg)), Type::U16 => quote!(defmt::export::u16(#arg)), Type::U32 => quote!(defmt::export::u32(#arg)), Type::U64 => quote!(defmt::export::u64(#arg)), Type::U128 => quote!(defmt::export::u128(#arg)), Type::Usize => quote!(defmt::export::usize(#arg)), Type::F32 => quote!(defmt::export::f32(#arg)), Type::F64 => quote!(defmt::export::f64(#arg)), Type::Bool => quote!(defmt::export::bool(#arg)), Type::Str => quote!(defmt::export::str(#arg)), Type::IStr => quote!(defmt::export::istr(#arg)), Type::Char => quote!(defmt::export::char(#arg)), Type::Format => quote!(defmt::export::fmt(#arg)), Type::FormatSlice => quote!(defmt::export::fmt_slice(#arg)), Type::FormatArray(len) => quote!(defmt::export::fmt_array({ let tmp: &[_; #len] = #arg; tmp })), Type::Debug => quote!(defmt::export::debug(#arg)), Type::Display => quote!(defmt::export::display(#arg)), Type::FormatSequence => unreachable!(), Type::U8Slice => quote!(defmt::export::slice(#arg)), // We cast to the expected array type (which should be a no-op cast) to provoke // a type mismatch error on mismatched lengths: // ``Symbol’s value as variable is void: // Type::U8Array(len) => quote!(defmt::export::u8_array({ let tmp: &[u8; #len] = #arg; tmp })), Type::BitField(_) => { let all_bitfields = params.iter().filter(|param| param.index == arg_index); let (smallest_bit_index, largest_bit_index) = defmt_parser::get_max_bitfield_range(all_bitfields).unwrap(); // indices of the lowest and the highest octet which contains bitfield-relevant data let lowest_byte = smallest_bit_index / 8; let highest_byte = (largest_bit_index - 1) / 8; let truncated_sz = highest_byte - lowest_byte + 1; // in bytes // shift away unneeded lower octet // TODO: create helper for shifting because readability match truncated_sz { 1 => { quote!(defmt::export::u8(&defmt::export::truncate((*#arg) >> (#lowest_byte * 8)))) } 2 => { quote!(defmt::export::u16(&defmt::export::truncate((*#arg) >> (#lowest_byte * 8)))) } 3..=4 => { quote!(defmt::export::u32(&defmt::export::truncate((*#arg) >> (#lowest_byte * 8)))) } 5..=8 => { quote!(defmt::export::u64(&defmt::export::truncate((*#arg) >> (#lowest_byte * 8)))) } 9..=16 => { quote!(defmt::export::u128(&defmt::export::truncate((*#arg) >> (#lowest_byte * 8)))) } _ => unreachable!(), } } } } defmt-macros-0.3.6/src/function_like/log/env_filter/parse.rs000064400000000000000000000117741046102023000222430ustar 00000000000000use defmt_parser::Level; #[cfg(not(test))] use proc_macro_error::abort_call_site as panic; use std::fmt; use syn::Ident; // None = "off" pseudo-level pub(crate) type LogLevelOrOff = Option; // NOTE this is simpler than `syn::Path`; we do not want to accept e.g. `Vec::::new` #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] pub(crate) struct ModulePath { segments: Vec, } /// Parses the contents of the `DEFMT_LOG` env var pub(crate) fn defmt_log(input: &str) -> impl Iterator + '_ { input.rsplit(',').map(|entry| { if let Some((path, log_level)) = entry.rsplit_once('=') { let module_path = ModulePath::parse(path); let log_level = parse_log_level(log_level).unwrap_or_else(|_| { panic!( "unknown log level `{}` in DEFMT_LOG env var. \ expected one of: off, error, info, warn, debug, trace", log_level ) }); Entry::ModulePathLogLevel { module_path, log_level, } } else if let Ok(log_level) = parse_log_level(entry) { Entry::LogLevel(log_level) } else { Entry::ModulePath(ModulePath::parse(entry)) } }) } #[derive(Debug, PartialEq)] pub(crate) enum Entry { LogLevel(LogLevelOrOff), ModulePath(ModulePath), ModulePathLogLevel { module_path: ModulePath, log_level: LogLevelOrOff, }, } impl ModulePath { pub(crate) fn from_crate_name(input: &str) -> Self { if input.is_empty() && input.contains("::") { panic!( "DEFMT_LOG env var: crate name cannot be an empty string or contain path separators" ) } Self::parse(input) } pub(super) fn parse(input: &str) -> Self { if input.is_empty() { panic!("DEFMT_LOG env var: module path cannot be an empty string") } input.split("::").for_each(validate_identifier); Self { segments: input .split("::") .map(|segment| segment.to_string()) .collect(), } } pub(super) fn crate_name(&self) -> &str { &self.segments[0] } } impl fmt::Display for ModulePath { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.segments.join("::")) } } fn parse_log_level(input: &str) -> Result { Ok(Some(match input { "debug" => Level::Debug, "error" => Level::Error, "info" => Level::Info, "off" => return Ok(None), "trace" => Level::Trace, "warn" => Level::Warn, _ => return Err(()), })) } fn validate_identifier(input: &str) { syn::parse_str::(input) .unwrap_or_else(|_| panic!("`{input}` is not a valid identifier")); } #[cfg(test)] mod tests { use pretty_assertions::assert_eq; use rstest::rstest; use super::*; #[test] fn parses_from_the_right() { let entries = defmt_log("krate=info,krate,info").collect::>(); assert_eq!( [ Entry::LogLevel(Some(Level::Info)), Entry::ModulePath(ModulePath { segments: vec!["krate".to_string()] }), Entry::ModulePathLogLevel { module_path: ModulePath { segments: vec!["krate".to_string()] }, log_level: Some(Level::Info) }, ], entries.as_slice() ); } #[test] fn after_sorting_innermost_modules_appear_last() { let mut paths = [ ModulePath::parse("krate::module::inner"), ModulePath::parse("krate"), ModulePath::parse("krate::module"), ]; paths.sort(); let expected = [ ModulePath::parse("krate"), ModulePath::parse("krate::module"), ModulePath::parse("krate::module::inner"), ]; assert_eq!(expected, paths); } #[test] fn accepts_raw_identifier() { ModulePath::parse("krate::r#mod"); } #[rstest] #[case::has_module("krate::module")] #[case::no_module("krate")] fn modpath_crate_name(#[case] input: &str) { let modpath = ModulePath::parse(input); assert_eq!("krate", modpath.crate_name()); } #[rstest] #[case::crate_name_is_invalid("some-crate::module")] #[case::module_name_is_invalid("krate::some-module")] #[case::with_level("krate::some-module=info")] #[should_panic = "not a valid identifier"] fn rejects_invalid_identifier(#[case] input: &str) { defmt_log(input).next(); } #[test] #[should_panic = "unknown log level"] fn rejects_unknown_log_level() { defmt_log("krate=module").next(); } #[test] #[should_panic = "module path cannot be an empty string"] fn rejects_empty_module_path() { defmt_log("=info").next(); } } defmt-macros-0.3.6/src/function_like/log/env_filter.rs000064400000000000000000000303531046102023000211230ustar 00000000000000use std::{ collections::{BTreeMap, BTreeSet}, env, }; use defmt_parser::Level; use proc_macro2::TokenStream as TokenStream2; use proc_macro_error::abort_call_site; use quote::quote; use self::parse::{Entry, LogLevelOrOff, ModulePath}; mod parse; #[derive(Debug)] pub(crate) struct EnvFilter { // to keep the module paths sorted by length we use a btreemap entries: BTreeMap, } impl EnvFilter { pub(crate) fn from_env_var() -> Self { let defmt_log = env::var("DEFMT_LOG").ok(); let cargo_crate_name = env::var("CARGO_CRATE_NAME") .unwrap_or_else(|_| abort_call_site!("`CARGO_CRATE_NAME` env var is not set")); Self::new(defmt_log.as_deref(), &cargo_crate_name) } fn new(defmt_log: Option<&str>, cargo_crate_name: &str) -> Self { // match `env_logger` behavior const LEVEL_WHEN_LEVEL_IS_NOT_SPECIFIED: LogLevelOrOff = Some(Level::Trace); const LEVEL_WHEN_NOTHING_IS_SPECIFIED: LogLevelOrOff = Some(Level::Error); let caller_crate = cargo_crate_name; let mut entries = BTreeMap::new(); let mut fallback_log_level = None; if let Some(input) = defmt_log { for entry in parse::defmt_log(input) { let (modpath, level) = match entry { Entry::LogLevel(log_level) => { if fallback_log_level.is_none() { fallback_log_level = Some(log_level); } continue; } Entry::ModulePath(module) => (module, LEVEL_WHEN_LEVEL_IS_NOT_SPECIFIED), Entry::ModulePathLogLevel { module_path, log_level, } => (module_path, log_level), }; if modpath.crate_name() == caller_crate && !entries.contains_key(&modpath) { entries.insert(modpath, level); } } } let modpath = ModulePath::from_crate_name(caller_crate); entries .entry(modpath) .or_insert_with(|| fallback_log_level.unwrap_or(LEVEL_WHEN_NOTHING_IS_SPECIFIED)); EnvFilter { entries } } /// Builds a compile-time check that returns `true` when `module_path!` can emit logs at the /// requested log `level` /// /// Returns `None` if the caller crate (at any module path) will never emit logs at requested log `level` pub(crate) fn path_check(&self, level: Level) -> Option { enum Criteria { Accept, Reject, } let modules_to_accept = self.modules_on_for(level); if modules_to_accept.is_empty() { return None; } let modules_to_reject = self.always_off_modules(); let module_to_criteria: BTreeMap<_, _> = modules_to_accept .iter() .map(|&path| (path, Criteria::Accept)) .chain( modules_to_reject .iter() .map(|&path| (path, Criteria::Reject)), ) .collect(); // iterate in reverse because we want to early accept innermost modules // the iteration will go `krate::module::inner`, then `krate::module` then `krate` let checks = module_to_criteria .iter() .rev() .map(|(&module_path, criteria)| { let check = codegen_is_inside_of_check(&module_path.to_string()); let retval = match criteria { Criteria::Accept => quote!(true), Criteria::Reject => quote!(false), }; quote!(if #check { return #retval; }) }) .collect::>(); Some(quote!({ const CHECK: bool = { const fn check() -> bool { let module_path = module_path!().as_bytes(); #(#checks)* false } check() }; CHECK })) } /// Returns the set of modules that can emit logs at requested `level` fn modules_on_for(&self, level: Level) -> BTreeSet<&ModulePath> { self.entries .iter() .rev() .filter_map(|(module_path, min_level)| { // `min_level == None` means "off" so exclude the module path in that case min_level.and_then(|min_level| { if level >= min_level { Some(module_path) } else { None } }) }) .collect() } /// Returns the set of modules that must NOT emit logs (= that are set to `off`) fn always_off_modules(&self) -> BTreeSet<&ModulePath> { self.entries .iter() .rev() .filter_map(|(module_path, level_or_off)| { if level_or_off.is_none() { // `off` pseudo-level Some(module_path) } else { None } }) .collect() } } // NOTE this also returns `true` when `module_path == parent_module_path` // what we want to check is if the function that calls the proc-macro is inside `parent_module_path` // `module_path!` returns the path to the module the function is in, not the path to the function // itself fn codegen_is_inside_of_check(parent_module_path: &str) -> TokenStream2 { let parent = parent_module_path.as_bytes(); let parent_len = parent.len(); let byte_checks = parent .iter() .enumerate() .map(|(index, byte)| quote!(module_path[#index] == #byte)) .collect::>(); quote!( // start of const-context `[u8]::starts_with(needle)` if #parent_len > module_path.len() { false } else { #(#byte_checks &&)* // end of const-context `[u8]::starts_with` // check that what follows the `module_path` is the end of a path segment if #parent_len == module_path.len() { // end of the entire module path true } else { // end of module path _segment_ // // `module_path` comes from `module_path!`; we assume it's well-formed so we // don't check *everything* that comes after `needle`; just the first // character of what should be the path separator ("::") module_path[#parent_len] == b':' } }) } #[cfg(test)] mod tests { use maplit::btreeset; use pretty_assertions::assert_eq; use super::*; #[test] fn when_duplicates_entries_in_defmt_log_use_last_entry() { let env_filter = EnvFilter::new(Some("krate=info,krate=debug"), "krate"); let expected = [ModulePath::parse("krate")]; assert_eq!( expected.iter().collect::>(), env_filter.modules_on_for(Level::Debug) ); assert_eq!(btreeset![], env_filter.modules_on_for(Level::Trace)); } #[test] fn when_empty_defmt_log_use_error() { let env_filter = EnvFilter::new(None, "krate"); let expected = [ModulePath::parse("krate")]; assert_eq!( expected.iter().collect::>(), env_filter.modules_on_for(Level::Error) ); assert_eq!(btreeset![], env_filter.modules_on_for(Level::Warn)); } #[test] fn when_no_level_in_defmt_log_use_trace() { let env_filter = EnvFilter::new(Some("krate"), "krate"); let expected = [ModulePath::parse("krate")]; assert_eq!( expected.iter().collect::>(), env_filter.modules_on_for(Level::Trace) ); } #[test] fn when_level_in_defmt_log_use_it() { let env_filter = EnvFilter::new(Some("krate=info"), "krate"); let expected = [ModulePath::parse("krate")]; assert_eq!( expected.iter().collect::>(), env_filter.modules_on_for(Level::Info) ); assert_eq!(btreeset![], env_filter.modules_on_for(Level::Debug)); } #[test] fn when_only_level_is_specified_in_defmt_log_it_applies_to_all_crates() { let env_filter = EnvFilter::new(Some("info"), "krate"); let expected = [ModulePath::parse("krate")]; assert_eq!( expected.iter().collect::>(), env_filter.modules_on_for(Level::Info) ); assert_eq!(btreeset![], env_filter.modules_on_for(Level::Debug)); } #[test] fn moduleless_level_has_lower_precedence() { let env_filter = EnvFilter::new(Some("krate=info,warn"), "krate"); let expected = [ModulePath::parse("krate")]; assert_eq!( expected.iter().collect::>(), env_filter.modules_on_for(Level::Info) ); assert_eq!(btreeset![], env_filter.modules_on_for(Level::Debug)); } #[test] fn moduleless_level_behaves_like_a_krate_level_pair() { let env_filter = EnvFilter::new(Some("krate::module=info,warn"), "krate"); let expected = [ ModulePath::parse("krate"), ModulePath::parse("krate::module"), ]; assert_eq!( expected.iter().collect::>(), env_filter.modules_on_for(Level::Warn) ); let expected = [ModulePath::parse("krate::module")]; assert_eq!( expected.iter().collect::>(), env_filter.modules_on_for(Level::Info) ); assert_eq!(btreeset![], env_filter.modules_on_for(Level::Debug)); } #[test] fn module_paths_different_levels() { let env_filter = EnvFilter::new(Some("krate=info,krate::module=debug"), "krate"); let expected = [ ModulePath::parse("krate"), ModulePath::parse("krate::module"), ]; assert_eq!( expected.iter().collect::>(), env_filter.modules_on_for(Level::Info) ); let expected = [ModulePath::parse("krate::module")]; assert_eq!( expected.iter().collect::>(), env_filter.modules_on_for(Level::Debug) ); assert_eq!(btreeset![], env_filter.modules_on_for(Level::Trace)); } #[test] fn blanket_off() { let env_filter = EnvFilter::new(Some("off"), "krate"); assert_eq!(btreeset![], env_filter.modules_on_for(Level::Error)); let expected = [ModulePath::parse("krate")]; assert_eq!( expected.iter().collect::>(), env_filter.always_off_modules() ); } #[test] fn blanket_off_plus_override() { let env_filter = EnvFilter::new(Some("krate::module=error,off"), "krate"); let expected = [ModulePath::parse("krate::module")]; assert_eq!( expected.iter().collect::>(), env_filter.modules_on_for(Level::Error) ); assert_eq!(btreeset![], env_filter.modules_on_for(Level::Warn)); let expected = [ModulePath::parse("krate")]; assert_eq!( expected.iter().collect::>(), env_filter.always_off_modules() ); } #[test] fn does_not_match_partial_crate_name() { let env_filter = EnvFilter::new(Some("fooo=warn"), "foo"); let expected = [ModulePath::parse("foo")]; assert_eq!( expected.iter().collect::>(), env_filter.modules_on_for(Level::Error) ); assert_eq!(btreeset![], env_filter.modules_on_for(Level::Warn)); } // doesn't affect runtime performance but it makes the expanded code smaller #[ignore = "TODO(P-low/optimization): impl & more test cases"] #[test] fn when_module_paths_with_same_level_remove_inner_ones() { let env_filter = EnvFilter::new(Some("krate=info,krate::module=info"), "krate"); let expected = [ModulePath::parse("krate")]; assert_eq!( expected.iter().collect::>(), env_filter.modules_on_for(Level::Info) ); assert_eq!(btreeset![], env_filter.modules_on_for(Level::Debug)); } } defmt-macros-0.3.6/src/function_like/log.rs000064400000000000000000000040111046102023000167560ustar 00000000000000use defmt_parser::{Level, ParserMode}; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use proc_macro_error::abort; use quote::quote; use syn::parse_macro_input; use crate::construct; use self::env_filter::EnvFilter; pub(crate) use self::{args::Args, codegen::Codegen}; mod args; mod codegen; mod env_filter; pub(crate) fn expand(level: Level, args: TokenStream) -> TokenStream { expand_parsed(level, parse_macro_input!(args as Args)).into() } pub(crate) fn expand_parsed(level: Level, args: Args) -> TokenStream2 { let format_string = args.format_string.value(); let fragments = match defmt_parser::parse(&format_string, ParserMode::Strict) { Ok(args) => args, Err(e) => abort!(args.format_string, "{}", e), }; let formatting_exprs = args .formatting_args .map(|punctuated| punctuated.into_iter().collect()) .unwrap_or_else(Vec::new); let Codegen { patterns, exprs } = Codegen::new( &fragments, formatting_exprs.len(), args.format_string.span(), ); let header = construct::interned_string(&format_string, level.as_str(), true); let env_filter = EnvFilter::from_env_var(); if let Some(filter_check) = env_filter.path_check(level) { quote!( match (#(&(#formatting_exprs)),*) { (#(#patterns),*) => { if #filter_check { // safety: will be released a few lines further down unsafe { defmt::export::acquire() }; defmt::export::header(&#header); #(#exprs;)* // safety: acquire() was called a few lines above unsafe { defmt::export::release() } } } } ) } else { // if logging is disabled match args, so they are not considered "unused" quote!( match (#(&(#formatting_exprs)),*) { _ => {} } ) } } defmt-macros-0.3.6/src/function_like/panic_like.rs000064400000000000000000000021531046102023000203000ustar 00000000000000use std::borrow::Cow; use defmt_parser::Level; use proc_macro::TokenStream; use quote::quote; use syn::parse_macro_input; use crate::{construct, function_like::log}; pub(crate) fn expand( args: TokenStream, zero_args_format_string: &str, transform_format_string: impl FnOnce(&str) -> String, ) -> TokenStream { let (format_string, formatting_args) = if args.is_empty() { // panic!() -> error!("panicked at 'explicit panic'") (Cow::from(zero_args_format_string), None) } else { // panic!("a", b, c) -> error!("panicked at 'a'", b, c) let log_args = parse_macro_input!(args as log::Args); let format_string = transform_format_string(&log_args.format_string.value()); (Cow::from(format_string), log_args.formatting_args) }; let format_string = construct::string_literal(&format_string); let log_stmt = log::expand_parsed( Level::Error, log::Args { format_string, formatting_args, }, ); quote!( { #log_stmt; defmt::export::panic() } ) .into() } defmt-macros-0.3.6/src/function_like/println.rs000064400000000000000000000033611046102023000176720ustar 00000000000000use defmt_parser::ParserMode; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use proc_macro_error::abort; use quote::quote; use syn::parse_macro_input; use crate::construct; use crate::function_like::log::{Args, Codegen}; pub(crate) fn expand(args: TokenStream) -> TokenStream { expand_parsed(parse_macro_input!(args as Args)).into() } pub(crate) fn expand_parsed(args: Args) -> TokenStream2 { let format_string = args.format_string.value(); let fragments = match defmt_parser::parse(&format_string, ParserMode::Strict) { Ok(args) => args, Err(e @ defmt_parser::Error::UnknownDisplayHint(_)) => abort!( args.format_string, "{}", e; help = "`defmt` uses a slightly different syntax than regular formatting in Rust. See https://defmt.ferrous-systems.com/macros.html for more details."; ), Err(e) => abort!(args.format_string, "{}", e), // No extra help }; let formatting_exprs = args .formatting_args .map(|punctuated| punctuated.into_iter().collect()) .unwrap_or_else(Vec::new); let Codegen { patterns, exprs } = Codegen::new( &fragments, formatting_exprs.len(), args.format_string.span(), ); let header = construct::interned_string(&format_string, "println", true); quote!({ match (#(&(#formatting_exprs)),*) { (#(#patterns),*) => { // safety: will be released a few lines further down unsafe { defmt::export::acquire(); } defmt::export::header(&#header); #(#exprs;)* // safety: acquire() was called a few lines above unsafe { defmt::export::release() } } } }) } defmt-macros-0.3.6/src/function_like/write/args.rs000064400000000000000000000007041046102023000202700ustar 00000000000000use syn::{ parse::{self, Parse, ParseStream}, Expr, Token, }; use crate::function_like::log; pub(crate) struct Args { pub(crate) formatter: Expr, _comma: Token![,], pub(crate) log_args: log::Args, } impl Parse for Args { fn parse(input: ParseStream) -> parse::Result { Ok(Self { formatter: input.parse()?, _comma: input.parse()?, log_args: input.parse()?, }) } } defmt-macros-0.3.6/src/function_like/write.rs000064400000000000000000000024201046102023000173310ustar 00000000000000use defmt_parser::ParserMode; use proc_macro::TokenStream; use proc_macro_error::abort; use quote::quote; use syn::parse_macro_input; use crate::{construct, function_like::log}; use self::args::Args; mod args; pub(crate) fn expand(args: TokenStream) -> TokenStream { let Args { formatter, log_args, .. } = parse_macro_input!(args as Args); let format_string = log_args.format_string.value(); let fragments = match defmt_parser::parse(&format_string, ParserMode::Strict) { Ok(args) => args, Err(e) => abort!(log_args.format_string, "{}", e), }; let formatting_exprs: Vec<_> = log_args .formatting_args .map(|punctuated| punctuated.into_iter().collect()) .unwrap_or_default(); let log::Codegen { patterns, exprs } = log::Codegen::new( &fragments, formatting_exprs.len(), log_args.format_string.span(), ); let format_tag = construct::interned_string(&format_string, "write", false); quote!({ let _typecheck_formatter: defmt::Formatter<'_> = #formatter; match (#(&(#formatting_exprs)),*) { (#(#patterns),*) => { defmt::export::istr(&#format_tag); #(#exprs;)* } } }) .into() } defmt-macros-0.3.6/src/function_like.rs000064400000000000000000000003311046102023000161760ustar 00000000000000pub(crate) mod assert_binop; pub(crate) mod assert_like; pub(crate) mod dbg; pub(crate) mod intern; pub(crate) mod internp; pub(crate) mod log; pub(crate) mod panic_like; pub(crate) mod println; pub(crate) mod write; defmt-macros-0.3.6/src/items/bitflags/input.rs000064400000000000000000000041161046102023000174250ustar 00000000000000use syn::{ parse::{Parse, ParseStream}, punctuated::Punctuated, token, Attribute, Expr, Ident, Token, Type, Visibility, }; #[allow(dead_code)] pub(super) struct Input { struct_attrs: Vec, vis: Visibility, struct_token: Token![struct], ident: Ident, colon_token: Token![:], ty: Type, brace_token: token::Brace, flags: Punctuated, } impl Parse for Input { fn parse(input: ParseStream) -> syn::Result { let flags; Ok(Self { struct_attrs: Attribute::parse_outer(input)?, vis: input.parse()?, struct_token: input.parse()?, ident: input.parse()?, colon_token: input.parse()?, ty: input.parse()?, brace_token: syn::braced!(flags in input), flags: Punctuated::parse_terminated(&flags)?, }) } } impl Input { pub(super) fn flags(&self) -> impl Iterator { self.flags.iter() } pub(super) fn ident(&self) -> &Ident { &self.ident } pub(super) fn ty(&self) -> &Type { &self.ty } } #[allow(dead_code)] pub(super) struct Flag { cfg_attrs: Vec, const_attrs: Vec, const_token: Token![const], ident: Ident, eq_token: Token![=], value: Expr, } impl Parse for Flag { fn parse(input: ParseStream) -> syn::Result { let const_attrs = Attribute::parse_outer(input)?; Ok(Self { cfg_attrs: extract_cfgs(&const_attrs), const_attrs, const_token: input.parse()?, ident: input.parse()?, eq_token: input.parse()?, value: input.parse()?, }) } } impl Flag { pub(super) fn cfg_attrs(&self) -> &[Attribute] { &self.cfg_attrs } pub(super) fn ident(&self) -> &Ident { &self.ident } } fn extract_cfgs(attrs: &[Attribute]) -> Vec { let mut cfgs = vec![]; for attr in attrs { if attr.path().is_ident("cfg") { cfgs.push(attr.clone()); } } cfgs } defmt-macros-0.3.6/src/items/bitflags.rs000064400000000000000000000056761046102023000163020ustar 00000000000000use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::{quote, ToTokens}; use syn::parse_macro_input; use crate::{cargo, construct}; use self::input::Input; mod input; pub(crate) fn expand(input: TokenStream) -> TokenStream { let bitflags_input = TokenStream2::from(input.clone()); let input = parse_macro_input!(input as Input); // Encode package and disambiguator to provide the decoder with all info it needs (even if // technically redundant, since it's also stored in the symbol we create). let format_string = format!( "{{={}:__internal_bitflags_{}@{}@{}@{}}}", input.ty().to_token_stream(), input.ident(), cargo::package_name(), construct::crate_local_disambiguator(), cargo::crate_name(), ); let format_tag = construct::interned_string(&format_string, "bitflags", false); let ident = input.ident(); let ty = input.ty(); let flag_statics = codegen_flag_statics(&input); quote!( const _: () = { fn assert() {} assert::<#ty>; #(#flag_statics)* }; defmt::export::bitflags! { #bitflags_input } impl defmt::Format for #ident { fn format(&self, f: defmt::Formatter) { defmt::unreachable!() } fn _format_tag() -> defmt::Str { #format_tag } fn _format_data(&self) { // There's a method available for every supported bitflags type. defmt::export::#ty(&self.bits()); } } ) .into() } fn codegen_flag_statics(input: &Input) -> Vec { input .flags() .enumerate() .map(|(i, flag)| { let cfg_attrs = flag.cfg_attrs(); let var_name = flag.ident(); let struct_name = input.ident(); let repr_ty = input.ty(); let sym_name = construct::mangled_symbol_name( "bitflags_value", &format!("{}::{i}::{}", input.ident(), flag.ident()), ); quote! { #(#cfg_attrs)* #[cfg_attr(target_os = "macos", link_section = ".defmt,end")] #[cfg_attr(not(target_os = "macos"), link_section = ".defmt.end")] #[export_name = #sym_name] static #var_name: u128 = { // NB: It might be tempting to just do `#value as u128` here, but that // causes a value such as `1 << 127` to be evaluated as an `i32`, which // overflows. So we instead coerce (but don't cast) it to the bitflags' raw // type, and then cast that to u128. let coerced_value: #repr_ty = #struct_name::#var_name.bits; coerced_value as u128 }; } }) .collect::>() } defmt-macros-0.3.6/src/items/timestamp.rs000064400000000000000000000037371046102023000165060ustar 00000000000000use defmt_parser::ParserMode; use proc_macro::TokenStream; use proc_macro_error::abort; use quote::format_ident; use quote::quote; use syn::parse_macro_input; use crate::{construct, function_like::log}; pub(crate) fn expand(args: TokenStream) -> TokenStream { let args = parse_macro_input!(args as log::Args); let format_string = args.format_string.value(); let fragments = match defmt_parser::parse(&format_string, ParserMode::Strict) { Ok(args) => args, Err(e) => abort!(args.format_string, "{}", e), }; let formatting_exprs: Vec<_> = args .formatting_args .map(|punctuated| punctuated.into_iter().collect()) .unwrap_or_default(); let log::Codegen { patterns, exprs } = log::Codegen::new( &fragments, formatting_exprs.len(), args.format_string.span(), ); let var_name = format_ident!("S"); let var_item = construct::static_variable(&var_name, &format_string, "timestamp"); quote!( const _: () = { #[export_name = "_defmt_timestamp"] #[inline(never)] fn defmt_timestamp(fmt: ::defmt::Formatter<'_>) { match (#(&(#formatting_exprs)),*) { (#(#patterns),*) => { // NOTE: No format string index, and no finalize call. #(#exprs;)* } } } #var_item; // Unique symbol name to prevent multiple `timestamp!` invocations in the crate graph. // Uses `#var_name` to ensure it is not discarded by the linker. // This symbol itself is retained via a `EXTERN` directive in the linker script. #[no_mangle] #[cfg_attr(target_os = "macos", link_section = ".defmt,end.timestamp")] #[cfg_attr(not(target_os = "macos"), link_section = ".defmt.end.timestamp")] static __DEFMT_MARKER_TIMESTAMP_WAS_DEFINED: &u8 = &#var_name; }; ) .into() } defmt-macros-0.3.6/src/items.rs000064400000000000000000000001371046102023000144720ustar 00000000000000//! Procedural macros that expand to items pub(crate) mod bitflags; pub(crate) mod timestamp; defmt-macros-0.3.6/src/lib.rs000064400000000000000000000124161046102023000141220ustar 00000000000000//! INTERNAL; DO NOT USE. Please use the `defmt` crate to access the functionality implemented here #![doc(html_logo_url = "https://knurling.ferrous-systems.com/knurling_logo_light_text.svg")] use defmt_parser::Level; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use proc_macro_error::proc_macro_error; use quote::quote; mod attributes; mod cargo; mod construct; mod consts; mod derives; mod function_like; mod items; // NOTE some proc-macro functions have an `_` (underscore) suffix. This is intentional. // If left unsuffixed the procedural macros would shadow macros defined in `std` (like `assert`) // within the context of this entire crate, which leads to lots of confusion. /* # Attributes */ #[proc_macro_attribute] #[proc_macro_error] pub fn global_logger(args: TokenStream, item: TokenStream) -> TokenStream { attributes::global_logger::expand(args, item) } #[proc_macro_attribute] #[proc_macro_error] pub fn panic_handler(args: TokenStream, item: TokenStream) -> TokenStream { attributes::panic_handler::expand(args, item) } /* # Derives */ #[proc_macro_derive(Format, attributes(defmt))] #[proc_macro_error] pub fn format(input: TokenStream) -> TokenStream { derives::format::expand(input) } /* # Function-like */ #[proc_macro] #[proc_macro_error] pub fn assert_(args: TokenStream) -> TokenStream { function_like::assert_like::assert::expand(args) } #[proc_macro] #[proc_macro_error] pub fn assert_eq_(args: TokenStream) -> TokenStream { function_like::assert_binop::eq(args) } #[proc_macro] #[proc_macro_error] pub fn assert_ne_(args: TokenStream) -> TokenStream { function_like::assert_binop::ne(args) } /* ## `debug_` variants */ // NOTE these `debug_*` macros can be written using `macro_rules!` (that'd be simpler) but that // results in an incorrect source code location being reported: the location of the `macro_rules!` // statement is reported. Using a proc-macro results in the call site being reported, which is what // we want #[proc_macro] #[proc_macro_error] pub fn debug_assert_(input: TokenStream) -> TokenStream { let assert = TokenStream2::from(assert_(input)); quote!(if cfg!(debug_assertions) { #assert }) .into() } #[proc_macro] #[proc_macro_error] pub fn debug_assert_eq_(input: TokenStream) -> TokenStream { let assert = TokenStream2::from(assert_eq_(input)); quote!(if cfg!(debug_assertions) { #assert }) .into() } #[proc_macro] #[proc_macro_error] pub fn debug_assert_ne_(input: TokenStream) -> TokenStream { let assert = TokenStream2::from(assert_ne_(input)); quote!(if cfg!(debug_assertions) { #assert }) .into() } /* ## end of `debug_` variants */ #[proc_macro] #[proc_macro_error] pub fn dbg(args: TokenStream) -> TokenStream { function_like::dbg::expand(args) } #[proc_macro] #[proc_macro_error] pub fn intern(args: TokenStream) -> TokenStream { function_like::intern::expand(args) } #[proc_macro] #[proc_macro_error] pub fn internp(args: TokenStream) -> TokenStream { function_like::internp::expand(args) } #[proc_macro] #[proc_macro_error] pub fn println(args: TokenStream) -> TokenStream { function_like::println::expand(args) } /* ## Logging macros */ #[proc_macro] #[proc_macro_error] pub fn trace(args: TokenStream) -> TokenStream { function_like::log::expand(Level::Trace, args) } #[proc_macro] #[proc_macro_error] pub fn debug(args: TokenStream) -> TokenStream { function_like::log::expand(Level::Debug, args) } #[proc_macro] #[proc_macro_error] pub fn info(args: TokenStream) -> TokenStream { function_like::log::expand(Level::Info, args) } #[proc_macro] #[proc_macro_error] pub fn warn(args: TokenStream) -> TokenStream { function_like::log::expand(Level::Warn, args) } #[proc_macro] #[proc_macro_error] pub fn error(args: TokenStream) -> TokenStream { function_like::log::expand(Level::Error, args) } /* ## end of logging macros */ #[proc_macro] #[proc_macro_error] pub fn panic_(args: TokenStream) -> TokenStream { function_like::panic_like::expand(args, "panicked at 'explicit panic'", |format_string| { format!("panicked at '{format_string}'") }) } #[proc_macro] #[proc_macro_error] pub fn todo_(args: TokenStream) -> TokenStream { function_like::panic_like::expand(args, "panicked at 'not yet implemented'", |format_string| { format!("panicked at 'not yet implemented: {format_string}'") }) } #[proc_macro] #[proc_macro_error] pub fn unreachable_(args: TokenStream) -> TokenStream { function_like::panic_like::expand( args, "panicked at 'internal error: entered unreachable code'", |format_string| { format!( "panicked at 'internal error: entered unreachable code: {}'", format_string ) }, ) } #[proc_macro] #[proc_macro_error] pub fn unwrap(args: TokenStream) -> TokenStream { function_like::assert_like::unwrap::expand(args) } #[proc_macro] #[proc_macro_error] pub fn write(args: TokenStream) -> TokenStream { function_like::write::expand(args) } /* # Items */ #[proc_macro] #[proc_macro_error] pub fn bitflags(ts: TokenStream) -> TokenStream { items::bitflags::expand(ts) } #[proc_macro] #[proc_macro_error] pub fn timestamp(args: TokenStream) -> TokenStream { items::timestamp::expand(args) }