diff options
Diffstat (limited to 'mingling_macros/src')
| -rw-r--r-- | mingling_macros/src/extensions.rs | 3 | ||||
| -rw-r--r-- | mingling_macros/src/extensions/buffer.rs | 95 | ||||
| -rw-r--r-- | mingling_macros/src/func.rs | 1 | ||||
| -rw-r--r-- | mingling_macros/src/func/r_print.rs | 62 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 98 |
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: |
