bindgen: snap

This commit is contained in:
nym21
2026-03-16 09:04:10 +01:00
parent c1565c5f42
commit 46d85d397d
7 changed files with 220 additions and 357 deletions

View File

@@ -439,19 +439,6 @@ fn analyze_instance(child_bases: &BTreeMap<String, String>) -> InstanceAnalysis
field_parts.insert(field_name.clone(), relative);
}
// If all field_parts are empty (all children returned the same base),
// use the field keys as suffix discriminators. This handles patterns like
// period windows (all/_4y/_2y/_1y) where children differ by a suffix
// that corresponds to the tree key.
if field_parts.len() > 1 && field_parts.values().all(|v| v.is_empty()) {
return InstanceAnalysis {
base,
field_parts,
is_suffix_mode: true,
has_outlier: false,
};
}
return InstanceAnalysis {
base,
field_parts,

View File

@@ -5,6 +5,14 @@ use crate::{GenericSyntax, LanguageSyntax, to_snake_case};
/// Rust-specific code generation syntax.
pub struct RustSyntax;
/// Escape braces in a template string for use in `format!()`, preserving `{disc}`.
fn escape_rust_format(template: &str) -> String {
template
.replace('{', "{{")
.replace('}', "}}")
.replace("{{disc}}", "{disc}")
}
impl LanguageSyntax for RustSyntax {
fn field_name(&self, name: &str) -> String {
to_snake_case(name)
@@ -67,35 +75,22 @@ impl LanguageSyntax for RustSyntax {
let static_part = template.trim_end_matches("{disc}").trim_end_matches('_');
format!("_m(\"{}\", &disc)", static_part)
} else {
let rust_template = template
.replace("{disc}", "{disc}")
.replace('{', "{{")
.replace('}', "}}")
.replace("{{disc}}", "{disc}");
format!("format!(\"{}\")", rust_template)
format!("format!(\"{}\")", escape_rust_format(template))
}
}
fn template_expr(&self, acc_var: &str, template: &str) -> String {
if template == "{disc}" {
// _m(acc, disc) in Rust
format!("_m(&{}, &disc)", acc_var)
} else if template.is_empty() {
// Identity
acc_var.to_string()
} else if !template.contains("{disc}") {
// Static suffix
format!("_m(&{}, \"{}\")", acc_var, template)
} else {
// Template with disc: _m(&acc, &format!("ratio_{disc}_bps", disc=disc))
let rust_template = template
.replace("{disc}", "{disc}")
.replace('{', "{{")
.replace('}', "}}")
.replace("{{disc}}", "{disc}");
format!(
"_m(&{}, &format!(\"{}\", disc=disc))",
acc_var, rust_template
acc_var,
escape_rust_format(template)
)
}
}

View File

@@ -695,12 +695,27 @@ pub fn generate_structural_patterns(
" \"\"\"Pattern struct for repeated tree structure.\"\"\""
)
.unwrap();
// Skip constructor for non-parameterizable patterns (inlined at tree level)
if !metadata.is_parameterizable(&pattern.name) {
writeln!(output, " pass\n").unwrap();
continue;
}
writeln!(output, " ").unwrap();
writeln!(
output,
" def __init__(self, client: BrkClientBase, acc: str):"
)
.unwrap();
if pattern.is_templated() {
writeln!(
output,
" def __init__(self, client: BrkClientBase, acc: str, disc: str):"
)
.unwrap();
} else {
writeln!(
output,
" def __init__(self, client: BrkClientBase, acc: str):"
)
.unwrap();
}
writeln!(
output,
" \"\"\"Create pattern node with accumulated metric name.\"\"\""

View File

@@ -95,12 +95,27 @@ fn generate_tree_class(
child.name,
GenericSyntax::PYTHON,
);
writeln!(
output,
" self.{}: {} = {}(client, '{}')",
field_name_py, py_type, child.field.rust_type, child.base_result.base
)
.unwrap();
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();
}
}
}

View File

@@ -130,12 +130,7 @@ impl StructuralPattern {
/// Extract the discriminator value by matching a template against a concrete string.
/// E.g., template `"ratio_{disc}_bps"` matched against `"ratio_pct99_bps"` yields `"pct99"`.
fn extract_disc(template: &str, value: &str) -> Option<String> {
let parts: Vec<&str> = template.split("{disc}").collect();
if parts.len() != 2 {
return None;
}
let prefix = parts[0];
let suffix = parts[1];
let (prefix, suffix) = template.split_once("{disc}")?;
if value.starts_with(prefix) && value.ends_with(suffix) {
let disc = &value[prefix.len()..value.len() - suffix.len()];
if !disc.is_empty() {