bindgen: snap

This commit is contained in:
nym21
2026-03-16 09:19:55 +01:00
parent 46d85d397d
commit c5a270aabc
5 changed files with 123 additions and 148 deletions
+35 -42
View File
@@ -43,27 +43,48 @@ pub fn analyze_pattern_modes(
) -> BTreeMap<String, PatternBaseResult> { ) -> BTreeMap<String, PatternBaseResult> {
// Collect analyses from all instances, keyed by pattern name // Collect analyses from all instances, keyed by pattern name
let mut all_analyses: BTreeMap<String, Vec<InstanceAnalysis>> = BTreeMap::new(); let mut all_analyses: BTreeMap<String, Vec<InstanceAnalysis>> = BTreeMap::new();
// Also collect base results for each node, keyed by tree path // Base results for each node, keyed by tree path
let mut node_bases: BTreeMap<String, PatternBaseResult> = BTreeMap::new(); let mut node_bases: BTreeMap<String, PatternBaseResult> = BTreeMap::new();
// Track which tree path belongs to which pattern (avoids re-traversal)
let mut path_to_pattern: BTreeMap<String, String> = BTreeMap::new();
// Bottom-up traversal // Pass 1: bottom-up traversal
collect_instance_analyses(tree, "", pattern_lookup, &mut all_analyses, &mut node_bases); collect_instance_analyses(
tree,
"",
pattern_lookup,
&mut all_analyses,
&mut node_bases,
&mut path_to_pattern,
);
// For each pattern, determine mode from collected instances // Determine initial modes
for pattern in patterns.iter_mut() { for pattern in patterns.iter_mut() {
if let Some(analyses) = all_analyses.get(&pattern.name) { if let Some(analyses) = all_analyses.get(&pattern.name) {
pattern.mode = determine_pattern_mode(analyses, &pattern.fields); pattern.mode = determine_pattern_mode(analyses, &pattern.fields);
} }
} }
// Second pass: fill mixed-empty field_parts now that pattern modes are known. // Pass 2: fill mixed-empty field_parts now that pattern modes are known
fill_mixed_empty_field_parts(tree, "", pattern_lookup, patterns, &mut node_bases); fill_mixed_empty_field_parts(tree, "", pattern_lookup, patterns, &mut node_bases);
// Re-collect analyses from updated node_bases and re-determine pattern modes // Re-determine modes from updated node_bases (no tree re-traversal needed)
let mut all_analyses2: BTreeMap<String, Vec<InstanceAnalysis>> = BTreeMap::new(); let mut updated_analyses: BTreeMap<String, Vec<InstanceAnalysis>> = BTreeMap::new();
collect_updated_analyses(tree, "", pattern_lookup, &node_bases, &mut all_analyses2); for (path, pattern_name) in &path_to_pattern {
if let Some(br) = node_bases.get(path) {
updated_analyses
.entry(pattern_name.clone())
.or_default()
.push(InstanceAnalysis {
base: br.base.clone(),
field_parts: br.field_parts.clone(),
is_suffix_mode: br.is_suffix_mode,
has_outlier: br.has_outlier,
});
}
}
for pattern in patterns.iter_mut() { for pattern in patterns.iter_mut() {
if let Some(analyses) = all_analyses2.get(&pattern.name) { if let Some(analyses) = updated_analyses.get(&pattern.name) {
pattern.mode = determine_pattern_mode(analyses, &pattern.fields); pattern.mode = determine_pattern_mode(analyses, &pattern.fields);
} }
} }
@@ -71,39 +92,6 @@ pub fn analyze_pattern_modes(
node_bases node_bases
} }
/// Re-collect instance analyses from updated node_bases for pattern mode redetermination.
fn collect_updated_analyses(
node: &TreeNode,
path: &str,
pattern_lookup: &BTreeMap<Vec<PatternField>, String>,
node_bases: &BTreeMap<String, PatternBaseResult>,
all_analyses: &mut BTreeMap<String, Vec<InstanceAnalysis>>,
) {
let TreeNode::Branch(children) = node else {
return;
};
for (field_name, child_node) in children {
let child_path = build_child_path(path, field_name);
collect_updated_analyses(child_node, &child_path, pattern_lookup, node_bases, all_analyses);
}
let fields = get_node_fields(children, pattern_lookup);
if let Some(pattern_name) = pattern_lookup.get(&fields) {
if let Some(base_result) = node_bases.get(path) {
all_analyses
.entry(pattern_name.clone())
.or_default()
.push(InstanceAnalysis {
base: base_result.base.clone(),
field_parts: base_result.field_parts.clone(),
is_suffix_mode: base_result.is_suffix_mode,
has_outlier: base_result.has_outlier,
});
}
}
}
/// Second pass: fill empty field_parts for nodes that have a mix of empty and /// Second pass: fill empty field_parts for nodes that have a mix of empty and
/// non-empty parts, using shortest leaf names for children that need disc. /// non-empty parts, using shortest leaf names for children that need disc.
fn fill_mixed_empty_field_parts( fn fill_mixed_empty_field_parts(
@@ -187,6 +175,7 @@ fn collect_instance_analyses(
pattern_lookup: &BTreeMap<Vec<PatternField>, String>, pattern_lookup: &BTreeMap<Vec<PatternField>, String>,
all_analyses: &mut BTreeMap<String, Vec<InstanceAnalysis>>, all_analyses: &mut BTreeMap<String, Vec<InstanceAnalysis>>,
node_bases: &mut BTreeMap<String, PatternBaseResult>, node_bases: &mut BTreeMap<String, PatternBaseResult>,
path_to_pattern: &mut BTreeMap<String, String>,
) -> Option<String> { ) -> Option<String> {
match node { match node {
TreeNode::Leaf(leaf) => { TreeNode::Leaf(leaf) => {
@@ -204,6 +193,7 @@ fn collect_instance_analyses(
pattern_lookup, pattern_lookup,
all_analyses, all_analyses,
node_bases, node_bases,
path_to_pattern,
) { ) {
child_bases.insert(field_name.clone(), base); child_bases.insert(field_name.clone(), base);
} }
@@ -272,6 +262,7 @@ fn collect_instance_analyses(
// Get the pattern name for this node (if any) // Get the pattern name for this node (if any)
let fields = get_node_fields(children, pattern_lookup); let fields = get_node_fields(children, pattern_lookup);
if let Some(pattern_name) = pattern_lookup.get(&fields) { if let Some(pattern_name) = pattern_lookup.get(&fields) {
path_to_pattern.insert(path.to_string(), pattern_name.clone());
all_analyses all_analyses
.entry(pattern_name.clone()) .entry(pattern_name.clone())
.or_default() .or_default()
@@ -1107,6 +1098,7 @@ mod tests {
let mut all_analyses = BTreeMap::new(); let mut all_analyses = BTreeMap::new();
let mut node_bases = BTreeMap::new(); let mut node_bases = BTreeMap::new();
let mut path_to_pattern = BTreeMap::new();
let pattern_lookup = BTreeMap::new(); let pattern_lookup = BTreeMap::new();
collect_instance_analyses( collect_instance_analyses(
@@ -1115,6 +1107,7 @@ mod tests {
&pattern_lookup, &pattern_lookup,
&mut all_analyses, &mut all_analyses,
&mut node_bases, &mut node_bases,
&mut path_to_pattern,
); );
let result = node_bases.get("test").expect("should have node_bases entry"); let result = node_bases.get("test").expect("should have node_bases entry");
+56 -22
View File
@@ -8,7 +8,7 @@ use std::fmt::Write;
use brk_types::MetricLeafWithSchema; use brk_types::MetricLeafWithSchema;
use crate::{ClientMetadata, LanguageSyntax, PatternField, StructuralPattern}; use crate::{ClientMetadata, LanguageSyntax, PatternBaseResult, PatternField, StructuralPattern};
/// Create a path suffix from a name. /// Create a path suffix from a name.
/// Adds `_` prefix only if the name doesn't already start with `_`. /// Adds `_` prefix only if the name doesn't already start with `_`.
@@ -102,10 +102,11 @@ pub fn generate_parameterized_field<S: LanguageSyntax>(
.unwrap(); .unwrap();
} }
/// Generate a tree node field with a specific child node for pattern instance base detection. /// Generate a tree node field using pre-computed base results.
/// ///
/// This is used when generating tree nodes where we need to detect the pattern instance /// Handles pattern fields (both templated and non-templated), leaf fields,
/// base from descendant leaf names. /// and non-pattern branch fields. For templated patterns, extracts the
/// discriminator from the base result's field_parts.
pub fn generate_tree_node_field<S: LanguageSyntax>( pub fn generate_tree_node_field<S: LanguageSyntax>(
output: &mut String, output: &mut String,
syntax: &S, syntax: &S,
@@ -113,36 +114,69 @@ pub fn generate_tree_node_field<S: LanguageSyntax>(
metadata: &ClientMetadata, metadata: &ClientMetadata,
indent: &str, indent: &str,
child_name: &str, child_name: &str,
pattern_base: Option<&str>, client_expr: &str,
base_result: Option<&PatternBaseResult>,
) { ) {
let field_name = syntax.field_name(&field.name); let field_name = syntax.field_name(&field.name);
let type_ann = metadata.field_type_annotation(field, false, None, syntax.generic_syntax()); let type_ann = metadata.field_type_annotation(field, false, None, syntax.generic_syntax());
let value = if metadata.is_pattern_type(&field.rust_type) { let value = if metadata.is_pattern_type(&field.rust_type) {
// Use metric base only for parameterizable patterns let pattern = metadata.find_pattern(&field.rust_type);
let use_base = metadata let use_base = pattern.is_some_and(|p| p.is_parameterizable()) && base_result.is_some();
.find_pattern(&field.rust_type)
.is_some_and(|p| p.is_parameterizable())
&& pattern_base.is_some();
let path_arg = if use_base { if use_base {
syntax.string_literal(pattern_base.unwrap()) let br = base_result.unwrap();
let base_arg = syntax.string_literal(&br.base);
if let Some(pat) = pattern
&& pat.is_templated()
{
let disc = pat
.extract_disc_from_instance(&br.field_parts)
.unwrap_or_default();
let disc_arg = syntax.string_literal(&disc);
format!(
"{}({}, {}, {})",
syntax.constructor_name(&field.rust_type),
client_expr,
base_arg,
disc_arg
)
} else {
format!(
"{}({}, {})",
syntax.constructor_name(&field.rust_type),
client_expr,
base_arg
)
}
} else { } else {
syntax.path_expr("base_path", &path_suffix(child_name)) let path_arg = syntax.path_expr("base_path", &path_suffix(child_name));
}; format!(
syntax.constructor(&field.rust_type, &path_arg) "{}({}, {})",
syntax.constructor_name(&field.rust_type),
client_expr,
path_arg
)
}
} else if let Some(accessor) = metadata.find_index_set_pattern(&field.indexes) { } else if let Some(accessor) = metadata.find_index_set_pattern(&field.indexes) {
// Leaf field - use metric name if provided, else tree path let path_arg = base_result
let path_arg = pattern_base .map(|br| syntax.string_literal(&br.base))
.map(|name| syntax.string_literal(name))
.unwrap_or_else(|| syntax.path_expr("base_path", &path_suffix(child_name))); .unwrap_or_else(|| syntax.path_expr("base_path", &path_suffix(child_name)));
syntax.constructor(&accessor.name, &path_arg) format!(
"{}({}, {})",
syntax.constructor_name(&accessor.name),
client_expr,
path_arg
)
} else if field.is_branch() { } else if field.is_branch() {
// Non-pattern branch - instantiate the nested struct
let path_expr = syntax.path_expr("base_path", &path_suffix(child_name)); let path_expr = syntax.path_expr("base_path", &path_suffix(child_name));
syntax.constructor(&field.rust_type, &path_expr) format!(
"{}({}, {})",
syntax.constructor_name(&field.rust_type),
client_expr,
path_expr
)
} else { } else {
// All metrics must be indexed
panic!( panic!(
"Field '{}' is a leaf with no index accessor. All metrics must be indexed.", "Field '{}' is a leaf with no index accessor. All metrics must be indexed.",
field.name field.name
@@ -7,7 +7,7 @@ use brk_types::TreeNode;
use crate::{ use crate::{
ClientMetadata, Endpoint, GenericSyntax, JavaScriptSyntax, PatternField, build_child_path, ClientMetadata, Endpoint, GenericSyntax, JavaScriptSyntax, PatternField, build_child_path,
generate_leaf_field, prepare_tree_node, to_camel_case, generate_leaf_field, generate_tree_node_field, prepare_tree_node, to_camel_case,
}; };
use super::api::generate_api_methods; use super::api::generate_api_methods;
@@ -220,29 +220,16 @@ fn generate_tree_initializer(
); );
writeln!(output, "{}}},", indent_str).unwrap(); writeln!(output, "{}}},", indent_str).unwrap();
} else { } else {
// Use pattern factory generate_tree_node_field(
let pattern = metadata.find_pattern(&child.field.rust_type); output,
if let Some(pat) = pattern &syntax,
&& pat.is_templated() &child.field,
{ metadata,
// Templated: extract discriminator using the pattern's templates &indent_str,
let disc = pat child.name,
.extract_disc_from_instance(&child.base_result.field_parts) "this",
.unwrap_or_default(); Some(&child.base_result),
writeln!( );
output,
"{}{}: create{}(this, '{}', '{}'),",
indent_str, field_name, child.field.rust_type, child.base_result.base, disc
)
.unwrap();
} else {
writeln!(
output,
"{}{}: create{}(this, '{}'),",
indent_str, field_name, child.field.rust_type, child.base_result.base
)
.unwrap();
}
} }
} }
} }
@@ -6,8 +6,8 @@ use std::fmt::Write;
use brk_types::TreeNode; use brk_types::TreeNode;
use crate::{ use crate::{
ClientMetadata, GenericSyntax, PatternField, PythonSyntax, build_child_path, ClientMetadata, LanguageSyntax, PatternField, PythonSyntax, build_child_path,
escape_python_keyword, generate_leaf_field, prepare_tree_node, to_snake_case, generate_leaf_field, generate_tree_node_field, prepare_tree_node,
}; };
/// Generate tree classes /// Generate tree classes
@@ -70,8 +70,6 @@ fn generate_tree_class(
let syntax = PythonSyntax; let syntax = PythonSyntax;
for child in &ctx.children { for child in &ctx.children {
let field_name_py = escape_python_keyword(&to_snake_case(child.name));
if child.is_leaf { if child.is_leaf {
if let TreeNode::Leaf(leaf) = child.node { if let TreeNode::Leaf(leaf) = child.node {
generate_leaf_field( generate_leaf_field(
@@ -79,43 +77,24 @@ fn generate_tree_class(
); );
} }
} else if child.should_inline { } else if child.should_inline {
// Inline class let field_name = syntax.field_name(child.name);
writeln!( writeln!(
output, output,
" self.{}: {} = {}(client)", " self.{}: {} = {}(client)",
field_name_py, child.inline_type_name, child.inline_type_name field_name, child.inline_type_name, child.inline_type_name
) )
.unwrap(); .unwrap();
} else { } else {
// Use pattern class with metric base generate_tree_node_field(
let py_type = metadata.resolve_tree_field_type( output,
&syntax,
&child.field, &child.field,
child.child_fields.as_deref(), metadata,
name, " ",
child.name, child.name,
GenericSyntax::PYTHON, "client",
Some(&child.base_result),
); );
let pattern = metadata.find_pattern(&child.field.rust_type);
if let Some(pat) = pattern
&& pat.is_templated()
{
let disc = pat
.extract_disc_from_instance(&child.base_result.field_parts)
.unwrap_or_default();
writeln!(
output,
" self.{}: {} = {}(client, '{}', '{}')",
field_name_py, py_type, child.field.rust_type, child.base_result.base, disc
)
.unwrap();
} else {
writeln!(
output,
" self.{}: {} = {}(client, '{}')",
field_name_py, py_type, child.field.rust_type, child.base_result.base
)
.unwrap();
}
} }
} }
+10 -28
View File
@@ -97,34 +97,16 @@ fn generate_tree_node(
) )
.unwrap(); .unwrap();
} else { } else {
// Pattern type - use ::new() constructor generate_tree_node_field(
let pattern = metadata.find_pattern(&child.field.rust_type); output,
if let Some(pat) = pattern &syntax,
&& pat.is_templated() &child.field,
{ metadata,
let disc = pat " ",
.extract_disc_from_instance(&child.base_result.field_parts) child.name,
.unwrap_or_default(); "client.clone()",
writeln!( Some(&child.base_result),
output, );
" {}: {}::new(client.clone(), {}, {}.to_string()),",
field_name,
child.field.rust_type,
syntax.string_literal(&child.base_result.base),
syntax.string_literal(&disc),
)
.unwrap();
} else {
generate_tree_node_field(
output,
&syntax,
&child.field,
metadata,
" ",
child.name,
Some(&child.base_result.base),
);
}
} }
} }