1use crate::ast::SlotShape;
32use crate::ast::{NodeMeta, PolydatNode, Port, PortType, Slot, Value};
33use crate::dsl::const_constraints::ConstConstraint;
34
35pub struct AssertType {
46 meta: NodeMeta,
47 expected: PortType,
48}
49
50impl AssertType {
51 pub fn new(typ: PortType) -> Self {
53 let name = match typ {
54 PortType::U64 => "assert_u64",
55 PortType::F64 => "assert_f64",
56 PortType::Bool => "assert_bool",
57 PortType::Str => "assert_str",
58 PortType::Bytes => "assert_bytes",
59 PortType::Json => "assert_json",
60 PortType::U32 => "assert_u32",
61 PortType::I32 => "assert_i32",
62 PortType::I64 => "assert_i64",
63 PortType::F32 => "assert_f32",
64 PortType::U8 => "assert_u8",
65 PortType::I8 => "assert_i8",
66 PortType::U16 => "assert_u16",
67 PortType::I16 => "assert_i16",
68 PortType::F16 => "assert_f16",
69 PortType::U128 => "assert_u128",
70 PortType::I128 => "assert_i128",
71 PortType::Reg128 => "assert_reg128",
72 PortType::RegI8x16 => "assert_reg_i8x16",
73 PortType::RegI16x8 => "assert_reg_i16x8",
74 PortType::RegI32x4 => "assert_reg_i32x4",
75 PortType::RegI64x2 => "assert_reg_i64x2",
76 PortType::RegF16x8 => "assert_reg_f16x8",
77 PortType::RegF32x4 => "assert_reg_f32x4",
78 PortType::RegF64x2 => "assert_reg_f64x2",
79 PortType::Ext => "assert_ext",
80 PortType::Handle => "assert_handle",
81 PortType::VecF32 => "assert_vec_f32",
82 PortType::VecI32 => "assert_vec_i32",
83 PortType::VecF64 => "assert_vec_f64",
84 PortType::VecI64 => "assert_vec_i64",
85 PortType::VecF16 => "assert_vec_f16",
86 PortType::VecI16 => "assert_vec_i16",
87 PortType::VecI8 => "assert_vec_i8",
88 PortType::Dyn => "assert_dyn",
89 };
90 Self {
91 meta: NodeMeta {
92 name: name.into(),
93 outs: vec![Port::new("output", typ)],
94 ins: vec![Slot::Wire(Port::new("input", typ))],
95 },
96 expected: typ,
97 }
98 }
99
100 pub fn expected(&self) -> PortType {
102 self.expected
103 }
104}
105
106impl PolydatNode for AssertType {
107 fn meta(&self) -> &NodeMeta {
108 &self.meta
109 }
110
111 fn eval(&self, inputs: &[Value], outputs: &mut [Value]) {
112 let v = &inputs[0];
113 if !value_matches(v, self.expected) {
114 panic!(
115 "{}: expected runtime value of type {:?}, got {:?}",
116 self.meta.name, self.expected, v
117 );
118 }
119 outputs[0] = v.clone();
120 }
121
122 fn compiled_u64(&self) -> Option<crate::ast::CompiledU64Op> {
128 if self.expected.slot_color() == crate::ast::SlotColor::Ref2 {
129 return None;
130 }
131 Some(Box::new(|inputs: &[u64], outputs: &mut [u64]| {
132 outputs.copy_from_slice(inputs)
133 }))
134 }
135
136 fn compiled_slot(
137 &self,
138 _wire_types: &[PortType],
139 _engine: crate::compile::select::Engine,
140 ) -> Option<crate::ast::CompiledSlotKit> {
141 crate::compile::assembly::ref_copy_kit(self.expected)
142 }
143}
144
145fn value_matches(v: &Value, typ: PortType) -> bool {
146 match (v, typ) {
147 (Value::U64(_), PortType::U64) => true,
148 (Value::F64(_), PortType::F64) => true,
149 (Value::Bool(_), PortType::Bool) => true,
150 (Value::Str(_), PortType::Str) => true,
151 (Value::Bytes(_), PortType::Bytes) => true,
152 (Value::Json(_), PortType::Json) => true,
153 (Value::U64(_), PortType::U32) => true,
156 (Value::U64(_), PortType::I32) => true,
157 (Value::U64(_), PortType::I64) => true,
158 (Value::F64(_), PortType::F32) => true,
159 (Value::U64(_), PortType::U8 | PortType::U16) => true,
160 (Value::U64(_), PortType::F16) => true,
163 (Value::F64(_), PortType::F16) => true,
164 (Value::I64(_), PortType::I64 | PortType::I32 | PortType::I8 | PortType::I16) => true,
168 (Value::U64(_), PortType::I8 | PortType::I16) => true,
169 (Value::U128(_), PortType::U128) => true,
170 (Value::I128(_), PortType::I128) => true,
171 (
173 Value::Reg128(_, _),
174 PortType::Reg128
175 | PortType::RegI8x16
176 | PortType::RegI16x8
177 | PortType::RegI32x4
178 | PortType::RegI64x2
179 | PortType::RegF16x8
180 | PortType::RegF32x4
181 | PortType::RegF64x2,
182 ) => true,
183 (Value::Ext(_), PortType::Ext) => true,
185 _ => false,
186 }
187}
188
189pub struct AssertValue {
203 meta: NodeMeta,
204 typ: PortType,
205 constraint: ConstConstraint,
206}
207
208impl AssertValue {
209 pub fn new(typ: PortType, constraint: ConstConstraint) -> Self {
211 let name = match (&typ, &constraint) {
212 (PortType::U64, ConstConstraint::NonZeroU64) => "assert_u64_nonzero",
213 (PortType::U64, ConstConstraint::RangeU64 { .. }) => "assert_u64_range",
214 (PortType::U64, ConstConstraint::AllowedU64(_)) => "assert_u64_allowed",
215 (PortType::F64, ConstConstraint::RangeF64 { .. }) => "assert_f64_range",
216 (PortType::Str, ConstConstraint::NonEmptyStr) => "assert_str_non_empty",
217 (PortType::Str, ConstConstraint::StrParser(_)) => "assert_str_parses",
218 _ => "assert_value",
221 };
222 Self {
223 meta: NodeMeta {
224 name: name.into(),
225 outs: vec![Port::new("output", typ)],
226 ins: vec![Slot::Wire(Port::new("input", typ))],
227 },
228 typ,
229 constraint,
230 }
231 }
232
233 pub fn constraint(&self) -> &ConstConstraint {
235 &self.constraint
236 }
237
238 pub fn port_type(&self) -> PortType {
240 self.typ
241 }
242}
243
244impl PolydatNode for AssertValue {
245 fn meta(&self) -> &NodeMeta {
246 &self.meta
247 }
248
249 fn eval(&self, inputs: &[Value], outputs: &mut [Value]) {
250 match check_value(&self.constraint, &inputs[0], "value") {
255 Some(Ok(())) => {}
256 Some(Err(msg)) => panic!("{}: {msg}", self.meta.name),
257 None => panic!(
258 "{}: unsupported runtime value variant {:?}",
259 self.meta.name, inputs[0]
260 ),
261 }
262 outputs[0] = inputs[0].clone();
263 }
264
265 fn compiled_u64(&self) -> Option<crate::ast::CompiledU64Op> {
270 use crate::dsl::factory::ConstArg;
271 let lift: fn(u64) -> ConstArg = match self.typ {
272 PortType::U64 | PortType::U32 | PortType::U16 | PortType::U8 => ConstArg::Int,
273 PortType::F64 => |slot| ConstArg::Float(f64::from_bits(slot)),
274 _ => return None,
275 };
276 let name = self.meta.name.clone();
277 let constraint = self.constraint;
278 Some(Box::new(move |inputs: &[u64], outputs: &mut [u64]| {
279 if let Err(msg) = constraint.check(&lift(inputs[0]), "value") {
280 panic!("{name}: {msg}");
281 }
282 outputs[0] = inputs[0];
283 }))
284 }
285
286 fn compiled_slot(
289 &self,
290 _wire_types: &[PortType],
291 _engine: crate::compile::select::Engine,
292 ) -> Option<crate::ast::CompiledSlotKit> {
293 use crate::dsl::factory::ConstArg;
294 if self.typ != PortType::Str {
295 return None;
296 }
297 let name = self.meta.name.clone();
298 let constraint = self.constraint;
299 let copy = crate::compile::assembly::ref_copy_kit(PortType::Str)?;
300 Some(crate::ast::CompiledSlotKit {
301 scratch: copy.scratch,
302 op: Box::new(
303 move |inputs: &[u64],
304 outputs: &mut [u64],
305 scratch: &mut [crate::ast::ScratchBuf]| {
306 let text = unsafe {
309 std::str::from_utf8_unchecked(std::slice::from_raw_parts(
310 inputs[0] as usize as *const u8,
311 inputs[1] as usize,
312 ))
313 };
314 if let Err(msg) = constraint.check(&ConstArg::Str(text.to_string()), "value") {
315 panic!("{name}: {msg}");
316 }
317 (copy.op)(inputs, outputs, scratch);
318 },
319 ),
320 })
321 }
322}
323
324pub fn check_value(
333 constraint: &ConstConstraint,
334 value: &Value,
335 name: &str,
336) -> Option<Result<(), String>> {
337 use crate::dsl::factory::ConstArg;
338 let arg = match value {
339 Value::U64(v) => ConstArg::Int(*v),
340 Value::F64(v) => ConstArg::Float(*v),
341 Value::Str(s) => ConstArg::Str(s.to_string()),
342 _ => return None,
343 };
344 Some(constraint.check(&arg, name))
345}
346
347pub fn describe_constraint(constraint: &ConstConstraint) -> String {
349 match constraint {
350 ConstConstraint::RangeU64 { min, max } => format!("in [{min}, {max}]"),
351 ConstConstraint::RangeF64 { min, max } => format!("in [{min}, {max}]"),
352 ConstConstraint::AllowedU64(allowed) => format!("one of {allowed:?}"),
353 ConstConstraint::NonZeroU64 => "non-zero".into(),
354 ConstConstraint::NonEmptyStr => "non-empty".into(),
355 ConstConstraint::StrParser(_) => "accepted by the port's parser".into(),
356 ConstConstraint::PositiveFiniteF64 => "positive and finite".into(),
357 ConstConstraint::FiniteF64 => "finite".into(),
358 }
359}
360
361pub fn same_constraint(a: &ConstConstraint, b: &ConstConstraint) -> bool {
364 use ConstConstraint as C;
365 match (a, b) {
366 (C::RangeU64 { min: a0, max: a1 }, C::RangeU64 { min: b0, max: b1 }) => {
367 a0 == b0 && a1 == b1
368 }
369 (C::RangeF64 { min: a0, max: a1 }, C::RangeF64 { min: b0, max: b1 }) => {
370 a0.to_bits() == b0.to_bits() && a1.to_bits() == b1.to_bits()
371 }
372 (C::AllowedU64(a), C::AllowedU64(b)) => a == b,
373 (C::StrParser(a), C::StrParser(b)) => std::ptr::fn_addr_eq(*a, *b),
374 (C::NonZeroU64, C::NonZeroU64)
375 | (C::NonEmptyStr, C::NonEmptyStr)
376 | (C::PositiveFiniteF64, C::PositiveFiniteF64)
377 | (C::FiniteF64, C::FiniteF64) => true,
378 _ => false,
379 }
380}
381
382pub fn assert_type_node(typ: PortType) -> Box<dyn PolydatNode> {
386 Box::new(AssertType::new(typ))
387}
388
389pub fn assert_value_node(typ: PortType, constraint: ConstConstraint) -> Box<dyn PolydatNode> {
391 Box::new(AssertValue::new(typ, constraint))
392}
393
394#[cfg(test)]
399mod tests {
400 use super::*;
401
402 #[test]
403 fn assert_u64_passes_u64_through() {
404 let node = AssertType::new(PortType::U64);
405 let mut out = [Value::None];
406 node.eval(&[Value::U64(42)], &mut out);
407 assert_eq!(out[0].as_u64(), 42);
408 }
409
410 #[test]
411 #[should_panic(expected = "expected runtime value of type U64")]
412 fn assert_u64_panics_on_string() {
413 let node = AssertType::new(PortType::U64);
414 let mut out = [Value::None];
415 node.eval(&[Value::Str("not a number".into())], &mut out);
416 }
417
418 #[test]
419 fn assert_value_nonzero_passes_nonzero() {
420 let node = AssertValue::new(PortType::U64, ConstConstraint::NonZeroU64);
421 let mut out = [Value::None];
422 node.eval(&[Value::U64(7)], &mut out);
423 assert_eq!(out[0].as_u64(), 7);
424 }
425
426 #[test]
427 #[should_panic(expected = "must be non-zero")]
428 fn assert_value_nonzero_panics_on_zero() {
429 let node = AssertValue::new(PortType::U64, ConstConstraint::NonZeroU64);
430 let mut out = [Value::None];
431 node.eval(&[Value::U64(0)], &mut out);
432 }
433
434 #[test]
435 fn assert_value_range_f64_passes_unit_interval() {
436 let node = AssertValue::new(
437 PortType::F64,
438 ConstConstraint::RangeF64 { min: 0.0, max: 1.0 },
439 );
440 let mut out = [Value::None];
441 node.eval(&[Value::F64(0.5)], &mut out);
442 assert_eq!(out[0].as_f64(), 0.5);
443 }
444
445 #[test]
446 #[should_panic(expected = "must be in [0, 1]")]
447 fn assert_value_range_f64_panics_on_out_of_range() {
448 let node = AssertValue::new(
449 PortType::F64,
450 ConstConstraint::RangeF64 { min: 0.0, max: 1.0 },
451 );
452 let mut out = [Value::None];
453 node.eval(&[Value::F64(1.5)], &mut out);
454 }
455}