diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-08-04 14:26:14 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-08-04 14:26:14 +0800 |
| commit | 5b281566c7ba56d3de1cdc9f0909da6eb32884ab (patch) | |
| tree | 438f640076c90e0c03c2d1dc38625768d84e380c /mingling_macros/src | |
| parent | 26ef5d88f36f69bb856aedc7bb50138933d1e036 (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')
| -rw-r--r-- | mingling_macros/src/systems/dispatch_tree_gen.rs | 78 |
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, + "e! { + 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 + } } } } |
