Skip to main content

sim_macros/
lib.rs

1//! Proc-macro surface for authored SIM libraries.
2//!
3//! `#[sim_class]`, `#[sim_constructor]`, `#[sim_fn]`, `#[sim_macro]`,
4//! `#[sim_codec]`, `#[sim_number_domain]`, `#[sim_site]`, `#[case]`, and
5//! `#[shape]` are marker attributes. They do not transform the attached item on
6//! their own. `#[sim_lib(...)]` scans an inline module for those markers,
7//! validates the contracts, and generates the runtime registration and optional
8//! native ABI glue.
9//!
10//! Invalid shape literals and missing generated-class constructors are rejected
11//! during macro expansion rather than silently widening contracts.
12
13#![forbid(unsafe_code)]
14#![allow(deprecated)]
15#![deny(missing_docs)]
16
17mod attrs;
18mod function_expand;
19mod macro_expand;
20mod module_expand;
21mod native_class_expand;
22mod native_macro_expand;
23mod native_number_expand;
24mod parse;
25mod shape;
26
27use attrs::{bool_attr, camel_case, string_attr};
28use parse::ParsedModule;
29use proc_macro::TokenStream;
30use proc_macro2::Span;
31use quote::{format_ident, quote};
32use syn::{ItemMod, LitStr, Token, parse_macro_input, punctuated::Punctuated};
33
34/// Expands an inline module into a SIM library.
35///
36/// Parses the module's `#[sim_class]`, `#[sim_constructor]`, `#[sim_fn]`,
37/// `#[sim_macro]`, `#[sim_codec]`, `#[sim_number_domain]`, `#[sim_site]`,
38/// `#[case]`, and `#[shape]` items and generates the library registration plus an
39/// `__SIM_LIB_EXPANSION` snapshot.
40/// Requires `id` and `version` attributes; `native_export` is optional. The
41/// annotated module must be inline.
42#[proc_macro_attribute]
43pub fn sim_lib(attr: TokenStream, item: TokenStream) -> TokenStream {
44    let attr =
45        parse_macro_input!(attr with Punctuated::<syn::MetaNameValue, Token![,]>::parse_terminated);
46    let mut module = parse_macro_input!(item as ItemMod);
47
48    let lib_id = match string_attr(&attr, "id") {
49        Ok(value) => value,
50        Err(err) => return err,
51    };
52    let version = match string_attr(&attr, "version") {
53        Ok(value) => value,
54        Err(err) => return err,
55    };
56    let native_export = match bool_attr(&attr, "native_export") {
57        Ok(value) => value,
58        Err(err) => return err,
59    };
60
61    let Some((_, items)) = &mut module.content else {
62        return syn::Error::new_spanned(&module, "#[sim_lib] requires an inline module")
63            .to_compile_error()
64            .into();
65    };
66
67    let parsed = match ParsedModule::parse(items) {
68        Ok(parsed) => parsed,
69        Err(err) => return err.to_compile_error().into(),
70    };
71
72    let lib_ident = format_ident!("{}Lib", camel_case(&module.ident.to_string()));
73    let generated = match parsed.expand(&module.vis, &lib_ident, &lib_id, &version, native_export) {
74        Ok(generated) => generated,
75        Err(err) => return err.to_compile_error().into(),
76    };
77    let expansion_text = generated.to_string();
78    let expansion_text = LitStr::new(&expansion_text, Span::call_site());
79
80    *items = parsed.cleaned_items;
81    items.push(syn::parse_quote! {
82        pub const __SIM_LIB_EXPANSION: &str = #expansion_text;
83    });
84    items.extend(
85        syn::parse2::<syn::File>(generated)
86            .expect("generated sim_lib items should parse")
87            .items,
88    );
89
90    quote!(#module).into()
91}
92
93/// Marks a struct inside a `#[sim_lib]` module as a SIM class. It is consumed by
94/// `#[sim_lib]`; applied on its own it leaves the item unchanged.
95#[proc_macro_attribute]
96pub fn sim_class(_attr: TokenStream, item: TokenStream) -> TokenStream {
97    item
98}
99
100/// Marks a function inside a `#[sim_lib]` module as a class constructor. It is
101/// consumed by `#[sim_lib]`; applied on its own it leaves the item unchanged.
102#[proc_macro_attribute]
103pub fn sim_constructor(_attr: TokenStream, item: TokenStream) -> TokenStream {
104    item
105}
106
107/// Marks a function inside a `#[sim_lib]` module as a SIM operation. It is
108/// consumed by `#[sim_lib]`; applied on its own it leaves the item unchanged.
109#[proc_macro_attribute]
110pub fn sim_fn(_attr: TokenStream, item: TokenStream) -> TokenStream {
111    item
112}
113
114/// Marks a macro export inside a `#[sim_lib]` module. The marker names the macro
115/// symbol plus the expansion function called by host registration and generated
116/// native ABI dispatch.
117#[proc_macro_attribute]
118pub fn sim_macro(_attr: TokenStream, item: TokenStream) -> TokenStream {
119    item
120}
121
122/// Marks a codec export inside a `#[sim_lib]` module. The marker names the
123/// codec symbol plus decoder and encoder functions that are called by generated
124/// native ABI dispatch.
125#[proc_macro_attribute]
126pub fn sim_codec(_attr: TokenStream, item: TokenStream) -> TokenStream {
127    item
128}
129
130/// Marks a number-domain export inside a `#[sim_lib]` module. The marker names
131/// the domain symbol plus guest functions that are called by generated native
132/// ABI dispatch for parsing, literal encoding, and optional scalar operations.
133#[proc_macro_attribute]
134pub fn sim_number_domain(_attr: TokenStream, item: TokenStream) -> TokenStream {
135    item
136}
137
138/// Marks a site export inside a `#[sim_lib]` module. The marker names the site
139/// symbol plus the function called by generated native ABI dispatch for
140/// realization requests.
141#[proc_macro_attribute]
142pub fn sim_site(_attr: TokenStream, item: TokenStream) -> TokenStream {
143    item
144}
145
146/// Marks a function inside a `#[sim_lib]` module as a test case. It is consumed
147/// by `#[sim_lib]`; applied on its own it leaves the item unchanged.
148#[proc_macro_attribute]
149pub fn case(_attr: TokenStream, item: TokenStream) -> TokenStream {
150    item
151}
152
153/// Attaches a shape literal to an item inside a `#[sim_lib]` module. It is
154/// consumed by `#[sim_lib]`; applied on its own it leaves the item unchanged.
155#[proc_macro_attribute]
156pub fn shape(_attr: TokenStream, item: TokenStream) -> TokenStream {
157    item
158}
159
160#[cfg(test)]
161mod tests;