aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/extensions/buffer.rs
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-20 08:42:16 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-20 08:42:16 +0800
commit7b3a1d7fd9f60b91a7f9602374c081882d46facc (patch)
treedc0c8a9bf095308e6d29091db4b5037c29ed5742 /mingling_macros/src/extensions/buffer.rs
parent9dc737acec1eab40ef65bef8e6c729a4e5e06401 (diff)
feat(macros): add `#[buffer]` attribute and re-export `r_print!(ln)`
macros Reintroduce `r_print!` and `r_println!` macros as public exports, now supporting both explicit buffer argument and implicit `#[buffer]` attr. Add `#[buffer]` attribute macro that wraps unit-returning functions to produce a `RenderResult` with an automatically injected buffer variable. Relax `RenderResult::print()` and `println()` to accept `impl AsRef<str>`.
Diffstat (limited to 'mingling_macros/src/extensions/buffer.rs')
-rw-r--r--mingling_macros/src/extensions/buffer.rs95
1 files changed, 95 insertions, 0 deletions
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()
+}