use std::sync::OnceLock;
use crate::scope_kernel::ScopeKernel;
use polydat::ast::{PortType, Value};
use polydat::kernel::WriteError;
fn nmbrs_dirty_debug_enabled() -> bool {
static FLAG: OnceLock<bool> = OnceLock::new();
*FLAG.get_or_init(|| std::env::var("NMBRS_DIRTY_DEBUG").is_ok())
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum WriteOutcome {
Stored,
NoSlot,
TypeMismatch { reason: String },
Coordinate { reason: String },
Const { reason: String },
}
#[derive(Debug)]
pub enum HostWriteError {
Write(WriteError),
Convert {
slot: String,
error: polydat::convert::ConvertError,
},
}
impl std::fmt::Display for HostWriteError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HostWriteError::Write(e) => write!(f, "{e}"),
HostWriteError::Convert { slot, error } => write!(f, "'{slot}': {error}"),
}
}
}
impl From<HostWriteError> for WriteOutcome {
fn from(e: HostWriteError) -> Self {
let reason = e.to_string();
match e {
HostWriteError::Write(WriteError::UnknownWire { .. }) => WriteOutcome::NoSlot,
HostWriteError::Write(WriteError::CoordinateSlot { .. }) => {
WriteOutcome::Coordinate { reason }
}
HostWriteError::Write(WriteError::ConstSlot { .. }) => WriteOutcome::Const { reason },
HostWriteError::Write(WriteError::TypeMismatch { .. })
| HostWriteError::Write(WriteError::FromParent { .. })
| HostWriteError::Convert { .. } => WriteOutcome::TypeMismatch { reason },
}
}
}
pub fn write_input(
kernel: &mut dyn polydat::Kernel,
index: usize,
name: &str,
value: Value,
) -> Result<(), HostWriteError> {
let value = match kernel.input_port_type(name) {
Some(port) => {
polydat::convert::to_port(value, port).map_err(|error| HostWriteError::Convert {
slot: name.to_string(),
error,
})?
}
None => value,
};
kernel
.set_input_at(index, value)
.map_err(HostWriteError::Write)
}
pub trait WireSource: Send + Sync {
fn get(&self, name: &str) -> Option<Value>;
fn names(&self) -> Box<dyn Iterator<Item = String> + '_>;
fn write(&self, _name: &str, _value: Value) -> WriteOutcome {
WriteOutcome::NoSlot
}
fn reset(&self, _name: &str) -> WriteOutcome {
WriteOutcome::NoSlot
}
fn advance(&self, _coord: u64) {}
}
pub struct KernelWires<'a>(pub &'a dyn polydat::Kernel);
impl WireSource for KernelWires<'_> {
fn get(&self, name: &str) -> Option<Value> {
use polydat::kernel::interp::Lookup as _;
polydat::kernel::interp::KernelLookup::new(self.0).lookup(name)
}
fn names(&self) -> Box<dyn Iterator<Item = String> + '_> {
let outputs = self.0.output_names();
let inputs_only: Vec<String> = self
.0
.input_names()
.into_iter()
.filter(|n| !outputs.contains(n))
.collect();
Box::new(outputs.into_iter().chain(inputs_only))
}
}
impl WireSource for ScopeKernel {
fn get(&self, name: &str) -> Option<Value> {
self.lookup(name)
}
fn names(&self) -> Box<dyn Iterator<Item = String> + '_> {
let program = self.program();
let outputs: Vec<String> = program
.output_names()
.iter()
.map(|s| s.to_string())
.collect();
let inputs_only: Vec<String> = program
.input_names()
.iter()
.filter(|n| !outputs.contains(n))
.cloned()
.collect();
Box::new(outputs.into_iter().chain(inputs_only))
}
}
pub trait ProgramKernel {
fn split_program(
&mut self,
) -> (
&mut dyn polydat::Kernel,
std::sync::Arc<polydat::kernel::PolydatProgram>,
);
}
impl ProgramKernel for ScopeKernel {
fn split_program(
&mut self,
) -> (
&mut dyn polydat::Kernel,
std::sync::Arc<polydat::kernel::PolydatProgram>,
) {
let program = self.program().clone();
(self.kernel_mut(), program)
}
}
impl ProgramKernel for polydat::kernel::PolydatKernel {
fn split_program(
&mut self,
) -> (
&mut dyn polydat::Kernel,
std::sync::Arc<polydat::kernel::PolydatProgram>,
) {
let program = self.program().clone();
(self, program)
}
}
pub struct CycleWires<'a> {
kernel: std::sync::Mutex<&'a mut dyn polydat::Kernel>,
program: Option<std::sync::Arc<polydat::kernel::PolydatProgram>>,
readings: std::sync::Mutex<std::collections::HashMap<usize, Value>>,
}
impl<'a> CycleWires<'a> {
pub fn new<K: ProgramKernel + ?Sized>(kernel: &'a mut K) -> Self {
let (kernel, program) = kernel.split_program();
Self::over(kernel, program)
}
pub fn over(
kernel: &'a mut dyn polydat::Kernel,
program: std::sync::Arc<polydat::kernel::PolydatProgram>,
) -> Self {
Self {
kernel: std::sync::Mutex::new(kernel),
program: Some(program),
readings: std::sync::Mutex::new(std::collections::HashMap::new()),
}
}
pub fn of(kernel: &'a mut dyn polydat::Kernel) -> Self {
Self {
kernel: std::sync::Mutex::new(kernel),
program: None,
readings: std::sync::Mutex::new(std::collections::HashMap::new()),
}
}
fn output_index(&self, k: &dyn polydat::Kernel, name: &str) -> Option<usize> {
match &self.program {
Some(p) => p.output_index(name),
None => k.output_index(name),
}
}
fn input_index(&self, k: &dyn polydat::Kernel, name: &str) -> Option<usize> {
match &self.program {
Some(p) => p.find_input(name),
None => k.input_index(name),
}
}
fn forget_readings(&self) {
self.readings
.lock()
.expect("CycleWires readings poisoned")
.clear();
}
}
impl<'a> WireSource for CycleWires<'a> {
fn get(&self, name: &str) -> Option<Value> {
let mut k = self.kernel.lock().expect("CycleWires mutex poisoned");
if let Some(output_idx) = self.output_index(&**k, name) {
let mut readings = self.readings.lock().expect("CycleWires readings poisoned");
let v = readings
.entry(output_idx)
.or_insert_with(|| k.pull_at(output_idx))
.clone();
if nmbrs_dirty_debug_enabled() && name == "query" {
let s = v.to_display_string();
let head: String = s.chars().take(64).collect();
eprintln!("DIRTY: wires.get(query) OUTPUT head=\"{head}\"");
}
return Some(v);
}
let v = {
use polydat::kernel::interp::Lookup as _;
polydat::kernel::interp::KernelLookup::new(&**k).lookup(name)
};
if nmbrs_dirty_debug_enabled() && name == "query" {
let head = v
.as_ref()
.map(|x| x.to_display_string().chars().take(64).collect::<String>())
.unwrap_or_else(|| "<none>".into());
eprintln!("DIRTY: wires.get(query) INPUT/CONST head=\"{head}\"");
}
v
}
fn names(&self) -> Box<dyn Iterator<Item = String> + '_> {
let (outputs, inputs): (Vec<String>, Vec<String>) = match &self.program {
Some(p) => (
p.output_names().iter().map(|s| s.to_string()).collect(),
p.input_names(),
),
None => {
let k = self.kernel.lock().expect("CycleWires mutex poisoned");
(k.output_names(), k.input_names())
}
};
let inputs_only: Vec<String> = inputs
.into_iter()
.filter(|n| !outputs.contains(n))
.collect();
Box::new(outputs.into_iter().chain(inputs_only))
}
fn reset(&self, name: &str) -> WriteOutcome {
let mut k = self.kernel.lock().expect("CycleWires mutex poisoned");
self.forget_readings();
let Some(idx) = self.input_index(&**k, name) else {
return WriteOutcome::NoSlot;
};
let Some(default) = k.input_default_at(idx) else {
return WriteOutcome::NoSlot;
};
match write_input(&mut **k, idx, name, default) {
Ok(()) => WriteOutcome::Stored,
Err(e) => e.into(),
}
}
fn write(&self, name: &str, value: Value) -> WriteOutcome {
let mut k = self.kernel.lock().expect("CycleWires mutex poisoned");
self.forget_readings();
let found = self.input_index(&**k, name);
if std::env::var("NMBRS_DEBUG_WIRES")
.map(|v| v == "1")
.unwrap_or(false)
{
let cells: Vec<String> = k.cells_in_scope().iter().map(|c| c.name.clone()).collect();
let slot_cell = found.map(|idx| k.input_is_cell_bound(idx));
eprintln!(
"WIRES.write name={name} value={value:?} slot_cell_bound={slot_cell:?} cells_in_scope={cells:?}"
);
}
let Some(idx) = found else {
return WriteOutcome::NoSlot;
};
match write_input(&mut **k, idx, name, value) {
Ok(()) => WriteOutcome::Stored,
Err(e) => e.into(),
}
}
fn advance(&self, coord: u64) {
let mut k = self.kernel.lock().expect("CycleWires mutex poisoned");
self.forget_readings();
if k.coord_count() > 0 {
k.set_inputs(&[coord]);
}
}
}
pub struct NullWireSource;
impl WireSource for NullWireSource {
fn get(&self, _name: &str) -> Option<Value> {
None
}
fn names(&self) -> Box<dyn Iterator<Item = String> + '_> {
Box::new(std::iter::empty())
}
}
pub static NULL_WIRES: NullWireSource = NullWireSource;
pub fn substitute_via_wires(template: &str, wires: &dyn WireSource) -> Result<String, String> {
let chars: Vec<char> = template.chars().collect();
let n = chars.len();
let mut out = String::with_capacity(template.len());
let mut i = 0;
while i < n {
if chars[i] == '\\' && i + 1 < n && (chars[i + 1] == '{' || chars[i + 1] == '}') {
out.push(chars[i]);
out.push(chars[i + 1]);
i += 2;
continue;
}
if i + 1 < n && chars[i] == '{' && chars[i + 1] == '{' {
let start = i;
let mut j = i + 2;
while j + 1 < n && !(chars[j] == '}' && chars[j + 1] == '}') {
j += 1;
}
let end = (j + 2).min(n);
out.extend(&chars[start..end]);
i = end;
continue;
}
if chars[i] != '{' {
out.push(chars[i]);
i += 1;
continue;
}
let next_nonspace = chars[i + 1..].iter().find(|c| !c.is_whitespace()).copied();
if matches!(next_nonspace, Some('\'') | Some('"')) {
out.push('{');
i += 1;
continue;
}
let body_start = i + 1;
let mut j = body_start;
let mut depth: u32 = 1;
while j < n {
if chars[j] == '{' {
depth += 1;
}
if chars[j] == '}' {
depth -= 1;
if depth == 0 {
break;
}
}
j += 1;
}
if j >= n {
out.push('{');
i += 1;
continue;
}
let body: String = chars[body_start..j].iter().collect();
let body = body.trim();
let after = j + 1;
if body.is_empty() {
out.push('{');
out.push('}');
i = after;
continue;
}
if body.contains(':') {
return Err(format!(
"qualifier-prefixed bind point `{{{body}}}` is not supported \
at cycle time; only bare `{{name}}` references are answered \
by the dispenser's WireSource"
));
}
let wire_name: std::borrow::Cow<'_, str> = if is_bare_ident(body) {
std::borrow::Cow::Borrowed(body)
} else if is_dotted_ident(body) {
std::borrow::Cow::Owned(body.replace('.', "__"))
} else {
out.push('{');
out.push_str(body);
out.push('}');
i = after;
continue;
};
let body = wire_name.as_ref();
match wires.get(body) {
Some(v) => {
if is_binary_natural(v.port_type()) {
return Err(format!(
"wire `{body}` holds {} which has no text-spliced \
representation suitable for a wire-protocol field. \
Either use the pure-token form (the entire field \
value is exactly `{{{body}}}` with no surrounding \
text) so the adapter binds the typed value via \
its parameter API, or split the field so the \
wire is bound as a typed parameter alongside a \
text template containing placeholders (e.g. CQL \
`?`).",
v.port_type()
));
}
match v.to_display_strict() {
Some(s) => out.push_str(&s),
None => {
return Err(format!(
"unresolved bind point `{{{body}}}`: wire \
`{body}` resolved to `Value::None` (no value \
bound in the dispenser's Polydat context chain). \
Set a workload-param default for `{body}`, \
bind it via `bindings:` / `set:`, or mark \
the bind-point as optional once SRD-74 \
Rule 2 syntax lands."
));
}
}
}
None => {
return Err(format!(
"unresolved bind point `{{{body}}}`: no wire named \
`{body}` in the dispenser's Polydat context"
));
}
}
i = after;
}
Ok(out)
}
pub fn resolve_op_fields_via_wires(
op_fields: &[(String, serde_json::Value)],
wires: &dyn WireSource,
) -> Result<crate::adapter::ResolvedFields, String> {
use polydat::ast::Value;
let mut names = Vec::with_capacity(op_fields.len());
let mut values = Vec::with_capacity(op_fields.len());
for (key, json_value) in op_fields {
names.push(key.clone());
let serde_json::Value::String(s) = json_value else {
values.push(Value::Str(json_value.to_string().into()));
continue;
};
let trimmed = s.trim();
let pure_token = trimmed.starts_with('{')
&& trimmed.ends_with('}')
&& !trimmed.starts_with("{{")
&& trimmed.len() >= 2
&& trimmed[1..trimmed.len() - 1]
.chars()
.all(|c| c != '{' && c != '}');
if pure_token {
let body = trimmed[1..trimmed.len() - 1].trim();
let bare = match body.split_once(':') {
Some((_, n)) => n,
None => body,
};
if is_bare_ident(bare) {
match wires.get(bare) {
Some(v) => {
values.push(v);
continue;
}
None => {
return Err(format!(
"unresolved bind point `{{{bare}}}` in field '{key}': \
no wire named `{bare}` in the dispenser's Polydat context"
));
}
}
}
}
let rendered = substitute_via_wires(s, wires).map_err(|e| format!("field '{key}': {e}"))?;
values.push(Value::Str(rendered.into()));
}
Ok(crate::adapter::ResolvedFields::new(names, values))
}
fn is_binary_natural(t: PortType) -> bool {
matches!(
t,
PortType::VecF32
| PortType::VecI32
| PortType::VecF64
| PortType::VecI64
| PortType::VecF16
| PortType::VecI16
| PortType::Handle
| PortType::Bytes
)
}
fn is_bare_ident(s: &str) -> bool {
let mut chars = s.chars();
match chars.next() {
Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
_ => return false,
}
chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
}
fn is_dotted_ident(s: &str) -> bool {
let segments: Vec<&str> = s.split('.').collect();
segments.len() >= 2 && segments.iter().all(|seg| is_bare_ident(seg))
}
#[cfg(test)]
mod tests {
use super::*;
use polydat::dsl::compile::compile_polydat_interpreter;
#[test]
fn scope_kernel_get_resolves_inputs_and_constants() {
let mut k = crate::scope_kernel::ScopeKernel::compile(
"input cycle: u64\n\
folded := 42\n",
)
.unwrap();
k.set_inputs(&[7]);
let wires: &dyn WireSource = &k;
assert_eq!(wires.get("folded").map(|v| v.as_u64()), Some(42));
assert_eq!(wires.get("cycle").map(|v| v.as_u64()), Some(7));
}
#[test]
fn scope_kernel_get_returns_none_for_pull_only_outputs_in_push_1() {
let mut k = crate::scope_kernel::ScopeKernel::compile(
"input cycle: u64\n\
cyc_dep := hash(cycle)\n",
)
.unwrap();
k.set_inputs(&[7]);
let wires: &dyn WireSource = &k;
assert!(wires.get("cyc_dep").is_none());
}
#[test]
fn scope_kernel_get_returns_none_for_unknown_name() {
let k = crate::scope_kernel::ScopeKernel::compile("input cycle: u64\nx := 1\n").unwrap();
let wires: &dyn WireSource = &k;
assert!(wires.get("not_a_real_name").is_none());
}
#[test]
fn scope_kernel_names_lists_declared_outputs_and_inputs() {
let k =
crate::scope_kernel::ScopeKernel::compile("input cycle: u64\nfolded := 42\n").unwrap();
let wires: &dyn WireSource = &k;
let names: Vec<String> = wires.names().collect();
assert!(
names.iter().any(|n| n == "folded"),
"folded should appear: {names:?}"
);
assert!(
names.iter().any(|n| n == "cycle"),
"cycle should appear: {names:?}"
);
}
#[test]
fn null_wires_returns_none_and_empty() {
let wires: &dyn WireSource = &NULL_WIRES;
assert!(wires.get("anything").is_none());
assert_eq!(wires.names().count(), 0);
}
#[test]
fn cycle_wires_pulls_outputs() {
let mut k = crate::scope_kernel::ScopeKernel::compile(
"input cycle: u64\n\
folded := 42\n\
cyc_dep := hash(cycle)\n",
)
.unwrap();
k.set_inputs(&[7]);
let cw = CycleWires::new(&mut k);
let wires: &dyn WireSource = &cw;
let v = wires.get("cyc_dep").expect("cyc_dep should resolve");
assert!(v.as_u64() != 0, "hash result should be non-zero");
assert_eq!(wires.get("folded").map(|v| v.as_u64()), Some(42));
assert_eq!(wires.get("cycle").map(|v| v.as_u64()), Some(7));
}
#[test]
fn cycle_wires_reset_restores_declared_default() {
let mut k = compile_polydat_interpreter(
"input cycle: u64\nextern pressure: u64 = 7\nx := pressure + 1\n",
)
.unwrap();
let cw = CycleWires::new(&mut k);
let wires: &dyn WireSource = &cw;
assert_eq!(
wires.write("pressure", Value::U64(42)),
WriteOutcome::Stored
);
assert_eq!(wires.get("pressure").map(|v| v.as_u64()), Some(42));
assert_eq!(
wires.reset("pressure"),
WriteOutcome::Stored,
"reset writes the declared default through the typed boundary"
);
assert_eq!(
wires.get("pressure").map(|v| v.as_u64()),
Some(7),
"the wire returns to its declared initial value, not to None/0"
);
assert_eq!(wires.reset("nonesuch"), WriteOutcome::NoSlot);
}
#[test]
fn cycle_wires_returns_none_for_unknown_name() {
let mut k = compile_polydat_interpreter("input cycle: u64\nx := 1\n").unwrap();
let cw = CycleWires::new(&mut k);
let wires: &dyn WireSource = &cw;
assert!(wires.get("not_a_real_name").is_none());
}
#[test]
fn cycle_wires_resolves_phase_binding_lhs_names() {
let mut k = compile_polydat_interpreter(
"input cycle: u64\n\
keyspace := \"baselines\"\n\
table := \"vec_label_00\"\n\
# `pick`-equivalent with constant booleans for testability:\n\
# take the first value when its selector is true.\n\
target_index_table := \"system_views.sai_column_indexes\"\n",
)
.unwrap();
k.set_inputs(&[0]);
let cw = CycleWires::new(&mut k);
let wires: &dyn WireSource = &cw;
assert_eq!(
wires
.get("target_index_table")
.map(|v| v.as_str().to_string()),
Some("system_views.sai_column_indexes".to_string()),
"phase-binding LHS should resolve through CycleWires",
);
assert_eq!(
wires.get("keyspace").map(|v| v.as_str().to_string()),
Some("baselines".to_string()),
);
}
#[test]
fn substitute_via_wires_resolves_bare_names() {
let mut k = compile_polydat_interpreter(
"input cycle: u64\n\
keyspace := \"baselines\"\n\
table := \"vec_label_00\"\n",
)
.unwrap();
let cw = CycleWires::new(&mut k);
let resolved =
substitute_via_wires("SELECT * FROM {keyspace}.{table} WHERE x = 1", &cw).unwrap();
assert_eq!(resolved, "SELECT * FROM baselines.vec_label_00 WHERE x = 1");
}
#[test]
fn substitute_via_wires_passes_through_literal_braces() {
let mut k = compile_polydat_interpreter(
"input cycle: u64\n\
ks := \"baselines\"\n",
)
.unwrap();
let cw = CycleWires::new(&mut k);
let resolved = substitute_via_wires(
"CREATE KEYSPACE {ks} WITH replication = {'class': 'SimpleStrategy'}",
&cw,
)
.unwrap();
assert_eq!(
resolved,
"CREATE KEYSPACE baselines WITH replication = {'class': 'SimpleStrategy'}",
);
}
#[test]
fn substitute_via_wires_rejects_vec_f32_in_text_template() {
use polydat::ast::SliceArc;
struct VecWire;
impl WireSource for VecWire {
fn get(&self, name: &str) -> Option<Value> {
match name {
"id" => Some(Value::U64(7)),
"vec" => Some(Value::VecF32(SliceArc::from_vec(vec![0.1_f32, 0.2, 0.3]))),
_ => None,
}
}
fn names(&self) -> Box<dyn Iterator<Item = String> + '_> {
Box::new(["id", "vec"].iter().map(|s| s.to_string()))
}
}
let err = substitute_via_wires("INSERT ... VALUES ('{id}', {vec})", &VecWire)
.expect_err("VecF32 splice into text template must error");
assert!(
err.contains("vec"),
"diagnostic should name the offending wire: {err}"
);
assert!(
err.contains("vec_f32") || err.contains("VecF32"),
"diagnostic should name the offending type: {err}"
);
assert!(
err.contains("pure-token") || err.contains("typed"),
"diagnostic should point at the fix: {err}"
);
}
#[test]
fn substitute_via_wires_rejects_vec_i32_in_text_template() {
use polydat::ast::SliceArc;
struct VecI32Wire;
impl WireSource for VecI32Wire {
fn get(&self, name: &str) -> Option<Value> {
if name == "vec" {
Some(Value::VecI32(SliceArc::from_vec(vec![1_i32, 2, 3])))
} else {
None
}
}
fn names(&self) -> Box<dyn Iterator<Item = String> + '_> {
Box::new(std::iter::once("vec".to_string()))
}
}
let err = substitute_via_wires("x = {vec}", &VecI32Wire).unwrap_err();
assert!(
err.contains("vec_i32") || err.contains("VecI32"),
"VecI32 should also be rejected: {err}"
);
}
#[test]
fn substitute_via_wires_errors_on_unresolved_name() {
let mut k = compile_polydat_interpreter("input cycle: u64\nx := \"a\"\n").unwrap();
let cw = CycleWires::new(&mut k);
let err = substitute_via_wires("hi {nonexistent}", &cw).unwrap_err();
assert!(
err.contains("nonexistent"),
"diagnostic should name the wire: {err}"
);
assert!(
err.contains("unresolved"),
"diagnostic should call out unresolved: {err}"
);
}
#[test]
fn substitute_via_wires_errors_when_wire_resolves_to_none() {
let mut k = compile_polydat_interpreter(
"input cycle: u64\n\
extern undef: str\n\
const x := \"{undef}\"\n",
)
.unwrap();
let cw = CycleWires::new(&mut k);
let err = substitute_via_wires("source_model='{x}'", &cw).unwrap_err();
assert!(
err.contains("`{x}`"),
"diagnostic should name the bind-point: {err}"
);
assert!(
err.contains("Value::None") || err.contains("no value bound"),
"diagnostic should explain the None resolution: {err}"
);
}
#[test]
fn to_display_strict_returns_none_for_value_none() {
use polydat::ast::Value;
assert_eq!(Value::None.to_display_strict(), None);
assert_eq!(
Value::Str("hello".into()).to_display_strict(),
Some("hello".to_string())
);
assert_eq!(Value::U64(42).to_display_strict(), Some("42".to_string()));
}
#[test]
fn substitute_via_wires_resolves_inside_cql_options_map() {
let mut k = compile_polydat_interpreter(
"input cycle: u64\n\
optimize_for := \"RECALL\"\n\
similarity_function := \"EUCLIDEAN\"\n",
)
.unwrap();
let cw = CycleWires::new(&mut k);
let resolved = substitute_via_wires(
"WITH OPTIONS = {'optimize_for': '{optimize_for}', 'similarity_function': '{similarity_function}'}",
&cw,
).unwrap();
assert_eq!(
resolved,
"WITH OPTIONS = {'optimize_for': 'RECALL', 'similarity_function': 'EUCLIDEAN'}",
"nested `{{name}}` placeholders inside CQL map literals must resolve"
);
}
#[test]
fn substitute_via_wires_errors_on_qualifier_prefix() {
let mut k = compile_polydat_interpreter("input cycle: u64\nx := \"a\"\n").unwrap();
let cw = CycleWires::new(&mut k);
let err = substitute_via_wires("hi {bind:x}", &cw).unwrap_err();
assert!(
err.contains("bind:x"),
"diagnostic should name the qualifier form: {err}"
);
}
#[test]
fn substitute_via_wires_passes_through_inline_expr() {
let mut k = compile_polydat_interpreter("input cycle: u64\nx := \"a\"\n").unwrap();
let cw = CycleWires::new(&mut k);
let resolved = substitute_via_wires("v = {{x + 1}}", &cw).unwrap();
assert_eq!(resolved, "v = {{x + 1}}");
}
#[test]
fn cycle_wires_resolves_iter_var_through_subscope_chain() {
use polydat::ast::Value;
let mut parent = crate::scope_kernel::ScopeKernel::compile(
"input cycle: u64\n\
extern optimize_for: String\n",
)
.unwrap();
let opt_idx = parent
.program()
.find_input("optimize_for")
.expect("optimize_for input slot");
parent
.set_input_at(opt_idx, Value::Str("RECALL".into()))
.expect("write optimize_for");
let child_scope = crate::scope_kernel::ScopeKernel::compile(
"input cycle: u64\n\
extern optimize_for: String\n",
)
.unwrap();
let mut child = child_scope
.bind_under(parent.kernel(), &[])
.expect("the child binds under the parent");
let cw = CycleWires::new(&mut child);
let wires: &dyn WireSource = &cw;
assert_eq!(
wires.get("optimize_for").map(|v| v.as_str().to_string()),
Some("RECALL".to_string()),
"iter-var input populated on parent should propagate \
through binding to child's WireSource",
);
}
#[test]
fn extern_decl_only_produces_input_no_output() {
use polydat::dsl::compile::compile_polydat_interpreter;
let k = compile_polydat_interpreter(
"input cycle: u64\n\
extern optimize_for: String\n",
)
.unwrap();
let outputs: Vec<&str> = k.program().output_names().to_vec();
eprintln!("DBG outputs from `extern optimize_for: String`: {outputs:?}");
}
#[test]
fn cycle_wires_advance_drives_per_row_pulls() {
let mut k = compile_polydat_interpreter(
"input cycle: u64\n\
id := format_u64(cycle, 10)\n",
)
.unwrap();
k.set_inputs(&[0]);
let cw = CycleWires::new(&mut k);
let wires: &dyn WireSource = &cw;
assert_eq!(
wires.get("id").map(|v| v.as_str().to_string()),
Some("0".to_string())
);
wires.advance(42);
assert_eq!(
wires.get("id").map(|v| v.as_str().to_string()),
Some("42".to_string())
);
wires.advance(7);
assert_eq!(
wires.get("id").map(|v| v.as_str().to_string()),
Some("7".to_string())
);
}
#[test]
fn null_wires_advance_is_noop() {
let wires: &dyn WireSource = &NULL_WIRES;
wires.advance(0);
wires.advance(123);
assert!(wires.get("anything").is_none());
}
#[test]
fn cycle_wires_write_lands_on_input_slot_visible_to_get() {
let mut k = compile_polydat_interpreter(
"input cycle: u64\n\
extern count: u64\n\
extern body: Json\n",
)
.unwrap();
k.set_inputs(&[0]);
let cw = CycleWires::new(&mut k);
let wires: &dyn WireSource = &cw;
assert_eq!(wires.write("count", Value::U64(42)), WriteOutcome::Stored);
assert_eq!(wires.get("count").map(|v| v.as_u64()), Some(42));
assert_eq!(wires.write("count", Value::U64(7)), WriteOutcome::Stored);
assert_eq!(wires.get("count").map(|v| v.as_u64()), Some(7));
}
#[test]
fn cycle_wires_write_returns_no_slot_for_unknown_name() {
let mut k = compile_polydat_interpreter("input cycle: u64\nx := 1\n").unwrap();
let cw = CycleWires::new(&mut k);
let wires: &dyn WireSource = &cw;
assert_eq!(wires.write("nope", Value::U64(99)), WriteOutcome::NoSlot);
}
#[test]
fn cycle_wires_write_feeds_eval_cone_through_get() {
let mut k = compile_polydat_interpreter(
"input cycle: u64\n\
extern count: u64\n\
row_count := count\n",
)
.unwrap();
k.set_inputs(&[0]);
let cw = CycleWires::new(&mut k);
let wires: &dyn WireSource = &cw;
assert_eq!(wires.write("count", Value::U64(123)), WriteOutcome::Stored);
assert_eq!(wires.get("row_count").map(|v| v.as_u64()), Some(123));
}
#[test]
fn null_wires_write_returns_no_slot() {
let wires: &dyn WireSource = &NULL_WIRES;
assert_eq!(wires.write("anything", Value::U64(1)), WriteOutcome::NoSlot);
}
#[test]
fn resolve_op_fields_four_case_dispatch() {
let mut k = compile_polydat_interpreter(
"input cycle: u64\n\
table := \"users\"\n\
count := 42\n",
)
.unwrap();
let cw = CycleWires::new(&mut k);
let fields: Vec<(String, serde_json::Value)> = vec![
("limit_num".into(), serde_json::json!(100)),
(
"typed_ref".into(),
serde_json::Value::String("{count}".into()),
),
(
"templated".into(),
serde_json::Value::String("SELECT * FROM {table} WHERE x = 1".into()),
),
(
"literal_stmt".into(),
serde_json::Value::String("SELECT 1".into()),
),
];
let resolved = resolve_op_fields_via_wires(&fields, &cw).expect("four-case dispatch");
assert!(
matches!(resolved.get_value("limit_num"),
Some(polydat::ast::Value::Str(s)) if s.contains("100")),
"case 1: got {:?}",
resolved.get_value("limit_num")
);
assert_eq!(
resolved.get_value("typed_ref").map(|v| v.as_u64()),
Some(42),
"case 2: typed wire ref preserves U64"
);
assert_eq!(
resolved.get_str("templated"),
Some("SELECT * FROM users WHERE x = 1"),
"case 3: text template"
);
assert_eq!(
resolved.get_str("literal_stmt"),
Some("SELECT 1"),
"case 4: bare string literal"
);
}
#[test]
fn cycle_wires_caches_across_repeated_gets() {
let mut k = compile_polydat_interpreter("input cycle: u64\nh := hash(cycle)\n").unwrap();
k.set_inputs(&[42]);
let cw = CycleWires::new(&mut k);
let wires: &dyn WireSource = &cw;
let v1 = wires.get("h").unwrap().as_u64();
let v2 = wires.get("h").unwrap().as_u64();
assert_eq!(v1, v2);
}
}