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(
138 &self,
139 _wire_types: &[PortType],
140 _engine: crate::compile::select::Engine,
141 ) -> Option<crate::ast::CompiledSlotKit> {
142 crate::compile::assembly::ref_copy_kit(self.expected)
143 }
144}
145
146fn value_matches(v: &Value, typ: PortType) -> bool {
147 match (v, typ) {
148 (Value::U64(_), PortType::U64) => true,
149 (Value::F64(_), PortType::F64) => true,
150 (Value::Bool(_), PortType::Bool) => true,
151 (Value::Str(_), PortType::Str) => true,
152 (Value::Bytes(_), PortType::Bytes) => true,
153 (Value::Json(_), PortType::Json) => true,
154 (Value::U64(_), PortType::U32) => true,
157 (Value::U64(_), PortType::I32) => true,
158 (Value::U64(_), PortType::I64) => true,
159 (Value::F64(_), PortType::F32) => true,
160 (Value::U64(_), PortType::U8 | PortType::U16) => true,
161 (Value::U64(_), PortType::F16) => true,
164 (Value::F64(_), PortType::F16) => true,
165 (Value::I64(_), PortType::I64 | PortType::I32 | PortType::I8 | PortType::I16) => true,
169 (Value::U64(_), PortType::I8 | PortType::I16) => true,
170 (Value::U128(_), PortType::U128) => true,
171 (Value::I128(_), PortType::I128) => true,
172 (
174 Value::Reg128(_, _),
175 PortType::Reg128
176 | PortType::RegI8x16
177 | PortType::RegI16x8
178 | PortType::RegI32x4
179 | PortType::RegI64x2
180 | PortType::RegF16x8
181 | PortType::RegF32x4
182 | PortType::RegF64x2,
183 ) => true,
184 (Value::Ext(_), PortType::Ext) => true,
186 _ => false,
187 }
188}
189
190pub struct AssertValue {
204 meta: NodeMeta,
205 typ: PortType,
206 constraint: ConstConstraint,
207}
208
209impl AssertValue {
210 pub fn new(typ: PortType, constraint: ConstConstraint) -> Self {
212 let name = match (&typ, &constraint) {
213 (PortType::U64, ConstConstraint::NonZeroU64) => "assert_u64_nonzero",
214 (PortType::U64, ConstConstraint::RangeU64 { .. }) => "assert_u64_range",
215 (PortType::U64, ConstConstraint::AllowedU64(_)) => "assert_u64_allowed",
216 (PortType::F64, ConstConstraint::RangeF64 { .. }) => "assert_f64_range",
217 (PortType::Str, ConstConstraint::NonEmptyStr) => "assert_str_non_empty",
218 (PortType::Str, ConstConstraint::StrParser(_)) => "assert_str_parses",
219 _ => "assert_value",
222 };
223 Self {
224 meta: NodeMeta {
225 name: name.into(),
226 outs: vec![Port::new("output", typ)],
227 ins: vec![Slot::Wire(Port::new("input", typ))],
228 },
229 typ,
230 constraint,
231 }
232 }
233
234 pub fn constraint(&self) -> &ConstConstraint {
236 &self.constraint
237 }
238
239 pub fn port_type(&self) -> PortType {
241 self.typ
242 }
243}
244
245impl PolydatNode for AssertValue {
246 fn meta(&self) -> &NodeMeta {
247 &self.meta
248 }
249
250 fn eval(&self, inputs: &[Value], outputs: &mut [Value]) {
251 let arg = match &inputs[0] {
256 Value::U64(v) => crate::dsl::factory::ConstArg::Int(*v),
257 Value::F64(v) => crate::dsl::factory::ConstArg::Float(*v),
258 Value::Str(s) => crate::dsl::factory::ConstArg::Str(s.to_string()),
259 other => panic!(
260 "{}: unsupported runtime value variant {:?}",
261 self.meta.name, other
262 ),
263 };
264 if let Err(msg) = self.constraint.check(&arg, "value") {
265 panic!("{}: {msg}", self.meta.name);
266 }
267 outputs[0] = inputs[0].clone();
268 }
269
270 fn compiled_u64(&self) -> Option<crate::ast::CompiledU64Op> {
275 use crate::dsl::factory::ConstArg;
276 let lift: fn(u64) -> ConstArg = match self.typ {
277 PortType::U64 | PortType::U32 | PortType::U16 | PortType::U8 => ConstArg::Int,
278 PortType::F64 => |slot| ConstArg::Float(f64::from_bits(slot)),
279 _ => return None,
280 };
281 let name = self.meta.name.clone();
282 let constraint = self.constraint;
283 Some(Box::new(move |inputs: &[u64], outputs: &mut [u64]| {
284 if let Err(msg) = constraint.check(&lift(inputs[0]), "value") {
285 panic!("{name}: {msg}");
286 }
287 outputs[0] = inputs[0];
288 }))
289 }
290
291 fn compiled_slot(
294 &self,
295 _wire_types: &[PortType],
296 _engine: crate::compile::select::Engine,
297 ) -> Option<crate::ast::CompiledSlotKit> {
298 use crate::dsl::factory::ConstArg;
299 if self.typ != PortType::Str {
300 return None;
301 }
302 let name = self.meta.name.clone();
303 let constraint = self.constraint;
304 let copy = crate::compile::assembly::ref_copy_kit(PortType::Str)?;
305 Some(crate::ast::CompiledSlotKit {
306 scratch: copy.scratch,
307 op: Box::new(
308 move |inputs: &[u64],
309 outputs: &mut [u64],
310 scratch: &mut [crate::ast::ScratchBuf]| {
311 let text = unsafe {
314 std::str::from_utf8_unchecked(std::slice::from_raw_parts(
315 inputs[0] as usize as *const u8,
316 inputs[1] as usize,
317 ))
318 };
319 if let Err(msg) = constraint.check(&ConstArg::Str(text.to_string()), "value") {
320 panic!("{name}: {msg}");
321 }
322 (copy.op)(inputs, outputs, scratch);
323 },
324 ),
325 })
326 }
327}
328
329pub fn assert_type_node(typ: PortType) -> Box<dyn PolydatNode> {
335 Box::new(AssertType::new(typ))
336}
337
338pub fn assert_value_node(typ: PortType, constraint: ConstConstraint) -> Box<dyn PolydatNode> {
340 Box::new(AssertValue::new(typ, constraint))
341}
342
343#[cfg(test)]
348mod tests {
349 use super::*;
350
351 #[test]
352 fn assert_u64_passes_u64_through() {
353 let node = AssertType::new(PortType::U64);
354 let mut out = [Value::None];
355 node.eval(&[Value::U64(42)], &mut out);
356 assert_eq!(out[0].as_u64(), 42);
357 }
358
359 #[test]
360 #[should_panic(expected = "expected runtime value of type U64")]
361 fn assert_u64_panics_on_string() {
362 let node = AssertType::new(PortType::U64);
363 let mut out = [Value::None];
364 node.eval(&[Value::Str("not a number".into())], &mut out);
365 }
366
367 #[test]
368 fn assert_value_nonzero_passes_nonzero() {
369 let node = AssertValue::new(PortType::U64, ConstConstraint::NonZeroU64);
370 let mut out = [Value::None];
371 node.eval(&[Value::U64(7)], &mut out);
372 assert_eq!(out[0].as_u64(), 7);
373 }
374
375 #[test]
376 #[should_panic(expected = "must be non-zero")]
377 fn assert_value_nonzero_panics_on_zero() {
378 let node = AssertValue::new(PortType::U64, ConstConstraint::NonZeroU64);
379 let mut out = [Value::None];
380 node.eval(&[Value::U64(0)], &mut out);
381 }
382
383 #[test]
384 fn assert_value_range_f64_passes_unit_interval() {
385 let node = AssertValue::new(
386 PortType::F64,
387 ConstConstraint::RangeF64 { min: 0.0, max: 1.0 },
388 );
389 let mut out = [Value::None];
390 node.eval(&[Value::F64(0.5)], &mut out);
391 assert_eq!(out[0].as_f64(), 0.5);
392 }
393
394 #[test]
395 #[should_panic(expected = "must be in [0, 1]")]
396 fn assert_value_range_f64_panics_on_out_of_range() {
397 let node = AssertValue::new(
398 PortType::F64,
399 ConstConstraint::RangeF64 { min: 0.0, max: 1.0 },
400 );
401 let mut out = [Value::None];
402 node.eval(&[Value::F64(1.5)], &mut out);
403 }
404}