aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/systems/dispatch_tree_gen.rs
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_macros/src/systems/dispatch_tree_gen.rs')
-rw-r--r--mingling_macros/src/systems/dispatch_tree_gen.rs107
1 files changed, 50 insertions, 57 deletions
diff --git a/mingling_macros/src/systems/dispatch_tree_gen.rs b/mingling_macros/src/systems/dispatch_tree_gen.rs
index 7383421..f390b33 100644
--- a/mingling_macros/src/systems/dispatch_tree_gen.rs
+++ b/mingling_macros/src/systems/dispatch_tree_gen.rs
@@ -1,27 +1,22 @@
-use std::collections::{BTreeMap, HashMap};
+use std::collections::BTreeMap;
use just_fmt::snake_case;
use proc_macro2::TokenStream;
use quote::quote;
-use crate::func::gen_program::resolve_type;
-
/// Generate the `get_nodes()` function body for a ProgramCollect impl.
-/// If `pathf_map` is non-empty, resolves internal dispatcher statics using full paths.
-pub(crate) fn gen_get_nodes(
- entries: &[(String, String, String)],
- pathf_map: &HashMap<String, String>,
-) -> TokenStream {
+pub(crate) fn gen_get_nodes(entries: &[(String, String, String)]) -> TokenStream {
let mut node_entries = Vec::new();
for (node_name, _disp_type, _entry_name) in entries {
let static_name_str = format!("__internal_dispatcher_{}", snake_case!(node_name));
- let resolved = resolve_type(&static_name_str, pathf_map);
+ let static_ident =
+ proc_macro2::Ident::new(&static_name_str, proc_macro2::Span::call_site());
let node_display_name = node_name.replace('.', " ");
let node_display_lit = syn::LitStr::new(&node_display_name, proc_macro2::Span::call_site());
node_entries.push(quote! {
- (#node_display_lit.to_string(), & #resolved)
+ (#node_display_lit.to_string(), &#static_ident)
});
}
@@ -38,20 +33,19 @@ pub(crate) fn gen_get_nodes(
///
/// Builds a hardcoded match tree: at each depth, group nodes by character.
/// Single-node groups use `starts_with`; multi-node groups recurse with `nth()` match.
-///
-/// If `pathf_map` is non-empty, resolves dispatcher types using full paths.
-pub(crate) fn gen_dispatch_args_trie(
- entries: &[(String, String, String)],
- pathf_map: &HashMap<String, String>,
-) -> TokenStream {
- // Prepare (display_name, disp_type) pairs.
- // display_name = node_name.replace('.', " ")
+pub(crate) fn gen_dispatch_args_trie(entries: &[(String, String, String)]) -> TokenStream {
let nodes: Vec<(String, String)> = entries
.iter()
.map(|(name, disp, _)| (name.replace('.', " "), disp.clone()))
.collect();
- let dispatch_body = build_dispatch_body(&nodes, 0, pathf_map);
+ let dispatch_body = build_dispatch_body(
+ &nodes,
+ 0,
+ &quote! {
+ return Ok(Self::build_entry_fallback(raw.to_vec()));
+ },
+ );
quote! {
fn dispatch_args_trie(
@@ -70,19 +64,21 @@ pub(crate) fn gen_dispatch_args_trie(
///
/// `nodes`: slice of (display_name, disp_type) for commands that share the same prefix so far.
/// `depth`: The character index currently being matched.
-/// `pathf_map`: optional mapping from type name to full path for resolving dispatchers.
+/// `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,
- pathf_map: &HashMap<String, String>,
+ no_match: &TokenStream,
) -> TokenStream {
if nodes.is_empty() {
- return quote! {
- return Ok(Self::build_dispatcher_not_found(raw.to_vec()));
- };
+ return no_match.clone();
}
- // Group by character at `depth`
let mut groups: BTreeMap<char, Vec<(String, String)>> = BTreeMap::new();
let mut exact_nodes: Vec<(String, String)> = Vec::new();
@@ -97,18 +93,17 @@ fn build_dispatch_body(
}
}
- // Build a dispatch arm for a single node via `starts_with`
let make_starts_with_arm = |name: &str, disp_type: &str| -> TokenStream {
let name_space = format!("{} ", name);
let name_lit = syn::LitStr::new(&name_space, proc_macro2::Span::call_site());
- let disp_resolved = resolve_type(disp_type, pathf_map);
+ let disp_ident = proc_macro2::Ident::new(disp_type, proc_macro2::Span::call_site());
let prefix_word_count = name.split_whitespace().count();
quote! {
if raw_str.starts_with(#name_lit) {
let prefix_len = #prefix_word_count;
let trimmed_args: Vec<String> = raw.iter().skip(prefix_len).cloned().collect();
- let __cp = <#disp_resolved as ::mingling::Dispatcher<Self::Enum>>::begin(
- &#disp_resolved::default(),
+ let __cp = <#disp_ident as ::mingling::Dispatcher<Self::Enum>>::begin(
+ &#disp_ident::default(),
trimmed_args,
);
return match __cp {
@@ -121,6 +116,20 @@ fn build_dispatch_body(
}
};
+ // 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 {
@@ -129,14 +138,16 @@ fn build_dispatch_body(
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_dispatcher_not_found(raw.to_vec()));
+ #level_no_match
}
});
} else {
- let sub_body = build_dispatch_body(sub_nodes, depth + 1, pathf_map);
+ let sub_body = build_dispatch_body(sub_nodes, depth + 1, &level_no_match);
arms.push(quote! {
Some(#ch_char) => {
#sub_body
@@ -145,36 +156,18 @@ fn build_dispatch_body(
}
}
- 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_dispatcher_not_found(raw.to_vec())),
- }
- };
- quote! {
- #(#exact_checks)*
- #match_body
- }
- } else if !exact_checks.is_empty() {
- quote! {
- #(#exact_checks)*
- return Ok(Self::build_dispatcher_not_found(raw.to_vec()));
- }
- } else if arms.is_empty() {
- quote! {
- return Ok(Self::build_dispatcher_not_found(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_dispatcher_not_found(raw.to_vec())),
+ _ => {
+ #level_no_match
+ }
}
}
}