1use crate::ast::SlotShape;
34use crate::ast::{NodeMeta, PolydatNode, Port, PortType, Slot, Value};
35use crate::dsl::const_constraints::ConstConstraint;
36
37pub struct AssertType {
48 meta: NodeMeta,
49 expected: PortType,
50}
51
52impl AssertType {
53 pub fn new(typ: PortType) -> Self {
55 let name = match typ {
56 PortType::U64 => "assert_u64",
57 PortType::F64 => "assert_f64",
58 PortType::Bool => "assert_bool",
59 PortType::Str => "assert_str",
60 PortType::Bytes => "assert_bytes",
61 PortType::Json => "assert_json",
62 PortType::U32 => "assert_u32",
63 PortType::I32 => "assert_i32",
64 PortType::I64 => "assert_i64",
65 PortType::F32 => "assert_f32",
66 PortType::U8 => "assert_u8",
67 PortType::I8 => "assert_i8",
68 PortType::U16 => "assert_u16",
69 PortType::I16 => "assert_i16",
70 PortType::F16 => "assert_f16",
71 PortType::U128 => "assert_u128",
72 PortType::I128 => "assert_i128",
73 PortType::Reg128 => "assert_reg128",
74 PortType::RegI8x16 => "assert_reg_i8x16",
75 PortType::RegI16x8 => "assert_reg_i16x8",
76 PortType::RegI32x4 => "assert_reg_i32x4",
77 PortType::RegI64x2 => "assert_reg_i64x2",
78 PortType::RegF16x8 => "assert_reg_f16x8",
79 PortType::RegF32x4 => "assert_reg_f32x4",
80 PortType::RegF64x2 => "assert_reg_f64x2",
81 PortType::Ext => "assert_ext",
82 PortType::Handle => "assert_handle",
83 PortType::VecF32 => "assert_vec_f32",
84 PortType::VecI32 => "assert_vec_i32",
85 PortType::VecF64 => "assert_vec_f64",
86 PortType::VecI64 => "assert_vec_i64",
87 PortType::VecF16 => "assert_vec_f16",
88 PortType::VecI16 => "assert_vec_i16",
89 PortType::VecI8 => "assert_vec_i8",
90 };
91 Self {
92 meta: NodeMeta {
93 name: name.into(),
94 outs: vec![Port::new("output", typ)],
95 ins: vec![Slot::Wire(Port::new("input", typ))],
96 },
97 expected: typ,
98 }
99 }
100
101 pub fn expected(&self) -> PortType {
103 self.expected
104 }
105}
106
107impl PolydatNode for AssertType {
108 fn meta(&self) -> &NodeMeta {
109 &self.meta
110 }
111
112 fn eval(&self, inputs: &[Value], outputs: &mut [Value]) {
113 let v = &inputs[0];
114 if !value_matches(v, self.expected) {
115 panic!(
116 "{}: expected runtime value of type {:?}, got {:?}",
117 self.meta.name, self.expected, v
118 );
119 }
120 outputs[0] = v.clone();
121 }
122
123 fn compiled_u64(&self) -> Option<crate::ast::CompiledU64Op> {
129 if self.expected.slot_color() == crate::ast::SlotColor::Ref2 {
130 return None;
131 }
132 Some(Box::new(|inputs: &[u64], outputs: &mut [u64]| {
133 outputs.copy_from_slice(inputs)
134 }))
135 }
136
137 fn compiled_slot(&self, _wire_types: &[PortType]) -> Option<crate::ast::CompiledSlotKit> {
138 crate::compile::assembly::ref_copy_kit(self.expected)
139 }
140}
141
142fn value_matches(v: &Value, typ: PortType) -> bool {
143 match (v, typ) {
144 (Value::U64(_), PortType::U64) => true,
145 (Value::F64(_), PortType::F64) => true,
146 (Value::Bool(_), PortType::Bool) => true,
147 (Value::Str(_), PortType::Str) => true,
148 (Value::Bytes(_), PortType::Bytes) => true,
149 (Value::Json(_), PortType::Json) => true,
150 (Value::U64(_), PortType::U32) => true,
153 (Value::U64(_), PortType::I32) => true,
154 (Value::U64(_), PortType::I64) => true,
155 (Value::F64(_), PortType::F32) => true,
156 (Value::U64(_), PortType::U8 | PortType::U16) => true,
157 (Value::U64(_), PortType::F16) => true,
160 (Value::F64(_), PortType::F16) => true,
161 (Value::I64(_), PortType::I64 | PortType::I32 | PortType::I8 | PortType::I16) => true,
165 (Value::U64(_), PortType::I8 | PortType::I16) => true,
166 (Value::U128(_), PortType::U128) => true,
167 (Value::I128(_), PortType::I128) => true,
168 (
170 Value::Reg128(_, _),
171 PortType::Reg128
172 | PortType::RegI8x16
173 | PortType::RegI16x8
174 | PortType::RegI32x4
175 | PortType::RegI64x2
176 | PortType::RegF16x8
177 | PortType::RegF32x4
178 | PortType::RegF64x2,
179 ) => true,
180 (Value::Ext(_), PortType::Ext) => true,
182 _ => false,
183 }
184}
185
186pub struct AssertValue {
200 meta: NodeMeta,
201 typ: PortType,
202 constraint: ConstConstraint,
203}
204
205impl AssertValue {
206 pub fn new(typ: PortType, constraint: ConstConstraint) -> Self {
208 let name = match (&typ, &constraint) {
209 (PortType::U64, ConstConstraint::NonZeroU64) => "assert_u64_nonzero",
210 (PortType::U64, ConstConstraint::RangeU64 { .. }) => "assert_u64_range",
211 (PortType::U64, ConstConstraint::AllowedU64(_)) => "assert_u64_allowed",
212 (PortType::F64, ConstConstraint::RangeF64 { .. }) => "assert_f64_range",
213 (PortType::Str, ConstConstraint::NonEmptyStr) => "assert_str_non_empty",
214 (PortType::Str, ConstConstraint::StrParser(_)) => "assert_str_parses",
215 _ => "assert_value",
218 };
219 Self {
220 meta: NodeMeta {
221 name: name.into(),
222 outs: vec![Port::new("output", typ)],
223 ins: vec![Slot::Wire(Port::new("input", typ))],
224 },
225 typ,
226 constraint,
227 }
228 }
229
230 pub fn constraint(&self) -> &ConstConstraint {
232 &self.constraint
233 }
234
235 pub fn port_type(&self) -> PortType {
237 self.typ
238 }
239}
240
241impl PolydatNode for AssertValue {
242 fn meta(&self) -> &NodeMeta {
243 &self.meta
244 }
245
246 fn eval(&self, inputs: &[Value], outputs: &mut [Value]) {
247 let arg = match &inputs[0] {
252 Value::U64(v) => crate::dsl::factory::ConstArg::Int(*v),
253 Value::F64(v) => crate::dsl::factory::ConstArg::Float(*v),
254 Value::Str(s) => crate::dsl::factory::ConstArg::Str(s.to_string()),
255 other => panic!(
256 "{}: unsupported runtime value variant {:?}",
257 self.meta.name, other
258 ),
259 };
260 if let Err(msg) = self.constraint.check(&arg, "value") {
261 panic!("{}: {msg}", self.meta.name);
262 }
263 outputs[0] = inputs[0].clone();
264 }
265
266 fn compiled_u64(&self) -> Option<crate::ast::CompiledU64Op> {
271 use crate::dsl::factory::ConstArg;
272 let lift: fn(u64) -> ConstArg = match self.typ {
273 PortType::U64 | PortType::U32 | PortType::U16 | PortType::U8 => ConstArg::Int,
274 PortType::F64 => |slot| ConstArg::Float(f64::from_bits(slot)),
275 _ => return None,
276 };
277 let name = self.meta.name.clone();
278 let constraint = self.constraint;
279 Some(Box::new(move |inputs: &[u64], outputs: &mut [u64]| {
280 if let Err(msg) = constraint.check(&lift(inputs[0]), "value") {
281 panic!("{name}: {msg}");
282 }
283 outputs[0] = inputs[0];
284 }))
285 }
286
287 fn compiled_slot(&self, _wire_types: &[PortType]) -> Option<crate::ast::CompiledSlotKit> {
290 use crate::dsl::factory::ConstArg;
291 if self.typ != PortType::Str {
292 return None;
293 }
294 let name = self.meta.name.clone();
295 let constraint = self.constraint;
296 let copy = crate::compile::assembly::ref_copy_kit(PortType::Str)?;
297 Some(crate::ast::CompiledSlotKit {
298 scratch: copy.scratch,
299 op: Box::new(
300 move |inputs: &[u64],
301 outputs: &mut [u64],
302 scratch: &mut [crate::ast::ScratchBuf]| {
303 let text = unsafe {
306 std::str::from_utf8_unchecked(std::slice::from_raw_parts(
307 inputs[0] as usize as *const u8,
308 inputs[1] as usize,
309 ))
310 };
311 if let Err(msg) = constraint.check(&ConstArg::Str(text.to_string()), "value") {
312 panic!("{name}: {msg}");
313 }
314 (copy.op)(inputs, outputs, scratch);
315 },
316 ),
317 })
318 }
319}
320
321pub fn assert_type_node(typ: PortType) -> Box<dyn PolydatNode> {
327 Box::new(AssertType::new(typ))
328}
329
330pub fn assert_value_node(typ: PortType, constraint: ConstConstraint) -> Box<dyn PolydatNode> {
332 Box::new(AssertValue::new(typ, constraint))
333}
334
335#[cfg(test)]
340mod tests {
341 use super::*;
342
343 #[test]
344 fn assert_u64_passes_u64_through() {
345 let node = AssertType::new(PortType::U64);
346 let mut out = [Value::None];
347 node.eval(&[Value::U64(42)], &mut out);
348 assert_eq!(out[0].as_u64(), 42);
349 }
350
351 #[test]
352 #[should_panic(expected = "expected runtime value of type U64")]
353 fn assert_u64_panics_on_string() {
354 let node = AssertType::new(PortType::U64);
355 let mut out = [Value::None];
356 node.eval(&[Value::Str("not a number".into())], &mut out);
357 }
358
359 #[test]
360 fn assert_value_nonzero_passes_nonzero() {
361 let node = AssertValue::new(PortType::U64, ConstConstraint::NonZeroU64);
362 let mut out = [Value::None];
363 node.eval(&[Value::U64(7)], &mut out);
364 assert_eq!(out[0].as_u64(), 7);
365 }
366
367 #[test]
368 #[should_panic(expected = "must be non-zero")]
369 fn assert_value_nonzero_panics_on_zero() {
370 let node = AssertValue::new(PortType::U64, ConstConstraint::NonZeroU64);
371 let mut out = [Value::None];
372 node.eval(&[Value::U64(0)], &mut out);
373 }
374
375 #[test]
376 fn assert_value_range_f64_passes_unit_interval() {
377 let node = AssertValue::new(
378 PortType::F64,
379 ConstConstraint::RangeF64 { min: 0.0, max: 1.0 },
380 );
381 let mut out = [Value::None];
382 node.eval(&[Value::F64(0.5)], &mut out);
383 assert_eq!(out[0].as_f64(), 0.5);
384 }
385
386 #[test]
387 #[should_panic(expected = "must be in [0, 1]")]
388 fn assert_value_range_f64_panics_on_out_of_range() {
389 let node = AssertValue::new(
390 PortType::F64,
391 ConstConstraint::RangeF64 { min: 0.0, max: 1.0 },
392 );
393 let mut out = [Value::None];
394 node.eval(&[Value::F64(1.5)], &mut out);
395 }
396}