Skip to main content

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}