feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
+1
@@ -0,0 +1 @@
|
||||
{"files":{".cargo_vcs_info.json":"3ac4f67ca8e41a8cd691947d4bed5c5c981180dc4d8c495b029126e0f17bce56","Cargo.lock":"1a3702967c07dca906eeba3b6c5caf1ca950a7c585b09b5566ffb924a25148a0","Cargo.toml":"5d18f4bf12826186cb4dcc981eee990a40715d0f851e11db488712ca55d59d9a","Cargo.toml.orig":"acd087b64daad1c226f2ecd1ebd60ac53a8a215acebdef30dcd6795dc9cda439","README.md":"592432ee6ec4bc412dca33878b5659eeed55f596a6e0a47c557b8d1c3e7196a3","build.rs":"e95adebcba8785d938de75f1555c0baf2ab7a7bf527c01fb9a5f36380fc4a2aa","src/attributes.rs":"90a5d3493cddb2f020302e80559554e52c150ddfa5a1fd78bebf3bc8456960e0","src/attributes/global_logger.rs":"f040f33e4ed055acea7c355c3b9bd83f664b63879ea68e9b2b2a527bc21bcace","src/attributes/panic_handler.rs":"fdae585c81f3d17bfc7b7a8ee4ed12b097b208f89ba955abfa8ae47a2b807646","src/cargo.rs":"42862e04cdd1cab46061ffb64a8bad5947d29e14dccb8cb53c7b0af5ef9a7bc4","src/construct.rs":"102081c3d84cccc4275cb2b08a581efe91d34bb49f705b7d2ae7b4e48f57aa49","src/construct/symbol.rs":"085e6450a615fd78d75b54b884a184b525811f44601233d0c8e54a53a489dae0","src/consts.rs":"3c39d7e829ff1fb0b8d5c7fd39e5084ab0b88417bafe6baf154a3a55d1f08b97","src/derives.rs":"3cf769348e8e12ecda93bdab2939ed2b104c9d160fdf0db160a126d3f98e2964","src/derives/format.rs":"5c1e5a67d72259268a5bfa687e54b06a4f7e2a82fb0dd81393825ced9cbc3d1e","src/derives/format/codegen.rs":"1fdfed305c37f16d7bcc93c8a1d8059fb29a806595a7411b25c27d63a7eaa561","src/derives/format/codegen/enum_data.rs":"8930b0e36740485c18c208c32cd9d5dabbc5ccc02ddc7b1aa9f0febda61a4b9a","src/derives/format/codegen/fields.rs":"1789482dfe920cfa895d5caf33bd576a2f5c8f80db741a21ec5b3f4c4a56aec5","src/function_like.rs":"c53829b5c90f726f81146cfa7741f24dd9e16219591a93d726a0ee7a25f7a07a","src/function_like/assert_binop.rs":"f6946a78e0fc93c86c19fc11f6d554106ca5eab3f0c4d457313df979d2cd43a4","src/function_like/assert_binop/args.rs":"754045bf1b3f8bb51299f76cee83a3d88b55fed9b57268ad63097b7e6ad7bd35","src/function_like/assert_like.rs":"96b123f1e02eb80eb5e60deea92d7eace1aefac0179fb0e3279e7a4504178fa3","src/function_like/assert_like/assert.rs":"dfcbc594da1877ffc382bdd86dc226d9c664cb543df389eef4c3e86a214d0728","src/function_like/assert_like/unwrap.rs":"93049f262b81a1f8782664922da0a27b812847d28973ca5d370bab7570c009eb","src/function_like/dbg.rs":"e6d9014f112fd54e495be2078bd5399d50bdbee301d83c6c04d254d584782fe9","src/function_like/dbg/args.rs":"ddc9677f6188b2ef7ca7ea068ac0d1e5cee6236c3fc64e05363f90ad64c61bfd","src/function_like/intern.rs":"6f265bec62e83ca75b99b90f4749b62d36dd5310f32ae2fe1447d716c61195ab","src/function_like/internp.rs":"4546356e90c5f059ac3eaeb97b3b824eaeab0bb3f58b83115003fea24c595f9c","src/function_like/log.rs":"d17e2efa264017db013305f8407e20bf5ddd1552317bb15f2d496230c34cf802","src/function_like/log/args.rs":"2c86b3f8d748faf1a2e4067232d60516cd9bd4d24921f4ea5848b0797ce0edfd","src/function_like/log/codegen.rs":"5eea62064a5c964dcb1133a467b93994fd91771e8555a372927be42e525c74c0","src/function_like/log/env_filter.rs":"8c28e1f1b8e65836e8eb6bead575d293e3ad1b40777921af320904bda3d08297","src/function_like/log/env_filter/parse.rs":"1615d093cc80c142c1c4d17ebe3a230e4b0c96143ebb2d15e7c09e055cec1049","src/function_like/panic_like.rs":"3e0b1e2bedccefc0026a8cccc08da6d2d9ef885ca0c9c08fb30c75fa0455d473","src/function_like/println.rs":"8f6583bcffe2ee4654602f6bd38f3940d74840be9cd72c89c1634512027dcd92","src/function_like/write.rs":"80f1bb9d71a22688e5af3e766035ae4b855ff8cfb76bc3f353b73e5cdc512e1c","src/function_like/write/args.rs":"67eca9612845b0e62c8098cf7be08b2ab172953df4c25cdc9cd107d673c0c742","src/items.rs":"ac6068952ec12add139db6d87779e94d1af772374d68a2b03ab6b59fecda22dd","src/items/bitflags.rs":"ac6086a81088fc912d91fb221ba24722598fe64993cc7372370d4c4dd50d6e08","src/items/bitflags/input.rs":"7d4e202ce7e9b60b690831c5f26483910934a3121d0cadfe7cab52d0445f01ad","src/items/timestamp.rs":"9d2f01929641c9880b3c2b65af72dda6fb327adf9a0b131faf6203fea972f3df","src/lib.rs":"bc4544a29520b2f45789b376dbeb60baff47d34b92d984de4949a85792f615e9"},"package":"3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e"}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "8459657ef6df5e6f3386512744110caef5f0612a",
|
||||
"dirty": true
|
||||
},
|
||||
"path_in_vcs": "macros"
|
||||
}
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "defmt-macros"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"defmt-parser",
|
||||
"maplit",
|
||||
"pretty_assertions",
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rstest",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt-parser"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||
|
||||
[[package]]
|
||||
name = "maplit"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
|
||||
dependencies = [
|
||||
"diff",
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "relative-path"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
|
||||
|
||||
[[package]]
|
||||
name = "rstest"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d5316d2a1479eeef1ea21e7f9ddc67c191d497abc8fc3ba2467857abbb68330"
|
||||
dependencies = [
|
||||
"rstest_macros",
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rstest_macros"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04a9df72cc1f67020b0d63ad9bfe4a323e459ea7eb68e03bd9824db49f9a4c25"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"glob",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"relative-path",
|
||||
"rustc_version",
|
||||
"syn",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
# 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 = "1.0.1"
|
||||
authors = ["The Knurling-rs developers"]
|
||||
build = "build.rs"
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "defmt macros"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"knurling",
|
||||
"defmt",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/knurling-rs/defmt"
|
||||
|
||||
[features]
|
||||
unstable-test = []
|
||||
|
||||
[lib]
|
||||
name = "defmt_macros"
|
||||
path = "src/lib.rs"
|
||||
proc-macro = true
|
||||
|
||||
[dependencies.defmt-parser]
|
||||
version = "=1.0.0"
|
||||
|
||||
[dependencies.proc-macro-error2]
|
||||
version = "2"
|
||||
|
||||
[dependencies.proc-macro2]
|
||||
version = "1"
|
||||
|
||||
[dependencies.quote]
|
||||
version = "1"
|
||||
|
||||
[dependencies.syn]
|
||||
version = "2"
|
||||
features = [
|
||||
"full",
|
||||
"extra-traits",
|
||||
]
|
||||
|
||||
[dev-dependencies.maplit]
|
||||
version = "1"
|
||||
|
||||
[dev-dependencies.pretty_assertions]
|
||||
version = "1"
|
||||
|
||||
[dev-dependencies.rstest]
|
||||
version = "0.19"
|
||||
default-features = false
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
# `defmt-macros`
|
||||
|
||||
`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 <https://defmt.ferrous-systems.com>.
|
||||
|
||||
This library contains the proc macros used by
|
||||
[`defmt`](https://crates.io/crates/defmt).
|
||||
|
||||
## MSRV
|
||||
|
||||
The minimum supported Rust version is 1.76 (or Ferrocene 24.05). `defmt` is tested against the latest stable Rust version and the MSRV.
|
||||
|
||||
## 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
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-env-changed=DEFMT_LOG");
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
//! Procedural macros that are attributes
|
||||
|
||||
pub(crate) mod global_logger;
|
||||
pub(crate) mod panic_handler;
|
||||
@@ -0,0 +1,66 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro_error2::{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()
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro_error2::{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()
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
use std::env;
|
||||
|
||||
pub(crate) fn package_name() -> String {
|
||||
env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "<unknown>".to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn crate_name() -> String {
|
||||
env::var("CARGO_CRATE_NAME").unwrap_or_else(|_| "<unknown>".to_string())
|
||||
}
|
||||
Vendored
+109
@@ -0,0 +1,109 @@
|
||||
use 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,
|
||||
prefix: Option<&str>,
|
||||
defmt_path: &syn::Path,
|
||||
) -> 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_path::export::fetch_add_string_index() })
|
||||
} else {
|
||||
let var_item = static_variable(&var_name, string, tag, prefix);
|
||||
quote!({
|
||||
#var_item
|
||||
&#var_name as *const u8 as u16
|
||||
})
|
||||
};
|
||||
|
||||
quote!({
|
||||
#defmt_path::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,
|
||||
prefix: Option<&str>,
|
||||
) -> TokenStream2 {
|
||||
let sym_name = mangled_symbol_name(tag, data);
|
||||
let section = linker_section(false, prefix, &sym_name);
|
||||
let section_for_macos = linker_section(true, prefix, &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()
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
use 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
|
||||
}
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
/// The (erased) "Format" type as it appears in `defmt` format strings
|
||||
pub(crate) const TYPE_FORMAT: &str = "?";
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
//! Procedural macros that are `#[derive(*)]` attributes
|
||||
|
||||
pub(crate) mod format;
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro_error2::{abort, abort_call_site};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{parse_macro_input, parse_quote, Data, DeriveInput};
|
||||
|
||||
mod codegen;
|
||||
|
||||
pub(crate) fn expand(input: TokenStream) -> TokenStream {
|
||||
let DeriveInput {
|
||||
attrs,
|
||||
vis: _,
|
||||
ident,
|
||||
mut generics,
|
||||
data,
|
||||
} = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
let defmt_path = match defmt_crate_path(&attrs) {
|
||||
Ok(defmt_path) => defmt_path,
|
||||
Err(e) => abort!(e),
|
||||
};
|
||||
|
||||
let encode_data = match &data {
|
||||
Data::Enum(data) => codegen::encode_enum_data(&ident, data, &defmt_path),
|
||||
Data::Struct(data) => codegen::encode_struct_data(&ident, data, &defmt_path),
|
||||
Data::Union(_) => abort_call_site!("`#[derive(Format)]` does not support unions"),
|
||||
};
|
||||
|
||||
let codegen::EncodeData {
|
||||
format_tag,
|
||||
stmts,
|
||||
where_predicates,
|
||||
} = 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 generics, where_predicates);
|
||||
|
||||
quote!(
|
||||
#[automatically_derived]
|
||||
impl #impl_generics #defmt_path::Format for #ident #type_generics #where_clause {
|
||||
fn format(&self, f: #defmt_path::Formatter) {
|
||||
use #defmt_path as defmt;
|
||||
#defmt_path::unreachable!()
|
||||
}
|
||||
|
||||
fn _format_tag() -> #defmt_path::Str {
|
||||
#format_tag
|
||||
}
|
||||
|
||||
fn _format_data(&self) {
|
||||
#(#stmts)*
|
||||
}
|
||||
}
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn defmt_crate_path(attrs: &[syn::Attribute]) -> Result<syn::Path, syn::Error> {
|
||||
let mut defmt_path = parse_quote!(defmt);
|
||||
let res = attrs
|
||||
.iter()
|
||||
.filter(|attr| attr.path().is_ident("defmt"))
|
||||
.try_for_each(|attr| {
|
||||
attr.parse_nested_meta(|meta| {
|
||||
if meta.path.get_ident().is_some_and(|ident| ident == "crate") {
|
||||
meta.input.parse::<syn::Token![=]>()?;
|
||||
defmt_path = meta.input.parse::<syn::Path>()?;
|
||||
Ok(())
|
||||
} else {
|
||||
let path = meta.path.to_token_stream().to_string().replace(' ', "");
|
||||
Err(meta.error(format_args!("unknown defmt attribute `{path}`")))
|
||||
}
|
||||
})
|
||||
});
|
||||
res.map(|()| defmt_path)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use syn::{DataStruct, Ident, ImplGenerics, TypeGenerics, WhereClause, WherePredicate};
|
||||
|
||||
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<TokenStream2>,
|
||||
pub(crate) where_predicates: Vec<WherePredicate>,
|
||||
}
|
||||
|
||||
pub(crate) fn encode_struct_data(
|
||||
ident: &Ident,
|
||||
data: &DataStruct,
|
||||
defmt_path: &syn::Path,
|
||||
) -> syn::Result<EncodeData> {
|
||||
let mut format_string = ident.to_string();
|
||||
let mut stmts = vec![];
|
||||
let mut field_patterns = vec![];
|
||||
|
||||
let (encode_fields_stmts, where_predicates) = fields::codegen(
|
||||
&data.fields,
|
||||
&mut format_string,
|
||||
&mut field_patterns,
|
||||
defmt_path,
|
||||
)?;
|
||||
|
||||
stmts.push(quote!(match self {
|
||||
Self { #(#field_patterns),* } => {
|
||||
#(#encode_fields_stmts;)*
|
||||
}
|
||||
}));
|
||||
|
||||
let format_tag = construct::interned_string(&format_string, "derived", false, None, defmt_path);
|
||||
Ok(EncodeData {
|
||||
format_tag,
|
||||
stmts,
|
||||
where_predicates,
|
||||
})
|
||||
}
|
||||
|
||||
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,
|
||||
where_predicates: Vec<WherePredicate>,
|
||||
) -> 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 all field types.
|
||||
where_clause.predicates.extend(where_predicates);
|
||||
|
||||
Self {
|
||||
impl_generics,
|
||||
type_generics,
|
||||
where_clause,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use proc_macro_error2::abort_call_site;
|
||||
use quote::quote;
|
||||
use syn::{DataEnum, Ident};
|
||||
|
||||
use crate::construct;
|
||||
|
||||
use super::EncodeData;
|
||||
|
||||
pub(crate) fn encode(
|
||||
ident: &Ident,
|
||||
data: &DataEnum,
|
||||
defmt_path: &syn::Path,
|
||||
) -> syn::Result<EncodeData> {
|
||||
if data.variants.is_empty() {
|
||||
return Ok(EncodeData {
|
||||
stmts: vec![quote!(match *self {})],
|
||||
format_tag: construct::interned_string("!", "derived", false, None, defmt_path),
|
||||
where_predicates: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
let mut format_string = String::new();
|
||||
let mut where_predicates = vec![];
|
||||
|
||||
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, encode_field_where_predicates) = super::fields::codegen(
|
||||
&variant.fields,
|
||||
&mut format_string,
|
||||
&mut field_patterns,
|
||||
defmt_path,
|
||||
)?;
|
||||
where_predicates.extend(encode_field_where_predicates.into_iter());
|
||||
let pattern = quote!( { #(#field_patterns),* } );
|
||||
|
||||
let encode_discriminant_stmt = discriminant_encoder.encode(index, defmt_path);
|
||||
|
||||
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, None, defmt_path);
|
||||
let stmts = vec![quote!(match self {
|
||||
#(#match_arms)*
|
||||
})];
|
||||
where_predicates.dedup_by(|a, b| a == b);
|
||||
|
||||
Ok(EncodeData {
|
||||
format_tag,
|
||||
stmts,
|
||||
where_predicates,
|
||||
})
|
||||
}
|
||||
|
||||
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, defmt_path: &syn::Path) -> 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_path::export::u8(&#index);)
|
||||
}
|
||||
DiscriminantEncoder::U16 => {
|
||||
let index = index as u16;
|
||||
quote!(#defmt_path::export::u16(&#index);)
|
||||
}
|
||||
DiscriminantEncoder::U32 => {
|
||||
let index = index as u32;
|
||||
quote!(#defmt_path::export::u32(&#index);)
|
||||
}
|
||||
DiscriminantEncoder::U64 => {
|
||||
let index = index as u64;
|
||||
quote!(#defmt_path::export::u64(&#index);)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
use std::fmt::Write as _;
|
||||
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{parse_quote, Field, Fields, Index, Type, WherePredicate};
|
||||
|
||||
use crate::consts;
|
||||
|
||||
pub(crate) fn codegen(
|
||||
fields: &Fields,
|
||||
format_string: &mut String,
|
||||
patterns: &mut Vec<TokenStream2>,
|
||||
defmt_path: &syn::Path,
|
||||
) -> syn::Result<(Vec<TokenStream2>, Vec<WherePredicate>)> {
|
||||
let (fields, fields_are_named) = match fields {
|
||||
Fields::Named(named) => (&named.named, true),
|
||||
Fields::Unit => return Ok((vec![], vec![])),
|
||||
Fields::Unnamed(unnamed) => (&unnamed.unnamed, false),
|
||||
};
|
||||
|
||||
if fields.is_empty() {
|
||||
return Ok((vec![], vec![]));
|
||||
}
|
||||
|
||||
if fields_are_named {
|
||||
format_string.push_str(" {{ ");
|
||||
} else {
|
||||
format_string.push('(');
|
||||
}
|
||||
|
||||
let mut stmts = vec![];
|
||||
let mut where_predicates = 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)?;
|
||||
// Find out if the field type is natively supported by defmt. `ty` will be None if not.
|
||||
let ty = as_native_type(&field.ty);
|
||||
// `field_ty` will be the field's type if it is not natively supported by defmt
|
||||
let field_ty = if ty.is_none() { Some(&field.ty) } else { None };
|
||||
// Get the field format specifier. Either the native specifier or '?'.
|
||||
let ty = ty.unwrap_or_else(|| consts::TYPE_FORMAT.to_string());
|
||||
let ident = field
|
||||
.ident
|
||||
.clone()
|
||||
.unwrap_or_else(|| format_ident!("arg{}", index));
|
||||
// Find the required trait bounds for the field and add the formatting statement depending on the field type and the formatting options
|
||||
let bound: Option<syn::Path> = if let Some(FormatOption::Debug2Format) = format_opt {
|
||||
stmts.push(quote!(#defmt_path::export::fmt(&#defmt_path::Debug2Format(&#ident))));
|
||||
field_ty.map(|_| parse_quote!(::core::fmt::Debug))
|
||||
} else if let Some(FormatOption::Display2Format) = format_opt {
|
||||
stmts.push(quote!(#defmt_path::export::fmt(&#defmt_path::Display2Format(&#ident))));
|
||||
field_ty.map(|_| parse_quote!(::core::fmt::Display))
|
||||
} else if ty == consts::TYPE_FORMAT {
|
||||
stmts.push(quote!(#defmt_path::export::fmt(#ident)));
|
||||
field_ty.map(|_| parse_quote!(#defmt_path::Format))
|
||||
} else {
|
||||
let method = format_ident!("{}", ty);
|
||||
stmts.push(quote!(#defmt_path::export::#method(#ident)));
|
||||
field_ty.map(|_| parse_quote!(#defmt_path::Format))
|
||||
};
|
||||
if let Some(bound) = bound {
|
||||
where_predicates.push(parse_quote!(#field_ty: #bound));
|
||||
}
|
||||
|
||||
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, where_predicates))
|
||||
}
|
||||
|
||||
#[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<Option<FormatOption>> {
|
||||
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<String> {
|
||||
match ty {
|
||||
Type::Path(path) => {
|
||||
let ident = path.path.get_ident()?;
|
||||
let ty_name = ident.to_string();
|
||||
|
||||
match &*ty_name {
|
||||
"u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" | "i16" | "i32" | "i64"
|
||||
| "i128" | "isize" | "f32" | "f64" | "bool" | "str" => Some(ty_name),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
Type::Reference(ty_ref) => as_native_type(&ty_ref.elem),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
pub(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;
|
||||
@@ -0,0 +1,90 @@
|
||||
use 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,
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
use 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<log::Args>,
|
||||
}
|
||||
|
||||
impl Parse for Args {
|
||||
fn parse(input: ParseStream) -> parse::Result<Self> {
|
||||
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()?),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
use 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<log::Args>,
|
||||
}
|
||||
|
||||
impl Parse for Args {
|
||||
fn parse(input: ParseStream) -> parse::Result<Self> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
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) -> 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()
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
use 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()
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
use 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::<Vec<_>>();
|
||||
|
||||
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()
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
Expr, Token,
|
||||
};
|
||||
|
||||
pub(crate) struct Args {
|
||||
pub(crate) exprs: Punctuated<Expr, Token![,]>,
|
||||
}
|
||||
|
||||
impl Parse for Args {
|
||||
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
|
||||
Ok(Self {
|
||||
exprs: Punctuated::parse_terminated(input)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
use proc_macro::TokenStream;
|
||||
use syn::{parse_macro_input, parse_quote, 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, None, &parse_quote!(defmt)).into()
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
use 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()
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
use defmt_parser::{Level, ParserMode};
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use proc_macro_error2::abort;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, parse_quote};
|
||||
|
||||
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::<Vec<_>>())
|
||||
.unwrap_or_default();
|
||||
|
||||
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,
|
||||
Some(level.as_str()),
|
||||
&parse_quote!(defmt),
|
||||
);
|
||||
let env_filter = EnvFilter::from_env_var();
|
||||
|
||||
if let Some(filter_check) = env_filter.path_check(level) {
|
||||
let content = if exprs.is_empty() {
|
||||
quote!(
|
||||
defmt::export::acquire_header_and_release(&#header);
|
||||
)
|
||||
} else {
|
||||
quote!(
|
||||
// safety: will be released a few lines further down
|
||||
unsafe { defmt::export::acquire_and_header(&#header); };
|
||||
#(#exprs;)*
|
||||
// safety: acquire() was called a few lines above
|
||||
unsafe { defmt::export::release() }
|
||||
)
|
||||
};
|
||||
|
||||
quote!(
|
||||
{
|
||||
option_env!("DEFMT_LOG");
|
||||
match (#(&(#formatting_exprs)),*) {
|
||||
(#(#patterns),*) => {
|
||||
if #filter_check {
|
||||
#content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
// if logging is disabled match args, so they are not considered "unused"
|
||||
quote!(
|
||||
{
|
||||
option_env!("DEFMT_LOG");
|
||||
match (#(&(#formatting_exprs)),*) {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use syn::{
|
||||
parse::{self, Parse, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
Expr, LitStr, Token,
|
||||
};
|
||||
|
||||
pub(crate) struct Args {
|
||||
pub(crate) format_string: LitStr,
|
||||
pub(crate) formatting_args: Option<Punctuated<Expr, Token![,]>>,
|
||||
}
|
||||
|
||||
impl Parse for Args {
|
||||
fn parse(input: ParseStream) -> parse::Result<Self> {
|
||||
Ok(Self {
|
||||
format_string: input.parse()?,
|
||||
formatting_args: if input.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let _comma: Token![,] = input.parse()?;
|
||||
Some(Punctuated::parse_terminated(input)?)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
use defmt_parser::{Fragment, Parameter, Type};
|
||||
use proc_macro2::{Ident as Ident2, Span as Span2, TokenStream as TokenStream2};
|
||||
use proc_macro_error2::abort;
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
pub(crate) struct Codegen {
|
||||
pub(crate) exprs: Vec<TokenStream2>,
|
||||
pub(crate) patterns: Vec<Ident2>,
|
||||
}
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
env,
|
||||
};
|
||||
|
||||
use defmt_parser::Level;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use proc_macro_error2::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<ModulePath, LogLevelOrOff>,
|
||||
}
|
||||
|
||||
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<TokenStream2> {
|
||||
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::<Vec<_>>();
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
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::<BTreeSet<_>>(),
|
||||
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::<BTreeSet<_>>(),
|
||||
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::<BTreeSet<_>>(),
|
||||
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::<BTreeSet<_>>(),
|
||||
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::<BTreeSet<_>>(),
|
||||
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::<BTreeSet<_>>(),
|
||||
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::<BTreeSet<_>>(),
|
||||
env_filter.modules_on_for(Level::Warn)
|
||||
);
|
||||
|
||||
let expected = [ModulePath::parse("krate::module")];
|
||||
assert_eq!(
|
||||
expected.iter().collect::<BTreeSet<_>>(),
|
||||
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::<BTreeSet<_>>(),
|
||||
env_filter.modules_on_for(Level::Info)
|
||||
);
|
||||
|
||||
let expected = [ModulePath::parse("krate::module")];
|
||||
assert_eq!(
|
||||
expected.iter().collect::<BTreeSet<_>>(),
|
||||
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::<BTreeSet<_>>(),
|
||||
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::<BTreeSet<_>>(),
|
||||
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::<BTreeSet<_>>(),
|
||||
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::<BTreeSet<_>>(),
|
||||
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::<BTreeSet<_>>(),
|
||||
env_filter.modules_on_for(Level::Info)
|
||||
);
|
||||
assert_eq!(btreeset![], env_filter.modules_on_for(Level::Debug));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
use defmt_parser::Level;
|
||||
#[cfg(not(test))]
|
||||
use proc_macro_error2::abort_call_site as panic;
|
||||
use std::fmt;
|
||||
use syn::Ident;
|
||||
|
||||
// None = "off" pseudo-level
|
||||
pub(crate) type LogLevelOrOff = Option<Level>;
|
||||
|
||||
// NOTE this is simpler than `syn::Path`; we do not want to accept e.g. `Vec::<Ty>::new`
|
||||
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub(crate) struct ModulePath {
|
||||
segments: Vec<String>,
|
||||
}
|
||||
|
||||
/// Parses the contents of the `DEFMT_LOG` env var
|
||||
pub(crate) fn defmt_log(input: &str) -> impl Iterator<Item = Entry> + '_ {
|
||||
input
|
||||
.rsplit(',')
|
||||
.filter(|entry| !entry.is_empty())
|
||||
.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<LogLevelOrOff, ()> {
|
||||
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::<Ident>(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::<Vec<_>>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
use 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()
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
use defmt_parser::ParserMode;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use proc_macro_error2::abort;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, parse_quote};
|
||||
|
||||
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::<Vec<_>>())
|
||||
.unwrap_or_default();
|
||||
|
||||
let Codegen { patterns, exprs } = Codegen::new(
|
||||
&fragments,
|
||||
formatting_exprs.len(),
|
||||
args.format_string.span(),
|
||||
);
|
||||
|
||||
let header =
|
||||
construct::interned_string(&format_string, "println", true, None, &parse_quote!(defmt));
|
||||
let content = if exprs.is_empty() {
|
||||
quote!(
|
||||
defmt::export::acquire_header_and_release(&#header);
|
||||
)
|
||||
} else {
|
||||
quote!(
|
||||
// safety: will be released a few lines further down
|
||||
unsafe { defmt::export::acquire_and_header(&#header); };
|
||||
#(#exprs;)*
|
||||
// safety: acquire() was called a few lines above
|
||||
unsafe { defmt::export::release() }
|
||||
)
|
||||
};
|
||||
quote!({
|
||||
match (#(&(#formatting_exprs)),*) {
|
||||
(#(#patterns),*) => {
|
||||
#content
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
use defmt_parser::ParserMode;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro_error2::abort;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, parse_quote};
|
||||
|
||||
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, None, &parse_quote!(defmt));
|
||||
quote!({
|
||||
let _typecheck_formatter: defmt::Formatter<'_> = #formatter;
|
||||
match (#(&(#formatting_exprs)),*) {
|
||||
(#(#patterns),*) => {
|
||||
defmt::export::istr(&#format_tag);
|
||||
#(#exprs;)*
|
||||
}
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
use 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<Self> {
|
||||
Ok(Self {
|
||||
formatter: input.parse()?,
|
||||
_comma: input.parse()?,
|
||||
log_args: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
//! Procedural macros that expand to items
|
||||
|
||||
pub(crate) mod bitflags;
|
||||
pub(crate) mod timestamp;
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{parse_macro_input, parse_quote};
|
||||
|
||||
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,
|
||||
None,
|
||||
&parse_quote!(defmt),
|
||||
);
|
||||
|
||||
let ident = input.ident();
|
||||
let ty = input.ty();
|
||||
let flag_statics = codegen_flag_statics(&input);
|
||||
quote!(
|
||||
const _: () = {
|
||||
fn assert<T: defmt::export::UnsignedInt>() {}
|
||||
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<TokenStream2> {
|
||||
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::<Vec<_>>()
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
token, Attribute, Expr, Ident, Token, Type, Visibility,
|
||||
};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(super) struct Input {
|
||||
struct_attrs: Vec<Attribute>,
|
||||
vis: Visibility,
|
||||
struct_token: Token![struct],
|
||||
ident: Ident,
|
||||
colon_token: Token![:],
|
||||
ty: Type,
|
||||
brace_token: token::Brace,
|
||||
flags: Punctuated<Flag, Token![;]>,
|
||||
}
|
||||
|
||||
impl Parse for Input {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
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<Item = &Flag> {
|
||||
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<Attribute>,
|
||||
const_attrs: Vec<Attribute>,
|
||||
const_token: Token![const],
|
||||
ident: Ident,
|
||||
eq_token: Token![=],
|
||||
value: Expr,
|
||||
}
|
||||
|
||||
impl Parse for Flag {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
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<Attribute> {
|
||||
let mut cfgs = vec![];
|
||||
|
||||
for attr in attrs {
|
||||
if attr.path().is_ident("cfg") {
|
||||
cfgs.push(attr.clone());
|
||||
}
|
||||
}
|
||||
|
||||
cfgs
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
use defmt_parser::ParserMode;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro_error2::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", None);
|
||||
|
||||
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()
|
||||
}
|
||||
Vendored
+209
@@ -0,0 +1,209 @@
|
||||
//! 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_error2::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)
|
||||
}
|
||||
Reference in new issue
Block a user