starlang_macros/lib.rs
1//! # starlang-macros
2//!
3//! Ergonomic macros for Starlang.
4//!
5//! This crate provides procedural macros that make working with Starlang
6//! more convenient and Elixir-like.
7//!
8//! # Available Macros
9//!
10//! ## `#[main]`
11//!
12//! Entry point macro that sets up both tokio and the Starlang runtime:
13//!
14//! ```ignore
15//! #[starlang::main]
16//! async fn main(handle: RuntimeHandle) -> Result<(), Box<dyn std::error::Error>> {
17//! // Start your processes here
18//! let pid = handle.spawn(|ctx| async move {
19//! // Process logic
20//! });
21//! Ok(())
22//! }
23//! ```
24//!
25//! ## `receive!`
26//!
27//! Pattern matching on received messages with optional timeout:
28//!
29//! ```ignore
30//! receive! { ctx,
31//! MyMessage::Ping => {
32//! println!("Got ping!");
33//! },
34//! MyMessage::Data(x) => {
35//! println!("Got data: {}", x);
36//! },
37//! after Duration::from_secs(5) => {
38//! println!("Timeout!");
39//! }
40//! }
41//! ```
42//!
43//! ## `#[derive(GenServerImpl)]`
44//!
45//! Derive macro for implementing GenServer boilerplate (future feature).
46
47extern crate proc_macro;
48
49use proc_macro::TokenStream;
50use quote::quote;
51use syn::{parse_macro_input, DeriveInput, ItemFn};
52
53/// Derive macro for GenServer implementation helpers.
54///
55/// This is a placeholder for future functionality. Currently it just
56/// generates an empty impl block.
57///
58/// # Example
59///
60/// ```ignore
61/// use starlang_macros::GenServerImpl;
62///
63/// #[derive(GenServerImpl)]
64/// struct MyServer {
65/// counter: i64,
66/// }
67/// ```
68#[proc_macro_derive(GenServerImpl, attributes(gen_server))]
69pub fn derive_gen_server_impl(input: TokenStream) -> TokenStream {
70 let input = parse_macro_input!(input as DeriveInput);
71 let name = input.ident;
72
73 // For now, just generate an empty marker trait impl
74 let expanded = quote! {
75 impl #name {
76 /// Returns the type name (generated by GenServerImpl derive).
77 pub fn type_name() -> &'static str {
78 stringify!(#name)
79 }
80 }
81 };
82
83 TokenStream::from(expanded)
84}
85
86/// Attribute macro for marking async functions as Starlang processes.
87///
88/// This is a placeholder for future functionality.
89///
90/// # Example
91///
92/// ```ignore
93/// #[starlang_process]
94/// async fn my_worker(ctx: Context) {
95/// // Process logic
96/// }
97/// ```
98#[proc_macro_attribute]
99pub fn starlang_process(_attr: TokenStream, item: TokenStream) -> TokenStream {
100 // For now, just pass through the item unchanged
101 item
102}
103
104/// Returns the current process's PID from task-local storage.
105///
106/// This macro is a convenient shorthand for `starlang::current_pid()`.
107/// It can be called from within any Starlang process without needing
108/// access to the Context.
109///
110/// # Panics
111///
112/// Panics if called outside of a Starlang process context.
113///
114/// # Example
115///
116/// ```ignore
117/// use starlang::prelude::*;
118///
119/// starlang::spawn(|_ctx| async move {
120/// let my_pid = starlang::self_pid!();
121/// println!("My PID is {:?}", my_pid);
122/// });
123/// ```
124#[proc_macro]
125pub fn self_pid(_input: TokenStream) -> TokenStream {
126 quote! {
127 ::starlang::current_pid()
128 }
129 .into()
130}
131
132/// Entry point macro that sets up both tokio and the Starlang runtime.
133///
134/// This macro transforms an async main function into a synchronous main that:
135/// 1. Starts the tokio runtime
136/// 2. Initializes the global Starlang runtime
137/// 3. Runs your async function
138///
139/// After initialization, you can use `starlang::spawn`, `starlang::handle()`, etc.
140/// from anywhere in your code.
141///
142/// # Example
143///
144/// ```ignore
145/// use starlang::prelude::*;
146///
147/// #[starlang::main]
148/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
149/// // Spawn using the global runtime
150/// let pid = starlang::spawn(|ctx| async move {
151/// println!("Hello from Starlang!");
152/// });
153///
154/// // Or get the handle explicitly
155/// let handle = starlang::handle();
156///
157/// // Keep the runtime alive
158/// tokio::signal::ctrl_c().await?;
159/// Ok(())
160/// }
161/// ```
162#[proc_macro_attribute]
163pub fn main(_attr: TokenStream, item: TokenStream) -> TokenStream {
164 let input = parse_macro_input!(item as ItemFn);
165
166 let fn_name = &input.sig.ident;
167 let fn_block = &input.block;
168 let fn_return = &input.sig.output;
169 let fn_vis = &input.vis;
170 let fn_attrs = &input.attrs;
171
172 let expanded = quote! {
173 #(#fn_attrs)*
174 #fn_vis fn #fn_name() #fn_return {
175 let rt = ::tokio::runtime::Runtime::new().expect("Failed to create tokio runtime");
176 rt.block_on(async {
177 // Initialize the global Starlang runtime
178 ::starlang::init();
179 #fn_block
180 })
181 }
182 };
183
184 TokenStream::from(expanded)
185}