use crate::dsl::const_constraints::ConstConstraint;
use crate::node::{GkNode, NodeMeta, Port, PortType, Slot, Value};
pub struct AssertType {
meta: NodeMeta,
expected: PortType,
}
impl AssertType {
pub fn new(typ: PortType) -> Self {
let name = match typ {
PortType::U64 => "assert_u64",
PortType::F64 => "assert_f64",
PortType::Bool => "assert_bool",
PortType::Str => "assert_str",
PortType::Bytes => "assert_bytes",
PortType::Json => "assert_json",
PortType::U32 => "assert_u32",
PortType::I32 => "assert_i32",
PortType::I64 => "assert_i64",
PortType::F32 => "assert_f32",
PortType::Ext => "assert_ext",
PortType::Handle => "assert_handle",
PortType::VecF32 => "assert_vec_f32",
PortType::VecI32 => "assert_vec_i32",
};
Self {
meta: NodeMeta {
name: name.into(),
outs: vec![Port::new("output", typ)],
ins: vec![Slot::Wire(Port::new("input", typ))],
},
expected: typ,
}
}
pub fn expected(&self) -> PortType {
self.expected
}
}
impl GkNode for AssertType {
fn meta(&self) -> &NodeMeta {
&self.meta
}
fn eval(&self, inputs: &[Value], outputs: &mut [Value]) {
let v = &inputs[0];
if !value_matches(v, self.expected) {
panic!(
"{}: expected runtime value of type {:?}, got {:?}",
self.meta.name, self.expected, v
);
}
outputs[0] = v.clone();
}
}
fn value_matches(v: &Value, typ: PortType) -> bool {
match (v, typ) {
(Value::U64(_), PortType::U64) => true,
(Value::F64(_), PortType::F64) => true,
(Value::Bool(_), PortType::Bool) => true,
(Value::Str(_), PortType::Str) => true,
(Value::Bytes(_), PortType::Bytes) => true,
(Value::Json(_), PortType::Json) => true,
(Value::U64(_), PortType::U32) => true,
(Value::U64(_), PortType::I32) => true,
(Value::U64(_), PortType::I64) => true,
(Value::F64(_), PortType::F32) => true,
(Value::Ext(_), PortType::Ext) => true,
_ => false,
}
}
pub struct AssertValue {
meta: NodeMeta,
typ: PortType,
constraint: ConstConstraint,
}
impl AssertValue {
pub fn new(typ: PortType, constraint: ConstConstraint) -> Self {
let name = match (&typ, &constraint) {
(PortType::U64, ConstConstraint::NonZeroU64) => "assert_u64_nonzero",
(PortType::U64, ConstConstraint::RangeU64 { .. }) => "assert_u64_range",
(PortType::U64, ConstConstraint::AllowedU64(_)) => "assert_u64_allowed",
(PortType::F64, ConstConstraint::RangeF64 { .. }) => "assert_f64_range",
(PortType::Str, ConstConstraint::NonEmptyStr) => "assert_str_non_empty",
(PortType::Str, ConstConstraint::StrParser(_)) => "assert_str_parses",
_ => "assert_value",
};
Self {
meta: NodeMeta {
name: name.into(),
outs: vec![Port::new("output", typ)],
ins: vec![Slot::Wire(Port::new("input", typ))],
},
typ,
constraint,
}
}
pub fn constraint(&self) -> &ConstConstraint {
&self.constraint
}
pub fn port_type(&self) -> PortType {
self.typ
}
}
impl GkNode for AssertValue {
fn meta(&self) -> &NodeMeta {
&self.meta
}
fn eval(&self, inputs: &[Value], outputs: &mut [Value]) {
let arg = match &inputs[0] {
Value::U64(v) => crate::dsl::factory::ConstArg::Int(*v),
Value::F64(v) => crate::dsl::factory::ConstArg::Float(*v),
Value::Str(s) => crate::dsl::factory::ConstArg::Str(s.to_string()),
other => panic!(
"{}: unsupported runtime value variant {:?}",
self.meta.name, other
),
};
if let Err(msg) = self.constraint.check(&arg, "value") {
panic!("{}: {msg}", self.meta.name);
}
outputs[0] = inputs[0].clone();
}
}
pub fn assert_type_node(typ: PortType) -> Box<dyn GkNode> {
Box::new(AssertType::new(typ))
}
pub fn assert_value_node(typ: PortType, constraint: ConstConstraint) -> Box<dyn GkNode> {
Box::new(AssertValue::new(typ, constraint))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn assert_u64_passes_u64_through() {
let node = AssertType::new(PortType::U64);
let mut out = [Value::None];
node.eval(&[Value::U64(42)], &mut out);
assert_eq!(out[0].as_u64(), 42);
}
#[test]
#[should_panic(expected = "expected runtime value of type U64")]
fn assert_u64_panics_on_string() {
let node = AssertType::new(PortType::U64);
let mut out = [Value::None];
node.eval(&[Value::Str("not a number".into())], &mut out);
}
#[test]
fn assert_value_nonzero_passes_nonzero() {
let node = AssertValue::new(PortType::U64, ConstConstraint::NonZeroU64);
let mut out = [Value::None];
node.eval(&[Value::U64(7)], &mut out);
assert_eq!(out[0].as_u64(), 7);
}
#[test]
#[should_panic(expected = "must be non-zero")]
fn assert_value_nonzero_panics_on_zero() {
let node = AssertValue::new(PortType::U64, ConstConstraint::NonZeroU64);
let mut out = [Value::None];
node.eval(&[Value::U64(0)], &mut out);
}
#[test]
fn assert_value_range_f64_passes_unit_interval() {
let node = AssertValue::new(
PortType::F64,
ConstConstraint::RangeF64 { min: 0.0, max: 1.0 },
);
let mut out = [Value::None];
node.eval(&[Value::F64(0.5)], &mut out);
assert_eq!(out[0].as_f64(), 0.5);
}
#[test]
#[should_panic(expected = "must be in [0, 1]")]
fn assert_value_range_f64_panics_on_out_of_range() {
let node = AssertValue::new(
PortType::F64,
ConstConstraint::RangeF64 { min: 0.0, max: 1.0 },
);
let mut out = [Value::None];
node.eval(&[Value::F64(1.5)], &mut out);
}
}