diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-06-28 09:06:08 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-06-28 09:06:08 +0800 |
| commit | 748c14588cf1c31c8b8d60a9c94349c0173ef607 (patch) | |
| tree | 4c09bfafd93b629a68f0f78902a33e8dd9ef18d1 /mingling_pathf/src/patterns/dispatcher.rs | |
| parent | 50f2d767e2d07685e49fb7deae68d506ea11a79d (diff) | |
feat(pathf): add build-time type path resolution system
Add `mingling_pathf` sub-crate and `pathf` feature for automatic
resolution of Mingling type module paths at build time. Scans source
files, identifies macro invocations via pattern matchers, and generates
mapping files consumed by `gen_program!()`.
Diffstat (limited to 'mingling_pathf/src/patterns/dispatcher.rs')
| -rw-r--r-- | mingling_pathf/src/patterns/dispatcher.rs | 116 |
1 files changed, 116 insertions, 0 deletions
diff --git a/mingling_pathf/src/patterns/dispatcher.rs b/mingling_pathf/src/patterns/dispatcher.rs new file mode 100644 index 0000000..7bb076c --- /dev/null +++ b/mingling_pathf/src/patterns/dispatcher.rs @@ -0,0 +1,116 @@ +use syn::Item; + +use crate::pattern_analyzer::{AnalyzeItem, AnalyzePattern}; + +/// Matches the `dispatcher!` macro, extracts the entry type name. +/// +/// Supported forms: +/// - `dispatcher!("greet", CMDGreet => EntryGreet)` — explicit +/// - `dispatcher!("greet")` — implicit, infers EntryType +/// - `dispatcher! { ... }` — with braces +pub struct DispatcherPattern; + +impl AnalyzePattern for DispatcherPattern { + fn contains(&self, content: &str) -> bool { + content.contains("dispatcher!") + } + + fn analyze(&self, content: &str) -> Vec<AnalyzeItem> { + let Ok(syntax) = syn::parse_file(content) else { + return Vec::new(); + }; + + let mut items = Vec::new(); + + for item in &syntax.items { + match item { + Item::Macro(m) => { + let macro_name = macro_simple_name(m); + 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, + }); + } + } + Item::Mod(item_mod) => { + if let Some((_, nested)) = &item_mod.content { + for n in nested { + if let Item::Macro(m) = n { + 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 + } +} + +fn macro_simple_name(m: &syn::ItemMacro) -> String { + m.mac + .path + .segments + .last() + .map(|s| s.ident.to_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> { + let stream = tokens.to_string(); + + // Explicit form: look for `=>` + if let Some(arrow_idx) = stream.find("=>") { + let after_arrow = stream[arrow_idx + 2..].trim(); + let entry_name = after_arrow + .split(|c: char| c.is_whitespace() || c == ',' || c == ')' || c == '}') + .next()?; + if !entry_name.is_empty() { + return Some(entry_name.trim().to_string()); + } + } + + // 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 last_segment = cmd_name.split('.').next_back()?; + let entry = format!("Entry{}", to_pascal_case(last_segment)); + Some(entry) + } else { + None + } +} + +fn to_pascal_case(s: &str) -> String { + s.split(['-', '_', '.']) + .filter(|s| !s.is_empty()) + .map(|s| { + let mut c = s.chars(); + match c.next() { + None => String::new(), + Some(f) => f.to_uppercase().collect::<String>() + c.as_str(), + } + }) + .collect() +} |
