use crate::ast::Value;
const PICK_HINT: &str =
"\n hint: did the probe phase that sets these booleans run before \
this phase? Check scenario-tree DFS order or declare a `detect_*` \
phase ahead of consumers.";
#[crate::polydat_node(category = Comparison, variadic_min = 1)]
fn pick(selectors: &[bool], values: &[Value]) -> Value {
let n = selectors.len();
debug_assert_eq!(n, values.len(),
"pick arity mismatch at eval: selectors={} values={}",
n, values.len());
if crate::library::debug_nodes_enabled() {
let sels: Vec<String> = selectors.iter().enumerate()
.map(|(i, s)| format!("b{i}={s}"))
.collect();
let vals: Vec<String> = values.iter().enumerate()
.map(|(i, v)| format!("v{i}={}", v.to_display_string()))
.collect();
crate::library::support::audit::debug(&format!(
"pick: selectors=[{}] values=[{}]",
sels.join(", "),
vals.join(", "),
));
}
let mut matched: Vec<usize> = Vec::new();
for (i, &sel) in selectors.iter().enumerate() {
if sel { matched.push(i); }
}
if matched.is_empty() {
panic!(
"pick: no selector matched (all N={n} booleans false); \
workload author guarantees one of {{b0, …, bN-1}} is \
true at this point{PICK_HINT}"
);
}
if matched.len() > 1 {
let positions: Vec<String> = matched.iter().map(|i| format!("b{i}")).collect();
panic!(
"pick: multiple selectors matched (positions {}); \
selectors must be mutually exclusive{PICK_HINT}",
positions.join(", ")
);
}
let first_pt = values[0].port_type();
for (i, v) in values.iter().enumerate().skip(1) {
let vpt = v.port_type();
if vpt != first_pt {
panic!(
"pick: value v{i} has type {vpt:?} but v0 has type {first_pt:?}; \
all value inputs must share a common type{PICK_HINT}"
);
}
}
values[matched[0]].clone()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::PolydatNode;
fn run(node: &Pick, inputs: Vec<Value>) -> Value {
let mut out = [Value::None];
node.eval(&inputs, &mut out);
out.into_iter().next().unwrap()
}
#[test]
fn pick_true_first_returns_first_value() {
let node = Pick::new(2);
let v = run(
&node,
vec![
Value::Bool(true),
Value::Bool(false),
Value::Str("a".into()),
Value::Str("b".into()),
],
);
assert_eq!(v.as_str(), "a");
}
#[test]
fn pick_true_second_returns_second_value() {
let node = Pick::new(2);
let v = run(
&node,
vec![
Value::Bool(false),
Value::Bool(true),
Value::Str("a".into()),
Value::Str("b".into()),
],
);
assert_eq!(v.as_str(), "b");
}
#[test]
#[should_panic(expected = "pick: no selector matched")]
fn pick_zero_selectors_panics() {
let node = Pick::new(2);
run(
&node,
vec![
Value::Bool(false),
Value::Bool(false),
Value::Str("a".into()),
Value::Str("b".into()),
],
);
}
#[test]
#[should_panic(expected = "pick: multiple selectors matched")]
fn pick_multiple_selectors_panics() {
let node = Pick::new(2);
run(
&node,
vec![
Value::Bool(true),
Value::Bool(true),
Value::Str("a".into()),
Value::Str("b".into()),
],
);
}
#[test]
#[should_panic(expected = "pick: value v1 has type")]
fn pick_mixed_value_types_panics_at_eval() {
let node = Pick::new(2);
run(
&node,
vec![
Value::Bool(true),
Value::Bool(false),
Value::U64(1),
Value::Str("b".into()),
],
);
}
#[test]
fn pick_variadic_n_works_for_2_3_4() {
let node = Pick::new(3);
let v = run(
&node,
vec![
Value::Bool(false),
Value::Bool(false),
Value::Bool(true),
Value::Str("x".into()),
Value::Str("y".into()),
Value::Str("z".into()),
],
);
assert_eq!(v.as_str(), "z");
let node = Pick::new(4);
let v = run(
&node,
vec![
Value::Bool(false),
Value::Bool(true),
Value::Bool(false),
Value::Bool(false),
Value::U64(10),
Value::U64(20),
Value::U64(30),
Value::U64(40),
],
);
assert_eq!(v.as_u64(), 20);
}
#[test]
fn pick_meta_has_correct_slot_count() {
use crate::ast::{PortType, Slot};
let node = Pick::new(3);
assert_eq!(node.meta().ins.len(), 6);
for i in 0..3 {
match &node.meta().ins[i] {
Slot::Wire(p) => assert_eq!(p.typ, PortType::Bool, "selector {i} should be Bool"),
_ => panic!("expected wire slot at {i}"),
}
}
}
}