zsh_module_macros/lib.rs
1use proc_macro::TokenStream;
2use syn::{DeriveInput, ItemFn, ItemStruct, parse_macro_input};
3
4// helpers
5mod utils;
6
7// features
8mod builtin;
9mod state;
10
11// derives
12mod derive;
13
14/// Declares a module-state struct.
15///
16/// Annotates a `struct` as the per-module state that `#[builtin]`-registered
17/// functions receive a `&mut` reference to.
18///
19/// # Example
20///
21/// ```ignore
22/// #[state]
23/// struct Greeter {
24/// count: u32,
25/// }
26/// ```
27///
28/// # Expansion (sketch, pre-hygiene)
29///
30/// The user's struct is re-emitted unchanged, plus a private companion module
31/// holding the linkme-registered state container and the `extern "Rust"`
32/// trampoline that `#[builtin]` wrappers call into:
33///
34/// ```ignore
35/// struct Greeter { count: u32 }
36///
37/// mod __zmod_greeter {
38/// use super::Greeter;
39/// use ::zsh_module::__ as zmod;
40/// use zmod::linkme;
41///
42/// impl zmod::module::SizedModuleState for Greeter {}
43/// static MODULE_CONTAINER: Container<Greeter> = Container::new();
44///
45/// #[linkme::distributed_slice(zmod::CONTAINERS)]
46/// static MODULE_VTABLE: &'static dyn ContainerHooks = &MODULE_CONTAINER;
47///
48/// #[unsafe(no_mangle)]
49/// extern "Rust" fn trampoline(cb: TrampolineCallback<Greeter>) -> i32 { /* ... */ }
50/// }
51/// ```
52#[proc_macro_attribute]
53pub fn state(_: TokenStream, item: TokenStream) -> TokenStream {
54 let input = parse_macro_input!(item as ItemStruct);
55 state::state_impl(input).into()
56}
57
58/// Registers a zsh builtin command.
59///
60/// The annotated function's first argument must be `&mut <State>` (or `&<State>`)
61/// where `<State>` is the type annotated with [`macro@state`].
62///
63/// # Arguments
64///
65/// | Key | Required | Default | Type | Meaning |
66/// |---------|----------|---------|-----------------|---------------------------------------------|
67/// | *name* | yes | — | `"..."` / `c"..."` | builtin name (first positional arg) |
68/// | `min` | no | `0` | integer literal | minimum positional args zsh will accept |
69/// | `max` | no | `-1` | integer literal | maximum positional args (`-1` = unlimited) |
70/// | `opts` | no | `c""` | `"..."` / `c"..."` | getopts-style option spec |
71///
72/// # Example
73///
74/// ```ignore
75/// #[builtin("greet", min = 0, max = 3, opts = "fl:")]
76/// fn greet(ctx: &mut Greeter, name: &CStr, args: &[&CStr], opts: &Flags) -> Result<()> {
77/// println!("hello, {}!", ctx.who);
78/// Ok(())
79/// }
80/// ```
81///
82/// # Expansion (sketch, pre-hygiene)
83///
84/// The user's function is re-emitted unchanged; a private companion module
85/// holds an `extern "C"` wrapper conforming to zsh's builtin ABI and the
86/// `linkme` slice entry that registers it:
87///
88/// ```ignore
89/// fn greet(/* original signature */) -> Result<()> { /* original body */ }
90///
91/// mod __zmod_greet {
92/// use super::{greet, Greeter};
93/// use ::zsh_module::__ as zmod;
94///
95/// extern "C" fn wrapper(name: *mut c_char, args: *mut *mut c_char,
96/// opts: *mut zmod::zsh::options, _id: i32) -> i32 { /* ... */ }
97///
98/// #[linkme::distributed_slice(zmod::BUILTINS)]
99/// static BUILTIN_ENTRY: zmod::zsh::builtin =
100/// zmod::zsh::builtin::new(c"greet", wrapper, 0, 3, 0, c"fl:");
101/// }
102/// ```
103#[proc_macro_attribute]
104pub fn builtin(args: TokenStream, item: TokenStream) -> TokenStream {
105 let input = parse_macro_input!(item as ItemFn);
106 let args = parse_macro_input!(args as builtin::BuiltinArgs);
107 builtin::builtin_impl(args, input).into()
108}
109
110
111#[proc_macro_derive(Activate)]
112pub fn activate_derive(input: TokenStream) -> TokenStream {
113 let input = parse_macro_input!(input as DeriveInput);
114 derive::activate_derive_impl(input).into()
115}
116
117#[proc_macro_derive(Deactivate)]
118pub fn deactivate_derive(input: TokenStream) -> TokenStream {
119 let input = parse_macro_input!(input as DeriveInput);
120 derive::deactivate_derive_impl(input).into()
121}