Skip to main content

polydat_nodes/
compare.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! Comparison and selection nodes.
5//!
6//! Two families:
7//!
8//! - **Comparison** (`u64_eq`, `u64_lt`, `f64_lt`, …): two-input
9//!   nodes that produce a u64 truth value (0 or 1). The DSL's
10//!   `==`, `!=`, `<`, `>`, `<=`, `>=` infix operators desugar to
11//!   these — type-aware dispatch in `compile_binding` picks the
12//!   `u64_*` or `f64_*` variant based on operand types.
13//!
14//! - **Selection** (`select_u64`, `select_f64`): three-input nodes
15//!   that pick between two operand values based on a u64 condition
16//!   (any nonzero → first arg, zero → second). Used to desugar
17//!   `if(cond, a, b)` once the compiler knows the result type.
18//!   Both branches always evaluate — no short-circuit. JIT level:
19//!   P3 (`JitOp::SelectU64` / `SelectF64`, a native conditional
20//!   select).
21//!
22//! Output of every comparison node is u64 so downstream code can
23//! mix them with bitwise operators (`a < b & c < d`) without
24//! widening, and pass them as the `cond` input to `select_*`.
25
26// ---------------------------------------------------------------------------
27// Comparison nodes
28// ---------------------------------------------------------------------------
29
30#[polydat::polydat_node(category = Comparison)]
31fn u64_eq(a: u64, b: u64) -> u64 {
32    if a == b { 1 } else { 0 }
33}
34
35#[polydat::polydat_node(category = Comparison)]
36fn u64_ne(a: u64, b: u64) -> u64 {
37    if a != b { 1 } else { 0 }
38}
39
40#[polydat::polydat_node(category = Comparison)]
41fn u64_lt(a: u64, b: u64) -> u64 {
42    if a < b { 1 } else { 0 }
43}
44
45#[polydat::polydat_node(category = Comparison)]
46fn u64_gt(a: u64, b: u64) -> u64 {
47    if a > b { 1 } else { 0 }
48}
49
50#[polydat::polydat_node(category = Comparison)]
51fn u64_le(a: u64, b: u64) -> u64 {
52    if a <= b { 1 } else { 0 }
53}
54
55#[polydat::polydat_node(category = Comparison)]
56fn u64_ge(a: u64, b: u64) -> u64 {
57    if a >= b { 1 } else { 0 }
58}
59
60// f64 comparisons follow IEEE 754 — NaN compares unequal to
61// itself and is neither <, >, <=, nor >=. Tests for NaN should
62// use `a != a`.
63
64#[polydat::polydat_node(category = Comparison)]
65fn f64_eq(a: f64, b: f64) -> u64 {
66    if a == b { 1 } else { 0 }
67}
68
69#[polydat::polydat_node(category = Comparison)]
70fn f64_ne(a: f64, b: f64) -> u64 {
71    if a != b { 1 } else { 0 }
72}
73
74#[polydat::polydat_node(category = Comparison)]
75fn f64_lt(a: f64, b: f64) -> u64 {
76    if a < b { 1 } else { 0 }
77}
78
79#[polydat::polydat_node(category = Comparison)]
80fn f64_gt(a: f64, b: f64) -> u64 {
81    if a > b { 1 } else { 0 }
82}
83
84#[polydat::polydat_node(category = Comparison)]
85fn f64_le(a: f64, b: f64) -> u64 {
86    if a <= b { 1 } else { 0 }
87}
88
89#[polydat::polydat_node(category = Comparison)]
90fn f64_ge(a: f64, b: f64) -> u64 {
91    if a >= b { 1 } else { 0 }
92}
93
94// ---------------------------------------------------------------------------
95// Selection nodes (the desugar target for `if(cond, a, b)`)
96// ---------------------------------------------------------------------------
97
98/// Pick between two u64 inputs based on a u64 condition.
99#[polydat::polydat_node(category = Comparison)]
100fn select_u64(cond: u64, a: u64, b: u64) -> u64 {
101    if cond != 0 { a } else { b }
102}
103
104// ---------------------------------------------------------------------------
105// String comparisons
106// ---------------------------------------------------------------------------
107//
108// Strings ride (ptr, len) pairs: these nodes have no inline lowering
109// and run through their slot kit, called from native code on P3.
110// The DSL desugar
111// in `binding.rs` picks `str_eq` / `str_ne` over the u64 / f64
112// variants when either operand has `PortType::Str`.
113
114/// Equality of two String wires. Returns 1 if equal else 0.
115///
116/// Signature: `str_eq(a: String, b: String) -> (u64)`
117#[polydat::polydat_node(category = Comparison)]
118fn str_eq(a: &str, b: &str) -> u64 {
119    if a == b { 1 } else { 0 }
120}
121
122/// Inequality of two String wires.
123///
124/// Signature: `str_ne(a: String, b: String) -> (u64)`
125#[polydat::polydat_node(category = Comparison)]
126fn str_ne(a: &str, b: &str) -> u64 {
127    if a != b { 1 } else { 0 }
128}
129
130/// Pick between two f64 inputs based on a u64 condition.
131/// f64s travel as raw u64 bit patterns through the compiled buffer.
132#[polydat::polydat_node(category = Comparison)]
133fn select_f64(cond: u64, a: f64, b: f64) -> f64 {
134    if cond != 0 { a } else { b }
135}
136
137/// Pick between two String inputs based on a u64 condition.
138/// Any nonzero `cond` → `a`; zero → `b`.
139#[polydat::polydat_node(category = Comparison)]
140fn select_str(cond: u64, a: String, b: String) -> String {
141    if cond != 0 { a } else { b }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147    use polydat::ast::{PolydatNode, Value};
148
149    fn run(node: &dyn PolydatNode, ins: Vec<Value>) -> Value {
150        let mut outs = vec![Value::U64(0)];
151        node.eval(&ins, &mut outs);
152        outs.into_iter().next().unwrap()
153    }
154
155    #[test]
156    fn u64_lt_gt_eq_basics() {
157        assert_eq!(
158            run(&U64Lt::new(), vec![Value::U64(1), Value::U64(2)]).as_u64(),
159            1
160        );
161        assert_eq!(
162            run(&U64Lt::new(), vec![Value::U64(2), Value::U64(2)]).as_u64(),
163            0
164        );
165        assert_eq!(
166            run(&U64Gt::new(), vec![Value::U64(3), Value::U64(2)]).as_u64(),
167            1
168        );
169        assert_eq!(
170            run(&U64Eq::new(), vec![Value::U64(5), Value::U64(5)]).as_u64(),
171            1
172        );
173        assert_eq!(
174            run(&U64Ne::new(), vec![Value::U64(5), Value::U64(5)]).as_u64(),
175            0
176        );
177        assert_eq!(
178            run(&U64Le::new(), vec![Value::U64(2), Value::U64(2)]).as_u64(),
179            1
180        );
181        assert_eq!(
182            run(&U64Ge::new(), vec![Value::U64(2), Value::U64(2)]).as_u64(),
183            1
184        );
185    }
186
187    #[test]
188    fn f64_comparisons_basics() {
189        assert_eq!(
190            run(&F64Lt::new(), vec![Value::F64(0.1), Value::F64(0.2)]).as_u64(),
191            1
192        );
193        assert_eq!(
194            run(&F64Gt::new(), vec![Value::F64(0.2), Value::F64(0.1)]).as_u64(),
195            1
196        );
197        assert_eq!(
198            run(&F64Eq::new(), vec![Value::F64(0.1), Value::F64(0.1)]).as_u64(),
199            1
200        );
201        // NaN: f64_eq of NaN with itself is 0 (IEEE 754).
202        assert_eq!(
203            run(
204                &F64Eq::new(),
205                vec![Value::F64(f64::NAN), Value::F64(f64::NAN)]
206            )
207            .as_u64(),
208            0
209        );
210    }
211
212    #[test]
213    fn select_u64_picks_by_cond() {
214        let mut outs = vec![Value::U64(0)];
215        SelectU64::new().eval(&[Value::U64(1), Value::U64(10), Value::U64(20)], &mut outs);
216        assert_eq!(outs[0].as_u64(), 10);
217        SelectU64::new().eval(&[Value::U64(0), Value::U64(10), Value::U64(20)], &mut outs);
218        assert_eq!(outs[0].as_u64(), 20);
219    }
220
221    #[test]
222    fn select_f64_picks_by_cond() {
223        let mut outs = vec![Value::F64(0.0)];
224        SelectF64::new().eval(
225            &[Value::U64(1), Value::F64(0.5), Value::F64(1.05)],
226            &mut outs,
227        );
228        assert_eq!(outs[0].as_f64(), 0.5);
229        SelectF64::new().eval(
230            &[Value::U64(0), Value::F64(0.5), Value::F64(1.05)],
231            &mut outs,
232        );
233        assert_eq!(outs[0].as_f64(), 1.05);
234    }
235
236    #[test]
237    fn str_eq_ne_basics() {
238        let mut out = vec![Value::U64(0)];
239        StrEq::new().eval(
240            &[Value::Str("LATENCY".into()), Value::Str("LATENCY".into())],
241            &mut out,
242        );
243        assert_eq!(out[0].as_u64(), 1);
244        StrEq::new().eval(
245            &[Value::Str("LATENCY".into()), Value::Str("RECALL".into())],
246            &mut out,
247        );
248        assert_eq!(out[0].as_u64(), 0);
249        StrNe::new().eval(
250            &[Value::Str("LATENCY".into()), Value::Str("RECALL".into())],
251            &mut out,
252        );
253        assert_eq!(out[0].as_u64(), 1);
254        StrNe::new().eval(&[Value::Str("a".into()), Value::Str("a".into())], &mut out);
255        assert_eq!(out[0].as_u64(), 0);
256    }
257
258    #[test]
259    fn select_str_picks_by_cond() {
260        let mut out = vec![Value::Str(String::new().into())];
261        SelectStr::default().eval(
262            &[
263                Value::U64(1),
264                Value::Str("yes".into()),
265                Value::Str("no".into()),
266            ],
267            &mut out,
268        );
269        assert_eq!(out[0].as_str(), "yes");
270        SelectStr::default().eval(
271            &[
272                Value::U64(0),
273                Value::Str("yes".into()),
274                Value::Str("no".into()),
275            ],
276            &mut out,
277        );
278        assert_eq!(out[0].as_str(), "no");
279    }
280}