fj_proc/lib.rs
1mod expand;
2mod parse;
3
4use proc_macro::TokenStream;
5use syn::{parse_macro_input, FnArg, ItemFn};
6
7/// Define a function-based model.
8///
9/// The simplest model function takes no parameters and returns a hard-coded
10/// `fj::Shape`.
11///
12/// ``` rust ignore
13/// # use fj_proc::model;
14/// use fj::{Circle, Sketch, Shape};
15/// #[model]
16/// fn model() -> Shape {
17/// let circle = Circle::from_radius(10.0);
18/// Sketch::from_circle(circle).into()
19/// }
20/// ```
21///
22/// For convenience, you can also return anything that could be converted into
23/// a `fj::Shape` (e.g. a `fj::Sketch`).
24///
25/// ``` rust ignore
26/// # use fj_proc::model;
27/// use fj::{Circle, Sketch};
28/// #[model]
29/// fn model() -> Sketch {
30/// let circle = Circle::from_radius(10.0);
31/// Sketch::from_circle(circle)
32/// }
33/// ```
34///
35/// The return type is checked at compile time. That means something like this
36/// won't work because `()` can't be converted into a `fj::Shape`.
37///
38/// ``` rust ignore
39/// # use fj_proc::model;
40/// #[model]
41/// fn model() { todo!() }
42/// ```
43///
44/// The model function's arguments can be anything that implement
45/// [`std::str::FromStr`].
46///
47/// ``` rust ignore
48/// # use fj_proc::model;
49/// #[model]
50/// fn cylinder(height: f64, label: String, is_horizontal: bool) -> fj::Shape { todo!() }
51/// ```
52///
53/// Constraints and default values can be added to an argument using the
54/// `#[param]` attribute.
55///
56/// ``` rust ignore
57/// use fj::syntax::*;
58///
59/// #[fj::model]
60/// pub fn spacer(
61/// #[param(default = 1.0, min = inner * 1.01)] outer: f64,
62/// #[param(default = 0.5, max = outer * 0.99)] inner: f64,
63/// #[param(default = 1.0)] height: f64,
64/// ) -> fj::Shape {
65/// let outer_edge = fj::Sketch::from_circle(fj::Circle::from_radius(outer));
66/// let inner_edge = fj::Sketch::from_circle(fj::Circle::from_radius(inner));
67///
68/// let footprint = outer_edge.difference(&inner_edge);
69/// let spacer = footprint.sweep([0., 0., height]);
70///
71/// spacer.into()
72/// }
73/// ```
74///
75/// For more complex situations, model functions are allowed to return any
76/// error type that converts into a model error.
77///
78/// ``` rust ignore
79/// #[fj::model]
80/// pub fn model() -> Result<fj::Shape, std::env::VarError> {
81/// let home_dir = std::env::var("HOME")?;
82///
83/// todo!("Do something with {home_dir}")
84/// }
85///
86/// fn assert_convertible(e: std::env::VarError) -> fj::models::Error { e.into() }
87/// ```
88#[proc_macro_attribute]
89pub fn model(_: TokenStream, input: TokenStream) -> TokenStream {
90 let item = parse_macro_input!(input as syn::ItemFn);
91
92 match parse::parse(&item) {
93 Ok(init) => {
94 let item = without_param_attrs(item);
95
96 let attrs = item.attrs;
97 let vis = item.vis;
98 let sig = item.sig;
99 let statements = item.block.stmts;
100
101 let item = quote::quote! {
102 #(#attrs)* #vis #sig {
103 fj::abi::initialize_panic_handling();
104 #(#statements)*
105 }
106 };
107
108 let tokens = quote::quote! {
109 #item
110 #init
111
112 };
113
114 tokens.into()
115 }
116 Err(e) => e.into_compile_error().into(),
117 }
118}
119
120/// Strip out any of our `#[param(...)]` attributes so the item will compile.
121fn without_param_attrs(mut item: ItemFn) -> ItemFn {
122 for input in &mut item.sig.inputs {
123 let attrs = match input {
124 FnArg::Receiver(r) => &mut r.attrs,
125 FnArg::Typed(t) => &mut t.attrs,
126 };
127 attrs.retain(|attr| !attr.path().is_ident("param"));
128 }
129
130 item
131}