use crate::ast::SlotShape;
use crate::ast::{NodeMeta, PolydatNode, Port, PortType, Slot, Value};
use crate::dsl::const_constraints::ConstConstraint;
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::U8 => "assert_u8",
PortType::I8 => "assert_i8",
PortType::U16 => "assert_u16",
PortType::I16 => "assert_i16",
PortType::F16 => "assert_f16",
PortType::U128 => "assert_u128",
PortType::I128 => "assert_i128",
PortType::Reg128 => "assert_reg128",
PortType::RegI8x16 => "assert_reg_i8x16",
PortType::RegI16x8 => "assert_reg_i16x8",
PortType::RegI32x4 => "assert_reg_i32x4",
PortType::RegI64x2 => "assert_reg_i64x2",
PortType::RegF16x8 => "assert_reg_f16x8",
PortType::RegF32x4 => "assert_reg_f32x4",
PortType::RegF64x2 => "assert_reg_f64x2",
PortType::Ext => "assert_ext",
PortType::Handle => "assert_handle",
PortType::VecF32 => "assert_vec_f32",
PortType::VecI32 => "assert_vec_i32",
PortType::VecF64 => "assert_vec_f64",
PortType::VecI64 => "assert_vec_i64",
PortType::VecF16 => "assert_vec_f16",
PortType::VecI16 => "assert_vec_i16",
PortType::VecI8 => "assert_vec_i8",
};
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 PolydatNode 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 compiled_u64(&self) -> Option<crate::ast::CompiledU64Op> {
if self.expected.slot_color() == crate::ast::SlotColor::Ref2 {
return None;
}
Some(Box::new(|inputs: &[u64], outputs: &mut [u64]| {
outputs.copy_from_slice(inputs)
}))
}
fn compiled_slot(&self, _wire_types: &[PortType]) -> Option<crate::ast::CompiledSlotKit> {
crate::compile::assembly::ref_copy_kit(self.expected)
}
}
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::U64(_), PortType::U8 | PortType::U16) => true,
(Value::U64(_), PortType::F16) => true,
(Value::F64(_), PortType::F16) => true,
(Value::I64(_), PortType::I64 | PortType::I32 | PortType::I8 | PortType::I16) => true,
(Value::U64(_), PortType::I8 | PortType::I16) => true,
(Value::U128(_), PortType::U128) => true,
(Value::I128(_), PortType::I128) => true,
(
Value::Reg128(_, _),
PortType::Reg128
| PortType::RegI8x16
| PortType::RegI16x8
| PortType::RegI32x4
| PortType::RegI64x2
| PortType::RegF16x8
| PortType::RegF32x4
| PortType::RegF64x2,
) => 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 PolydatNode 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();
}
fn compiled_u64(&self) -> Option<crate::ast::CompiledU64Op> {
use crate::dsl::factory::ConstArg;
let lift: fn(u64) -> ConstArg = match self.typ {
PortType::U64 | PortType::U32 | PortType::U16 | PortType::U8 => ConstArg::Int,
PortType::F64 => |slot| ConstArg::Float(f64::from_bits(slot)),
_ => return None,
};
let name = self.meta.name.clone();
let constraint = self.constraint;
Some(Box::new(move |inputs: &[u64], outputs: &mut [u64]| {
if let Err(msg) = constraint.check(&lift(inputs[0]), "value") {
panic!("{name}: {msg}");
}
outputs[0] = inputs[0];
}))
}
fn compiled_slot(&self, _wire_types: &[PortType]) -> Option<crate::ast::CompiledSlotKit> {
use crate::dsl::factory::ConstArg;
if self.typ != PortType::Str {
return None;
}
let name = self.meta.name.clone();
let constraint = self.constraint;
let copy = crate::compile::assembly::ref_copy_kit(PortType::Str)?;
Some(crate::ast::CompiledSlotKit {
scratch: copy.scratch,
op: Box::new(
move |inputs: &[u64],
outputs: &mut [u64],
scratch: &mut [crate::ast::ScratchBuf]| {
let text = unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(
inputs[0] as usize as *const u8,
inputs[1] as usize,
))
};
if let Err(msg) = constraint.check(&ConstArg::Str(text.to_string()), "value") {
panic!("{name}: {msg}");
}
(copy.op)(inputs, outputs, scratch);
},
),
})
}
}
pub fn assert_type_node(typ: PortType) -> Box<dyn PolydatNode> {
Box::new(AssertType::new(typ))
}
pub fn assert_value_node(typ: PortType, constraint: ConstConstraint) -> Box<dyn PolydatNode> {
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);
}
}