Skip to content

Commit 2f8967f

Browse files
committed
Add settings
1 parent 2b2e930 commit 2f8967f

File tree

16 files changed

+820
-762
lines changed

16 files changed

+820
-762
lines changed

macros/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ edition = "2024"
66
[dependencies]
77
proc-macro2 = "1.0.95"
88
quote = "1.0.40"
9-
syn = "2"
9+
syn = { version = "2", features = ["full"] }
1010

1111
[lib]
1212
proc-macro = true

macros/src/args.rs

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,21 @@
22

33
use proc_macro2::Span;
44
use syn::{
5-
LitStr, Token,
6-
parse::{self, Parse, ParseStream},
5+
parse::{self, Parse, ParseStream}, spanned::Spanned, ExprRange, LitStr, RangeLimits, Token
76
};
87

98
//FOO: i32 = 0, "x * 3.0"
109
//FOO: i32 = 0 // defaults to "x"
11-
pub(crate) struct Args {
10+
pub(crate) struct MetricArgs {
1211
pub(crate) name: syn::Ident,
1312
pub(crate) ty: syn::Ident,
1413
pub(crate) initial_val: syn::Expr,
1514
pub(crate) expression_string: syn::LitStr,
1615
}
1716

18-
impl Parse for Args {
17+
impl Parse for MetricArgs {
1918
fn parse(input: ParseStream) -> parse::Result<Self> {
20-
let name = input.parse()?;
19+
let name: syn::Ident = input.parse()?;
2120
let _comma: Token![:] = input.parse()?;
2221
let ty = input.parse()?;
2322
let _comma: Token![=] = input.parse()?;
@@ -29,7 +28,7 @@ impl Parse for Args {
2928
let expression_string = match (comma, expression_string) {
3029
(Ok(_), Ok(expr)) => expr,
3130
(Ok(_), Err(e)) => return Err(e),
32-
(Err(_), _) => LitStr::new("x", Span::mixed_site()),
31+
(Err(_), _) => LitStr::new(&name.to_string(), name.span()),
3332
};
3433

3534
Ok(Self {
@@ -40,3 +39,58 @@ impl Parse for Args {
4039
})
4140
}
4241
}
42+
43+
// FOO: i32 = 0, 0..=10, 2
44+
// FOO: i32 = 0, 0..=10, // Step size defaults to 1
45+
// FOO: i32 = 0 // range defaults to the types full range
46+
// TODO Implement the defaults
47+
pub(crate) struct SettingArgs {
48+
pub(crate) name: syn::Ident,
49+
pub(crate) ty: syn::Ident,
50+
pub(crate) initial_val: syn::Expr,
51+
pub(crate) range_start: syn::LitFloat,
52+
pub(crate) range_end: syn::LitFloat,
53+
pub(crate) step_size: syn::LitFloat,
54+
}
55+
56+
impl Parse for SettingArgs {
57+
fn parse(input: ParseStream) -> parse::Result<Self> {
58+
let name = input.parse()?;
59+
let _colon: Token![:] = input.parse()?;
60+
let ty = input.parse()?;
61+
let _eq: Token![=] = input.parse()?;
62+
let initial_val = input.parse()?;
63+
64+
let _comma: parse::Result<Token![,]> = input.parse();
65+
let range: syn::Expr = input.parse()?;
66+
67+
let syn::Expr::Range(range) = range else {
68+
panic!("Invalid range")
69+
};
70+
71+
let range_start = range.start.expect("Only inclusive ranges with both a start and end are supported");
72+
let range_end = range.end.expect("Only inclusive ranges with both a start and end are supported");
73+
assert!(matches!(range.limits, RangeLimits::Closed(_)), "Only inclusive ranges with both a start and end are supported");
74+
75+
let _comma: parse::Result<Token![,]> = input.parse();
76+
let step_size = input.parse()?;
77+
78+
Ok(Self {
79+
name,
80+
ty,
81+
initial_val,
82+
range_start: expr_to_float_lit(*range_start),
83+
range_end: expr_to_float_lit(*range_end),
84+
step_size
85+
})
86+
}
87+
}
88+
89+
90+
fn expr_to_float_lit(e: syn::Expr) -> syn::LitFloat {
91+
match e.clone() {
92+
syn::Expr::Lit(syn::ExprLit{ lit: syn::Lit::Float(f), .. }) => f,
93+
syn::Expr::Lit(syn::ExprLit{ lit: syn::Lit::Int(i), .. }) => syn::LitFloat::new(&format!("{}.0", i.base10_digits()), e.span()),
94+
_ => panic!("expected float literal")
95+
}
96+
}

macros/src/lib.rs

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use proc_macro::{Span, TokenStream};
77
use quote::quote;
88
use syn::parse_macro_input;
99

10-
use crate::symbol::Symbol;
10+
use crate::symbol::{MetricsSymbol, SettingSymbol};
1111

1212
mod args;
1313
mod cargo;
@@ -28,9 +28,9 @@ mod symbol;
2828
/// ```
2929
#[proc_macro]
3030
pub fn make_metric(args: TokenStream) -> TokenStream {
31-
let args = parse_macro_input!(args as args::Args);
31+
let args = parse_macro_input!(args as args::MetricArgs);
3232

33-
let sym_name = Symbol::new(
33+
let sym_name = MetricsSymbol::new(
3434
args.ty.to_string(),
3535
args.name.to_string(),
3636
args.expression_string.value(),
@@ -64,6 +64,58 @@ pub fn make_metric(args: TokenStream) -> TokenStream {
6464
.into()
6565
}
6666

67+
/// Create a Setting instance that will be shown as a slider in the probe-plotter utility
68+
///
69+
/// ```
70+
/// make_setting!(NAME_AS_SHOWN_NEXT_TO_SLIDER: DataType = defalt_value, min_value..=max_value, step_size)
71+
/// ```
72+
///
73+
/// Note that similar to `cortex_m::singleton!`, this should only be called once per setting. The macro will only return Some() the first time, then None.
74+
///
75+
/// ```
76+
/// let mut setting_foo = probe_plotter::make_setting!(FOO: i32 = 0, 0..=10, 1.0).unwrap();
77+
///
78+
/// let value = setting_foo.get();
79+
/// ```
80+
#[proc_macro]
81+
pub fn make_setting(args: TokenStream) -> TokenStream {
82+
let args = parse_macro_input!(args as args::SettingArgs);
83+
84+
let sym_name = SettingSymbol::new(
85+
args.ty.to_string(),
86+
args.name.to_string(),
87+
args.range_start.base10_parse().unwrap()..=args.range_end.base10_parse().unwrap(),
88+
args.step_size.base10_parse().unwrap(),
89+
)
90+
.mangle();
91+
92+
let name = args.name;
93+
let ty = args.ty;
94+
let initial_value = args.initial_val;
95+
96+
quote!(
97+
cortex_m::interrupt::free(|_| {
98+
#[unsafe(export_name = #sym_name)]
99+
static mut #name: (#ty, bool) =
100+
(0, false);
101+
102+
#[allow(unsafe_code)]
103+
let used = unsafe { #name.1 };
104+
if used {
105+
None
106+
} else {
107+
#[allow(unsafe_code)]
108+
unsafe {
109+
#name.1 = true;
110+
#name.0 = #initial_value;
111+
Some(::probe_plotter::Setting::new(&mut #name.0))
112+
}
113+
}
114+
})
115+
)
116+
.into()
117+
}
118+
67119
pub(crate) fn crate_local_disambiguator() -> u64 {
68120
// We want a deterministic, but unique-per-macro-invocation identifier. For that we
69121
// hash the call site `Span`'s debug representation, which contains a counter that

macros/src/symbol.rs

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// Based on defmt
22

3+
use std::ops::RangeInclusive;
4+
35
use crate::cargo;
46

5-
pub struct Symbol {
7+
pub struct MetricsSymbol {
68
/// Name of the Cargo package in which the symbol is being instantiated. Used for avoiding
79
/// symbol name collisions.
810
package: String,
@@ -23,7 +25,7 @@ pub struct Symbol {
2325
crate_name: String,
2426
}
2527

26-
impl Symbol {
28+
impl MetricsSymbol {
2729
pub fn new(ty: String, name: String, expr: String) -> Self {
2830
Self {
2931
// `CARGO_PKG_NAME` is set to the invoking package's name.
@@ -38,7 +40,7 @@ impl Symbol {
3840

3941
pub fn mangle(&self) -> String {
4042
format!(
41-
r#"{{"package":"{}","ty":"{}","name":"{}","expr": "{}","disambiguator":"{}","crate_name":"{}"}}"#,
43+
r#"{{"type":"Metric","package":"{}","ty":"{}","name":"{}","expr":"{}","disambiguator":"{}","crate_name":"{}"}}"#,
4244
json_escape(&self.package),
4345
json_escape(&self.ty),
4446
json_escape(&self.name),
@@ -49,6 +51,59 @@ impl Symbol {
4951
}
5052
}
5153

54+
pub struct SettingSymbol {
55+
/// Name of the Cargo package in which the symbol is being instantiated. Used for avoiding
56+
/// symbol name collisions.
57+
package: String,
58+
59+
/// Unique identifier that disambiguates otherwise equivalent invocations in the same crate.
60+
disambiguator: u64,
61+
62+
/// Underlaying data type
63+
ty: String,
64+
65+
/// Variable name
66+
name: String,
67+
68+
/// Range of valid values
69+
range: RangeInclusive<f64>,
70+
71+
/// Step size
72+
step_size: f64,
73+
74+
/// Crate name obtained via CARGO_CRATE_NAME (added since a Cargo package can contain many crates).
75+
crate_name: String,
76+
}
77+
78+
impl SettingSymbol {
79+
pub fn new(ty: String, name: String, range: RangeInclusive<f64>, step_size: f64) -> Self {
80+
Self {
81+
// `CARGO_PKG_NAME` is set to the invoking package's name.
82+
package: cargo::package_name(),
83+
disambiguator: super::crate_local_disambiguator(),
84+
ty,
85+
name,
86+
range,
87+
step_size,
88+
crate_name: cargo::crate_name(),
89+
}
90+
}
91+
92+
pub fn mangle(&self) -> String {
93+
format!(
94+
r#"{{"type":"Setting","package":"{}","ty":"{}","name":"{}","range":{{"start":{},"end":{}}},"step_size":{},"disambiguator":"{}","crate_name":"{}"}}"#,
95+
json_escape(&self.package),
96+
json_escape(&self.ty),
97+
json_escape(&self.name),
98+
self.range.start(),
99+
self.range.end(),
100+
self.step_size,
101+
self.disambiguator,
102+
json_escape(&self.crate_name),
103+
)
104+
}
105+
}
106+
52107
fn json_escape(string: &str) -> String {
53108
use std::fmt::Write;
54109

0 commit comments

Comments
 (0)