aboutsummaryrefslogtreecommitdiff
path: root/mingling_pathf/src/patterns
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-06-29 14:12:24 +0800
committer魏曹先生 <1992414357@qq.com>2026-06-29 14:12:24 +0800
commit2fa18e2190fb3c17892e13cb06d330e707dc05ec (patch)
tree9a8a64565ee98a84c64c8677951df94bf3aa7176 /mingling_pathf/src/patterns
parentfaae53e760743971c43800f6e6bc2fcbaec582b7 (diff)
feat(pathf): add dispatch tree config and pass feature to analyzer
Add `PathfinderConfig` struct to control dispatch tree extraction, and wire `use_dispatch_tree` through `DispatcherPattern`, `init_with_config`, and `analyze_and_build_type_mapping_for`. Expose config and wrapper from `mingling_core` under the `pathf` feature.
Diffstat (limited to 'mingling_pathf/src/patterns')
-rw-r--r--mingling_pathf/src/patterns/dispatcher.rs157
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()
+}