borderless_sdk_macros/
lib.rs

1use agent::AgentArgs;
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{parse_macro_input, Item, ItemMod};
5
6mod action;
7mod agent;
8mod contract;
9mod schedule;
10mod sink;
11mod state;
12mod utils;
13
14// TODO's:
15// - [ ] Check existence of serde crate
16// - [ ] Check re-naming of borderless crate
17
18#[proc_macro_attribute]
19pub fn contract(_attrs: TokenStream, input: TokenStream) -> TokenStream {
20    let module = parse_macro_input!(input as ItemMod);
21
22    // Check if module has some content
23    if module.content.is_none() {
24        return syn::Error::new_spanned(
25            module,
26            "Macro can only be implemented on modules that have some content",
27        )
28        .to_compile_error()
29        .into();
30    }
31    let (brace, mut items) = module.content.unwrap();
32
33    // Generate new tokens based on the module's content
34    let new_tokens = match contract::parse_module_content(brace.span.join(), &items, &module.ident)
35    {
36        Ok(tokens) => tokens,
37        Err(e) => return e.to_compile_error().into(),
38    };
39
40    // Add these new tokens to the existing items
41    items.push(Item::Verbatim(new_tokens));
42
43    // Also generate an error if the visibility is not public
44    match module.vis {
45        syn::Visibility::Public(_) => (),
46        _ => {
47            let tokens =
48                syn::Error::new_spanned(module.mod_token, "Contract module must be public")
49                    .to_compile_error();
50            items.push(Item::Verbatim(tokens));
51        }
52    }
53
54    let wasm_exports = contract::generate_wasm_exports(&module.ident);
55
56    // Generate a new module from the content of the original module
57    let new_module = ItemMod {
58        attrs: module.attrs,
59        vis: module.vis,
60        unsafety: module.unsafety,
61        mod_token: module.mod_token,
62        ident: module.ident,
63        content: Some((brace, items)),
64        semi: module.semi,
65    };
66
67    // Convert into token stream
68    quote! {
69        #new_module
70        #wasm_exports
71    }
72    .into()
73}
74
75#[proc_macro_attribute]
76pub fn agent(attrs: TokenStream, input: TokenStream) -> TokenStream {
77    let module = parse_macro_input!(input as ItemMod);
78    let parsed_attrs = syn::parse_macro_input!(attrs as AgentArgs);
79
80    // Check if module has some content
81    if module.content.is_none() {
82        return syn::Error::new_spanned(
83            module,
84            "Macro can only be implemented on modules that have some content",
85        )
86        .to_compile_error()
87        .into();
88    }
89    let (brace, mut items) = module.content.unwrap();
90
91    // If the agent has access to the websocket or not depends on the attributes
92    let use_ws = parsed_attrs.websocket.unwrap_or_default();
93
94    // Generate new tokens based on the module's content
95    let new_tokens = match agent::parse_module_content(brace.span.join(), &items, use_ws) {
96        Ok(tokens) => tokens,
97        Err(e) => return e.to_compile_error().into(),
98    };
99
100    // Add these new tokens to the existing items
101    items.push(Item::Verbatim(new_tokens));
102
103    // Also generate an error if the visibility is not public
104    match module.vis {
105        syn::Visibility::Public(_) => (),
106        _ => {
107            let tokens = syn::Error::new_spanned(module.mod_token, "Agent module must be public")
108                .to_compile_error();
109            items.push(Item::Verbatim(tokens));
110        }
111    }
112
113    let wasm_exports = agent::generate_wasm_exports(&module.ident);
114
115    // Generate websocket tokens, if websocket == true
116    let wasm_ws_exports = if use_ws {
117        agent::generate_ws_wasm_exports(&module.ident)
118    } else {
119        quote! {}
120    };
121
122    // Generate a new module from the content of the original module
123    let new_module = ItemMod {
124        attrs: module.attrs,
125        vis: module.vis,
126        unsafety: module.unsafety,
127        mod_token: module.mod_token,
128        ident: module.ident,
129        content: Some((brace, items)),
130        semi: module.semi,
131    };
132
133    // Convert into token stream
134    quote! {
135        #new_module
136        #wasm_exports
137        #wasm_ws_exports
138    }
139    .into()
140}
141
142#[proc_macro_derive(State)]
143pub fn derive_contract_state(input: TokenStream) -> TokenStream {
144    let input = parse_macro_input!(input);
145    let output = state::impl_state(input);
146
147    match output {
148        syn::Result::Ok(token_stream) => token_stream,
149        syn::Result::Err(err) => err.to_compile_error(),
150    }
151    .into()
152}
153
154#[proc_macro_derive(NamedSink)]
155pub fn derive_named_sink(input: TokenStream) -> TokenStream {
156    let input = parse_macro_input!(input);
157    let output = sink::impl_named_sink(input);
158
159    match output {
160        syn::Result::Ok(token_stream) => token_stream,
161        syn::Result::Err(err) => err.to_compile_error(),
162    }
163    .into()
164}
165
166#[proc_macro_attribute]
167pub fn action(_attrs: TokenStream, input: TokenStream) -> TokenStream {
168    input
169}
170
171#[proc_macro_attribute]
172pub fn schedule(_attrs: TokenStream, input: TokenStream) -> TokenStream {
173    input
174}