aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_macros/src')
-rw-r--r--mingling_macros/src/extensions.rs3
-rw-r--r--mingling_macros/src/extensions/buffer.rs95
-rw-r--r--mingling_macros/src/func.rs1
-rw-r--r--mingling_macros/src/func/r_print.rs62
-rw-r--r--mingling_macros/src/lib.rs98
5 files changed, 259 insertions, 0 deletions
diff --git a/mingling_macros/src/extensions.rs b/mingling_macros/src/extensions.rs
index aff20e2..022761b 100644
--- a/mingling_macros/src/extensions.rs
+++ b/mingling_macros/src/extensions.rs
@@ -13,6 +13,9 @@ use syn::{Ident, Token};
#[cfg(feature = "extra_macros")]
pub(crate) mod routeify;
+/// Extension: `#[buffer]` — wraps a unit-returning function to return `RenderResult`.
+pub(crate) mod buffer;
+
/// Parsed extensions from an attribute macro like `#[chain(routeify, other_ext)]`.
pub(crate) struct Extensions {
pub(crate) exts: Vec<Ident>,
diff --git a/mingling_macros/src/extensions/buffer.rs b/mingling_macros/src/extensions/buffer.rs
new file mode 100644
index 0000000..27eb612
--- /dev/null
+++ b/mingling_macros/src/extensions/buffer.rs
@@ -0,0 +1,95 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::spanned::Spanned;
+use syn::{ItemFn, ReturnType, Type, parse_macro_input};
+
+/// Checks whether the return type is unit `()`.
+fn is_unit_return_type(sig: &syn::Signature) -> bool {
+ match &sig.output {
+ ReturnType::Type(_, ty) => match &**ty {
+ Type::Tuple(tuple) => tuple.elems.is_empty(),
+ _ => false,
+ },
+ ReturnType::Default => true,
+ }
+}
+
+/// The `#[buffer]` attribute macro.
+///
+/// Wraps a unit-returning function to produce a `::mingling::RenderResult` by
+/// injecting a local `__render_result_buffer` variable. Inside the function
+/// body, the `r_print!` / `r_println!` macros can write into the buffer.
+///
+/// # Example
+///
+/// ```rust,ignore
+/// use mingling::macros::{buffer, r_println};
+///
+/// #[buffer]
+/// fn render_greeting(prev: Greeting) {
+/// r_println!("Hello, {}!", *prev);
+/// }
+/// ```
+///
+/// Expands to:
+///
+/// ```rust,ignore
+/// fn render_greeting(prev: Greeting) -> mingling::RenderResult {
+/// let mut __render_result_buffer = mingling::RenderResult::new();
+/// {
+/// r_println!("Hello, {}!", *prev);
+/// }
+/// __render_result_buffer
+/// }
+/// ```
+pub(crate) fn buffer_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
+ // Reject non-empty attribute arguments; #[buffer] must be bare
+ if !attr.is_empty() {
+ return syn::Error::new(
+ attr.into_iter().next().unwrap().span().into(),
+ "#[buffer] does not accept arguments",
+ )
+ .to_compile_error()
+ .into();
+ }
+
+ // Parse the function item
+ let input_fn = parse_macro_input!(item as ItemFn);
+
+ // Validate the function is not async
+ if input_fn.sig.asyncness.is_some() {
+ return syn::Error::new(input_fn.sig.span(), "Buffer function cannot be async")
+ .to_compile_error()
+ .into();
+ }
+
+ // Validate return type is unit
+ if !is_unit_return_type(&input_fn.sig) {
+ return syn::Error::new(
+ input_fn.sig.span(),
+ "#[buffer] function must not have a return value (must return `()`)",
+ )
+ .to_compile_error()
+ .into();
+ }
+
+ // Get function attributes (excluding the buffer attribute)
+ let mut fn_attrs = input_fn.attrs.clone();
+ fn_attrs.retain(|attr| !attr.path().is_ident("buffer"));
+
+ let vis = &input_fn.vis;
+ let fn_name = &input_fn.sig.ident;
+ let inputs = &input_fn.sig.inputs;
+ let fn_body = &input_fn.block;
+
+ let expanded = quote! {
+ #(#fn_attrs)*
+ #vis fn #fn_name(#inputs) -> ::mingling::RenderResult {
+ let mut __render_result_buffer = ::mingling::RenderResult::new();
+ #fn_body
+ __render_result_buffer
+ }
+ };
+
+ expanded.into()
+}
diff --git a/mingling_macros/src/func.rs b/mingling_macros/src/func.rs
index 720b20a..33bb094 100644
--- a/mingling_macros/src/func.rs
+++ b/mingling_macros/src/func.rs
@@ -8,5 +8,6 @@ pub(crate) mod node;
pub(crate) mod pack;
#[cfg(feature = "extra_macros")]
pub(crate) mod pack_err;
+pub(crate) mod r_print;
#[cfg(feature = "comp")]
pub(crate) mod suggest;
diff --git a/mingling_macros/src/func/r_print.rs b/mingling_macros/src/func/r_print.rs
new file mode 100644
index 0000000..e81b544
--- /dev/null
+++ b/mingling_macros/src/func/r_print.rs
@@ -0,0 +1,62 @@
+use proc_macro::TokenStream;
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use syn::parse::{Parse, ParseStream};
+use syn::{Ident, Token};
+
+/// Parsed input for `r_println!` and `r_print!`.
+///
+/// Two forms:
+/// - `(ident, format_args...)` — explicit buffer
+/// - `(format_args...)` — implicit `__render_result_buffer`
+enum PrintInput {
+ Explicit { dst: Ident, args: TokenStream2 },
+ Implicit { args: TokenStream2 },
+}
+
+impl Parse for PrintInput {
+ fn parse(input: ParseStream) -> syn::Result<Self> {
+ // Peek: if the next token is an ident followed by a comma, it's the explicit form
+ if input.peek(Ident) && input.peek2(Token![,]) {
+ let dst: Ident = input.parse()?;
+ let _comma: Token![,] = input.parse()?;
+ let args: TokenStream2 = input.parse()?;
+ Ok(PrintInput::Explicit { dst, args })
+ } else {
+ let args: TokenStream2 = input.parse()?;
+ Ok(PrintInput::Implicit { args })
+ }
+ }
+}
+
+fn expand_print(input: TokenStream, method: &str) -> TokenStream {
+ let parsed: PrintInput = match syn::parse(input) {
+ Ok(p) => p,
+ Err(e) => return e.to_compile_error().into(),
+ };
+
+ let method_ident = Ident::new(method, proc_macro2::Span::call_site());
+
+ let expanded = match parsed {
+ PrintInput::Explicit { dst, args } => {
+ quote! {
+ #dst.#method_ident(format!(#args))
+ }
+ }
+ PrintInput::Implicit { args } => {
+ quote! {
+ __render_result_buffer.#method_ident(format!(#args))
+ }
+ }
+ };
+
+ expanded.into()
+}
+
+pub(crate) fn r_println(input: TokenStream) -> TokenStream {
+ expand_print(input, "println")
+}
+
+pub(crate) fn r_print(input: TokenStream) -> TokenStream {
+ expand_print(input, "print")
+}
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index 462c6dd..cc18958 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -1282,6 +1282,104 @@ pub fn routeify(attr: TokenStream, item: TokenStream) -> TokenStream {
extensions::routeify::routeify_impl(attr, item)
}
+/// Wraps a unit-returning function to produce a `RenderResult`.
+///
+/// The `#[buffer]` attribute macro injects a local `__render_result_buffer`
+/// variable of type `::mingling::RenderResult` and changes the function's
+/// return type to `::mingling::RenderResult`. Inside the body, use the
+/// `r_print!` and `r_println!` macros to write into the buffer.
+///
+/// # Example
+///
+/// ```rust,ignore
+/// use mingling::macros::{buffer, r_println};
+///
+/// #[buffer]
+/// fn render_my_type(prev: MyType) {
+/// r_println!("Value: {:?}", *prev);
+/// }
+/// ```
+///
+/// This expands to:
+///
+/// ```rust,ignore
+/// fn render_my_type(prev: MyType) -> mingling::RenderResult {
+/// let mut __render_result_buffer = mingling::RenderResult::new();
+/// {
+/// r_println!("Value: {:?}", *prev);
+/// }
+/// __render_result_buffer
+/// }
+/// ```
+///
+/// # Requirements
+///
+/// - The function must return `()` (unit).
+/// - The function cannot be async.
+#[proc_macro_attribute]
+pub fn buffer(attr: TokenStream, item: TokenStream) -> TokenStream {
+ extensions::buffer::buffer_impl(attr, item)
+}
+
+/// Prints text to a `RenderResult` buffer, with a trailing newline.
+///
+/// # Implicit buffer (inside `#[buffer]` functions)
+///
+/// ```rust,ignore
+/// use mingling::macros::{buffer, r_println};
+///
+/// #[buffer]
+/// fn render() {
+/// r_println!("Hello, {}!", name);
+/// }
+/// ```
+///
+/// # Explicit buffer
+///
+/// Pass a `RenderResult` variable as the first argument:
+///
+/// ```rust,ignore
+/// use mingling::macros::r_println;
+/// use mingling::RenderResult;
+///
+/// let mut r = RenderResult::new();
+/// r_println!(r, "value: {}", 42);
+/// assert_eq!(&*r, "value: 42\n");
+/// ```
+#[proc_macro]
+pub fn r_println(input: TokenStream) -> TokenStream {
+ func::r_print::r_println(input)
+}
+
+/// Prints text to a `RenderResult` buffer, without a trailing newline.
+///
+/// # Implicit buffer (inside `#[buffer]` functions)
+///
+/// ```rust,ignore
+/// use mingling::macros::{buffer, r_print};
+///
+/// #[buffer]
+/// fn render() {
+/// r_print!("Hello, ");
+/// r_println!("world!");
+/// }
+/// ```
+///
+/// # Explicit buffer
+///
+/// ```rust,ignore
+/// use mingling::macros::r_print;
+/// use mingling::RenderResult;
+///
+/// let mut r = RenderResult::new();
+/// r_print!(r, "value: {}", 42);
+/// assert_eq!(&*r, "value: 42");
+/// ```
+#[proc_macro]
+pub fn r_print(input: TokenStream) -> TokenStream {
+ func::r_print::r_print(input)
+}
+
/// Derive macro for automatically implementing the `Grouped` trait on a struct.
///
/// The `#[derive(Grouped)]` macro: