aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/systems
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-08-04 14:26:14 +0800
committer魏曹先生 <1992414357@qq.com>2026-08-04 14:26:14 +0800
commit5b281566c7ba56d3de1cdc9f0909da6eb32884ab (patch)
tree438f640076c90e0c03c2d1dc38625768d84e380c /mingling_macros/src/systems
parent26ef5d88f36f69bb856aedc7bb50138933d1e036 (diff)
refactor(dispatch_tree): match longest registered prefix in trie
Refactor `build_dispatch_body` to take a caller-provided `no_match` fallback token stream instead of hardcoding `build_entry_fallback` calls. Thread the fallback through recursive calls so child paths are preferred over exact endpoints at the same depth, matching the dynamic dispatcher's longest-prefix-wins behavior.
Diffstat (limited to 'mingling_macros/src/systems')
-rw-r--r--mingling_macros/src/systems/dispatch_tree_gen.rs78
1 files changed, 45 insertions, 33 deletions
diff --git a/mingling_macros/src/systems/dispatch_tree_gen.rs b/mingling_macros/src/systems/dispatch_tree_gen.rs
index 8e3660c..f390b33 100644
--- a/mingling_macros/src/systems/dispatch_tree_gen.rs
+++ b/mingling_macros/src/systems/dispatch_tree_gen.rs
@@ -39,7 +39,13 @@ pub(crate) fn gen_dispatch_args_trie(entries: &[(String, String, String)]) -> To
.map(|(name, disp, _)| (name.replace('.', " "), disp.clone()))
.collect();
- let dispatch_body = build_dispatch_body(&nodes, 0);
+ let dispatch_body = build_dispatch_body(
+ &nodes,
+ 0,
+ &quote! {
+ return Ok(Self::build_entry_fallback(raw.to_vec()));
+ },
+ );
quote! {
fn dispatch_args_trie(
@@ -58,11 +64,19 @@ pub(crate) fn gen_dispatch_args_trie(entries: &[(String, String, String)]) -> To
///
/// `nodes`: slice of (display_name, disp_type) for commands that share the same prefix so far.
/// `depth`: The character index currently being matched.
-fn build_dispatch_body(nodes: &[(String, String)], depth: usize) -> TokenStream {
+/// `no_match`: fallback code to run when no node in this subtree matches the input.
+///
+/// Matching follows the same "longest registered prefix" rule used by the
+/// dynamic dispatcher: a child (longer) path is preferred over an exact
+/// endpoint at the same depth. Only when every descendant fails to match is
+/// the exact endpoint here dispatched.
+fn build_dispatch_body(
+ nodes: &[(String, String)],
+ depth: usize,
+ no_match: &TokenStream,
+) -> TokenStream {
if nodes.is_empty() {
- return quote! {
- return Ok(Self::build_entry_fallback(raw.to_vec()));
- };
+ return no_match.clone();
}
let mut groups: BTreeMap<char, Vec<(String, String)>> = BTreeMap::new();
@@ -102,6 +116,20 @@ fn build_dispatch_body(nodes: &[(String, String)], depth: usize) -> TokenStream
}
};
+ // Fallback code for when neither a child path nor the exact endpoint(s)
+ // here match: run the exact endpoint checks for this node first (they must
+ // win over nothing at all), then pass control back up to the caller.
+ let exact_checks: Vec<TokenStream> = exact_nodes
+ .iter()
+ .map(|(name, disp_type)| make_starts_with_arm(name, disp_type))
+ .collect();
+
+ let level_no_match = {
+ let mut body = exact_checks.clone();
+ body.push(no_match.clone());
+ quote! { #(#body)* }
+ };
+
let mut arms = Vec::new();
for (&ch, sub_nodes) in &groups {
@@ -110,14 +138,16 @@ fn build_dispatch_body(nodes: &[(String, String)], depth: usize) -> TokenStream
if sub_nodes.len() == 1 {
let (name, disp_type) = &sub_nodes[0];
let arm = make_starts_with_arm(name, disp_type);
+ // Try the child first; if it does not match, fall through to the
+ // exact endpoint(s) here so the longer path wins when present.
arms.push(quote! {
Some(#ch_char) => {
#arm
- return Ok(Self::build_entry_fallback(raw.to_vec()));
+ #level_no_match
}
});
} else {
- let sub_body = build_dispatch_body(sub_nodes, depth + 1);
+ let sub_body = build_dispatch_body(sub_nodes, depth + 1, &level_no_match);
arms.push(quote! {
Some(#ch_char) => {
#sub_body
@@ -126,36 +156,18 @@ fn build_dispatch_body(nodes: &[(String, String)], depth: usize) -> TokenStream
}
}
- let exact_checks: Vec<TokenStream> = exact_nodes
- .iter()
- .map(|(name, disp_type)| make_starts_with_arm(name, disp_type))
- .collect();
-
- if !exact_checks.is_empty() && !groups.is_empty() {
- let match_body = quote! {
- match raw_chars.nth(0) {
- #(#arms)*
- _ => return Ok(Self::build_entry_fallback(raw.to_vec())),
- }
- };
- quote! {
- #(#exact_checks)*
- #match_body
- }
- } else if !exact_checks.is_empty() {
- quote! {
- #(#exact_checks)*
- return Ok(Self::build_entry_fallback(raw.to_vec()));
- }
- } else if arms.is_empty() {
- quote! {
- return Ok(Self::build_entry_fallback(raw.to_vec()));
- }
+ if groups.is_empty() {
+ // No children exist for this node; only the exact endpoint(s) apply.
+ let mut body = exact_checks;
+ body.push(no_match.clone());
+ quote! { #(#body)* }
} else {
quote! {
match raw_chars.nth(0) {
#(#arms)*
- _ => return Ok(Self::build_entry_fallback(raw.to_vec())),
+ _ => {
+ #level_no_match
+ }
}
}
}