diff options
Diffstat (limited to 'mingling_pathf/src/patterns')
| -rw-r--r-- | mingling_pathf/src/patterns/dispatcher.rs | 157 |
1 files changed, 118 insertions, 39 deletions
diff --git a/mingling_pathf/src/patterns/dispatcher.rs b/mingling_pathf/src/patterns/dispatcher.rs index c347351..b9f147d 100644 --- a/mingling_pathf/src/patterns/dispatcher.rs +++ b/mingling_pathf/src/patterns/dispatcher.rs @@ -2,14 +2,24 @@ use syn::Item; use crate::pattern_analyzer::{AnalyzeItem, AnalyzePattern}; -/// Matches the `dispatcher!` macro, extracts the entry type name. -/// +/// Matches the `dispatcher!` macro, extracts: +/// - `Entry*` — the entry type (always) +/// - `CMD*` — the dispatcher struct (always) +/// - `__internal_dispatcher_*` — dispatch tree static (when `use_dispatch_tree` is true) +pub struct DispatcherPattern { + pub use_dispatch_tree: bool, +} + +impl DispatcherPattern { + pub fn new(use_dispatch_tree: bool) -> Self { + Self { use_dispatch_tree } + } +} + /// Supported forms: /// - `dispatcher!("greet", CMDGreet => EntryGreet)` — explicit -/// - `dispatcher!("greet")` — implicit, infers EntryType +/// - `dispatcher!("greet")` — implicit, infers names /// - `dispatcher! { ... }` — with braces -pub struct DispatcherPattern; - impl AnalyzePattern for DispatcherPattern { fn contains(&self, content: &str) -> bool { content.contains("dispatcher!") @@ -29,12 +39,7 @@ impl AnalyzePattern for DispatcherPattern { if macro_name != "dispatcher" { continue; } - if let Some(entry_name) = extract_dispatcher_entry(&m.mac.tokens) { - items.push(AnalyzeItem { - module: String::new(), - item_name: entry_name, - }); - } + items.extend(extract_all_types(&m.mac.tokens, "", self.use_dispatch_tree)); } Item::Mod(item_mod) => { if let Some((_, nested)) = &item_mod.content { @@ -43,12 +48,11 @@ impl AnalyzePattern for DispatcherPattern { if macro_simple_name(m) != "dispatcher" { continue; } - if let Some(entry_name) = extract_dispatcher_entry(&m.mac.tokens) { - items.push(AnalyzeItem { - module: item_mod.ident.to_string(), - item_name: entry_name, - }); - } + items.extend(extract_all_types( + &m.mac.tokens, + &item_mod.ident.to_string(), + self.use_dispatch_tree, + )); } } } @@ -70,35 +74,105 @@ fn macro_simple_name(m: &syn::ItemMacro) -> String { .unwrap_or_default() } -/// Extracts the entry type name from the `dispatcher!` macro arguments. -/// -/// Input examples: -/// - `"greet", CMDGreet => EntryGreet` → `EntryGreet` -/// - `"remote.add"` → `EntryRemoteAdd` (implicit inference) -fn extract_dispatcher_entry(tokens: &proc_macro2::TokenStream) -> Option<String> { +/// Extracts all types generated by a `dispatcher!` call. +fn extract_all_types( + tokens: &proc_macro2::TokenStream, + module: &str, + use_dispatch_tree: bool, +) -> Vec<AnalyzeItem> { + let (cmd_name, cmd_struct, entry_struct) = parse_dispatcher_args(tokens); + let cmd_name = match cmd_name { + Some(n) => n, + None => return Vec::new(), + }; + + let mut items = Vec::new(); + + // Entry type — always + if let Some(ref entry) = entry_struct { + items.push(AnalyzeItem { + module: module.to_string(), + item_name: entry.clone(), + }); + } + + // CMD type — always + if let Some(ref cmd) = cmd_struct { + items.push(AnalyzeItem { + module: module.to_string(), + item_name: cmd.clone(), + }); + } + + // __internal_dispatcher_* — when configured + if use_dispatch_tree { + let internal_name = format!("__internal_dispatcher_{}", snake_case(&cmd_name)); + items.push(AnalyzeItem { + module: module.to_string(), + item_name: internal_name, + }); + } + + items +} + +/// Parses dispatcher arguments and returns (command_name, cmd_struct, entry_struct). +fn parse_dispatcher_args( + tokens: &proc_macro2::TokenStream, +) -> (Option<String>, Option<String>, Option<String>) { let stream = tokens.to_string(); - // Explicit form: look for `=>` + // Explicit form: "name", CMDType => EntryType if let Some(arrow_idx) = stream.find("=>") { + // Extract command name + let before_arrow = &stream[..arrow_idx]; + let cmd_name = extract_string_literal(before_arrow); + + // Extract CMD type: the ident before `=>` + let before_arrow_trimmed = before_arrow.trim(); + let cmd_type = before_arrow_trimmed + .split(|c: char| c.is_whitespace() || c == ',') + .filter_map(|s| { + let s = s.trim(); + if s.starts_with('"') || s.is_empty() { + None + } else { + Some(s.to_string()) + } + }) + .next_back(); + + // Extract entry type: after `=>` let after_arrow = stream[arrow_idx + 2..].trim(); - let entry_name = after_arrow + let entry_type = after_arrow .split(|c: char| c.is_whitespace() || c == ',' || c == ')' || c == '}') - .next()?; - if !entry_name.is_empty() { - return Some(entry_name.trim().to_string()); - } - } + .next() + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()); - // Implicit form: infer from command name - let stream = stream.trim(); - if let Some(start) = stream.find('"') { - let rest = &stream[start + 1..]; - let cmd_name = rest.split('"').next()?; - let entry = format!("Entry{}", to_pascal_case(cmd_name)); - Some(entry) - } else { - None + return (cmd_name, cmd_type, entry_type); } + + // Implicit form: "name" + let cmd_name = match extract_string_literal(&stream) { + Some(n) => n, + None => return (None, None, None), + }; + let pascal = to_pascal_case(&cmd_name); + ( + Some(cmd_name), + Some(format!("CMD{pascal}")), + Some(format!("Entry{pascal}")), + ) +} + +/// Extracts the first string literal from a token string. +fn extract_string_literal(s: &str) -> Option<String> { + let s = s.trim(); + let start = s.find('"')?; + let rest = &s[start + 1..]; + let end = rest.find('"')?; + Some(rest[..end].to_string()) } fn to_pascal_case(s: &str) -> String { @@ -113,3 +187,8 @@ fn to_pascal_case(s: &str) -> String { }) .collect() } + +/// Simple snake_case conversion (replaces `.`, `-` with `_`). +fn snake_case(s: &str) -> String { + s.replace(['.', '-'], "_").to_lowercase() +} |
