mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-16 09:49:44 -07:00
binder: snapshot
This commit is contained in:
@@ -336,9 +336,12 @@ fn generate_structural_patterns(
|
||||
|
||||
// Generate JSDoc typedef
|
||||
writeln!(output, "/**").unwrap();
|
||||
if pattern.is_generic {
|
||||
writeln!(output, " * @template T").unwrap();
|
||||
}
|
||||
writeln!(output, " * @typedef {{Object}} {}", pattern.name).unwrap();
|
||||
for field in &pattern.fields {
|
||||
let js_type = field_to_js_type(field, metadata);
|
||||
let js_type = field_to_js_type_generic(field, metadata, pattern.is_generic);
|
||||
writeln!(
|
||||
output,
|
||||
" * @property {{{}}} {}",
|
||||
@@ -488,17 +491,45 @@ fn generate_tree_path_field(
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert pattern field to JavaScript/JSDoc type
|
||||
fn field_to_js_type(field: &PatternField, metadata: &ClientMetadata) -> String {
|
||||
/// Convert pattern field to JavaScript/JSDoc type, with optional generic support
|
||||
fn field_to_js_type_generic(
|
||||
field: &PatternField,
|
||||
metadata: &ClientMetadata,
|
||||
is_generic: bool,
|
||||
) -> String {
|
||||
field_to_js_type_with_generic_value(field, metadata, is_generic, None)
|
||||
}
|
||||
|
||||
/// Convert pattern field to JavaScript/JSDoc type.
|
||||
/// - `is_generic`: If true and field.rust_type is "T", use T in the output
|
||||
/// - `generic_value_type`: For branch fields that reference a generic pattern, this is the concrete type to substitute
|
||||
fn field_to_js_type_with_generic_value(
|
||||
field: &PatternField,
|
||||
metadata: &ClientMetadata,
|
||||
is_generic: bool,
|
||||
generic_value_type: Option<&str>,
|
||||
) -> String {
|
||||
// For generic patterns, use T instead of concrete value type
|
||||
let value_type = if is_generic && field.rust_type == "T" {
|
||||
"T".to_string()
|
||||
} else {
|
||||
field.rust_type.clone()
|
||||
};
|
||||
|
||||
if metadata.is_pattern_type(&field.rust_type) {
|
||||
// Pattern type - use pattern name directly
|
||||
// Check if this pattern is generic and we have a value type
|
||||
if metadata.is_pattern_generic(&field.rust_type) {
|
||||
if let Some(vt) = generic_value_type {
|
||||
return format!("{}<{}>", field.rust_type, vt);
|
||||
}
|
||||
}
|
||||
field.rust_type.clone()
|
||||
} else if let Some(accessor) = metadata.find_index_set_pattern(&field.indexes) {
|
||||
// Leaf with accessor - use rust_type as the generic (e.g., DateIndexAccessor<Height>)
|
||||
format!("{}<{}>", accessor.name, field.rust_type)
|
||||
// Leaf with accessor - use value_type as the generic
|
||||
format!("{}<{}>", accessor.name, value_type)
|
||||
} else {
|
||||
// Leaf - use rust_type as the generic (e.g., MetricNode<Height>)
|
||||
format!("MetricNode<{}>", field.rust_type)
|
||||
// Leaf - use value_type as the generic
|
||||
format!("MetricNode<{}>", value_type)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,8 +564,36 @@ fn generate_tree_typedef(
|
||||
generated: &mut HashSet<String>,
|
||||
) {
|
||||
if let TreeNode::Branch(children) = node {
|
||||
// Build signature
|
||||
let fields = get_node_fields(children, pattern_lookup);
|
||||
// Build signature with child field info for generic pattern lookup
|
||||
let fields_with_child_info: Vec<(PatternField, Option<Vec<PatternField>>)> = children
|
||||
.iter()
|
||||
.map(|(child_name, child_node)| {
|
||||
let (rust_type, json_type, indexes, child_fields) = match child_node {
|
||||
TreeNode::Leaf(leaf) => (
|
||||
leaf.value_type().to_string(),
|
||||
leaf.schema.get("type").and_then(|v| v.as_str()).unwrap_or("object").to_string(),
|
||||
leaf.indexes().clone(),
|
||||
None,
|
||||
),
|
||||
TreeNode::Branch(grandchildren) => {
|
||||
let child_fields = get_node_fields(grandchildren, pattern_lookup);
|
||||
let pattern_name = pattern_lookup
|
||||
.get(&child_fields)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| format!("{}_{}", name, to_pascal_case(child_name)));
|
||||
(pattern_name.clone(), pattern_name, std::collections::BTreeSet::new(), Some(child_fields))
|
||||
}
|
||||
};
|
||||
(PatternField {
|
||||
name: child_name.clone(),
|
||||
rust_type,
|
||||
json_type,
|
||||
indexes,
|
||||
}, child_fields)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let fields: Vec<PatternField> = fields_with_child_info.iter().map(|(f, _)| f.clone()).collect();
|
||||
|
||||
// Skip if this matches a pattern (already generated)
|
||||
if pattern_lookup.contains_key(&fields)
|
||||
@@ -551,8 +610,12 @@ fn generate_tree_typedef(
|
||||
writeln!(output, "/**").unwrap();
|
||||
writeln!(output, " * @typedef {{Object}} {}", name).unwrap();
|
||||
|
||||
for field in &fields {
|
||||
let js_type = field_to_js_type(field, metadata);
|
||||
for (field, child_fields) in &fields_with_child_info {
|
||||
// For generic patterns, extract the value type from child fields
|
||||
let generic_value_type = child_fields.as_ref().and_then(|cf| {
|
||||
metadata.get_generic_value_type(&field.rust_type, cf)
|
||||
});
|
||||
let js_type = field_to_js_type_with_generic_value(field, metadata, false, generic_value_type.as_deref());
|
||||
writeln!(
|
||||
output,
|
||||
" * @property {{{}}} {}",
|
||||
|
||||
@@ -255,7 +255,12 @@ fn generate_structural_patterns(
|
||||
for pattern in patterns {
|
||||
let is_parameterizable = pattern.is_parameterizable();
|
||||
|
||||
writeln!(output, "class {}:", pattern.name).unwrap();
|
||||
// For generic patterns, inherit from Generic[T]
|
||||
if pattern.is_generic {
|
||||
writeln!(output, "class {}(Generic[T]):", pattern.name).unwrap();
|
||||
} else {
|
||||
writeln!(output, "class {}:", pattern.name).unwrap();
|
||||
}
|
||||
writeln!(
|
||||
output,
|
||||
" \"\"\"Pattern struct for repeated tree structure.\"\"\""
|
||||
@@ -298,7 +303,7 @@ fn generate_parameterized_python_field(
|
||||
metadata: &ClientMetadata,
|
||||
) {
|
||||
let field_name = to_snake_case(&field.name);
|
||||
let py_type = field_to_python_type(field, metadata);
|
||||
let py_type = field_to_python_type_generic(field, metadata, pattern.is_generic);
|
||||
|
||||
// For branch fields, pass the accumulated name to nested pattern
|
||||
if metadata.is_pattern_type(&field.rust_type) {
|
||||
@@ -388,15 +393,48 @@ fn generate_tree_path_python_field(
|
||||
|
||||
/// Convert pattern field to Python type annotation
|
||||
fn field_to_python_type(field: &PatternField, metadata: &ClientMetadata) -> String {
|
||||
field_to_python_type_generic(field, metadata, false)
|
||||
}
|
||||
|
||||
/// Convert pattern field to Python type annotation, with optional generic support
|
||||
fn field_to_python_type_generic(
|
||||
field: &PatternField,
|
||||
metadata: &ClientMetadata,
|
||||
is_generic: bool,
|
||||
) -> String {
|
||||
field_to_python_type_with_generic_value(field, metadata, is_generic, None)
|
||||
}
|
||||
|
||||
/// Convert pattern field to Python type annotation.
|
||||
/// - `is_generic`: If true and field.rust_type is "T", use T in the output
|
||||
/// - `generic_value_type`: For branch fields that reference a generic pattern, this is the concrete type to substitute
|
||||
fn field_to_python_type_with_generic_value(
|
||||
field: &PatternField,
|
||||
metadata: &ClientMetadata,
|
||||
is_generic: bool,
|
||||
generic_value_type: Option<&str>,
|
||||
) -> String {
|
||||
// For generic patterns, use T instead of concrete value type
|
||||
let value_type = if is_generic && field.rust_type == "T" {
|
||||
"T".to_string()
|
||||
} else {
|
||||
field.rust_type.clone()
|
||||
};
|
||||
|
||||
if metadata.is_pattern_type(&field.rust_type) {
|
||||
// Pattern type - use pattern name directly
|
||||
// Check if this pattern is generic and we have a value type
|
||||
if metadata.is_pattern_generic(&field.rust_type) {
|
||||
if let Some(vt) = generic_value_type {
|
||||
return format!("{}[{}]", field.rust_type, vt);
|
||||
}
|
||||
}
|
||||
field.rust_type.clone()
|
||||
} else if let Some(accessor) = metadata.find_index_set_pattern(&field.indexes) {
|
||||
// Leaf with accessor - use rust_type as the generic (e.g., DateIndexAccessor[Height])
|
||||
format!("{}[{}]", accessor.name, field.rust_type)
|
||||
// Leaf with accessor - use value_type as the generic
|
||||
format!("{}[{}]", accessor.name, value_type)
|
||||
} else {
|
||||
// Leaf - use rust_type as the generic (e.g., MetricNode[Height])
|
||||
format!("MetricNode[{}]", field.rust_type)
|
||||
// Leaf - use value_type as the generic
|
||||
format!("MetricNode[{}]", value_type)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,8 +469,36 @@ fn generate_tree_class(
|
||||
generated: &mut HashSet<String>,
|
||||
) {
|
||||
if let TreeNode::Branch(children) = node {
|
||||
// Build signature
|
||||
let fields = get_node_fields(children, pattern_lookup);
|
||||
// Build signature with child field info for generic pattern lookup
|
||||
let fields_with_child_info: Vec<(PatternField, Option<Vec<PatternField>>)> = children
|
||||
.iter()
|
||||
.map(|(child_name, child_node)| {
|
||||
let (rust_type, json_type, indexes, child_fields) = match child_node {
|
||||
TreeNode::Leaf(leaf) => (
|
||||
leaf.value_type().to_string(),
|
||||
leaf.schema.get("type").and_then(|v| v.as_str()).unwrap_or("object").to_string(),
|
||||
leaf.indexes().clone(),
|
||||
None,
|
||||
),
|
||||
TreeNode::Branch(grandchildren) => {
|
||||
let child_fields = get_node_fields(grandchildren, pattern_lookup);
|
||||
let pattern_name = pattern_lookup
|
||||
.get(&child_fields)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| format!("{}_{}", name, to_pascal_case(child_name)));
|
||||
(pattern_name.clone(), pattern_name, std::collections::BTreeSet::new(), Some(child_fields))
|
||||
}
|
||||
};
|
||||
(PatternField {
|
||||
name: child_name.clone(),
|
||||
rust_type,
|
||||
json_type,
|
||||
indexes,
|
||||
}, child_fields)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let fields: Vec<PatternField> = fields_with_child_info.iter().map(|(f, _)| f.clone()).collect();
|
||||
|
||||
// Skip if this matches a pattern (already generated)
|
||||
if pattern_lookup.contains_key(&fields)
|
||||
@@ -455,8 +521,12 @@ fn generate_tree_class(
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
for (field, (child_name, child_node)) in fields.iter().zip(children.iter()) {
|
||||
let py_type = field_to_python_type(field, metadata);
|
||||
for ((field, child_fields_opt), (child_name, child_node)) in fields_with_child_info.iter().zip(children.iter()) {
|
||||
// For generic patterns, extract the value type from child fields
|
||||
let generic_value_type = child_fields_opt.as_ref().and_then(|cf| {
|
||||
metadata.get_generic_value_type(&field.rust_type, cf)
|
||||
});
|
||||
let py_type = field_to_python_type_with_generic_value(field, metadata, false, generic_value_type.as_deref());
|
||||
let field_name_py = to_snake_case(&field.name);
|
||||
|
||||
if metadata.is_pattern_type(&field.rust_type) {
|
||||
|
||||
@@ -233,20 +233,21 @@ fn generate_pattern_structs(output: &mut String, patterns: &[StructuralPattern],
|
||||
|
||||
for pattern in patterns {
|
||||
let is_parameterizable = pattern.is_parameterizable();
|
||||
let generic_params = if pattern.is_generic { "<'a, T>" } else { "<'a>" };
|
||||
|
||||
writeln!(output, "/// Pattern struct for repeated tree structure.").unwrap();
|
||||
writeln!(output, "pub struct {}<'a> {{", pattern.name).unwrap();
|
||||
writeln!(output, "pub struct {}{} {{", pattern.name, generic_params).unwrap();
|
||||
|
||||
for field in &pattern.fields {
|
||||
let field_name = to_snake_case(&field.name);
|
||||
let type_annotation = field_to_type_annotation(field, metadata);
|
||||
let type_annotation = field_to_type_annotation_generic(field, metadata, pattern.is_generic);
|
||||
writeln!(output, " pub {}: {},", field_name, type_annotation).unwrap();
|
||||
}
|
||||
|
||||
writeln!(output, "}}\n").unwrap();
|
||||
|
||||
// Generate impl block with constructor
|
||||
writeln!(output, "impl<'a> {}<'a> {{", pattern.name).unwrap();
|
||||
writeln!(output, "impl{} {}{} {{", generic_params, pattern.name, generic_params).unwrap();
|
||||
|
||||
if is_parameterizable {
|
||||
writeln!(output, " /// Create a new pattern node with accumulated metric name.").unwrap();
|
||||
@@ -358,16 +359,45 @@ fn generate_tree_path_rust_field(
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a PatternField to the full type annotation
|
||||
fn field_to_type_annotation(field: &PatternField, metadata: &ClientMetadata) -> String {
|
||||
/// Convert a PatternField to the full type annotation, with optional generic support
|
||||
fn field_to_type_annotation_generic(
|
||||
field: &PatternField,
|
||||
metadata: &ClientMetadata,
|
||||
is_generic: bool,
|
||||
) -> String {
|
||||
field_to_type_annotation_with_generic(field, metadata, is_generic, None)
|
||||
}
|
||||
|
||||
/// Convert a PatternField to the full type annotation.
|
||||
/// - `is_generic`: If true and field.rust_type is "T", use T in the output
|
||||
/// - `generic_value_type`: For branch fields that reference a generic pattern, this is the concrete type to substitute
|
||||
fn field_to_type_annotation_with_generic(
|
||||
field: &PatternField,
|
||||
metadata: &ClientMetadata,
|
||||
is_generic: bool,
|
||||
generic_value_type: Option<&str>,
|
||||
) -> String {
|
||||
// For generic patterns, use T instead of concrete value type
|
||||
let value_type = if is_generic && field.rust_type == "T" {
|
||||
"T".to_string()
|
||||
} else {
|
||||
field.rust_type.clone()
|
||||
};
|
||||
|
||||
if metadata.is_pattern_type(&field.rust_type) {
|
||||
// Check if this pattern is generic and we have a value type
|
||||
if metadata.is_pattern_generic(&field.rust_type) {
|
||||
if let Some(vt) = generic_value_type {
|
||||
return format!("{}<'a, {}>", field.rust_type, vt);
|
||||
}
|
||||
}
|
||||
format!("{}<'a>", field.rust_type)
|
||||
} else if let Some(accessor) = metadata.find_index_set_pattern(&field.indexes) {
|
||||
// Leaf with a reusable accessor pattern
|
||||
format!("{}<'a, {}>", accessor.name, field.rust_type)
|
||||
format!("{}<'a, {}>", accessor.name, value_type)
|
||||
} else {
|
||||
// Leaf with unique index set - use MetricNode directly
|
||||
format!("MetricNode<'a, {}>", field.rust_type)
|
||||
format!("MetricNode<'a, {}>", value_type)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,15 +429,16 @@ fn generate_tree_node(
|
||||
generated: &mut HashSet<String>,
|
||||
) {
|
||||
if let TreeNode::Branch(children) = node {
|
||||
// Build the signature for this node
|
||||
let mut fields: Vec<PatternField> = children
|
||||
// Build the signature for this node, also tracking child fields for generic pattern lookup
|
||||
let mut fields_with_child_info: Vec<(PatternField, Option<Vec<PatternField>>)> = children
|
||||
.iter()
|
||||
.map(|(child_name, child_node)| {
|
||||
let (rust_type, json_type, indexes) = match child_node {
|
||||
let (rust_type, json_type, indexes, child_fields) = match child_node {
|
||||
TreeNode::Leaf(leaf) => (
|
||||
leaf.value_type().to_string(),
|
||||
leaf.schema.get("type").and_then(|v| v.as_str()).unwrap_or("object").to_string(),
|
||||
leaf.indexes().clone(),
|
||||
None,
|
||||
),
|
||||
TreeNode::Branch(grandchildren) => {
|
||||
// Get pattern name for this child
|
||||
@@ -416,18 +447,20 @@ fn generate_tree_node(
|
||||
.get(&child_fields)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| format!("{}_{}", name, to_pascal_case(child_name)));
|
||||
(pattern_name.clone(), pattern_name, std::collections::BTreeSet::new())
|
||||
(pattern_name.clone(), pattern_name, std::collections::BTreeSet::new(), Some(child_fields))
|
||||
}
|
||||
};
|
||||
PatternField {
|
||||
(PatternField {
|
||||
name: child_name.clone(),
|
||||
rust_type,
|
||||
json_type,
|
||||
indexes,
|
||||
}
|
||||
}, child_fields)
|
||||
})
|
||||
.collect();
|
||||
fields.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
fields_with_child_info.sort_by(|a, b| a.0.name.cmp(&b.0.name));
|
||||
|
||||
let fields: Vec<PatternField> = fields_with_child_info.iter().map(|(f, _)| f.clone()).collect();
|
||||
|
||||
// Check if this matches a reusable pattern
|
||||
if let Some(pattern_name) = pattern_lookup.get(&fields) {
|
||||
@@ -447,9 +480,13 @@ fn generate_tree_node(
|
||||
writeln!(output, "/// Catalog tree node.").unwrap();
|
||||
writeln!(output, "pub struct {}<'a> {{", name).unwrap();
|
||||
|
||||
for field in &fields {
|
||||
for (field, child_fields) in &fields_with_child_info {
|
||||
let field_name = to_snake_case(&field.name);
|
||||
let type_annotation = field_to_type_annotation(field, metadata);
|
||||
// For generic patterns, extract the value type from child fields
|
||||
let generic_value_type = child_fields.as_ref().and_then(|cf| {
|
||||
metadata.get_generic_value_type(&field.rust_type, cf)
|
||||
});
|
||||
let type_annotation = field_to_type_annotation_with_generic(field, metadata, false, generic_value_type.as_deref());
|
||||
writeln!(output, " pub {}: {},", field_name, type_annotation).unwrap();
|
||||
}
|
||||
|
||||
|
||||
+154
-12
@@ -28,6 +28,8 @@ pub struct ClientMetadata {
|
||||
pub used_indexes: BTreeSet<Index>,
|
||||
/// Index set patterns - sets of indexes that appear together on metrics
|
||||
pub index_set_patterns: Vec<IndexSetPattern>,
|
||||
/// Maps concrete field signatures to pattern names (includes generic pattern mappings)
|
||||
pub concrete_to_pattern: HashMap<Vec<PatternField>, String>,
|
||||
}
|
||||
|
||||
/// A pattern of indexes that appear together on multiple metrics
|
||||
@@ -48,6 +50,8 @@ pub struct StructuralPattern {
|
||||
pub fields: Vec<PatternField>,
|
||||
/// How each field modifies the accumulated name (field_name -> position)
|
||||
pub field_positions: HashMap<String, FieldNamePosition>,
|
||||
/// If true, all leaf fields use a type parameter T instead of concrete types
|
||||
pub is_generic: bool,
|
||||
}
|
||||
|
||||
impl StructuralPattern {
|
||||
@@ -112,7 +116,7 @@ impl ClientMetadata {
|
||||
/// Extract metadata from brk_query::Vecs
|
||||
pub fn from_vecs(vecs: &Vecs) -> Self {
|
||||
let catalog = vecs.catalog().clone();
|
||||
let structural_patterns = detect_structural_patterns(&catalog);
|
||||
let (structural_patterns, concrete_to_pattern) = detect_structural_patterns(&catalog);
|
||||
let (used_indexes, index_set_patterns) = detect_index_patterns(&catalog);
|
||||
|
||||
ClientMetadata {
|
||||
@@ -120,6 +124,7 @@ impl ClientMetadata {
|
||||
structural_patterns,
|
||||
used_indexes,
|
||||
index_set_patterns,
|
||||
concrete_to_pattern,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,19 +140,47 @@ impl ClientMetadata {
|
||||
self.structural_patterns.iter().any(|p| p.name == type_name)
|
||||
}
|
||||
|
||||
/// Build a lookup map from field signatures to pattern names
|
||||
pub fn pattern_lookup(&self) -> HashMap<Vec<PatternField>, String> {
|
||||
self.structural_patterns
|
||||
/// Find a pattern by name
|
||||
pub fn find_pattern(&self, name: &str) -> Option<&StructuralPattern> {
|
||||
self.structural_patterns.iter().find(|p| p.name == name)
|
||||
}
|
||||
|
||||
/// Check if a pattern is generic
|
||||
pub fn is_pattern_generic(&self, name: &str) -> bool {
|
||||
self.find_pattern(name).map(|p| p.is_generic).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Extract the value type from concrete fields for a generic pattern.
|
||||
/// Returns the first leaf field's rust_type if this pattern is generic.
|
||||
pub fn get_generic_value_type(&self, pattern_name: &str, fields: &[PatternField]) -> Option<String> {
|
||||
if !self.is_pattern_generic(pattern_name) {
|
||||
return None;
|
||||
}
|
||||
// Find first leaf field (has indexes)
|
||||
fields
|
||||
.iter()
|
||||
.map(|p| (p.fields.clone(), p.name.clone()))
|
||||
.collect()
|
||||
.find(|f| !f.indexes.is_empty())
|
||||
.map(|f| f.rust_type.clone())
|
||||
}
|
||||
|
||||
/// Build a lookup map from field signatures to pattern names.
|
||||
/// Includes both generic pattern signatures and concrete signatures.
|
||||
pub fn pattern_lookup(&self) -> HashMap<Vec<PatternField>, String> {
|
||||
// Start with concrete-to-pattern mappings (includes generic pattern concrete signatures)
|
||||
let mut lookup = self.concrete_to_pattern.clone();
|
||||
// Also add the normalized generic signatures
|
||||
for p in &self.structural_patterns {
|
||||
lookup.insert(p.fields.clone(), p.name.clone());
|
||||
}
|
||||
lookup
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect structural patterns in the tree using a bottom-up approach.
|
||||
/// For every branch node, create a signature from its children (sorted field names + types).
|
||||
/// Patterns that appear 2+ times are deduplicated.
|
||||
fn detect_structural_patterns(tree: &TreeNode) -> Vec<StructuralPattern> {
|
||||
/// Returns (patterns, concrete_to_pattern_mapping).
|
||||
fn detect_structural_patterns(tree: &TreeNode) -> (Vec<StructuralPattern>, HashMap<Vec<PatternField>, String>) {
|
||||
// Map from sorted fields signature to pattern name
|
||||
let mut signature_to_pattern: HashMap<Vec<PatternField>, String> = HashMap::new();
|
||||
// Count how many times each signature appears
|
||||
@@ -156,19 +189,42 @@ fn detect_structural_patterns(tree: &TreeNode) -> Vec<StructuralPattern> {
|
||||
// Process tree bottom-up to resolve all branch types
|
||||
resolve_branch_patterns(tree, &mut signature_to_pattern, &mut signature_counts);
|
||||
|
||||
// Build initial list of patterns (only those appearing 2+ times)
|
||||
// First, identify generic patterns by grouping ALL signatures by their normalized form.
|
||||
// Even if each concrete signature appears only once, if 2+ different value types
|
||||
// normalize to the same pattern, we create a generic pattern.
|
||||
let (generic_patterns, generic_mappings) = detect_generic_patterns(&signature_to_pattern);
|
||||
|
||||
// Build non-generic patterns: signatures appearing 2+ times that weren't merged into generics
|
||||
let mut patterns: Vec<StructuralPattern> = signature_to_pattern
|
||||
.iter()
|
||||
.filter(|(sig, _)| signature_counts.get(*sig).copied().unwrap_or(0) >= 2)
|
||||
.filter(|(sig, _)| {
|
||||
signature_counts.get(*sig).copied().unwrap_or(0) >= 2
|
||||
&& !generic_mappings.contains_key(*sig)
|
||||
})
|
||||
.map(|(fields, name)| StructuralPattern {
|
||||
name: name.clone(),
|
||||
fields: fields.clone(),
|
||||
field_positions: HashMap::new(),
|
||||
is_generic: false,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Build lookup for second pass
|
||||
let pattern_lookup: HashMap<Vec<PatternField>, String> = signature_to_pattern;
|
||||
// Add the generic patterns
|
||||
patterns.extend(generic_patterns);
|
||||
|
||||
// Build lookup for second pass - include all concrete signatures
|
||||
let mut pattern_lookup: HashMap<Vec<PatternField>, String> = HashMap::new();
|
||||
// Add non-generic patterns that appear 2+ times
|
||||
for (sig, name) in &signature_to_pattern {
|
||||
if signature_counts.get(sig).copied().unwrap_or(0) >= 2 {
|
||||
pattern_lookup.insert(sig.clone(), name.clone());
|
||||
}
|
||||
}
|
||||
// Add generic mappings (overwrite if there's overlap)
|
||||
pattern_lookup.extend(generic_mappings.clone());
|
||||
|
||||
// Build the concrete_to_pattern map to return
|
||||
let concrete_to_pattern = pattern_lookup.clone();
|
||||
|
||||
// Second pass: analyze field positions by traversing tree instances
|
||||
analyze_pattern_field_positions(tree, &mut patterns, &pattern_lookup);
|
||||
@@ -176,7 +232,93 @@ fn detect_structural_patterns(tree: &TreeNode) -> Vec<StructuralPattern> {
|
||||
// Sort by number of fields descending (larger patterns first)
|
||||
patterns.sort_by(|a, b| b.fields.len().cmp(&a.fields.len()));
|
||||
|
||||
patterns
|
||||
(patterns, concrete_to_pattern)
|
||||
}
|
||||
|
||||
/// Detect generic patterns by grouping all signatures by their normalized form.
|
||||
/// Returns (generic_patterns, concrete_signature -> generic_pattern_name mapping).
|
||||
fn detect_generic_patterns(
|
||||
signature_to_pattern: &HashMap<Vec<PatternField>, String>,
|
||||
) -> (Vec<StructuralPattern>, HashMap<Vec<PatternField>, String>) {
|
||||
// Group signatures by their normalized (generic) form
|
||||
let mut normalized_groups: HashMap<Vec<PatternField>, Vec<(Vec<PatternField>, String)>> = HashMap::new();
|
||||
|
||||
for (fields, name) in signature_to_pattern {
|
||||
if let Some(normalized) = normalize_fields_for_generic(fields) {
|
||||
normalized_groups
|
||||
.entry(normalized)
|
||||
.or_default()
|
||||
.push((fields.clone(), name.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut patterns = Vec::new();
|
||||
let mut mappings: HashMap<Vec<PatternField>, String> = HashMap::new();
|
||||
|
||||
// Create generic patterns for groups with 2+ different concrete signatures
|
||||
for (normalized_fields, group) in normalized_groups {
|
||||
if group.len() >= 2 {
|
||||
// Use the first pattern's name as the generic pattern name
|
||||
let generic_name = group[0].1.clone();
|
||||
|
||||
// Map all concrete signatures to this generic pattern
|
||||
for (concrete_fields, _) in &group {
|
||||
mappings.insert(concrete_fields.clone(), generic_name.clone());
|
||||
}
|
||||
|
||||
patterns.push(StructuralPattern {
|
||||
name: generic_name,
|
||||
fields: normalized_fields,
|
||||
field_positions: HashMap::new(),
|
||||
is_generic: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
(patterns, mappings)
|
||||
}
|
||||
|
||||
/// Normalize fields by replacing concrete value types with "T" for generic matching.
|
||||
/// Returns None if the pattern is not suitable for generics (e.g., mixed value types).
|
||||
fn normalize_fields_for_generic(fields: &[PatternField]) -> Option<Vec<PatternField>> {
|
||||
// Get all leaf field value types
|
||||
let leaf_types: Vec<&str> = fields
|
||||
.iter()
|
||||
.filter(|f| !f.indexes.is_empty()) // Only leaves have indexes
|
||||
.map(|f| f.rust_type.as_str())
|
||||
.collect();
|
||||
|
||||
// Need at least one leaf to be generic
|
||||
if leaf_types.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// All leaves must have the same value type
|
||||
let first_type = leaf_types[0];
|
||||
if !leaf_types.iter().all(|t| *t == first_type) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Create normalized fields with "T" as the value type
|
||||
let normalized: Vec<PatternField> = fields
|
||||
.iter()
|
||||
.map(|f| {
|
||||
if f.indexes.is_empty() {
|
||||
// Branch field - keep as is
|
||||
f.clone()
|
||||
} else {
|
||||
// Leaf field - replace value type with T
|
||||
PatternField {
|
||||
name: f.name.clone(),
|
||||
rust_type: "T".to_string(),
|
||||
json_type: "T".to_string(),
|
||||
indexes: f.indexes.clone(),
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Some(normalized)
|
||||
}
|
||||
|
||||
/// Analyze field positions for all patterns by traversing tree instances.
|
||||
|
||||
Reference in New Issue
Block a user