use std::collections::HashMap;
use crate::scope_kernel::ScopeKernel;
pub fn build_workload_params_kernel(
params: &HashMap<String, String>,
) -> Result<ScopeKernel, String> {
let source = render_workload_params_source(params);
crate::bindings::compile_scope_kernel(&source, &Default::default())
.map_err(|e| format!("workload params kernel: {e}\n--- generated source ---\n{source}"))
}
pub fn render_workload_params_source(params: &HashMap<String, String>) -> String {
if params.is_empty() {
return "const __empty := 0\n".to_string();
}
let mut keys: Vec<&String> = params.keys().collect();
keys.sort();
let mut out = String::new();
for name in keys {
let value = ¶ms[name];
let literal = format_value_as_polydat_literal(value);
out.push_str(&format!("const {name} := {literal}\n"));
}
out
}
fn format_value_as_polydat_literal(value: &str) -> String {
let trimmed = value.trim();
if trimmed.parse::<u64>().is_ok() || trimmed.parse::<f64>().is_ok() {
return trimmed.to_string();
}
if is_polydat_quoted_string(trimmed) || is_polydat_array_literal(trimmed) {
return trimmed.to_string();
}
let escaped = value.replace('\\', "\\\\").replace('"', "\\\"");
format!("\"{escaped}\"")
}
fn is_polydat_quoted_string(s: &str) -> bool {
if s.len() < 2 {
return false;
}
if !s.starts_with('"') || !s.ends_with('"') {
return false;
}
let bytes = s.as_bytes();
let mut i = 1;
let last = bytes.len() - 1;
while i < last {
if bytes[i] == b'\\' {
i += 2;
continue;
}
if bytes[i] == b'"' {
return false;
}
i += 1;
}
true
}
fn is_polydat_array_literal(s: &str) -> bool {
if !s.starts_with('[') || !s.ends_with(']') {
return false;
}
let mut depth: i32 = 0;
let mut in_string = false;
let mut escape = false;
for c in s.chars() {
if escape {
escape = false;
continue;
}
if in_string {
if c == '\\' {
escape = true;
} else if c == '"' {
in_string = false;
}
continue;
}
match c {
'"' => in_string = true,
'[' => depth += 1,
']' => {
depth -= 1;
if depth < 0 {
return false;
}
}
_ => {}
}
}
depth == 0
}
#[cfg(test)]
mod tests {
use super::*;
fn h(pairs: &[(&str, &str)]) -> HashMap<String, String> {
pairs
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
}
#[test]
fn renders_empty_params_as_placeholder() {
let src = render_workload_params_source(&HashMap::new());
assert_eq!(src, "const __empty := 0\n");
}
#[test]
fn renders_typed_params_with_native_literals() {
let src = render_workload_params_source(&h(&[
("dataset", "example"),
("k_values", "1,10"),
("count", "100"),
("ratio", "0.95"),
("strict", "true"),
]));
let expected = "const count := 100\n\
const dataset := \"example\"\n\
const k_values := \"1,10\"\n\
const ratio := 0.95\n\
const strict := \"true\"\n";
assert_eq!(src, expected);
}
#[test]
fn boolean_strings_compile_as_string_consts() {
let kernel = build_workload_params_kernel(&h(&[
("enable_hierarchy", "false"),
("debug_mode", "true"),
]))
.unwrap();
let eh = kernel
.lookup("enable_hierarchy")
.expect("enable_hierarchy must resolve");
let dm = kernel
.lookup("debug_mode")
.expect("debug_mode must resolve");
assert_eq!(eh.to_display_string(), "false");
assert_eq!(dm.to_display_string(), "true");
}
#[test]
fn escapes_quotes_in_string_values() {
let src = render_workload_params_source(&h(&[
("replication", r#"{'class': 'SimpleStrategy'}"#),
("with_quote", r#"a"b"#),
]));
assert!(
src.contains(r#"const replication := "{'class': 'SimpleStrategy'}""#),
"unexpected: {src}"
);
assert!(
src.contains(r#"const with_quote := "a\"b""#),
"embedded double-quote not escaped: {src}"
);
}
#[test]
fn deterministic_ordering_across_runs() {
let p = h(&[("z_last", "1"), ("a_first", "2"), ("m_middle", "3")]);
let s1 = render_workload_params_source(&p);
let s2 = render_workload_params_source(&p);
assert_eq!(s1, s2);
let a_pos = s1.find("a_first").unwrap();
let m_pos = s1.find("m_middle").unwrap();
let z_pos = s1.find("z_last").unwrap();
assert!(a_pos < m_pos && m_pos < z_pos);
}
#[test]
fn compiles_to_valid_kernel() {
let kernel =
build_workload_params_kernel(&h(&[("dataset", "example"), ("count", "100")])).unwrap();
let dataset = kernel.lookup("dataset").expect("dataset must resolve");
let count = kernel.lookup("count").expect("count must resolve");
assert_eq!(dataset.to_display_string(), "example");
assert_eq!(count.as_u64(), 100);
}
#[test]
fn compiles_with_no_params_using_placeholder() {
let kernel = build_workload_params_kernel(&HashMap::new()).unwrap();
let _ = kernel;
}
#[test]
fn boolean_values_emit_quoted_strings() {
let src = render_workload_params_source(&h(&[("flag_t", "true"), ("flag_f", "false")]));
assert!(src.contains("const flag_f := \"false\"\n"));
assert!(src.contains("const flag_t := \"true\"\n"));
}
}