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