ferroforge_macros/lib.rs
1//! FerroForge's procedural macros.
2//!
3//! Two entry points, and nothing between them, named as RTIC names the same
4//! ideas. `#[ferroforge::task]` turns a task definition into an ordinary
5//! generic function and a real context type; `ferroforge::app!` expands a
6//! firmware in place into a real `#[rtic::app]` whose handlers construct that
7//! context and call the function.
8//!
9//! Neither reads the other's crate. `app!` emits real Rust paths into the task
10//! crates a firmware depends on, so a wrong definition, binding or type is an
11//! ordinary compile error at the authored line.
12
13mod app;
14mod task;
15
16use ferroforge_contracts::{TaskArguments, TaskContract};
17use proc_macro::TokenStream;
18use syn::{ItemFn, parse_macro_input};
19
20/// The firmware's authored application, expanded in place into a real
21/// `#[rtic::app]`. Init and resources are written here and never move; each
22/// task declaration becomes an adapter that calls the selected definition.
23#[proc_macro]
24pub fn app(input: TokenStream) -> TokenStream {
25 let application = parse_macro_input!(input as app::App);
26 match app::expand(application) {
27 Ok(output) => output.into(),
28 Err(error) => error.to_compile_error().into(),
29 }
30}
31
32/// A task definition: expands to a real generic context and an ordinary generic
33/// function, with no mock layer. A firmware depends on this crate normally and
34/// its RTIC handler calls the function, so no source is transplanted and the
35/// body is compiled once, in place.
36#[proc_macro_attribute]
37pub fn task(attr: TokenStream, item: TokenStream) -> TokenStream {
38 let args = parse_macro_input!(attr as TaskArguments);
39 let function = parse_macro_input!(item as ItemFn);
40
41 let output = TaskContract::new(args, &function.sig)
42 .and_then(|contract| task::expand(contract, function));
43
44 match output {
45 Ok(output) => output.into(),
46 Err(error) => error.to_compile_error().into(),
47 }
48}