diff options
Diffstat (limited to 'mingling_macros/src/func')
| -rw-r--r-- | mingling_macros/src/func/dispatcher.rs | 2 | ||||
| -rw-r--r-- | mingling_macros/src/func/gen_program.rs | 112 | ||||
| -rw-r--r-- | mingling_macros/src/func/group.rs | 6 | ||||
| -rw-r--r-- | mingling_macros/src/func/pack.rs | 6 | ||||
| -rw-r--r-- | mingling_macros/src/func/pack_err.rs | 4 | ||||
| -rw-r--r-- | mingling_macros/src/func/r_print.rs | 127 |
6 files changed, 209 insertions, 48 deletions
diff --git a/mingling_macros/src/func/dispatcher.rs b/mingling_macros/src/func/dispatcher.rs index 808df75..a61dd26 100644 --- a/mingling_macros/src/func/dispatcher.rs +++ b/mingling_macros/src/func/dispatcher.rs @@ -135,7 +135,7 @@ pub(crate) fn dispatcher(input: TokenStream) -> TokenStream { } fn begin(&self, args: Vec<String>) -> ::mingling::ChainProcess<#program_type> { use ::mingling::Grouped; - #pack::new(args).to_chain() + ::mingling::Routable::to_chain(#pack::new(args)) } fn clone_dispatcher(&self) -> Box<dyn ::mingling::Dispatcher<#program_type>> { Box::new(#command_struct) diff --git a/mingling_macros/src/func/gen_program.rs b/mingling_macros/src/func/gen_program.rs index 7128a9a..db82504 100644 --- a/mingling_macros/src/func/gen_program.rs +++ b/mingling_macros/src/func/gen_program.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use proc_macro::TokenStream; use quote::quote; use syn::parse_macro_input; @@ -38,35 +40,33 @@ fn parse_entry_pair(entry: &proc_macro2::TokenStream) -> (proc_macro2::Ident, pr } /// Loads the pathf type mapping from `$OUT_DIR/{crate}/type_using.rs`. -/// Always compiled; returns empty map when pathf feature is not enabled. -fn load_pathf_map() -> std::collections::HashMap<String, String> { +/// Always compiled; returns None when pathf feature is not enabled, +/// or when the file does not exist / fails to load. +fn load_pathf_map() -> Option<std::collections::HashMap<String, String>> { if !cfg!(feature = "pathf") { - return std::collections::HashMap::new(); - } - let out_dir = std::env::var("OUT_DIR").ok(); - let crate_name = std::env::var("CARGO_PKG_NAME").ok(); - match (out_dir, crate_name) { - (Some(dir), Some(name)) => { - let path = std::path::Path::new(&dir).join(&name).join("type_using.rs"); - match std::fs::read_to_string(&path) { - Ok(content) => content - .lines() - .filter_map(|line| { - let line = line.trim(); - if let Some(rest) = line.strip_prefix("use ") { - let path = rest.strip_suffix(';').unwrap_or(rest); - if let Some((_mod, type_name)) = path.rsplit_once("::") { - return Some((type_name.to_string(), path.to_string())); - } - } - None - }) - .collect(), - Err(_) => std::collections::HashMap::new(), - } - } - _ => std::collections::HashMap::new(), + return None; } + let out_dir = std::env::var("OUT_DIR").ok()?; + let crate_name = std::env::var("CARGO_PKG_NAME").ok()?; + let path = std::path::Path::new(&out_dir) + .join(&crate_name) + .join("type_using.rs"); + let content = std::fs::read_to_string(&path).ok()?; + Some( + content + .lines() + .filter_map(|line| { + let line = line.trim(); + if let Some(rest) = line.strip_prefix("use ") { + let path = rest.strip_suffix(';').unwrap_or(rest); + if let Some((_mod, type_name)) = path.rsplit_once("::") { + return Some((type_name.to_string(), path.to_string())); + } + } + None + }) + .collect(), + ) } /// Resolves a type name to its full path token stream using the pathf mapping. @@ -138,7 +138,7 @@ pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream { match read_ctx { Ok(ctx) => { let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx); - crate::CompletionSuggest::new((ctx, suggest)).to_render() + ::mingling::Routable::<crate::ThisProgram>::to_render(crate::CompletionSuggest::new((ctx, suggest))) } Err(_) => std::process::exit(1), } @@ -156,7 +156,7 @@ pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream { match read_ctx { Ok(ctx) => { let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx); - crate::CompletionSuggest::new((ctx, suggest)).to_render() + ::mingling::Routable::<crate::ThisProgram>::to_render(crate::CompletionSuggest::new((ctx, suggest))) } Err(_) => std::process::exit(1), } @@ -289,10 +289,34 @@ pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream { .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) .collect(); - let pathf_map: std::collections::HashMap<String, String> = if cfg!(feature = "pathf") { - load_pathf_map() + let pathf_map: Option<HashMap<String, String>> = load_pathf_map(); + + #[cfg(feature = "pathf")] + let pathf_hint: proc_macro2::TokenStream = if pathf_map.is_none() { + quote! { + compile_error!( +"Cannot load type mapping computed by `pathf`. +If not yet configured, execute `mingling::build::analyze_and_build_type_mapping()` in your `build.rs`. + +fn main() { + // Require features: [\"builds\", \"pathf\"] + mingling::build::analyze_and_build_type_mapping().unwrap(); + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\\__ Write to `build.rs` + +} + "); + } } else { - std::collections::HashMap::new() + quote! {} + }; + + #[cfg(not(feature = "pathf"))] + let pathf_hint: proc_macro2::TokenStream = quote! {}; + + let pathf_map: HashMap<String, String> = if cfg!(feature = "pathf") { + pathf_map.unwrap_or_default() + } else { + HashMap::new() }; let pathf_uses: Vec<proc_macro2::TokenStream> = if cfg!(feature = "pathf") { @@ -318,7 +342,7 @@ pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream { ) -> Result<::mingling::RenderResult, ::mingling::error::StructuralRendererSerializeError> { #[allow(unused_imports)] #(#pathf_uses)* - match any.member_id { + match any.member_id() { #(#structural_renderer_tokens)* _ => { // Non-structural types: render ResultEmpty (which implements @@ -384,7 +408,7 @@ pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream { fn do_comp(any: &::mingling::AnyOutput<Self::Enum>, ctx: &::mingling::ShellContext) -> ::mingling::Suggest { #[allow(unused_imports)] #(#pathf_uses)* - match any.member_id { + match any.member_id() { #(#completion_tokens)* _ => ::mingling::Suggest::FileCompletion, } @@ -418,7 +442,7 @@ pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream { }).collect(); quote! { fn render(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { - match any.member_id { + match any.member_id() { #(#render_arms)* _ => ::mingling::RenderResult::default(), } @@ -470,9 +494,9 @@ pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream { fn do_chain( any: ::mingling::AnyOutput<Self::Enum>, ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::mingling::ChainProcess<Self::Enum>> + ::std::marker::Send>> { - match any.member_id { + match any.member_id() { #(#chain_arms_async)* - _ => ::core::panic!("No chain found for type id: {:?}", any.type_id), + _ => ::core::panic!("No chain found for type id: {:?}", any.type_id()), } } } @@ -481,9 +505,9 @@ pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream { fn do_chain( any: ::mingling::AnyOutput<Self::Enum>, ) -> ::mingling::ChainProcess<Self::Enum> { - match any.member_id { + match any.member_id() { #(#chain_arms_sync)* - _ => ::core::panic!("No chain found for type id: {:?}", any.type_id), + _ => ::core::panic!("No chain found for type id: {:?}", any.type_id()), } } } @@ -509,7 +533,9 @@ pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream { }; let expanded = quote! { - #[derive(Debug, PartialEq, Eq, Clone)] + #pathf_hint + + #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[repr(#repr_type)] #[allow(nonstandard_style)] pub enum #name { @@ -543,19 +569,19 @@ pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream { fn render_help(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { #[allow(unused_imports)] #(#pathf_uses)* - match any.member_id { + match any.member_id() { #(#help_tokens)* _ => ::mingling::RenderResult::default(), } } fn has_renderer(any: &::mingling::AnyOutput<Self::Enum>) -> bool { - match any.member_id { + match any.member_id() { #(#renderer_exist_tokens)* _ => false } } fn has_chain(any: &::mingling::AnyOutput<Self::Enum>) -> bool { - match any.member_id { + match any.member_id() { #(#chain_exist_tokens)* _ => false } diff --git a/mingling_macros/src/func/group.rs b/mingling_macros/src/func/group.rs index b865913..edb1fe1 100644 --- a/mingling_macros/src/func/group.rs +++ b/mingling_macros/src/func/group.rs @@ -133,7 +133,11 @@ pub(crate) fn group_macro(input: TokenStream) -> TokenStream { #type_use #alias_use - impl ::mingling::Grouped<__MinglingProgram> for #type_name { + /// SAFETY: This is an internal implementation of the `group!` macro, + /// guaranteeing that the enum value registered by the `register_type!` macro + /// is exactly the same as the actual return value, + /// which can be confirmed via the `Ident` in the `quote!` block. + unsafe impl ::mingling::Grouped<__MinglingProgram> for #type_name { fn member_id() -> __MinglingProgram { __MinglingProgram::#type_name } diff --git a/mingling_macros/src/func/pack.rs b/mingling_macros/src/func/pack.rs index a1a7e6b..7206b8e 100644 --- a/mingling_macros/src/func/pack.rs +++ b/mingling_macros/src/func/pack.rs @@ -138,7 +138,11 @@ pub(crate) fn pack(input: TokenStream) -> TokenStream { } } - impl ::mingling::Grouped<#group_name> for #type_name { + /// SAFETY: This is an internal implementation of the `pack!` macro, + /// guaranteeing that the enum value registered by the `register_type!` macro + /// is exactly the same as the actual return value, + /// which can be confirmed via the `Ident` in the `quote!` block. + unsafe impl ::mingling::Grouped<#group_name> for #type_name { fn member_id() -> #group_name { #group_name::#type_name } diff --git a/mingling_macros/src/func/pack_err.rs b/mingling_macros/src/func/pack_err.rs index 36e550a..2a318bc 100644 --- a/mingling_macros/src/func/pack_err.rs +++ b/mingling_macros/src/func/pack_err.rs @@ -139,8 +139,8 @@ pub(crate) fn pack_err_structural(input: TokenStream) -> TokenStream { .insert(type_name_str); let structural_data = quote! { - impl ::mingling::__private::StructuralDataSealed for #type_name {} - impl ::mingling::__private::StructuralData for #type_name {} + impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> for #type_name {} + impl ::mingling::__private::StructuralData<crate::ThisProgram> for #type_name {} }; // Generate the struct + impls (same as pack_err! but with Serialize derive + sealed) diff --git a/mingling_macros/src/func/r_print.rs b/mingling_macros/src/func/r_print.rs new file mode 100644 index 0000000..86ea52f --- /dev/null +++ b/mingling_macros/src/func/r_print.rs @@ -0,0 +1,127 @@ +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") +} + +pub(crate) fn r_eprintln(input: TokenStream) -> TokenStream { + expand_print(input, "eprintln") +} + +pub(crate) fn r_eprint(input: TokenStream) -> TokenStream { + expand_print(input, "eprint") +} + +/// Parsed input for `r_append!`. +/// +/// Two forms: +/// - `(dst, src)` — explicit buffer and source +/// - `(src)` — implicit `__render_result_buffer` +struct AppendInput { + dst: Option<Ident>, + src: proc_macro2::TokenStream, +} + +impl Parse for AppendInput { + fn parse(input: ParseStream) -> syn::Result<Self> { + if input.peek(Ident) && input.peek2(Token![,]) { + let dst: Ident = input.parse()?; + let _comma: Token![,] = input.parse()?; + let src: TokenStream2 = input.parse()?; + Ok(AppendInput { + dst: Some(dst), + src, + }) + } else { + let src: TokenStream2 = input.parse()?; + Ok(AppendInput { dst: None, src }) + } + } +} + +/// `r_append!` macro: appends the contents of another `RenderResult` to this one. +/// +/// Two forms: +/// - `r_append!(dst, src)` — appends `src` into `dst` +/// - `r_append!(src)` — appends `src` into `__render_result_buffer` +pub(crate) fn r_append(input: TokenStream) -> TokenStream { + let parsed: AppendInput = match syn::parse(input) { + Ok(p) => p, + Err(e) => return e.to_compile_error().into(), + }; + + let dst_ident = parsed.dst.clone(); + let src_tokens = parsed.src; + + let expanded = match dst_ident { + Some(dst) => { + quote! { + #dst.append_other(#src_tokens); + } + } + None => { + quote! { + __render_result_buffer.append_other(#src_tokens); + } + } + }; + + expanded.into() +} |
