otter_codegen/lib.rs
1use proc_macro::TokenStream;
2
3mod init_macro;
4mod nif_macro;
5mod raw_macro;
6mod resource_impl_macro;
7
8/// Mark a Rust function as a NIF.
9///
10/// The annotated function takes an env as its first parameter (a `CallEnv<'_>`)
11/// followed by one parameter per Erlang argument, each a type implementing
12/// `Decoder`; it returns any type implementing `Encoder`. The macro generates the
13/// `extern "C"` trampoline the BEAM calls: it decodes each argument (a failed
14/// decode becomes `badarg`), invokes your function inside a `catch_unwind` (a
15/// panic is turned into a raised exception rather than crossing the C-ABI
16/// boundary), and encodes the result (a failed encode becomes `badret`). List the
17/// function in [`init!`](macro@init)'s NIF table to export it.
18///
19/// To raise on purpose, return `Result<T, Raised<'_>>` and use `CallEnv::raise` /
20/// `CallEnv::badarg`.
21///
22/// # Options
23///
24/// - `schedule = "DirtyCpu"` — run on a dirty CPU scheduler (long, CPU-bound work).
25/// - `schedule = "DirtyIo"` — run on a dirty I/O scheduler (work that blocks).
26/// - `name = "erlang_name"` — export under a different Erlang name than the Rust
27/// function name.
28///
29/// ```text
30/// #[otter::nif]
31/// fn add(env: CallEnv<'_>, a: Integer<'_>, b: Integer<'_>) -> Integer<'_> {
32/// Integer::from_i64(env, a.to_i64(env).unwrap() + b.to_i64(env).unwrap())
33/// }
34///
35/// #[otter::nif(schedule = "DirtyCpu", name = "heavy")]
36/// fn heavy_impl(env: CallEnv<'_>, n: Integer<'_>) -> Integer<'_> { /* … */ }
37/// ```
38#[proc_macro_attribute]
39pub fn nif(attr: TokenStream, item: TokenStream) -> TokenStream {
40 nif_macro::expand(attr.into(), item.into())
41 .unwrap_or_else(|e| e.to_compile_error())
42 .into()
43}
44
45/// Declare the NIF library: its module name, exported NIFs, pre-interned atoms,
46/// resource types, and lifecycle callbacks. Invoke it exactly once per library.
47///
48/// It generates the `nif_init` entry point, the `load`/`upgrade`/`unload`
49/// scaffolding (which installs the private-data slot, registers resource types,
50/// and interns the declared atoms — all under a `catch_unwind`), and the
51/// `__otter_atoms` table that the `atom!` macro reads.
52///
53/// # Syntax
54///
55/// ```text
56/// otter::init!(
57/// "my_module", // the Erlang module name (required, first)
58/// [add, subtract], // the NIF table — functions marked #[otter::nif]
59/// atoms = [ok, error, not_found = "not found"],
60/// resources = [MyResource, Conn: "conn-v1"],
61/// load = on_load, // optional lifecycle callbacks
62/// upgrade = on_upgrade,
63/// unload = on_unload,
64/// );
65/// ```
66///
67/// The first two arguments are positional; everything after is an
68/// order-independent keyword entry:
69///
70/// - **`atoms = [name, alias = "beam name", …]`** — atoms interned once at load
71/// (and re-interned on upgrade). Use `alias = "…"` when the BEAM atom text is
72/// not a valid Rust identifier. Names are length-checked (≤255 chars) at
73/// compile time. Retrieve with the `atom!` macro.
74/// - **`resources = [Type, Type: "tag", …]`** — resource types to register. A
75/// bare `Type` is registered under an ABI-fingerprinted name (no cross-build
76/// takeover); `Type: "tag"` registers under a stable tagged name that opts into
77/// hot-upgrade takeover.
78/// - **`load = f` / `upgrade = f` / `unload = f`** — lifecycle callbacks. `load`
79/// and `upgrade` receive the env and the decoded `load_info` term and return
80/// `bool` (return `false` to veto the load); `unload` receives the env and
81/// cannot veto. Their `load_raw` / `upgrade_raw` / `unload_raw` variants
82/// (require the `raw` feature) additionally hand you the library's
83/// `user_priv_data` `void*` to manage yourself.
84/// - **`allow_panic_abort`** — a bare flag that opts out of the compile-time
85/// guard which otherwise rejects building this crate with `panic = "abort"`
86/// (abort would bypass otter's panic interception and crash the VM).
87#[proc_macro]
88pub fn init(input: TokenStream) -> TokenStream {
89 init_macro::expand(input.into())
90 .unwrap_or_else(|e| e.to_compile_error())
91 .into()
92}
93
94/// Optional marker attribute for an `impl Resource for T` block.
95///
96/// Currently it re-emits the impl block unchanged — writing `impl Resource for T`
97/// directly is equivalent. It exists as the designated annotation point for
98/// resource impls so that future codegen (or tooling) has a stable hook, and to
99/// document intent at the impl site. Registration still happens via
100/// [`init!`](macro@init)'s `resources = [...]` list.
101#[proc_macro_attribute]
102pub fn resource_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
103 resource_impl_macro::expand(item.into())
104 .unwrap_or_else(|e| e.to_compile_error())
105 .into()
106}
107
108/// Widen an item's visibility to `pub` only when the `raw` feature is enabled.
109///
110/// Write the item with its real, non-raw visibility; the macro emits the
111/// original verbatim under `#[cfg(not(feature = "raw"))]` and a `pub` copy under
112/// `#[cfg(feature = "raw")]`. The `cfg` resolves in the using crate, so this is
113/// meant for a crate that has a `raw` feature. Works on any visibility-bearing
114/// item (fn, method, struct, enum, type, const, static, mod, use), including
115/// items written with no visibility at all.
116///
117/// ```text
118/// #[raw] pub(crate) fn internal() {} // pub under `raw`, else pub(crate)
119/// #[raw] use crate::Thing; // pub use under `raw`, else private
120/// ```
121#[proc_macro_attribute]
122pub fn raw(attr: TokenStream, item: TokenStream) -> TokenStream {
123 raw_macro::expand(attr.into(), item.into())
124 .unwrap_or_else(|e| e.to_compile_error())
125 .into()
126}