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