Skip to content

Commit 9eb8523

Browse files
committed
Add settings
1 parent 2b2e930 commit 9eb8523

File tree

16 files changed

+862
-762
lines changed

16 files changed

+862
-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: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
// Based on defmt
22

3-
use proc_macro2::Span;
43
use syn::{
5-
LitStr, Token,
4+
LitStr, RangeLimits, Token,
65
parse::{self, Parse, ParseStream},
6+
spanned::Spanned,
77
};
88

99
//FOO: i32 = 0, "x * 3.0"
1010
//FOO: i32 = 0 // defaults to "x"
11-
pub(crate) struct Args {
11+
pub(crate) struct MetricArgs {
1212
pub(crate) name: syn::Ident,
1313
pub(crate) ty: syn::Ident,
1414
pub(crate) initial_val: syn::Expr,
1515
pub(crate) expression_string: syn::LitStr,
1616
}
1717

18-
impl Parse for Args {
18+
impl Parse for MetricArgs {
1919
fn parse(input: ParseStream) -> parse::Result<Self> {
20-
let name = input.parse()?;
20+
let name: syn::Ident = input.parse()?;
2121
let _comma: Token![:] = input.parse()?;
2222
let ty = input.parse()?;
2323
let _comma: Token![=] = input.parse()?;
@@ -29,7 +29,7 @@ impl Parse for Args {
2929
let expression_string = match (comma, expression_string) {
3030
(Ok(_), Ok(expr)) => expr,
3131
(Ok(_), Err(e)) => return Err(e),
32-
(Err(_), _) => LitStr::new("x", Span::mixed_site()),
32+
(Err(_), _) => LitStr::new(&name.to_string(), name.span()),
3333
};
3434

3535
Ok(Self {
@@ -40,3 +40,95 @@ impl Parse for Args {
4040
})
4141
}
4242
}
43+
44+
// FOO: i32 = 0, 0..=10, 2
45+
// FOO: i32 = 0, 0..=10, // Step size defaults to 1
46+
// FOO: i32 = 0 // range defaults to the types full range
47+
// TODO Implement the defaults
48+
pub(crate) struct SettingArgs {
49+
pub(crate) name: syn::Ident,
50+
pub(crate) ty: syn::Ident,
51+
pub(crate) initial_val: syn::Expr,
52+
pub(crate) range_start: syn::LitFloat,
53+
pub(crate) range_end: syn::LitFloat,
54+
pub(crate) step_size: syn::LitFloat,
55+
}
56+
57+
impl Parse for SettingArgs {
58+
fn parse(input: ParseStream) -> parse::Result<Self> {
59+
let name = input.parse()?;
60+
let _colon: Token![:] = input.parse()?;
61+
let ty = input.parse()?;
62+
let _eq: Token![=] = input.parse()?;
63+
let initial_val = input.parse()?;
64+
65+
let _comma: parse::Result<Token![,]> = input.parse();
66+
let range: syn::Expr = input.parse()?;
67+
68+
let syn::Expr::Range(range) = range else {
69+
panic!("Invalid range")
70+
};
71+
72+
let range_start = range
73+
.start
74+
.expect("Only inclusive ranges with both a start and end are supported");
75+
let range_end = range
76+
.end
77+
.expect("Only inclusive ranges with both a start and end are supported");
78+
assert!(
79+
matches!(range.limits, RangeLimits::Closed(_)),
80+
"Only inclusive ranges with both a start and end are supported"
81+
);
82+
83+
let _comma: parse::Result<Token![,]> = input.parse();
84+
let step_size: syn::Lit = input.parse()?;
85+
86+
let step_size = match step_size {
87+
syn::Lit::Int(i) => syn::LitFloat::new(&format!("{}.0", i.base10_digits()), i.span()),
88+
syn::Lit::Float(f) => f,
89+
x => return Err(syn::Error::new(x.span(), "expected float or int literal")),
90+
};
91+
92+
Ok(Self {
93+
name,
94+
ty,
95+
initial_val,
96+
range_start: expr_to_float_lit(*range_start)?,
97+
range_end: expr_to_float_lit(*range_end)?,
98+
step_size,
99+
})
100+
}
101+
}
102+
103+
// TODO: Clean up this mess
104+
fn expr_to_float_lit(e: syn::Expr) -> Result<syn::LitFloat, syn::Error> {
105+
let error_msg = "expected float or int literal";
106+
Ok(match e {
107+
syn::Expr::Lit(syn::ExprLit {
108+
lit: syn::Lit::Float(f),
109+
..
110+
}) => f,
111+
syn::Expr::Lit(syn::ExprLit {
112+
lit: syn::Lit::Int(i),
113+
..
114+
}) => syn::LitFloat::new(&format!("{}.0", i.base10_digits()), i.span()),
115+
syn::Expr::Unary(syn::ExprUnary {
116+
op: syn::UnOp::Neg(_),
117+
expr,
118+
..
119+
}) => match *expr {
120+
syn::Expr::Lit(syn::ExprLit { lit, .. }) => match lit {
121+
// TODO: Is there a better way to handle the minus sign?
122+
syn::Lit::Int(i) => {
123+
syn::LitFloat::new(&format!("-{}.0", i.base10_digits()), i.span())
124+
}
125+
syn::Lit::Float(f) => {
126+
syn::LitFloat::new(&format!("-{}", f.base10_digits()), f.span())
127+
}
128+
x => return Err(syn::Error::new(x.span(), error_msg)),
129+
},
130+
x => return Err(syn::Error::new(x.span(), error_msg)),
131+
},
132+
x => return Err(syn::Error::new(x.span(), error_msg)),
133+
})
134+
}

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)