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//!   P2 (compiled closure; could become a P3 conditional select
20//!   in a future pass).
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 live on the heap; the compiled-u64 fast path can't carry
109// them in raw u64 buffers, so these are eval-only. The DSL desugar
110// in `binding.rs` picks `str_eq` / `str_ne` over the u64 / f64
111// variants when either operand has `PortType::Str`.
112
113/// Equality of two String wires. Returns 1 if equal else 0.
114///
115/// Signature: `str_eq(a: String, b: String) -> (u64)`
116#[polydat::polydat_node(category = Comparison)]
117fn str_eq(a: &str, b: &str) -> u64 {
118    if a == b { 1 } else { 0 }
119}
120
121/// Inequality of two String wires.
122///
123/// Signature: `str_ne(a: String, b: String) -> (u64)`
124#[polydat::polydat_node(category = Comparison)]
125fn str_ne(a: &str, b: &str) -> u64 {
126    if a != b { 1 } else { 0 }
127}
128
129/// Pick between two f64 inputs based on a u64 condition.
130/// f64s travel as raw u64 bit patterns through the compiled buffer.
131#[polydat::polydat_node(category = Comparison)]
132fn select_f64(cond: u64, a: f64, b: f64) -> f64 {
133    if cond != 0 { a } else { b }
134}
135
136/// Pick between two String inputs based on a u64 condition.
137/// Any nonzero `cond` → `a`; zero → `b`.
138#[polydat::polydat_node(category = Comparison)]
139fn select_str(cond: u64, a: String, b: String) -> String {
140    if cond != 0 { a } else { b }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use polydat::ast::{PolydatNode, Value};
147
148    fn run(node: &dyn PolydatNode, ins: Vec<Value>) -> Value {
149        let mut outs = vec![Value::U64(0)];
150        node.eval(&ins, &mut outs);
151        outs.into_iter().next().unwrap()
152    }
153
154    #[test]
155    fn u64_lt_gt_eq_basics() {
156        assert_eq!(
157            run(&U64Lt::new(), vec![Value::U64(1), Value::U64(2)]).as_u64(),
158            1
159        );
160        assert_eq!(
161            run(&U64Lt::new(), vec![Value::U64(2), Value::U64(2)]).as_u64(),
162            0
163        );
164        assert_eq!(
165            run(&U64Gt::new(), vec![Value::U64(3), Value::U64(2)]).as_u64(),
166            1
167        );
168        assert_eq!(
169            run(&U64Eq::new(), vec![Value::U64(5), Value::U64(5)]).as_u64(),
170            1
171        );
172        assert_eq!(
173            run(&U64Ne::new(), vec![Value::U64(5), Value::U64(5)]).as_u64(),
174            0
175        );
176        assert_eq!(
177            run(&U64Le::new(), vec![Value::U64(2), Value::U64(2)]).as_u64(),
178            1
179        );
180        assert_eq!(
181            run(&U64Ge::new(), vec![Value::U64(2), Value::U64(2)]).as_u64(),
182            1
183        );
184    }
185
186    #[test]
187    fn f64_comparisons_basics() {
188        assert_eq!(
189            run(&F64Lt::new(), vec![Value::F64(0.1), Value::F64(0.2)]).as_u64(),
190            1
191        );
192        assert_eq!(
193            run(&F64Gt::new(), vec![Value::F64(0.2), Value::F64(0.1)]).as_u64(),
194            1
195        );
196        assert_eq!(
197            run(&F64Eq::new(), vec![Value::F64(0.1), Value::F64(0.1)]).as_u64(),
198            1
199        );
200        // NaN: f64_eq of NaN with itself is 0 (IEEE 754).
201        assert_eq!(
202            run(
203                &F64Eq::new(),
204                vec![Value::F64(f64::NAN), Value::F64(f64::NAN)]
205            )
206            .as_u64(),
207            0
208        );
209    }
210
211    #[test]
212    fn select_u64_picks_by_cond() {
213        let mut outs = vec![Value::U64(0)];
214        SelectU64::new().eval(&[Value::U64(1), Value::U64(10), Value::U64(20)], &mut outs);
215        assert_eq!(outs[0].as_u64(), 10);
216        SelectU64::new().eval(&[Value::U64(0), Value::U64(10), Value::U64(20)], &mut outs);
217        assert_eq!(outs[0].as_u64(), 20);
218    }
219
220    #[test]
221    fn select_f64_picks_by_cond() {
222        let mut outs = vec![Value::F64(0.0)];
223        SelectF64::new().eval(
224            &[Value::U64(1), Value::F64(0.5), Value::F64(1.05)],
225            &mut outs,
226        );
227        assert_eq!(outs[0].as_f64(), 0.5);
228        SelectF64::new().eval(
229            &[Value::U64(0), Value::F64(0.5), Value::F64(1.05)],
230            &mut outs,
231        );
232        assert_eq!(outs[0].as_f64(), 1.05);
233    }
234
235    #[test]
236    fn str_eq_ne_basics() {
237        let mut out = vec![Value::U64(0)];
238        StrEq::new().eval(
239            &[Value::Str("LATENCY".into()), Value::Str("LATENCY".into())],
240            &mut out,
241        );
242        assert_eq!(out[0].as_u64(), 1);
243        StrEq::new().eval(
244            &[Value::Str("LATENCY".into()), Value::Str("RECALL".into())],
245            &mut out,
246        );
247        assert_eq!(out[0].as_u64(), 0);
248        StrNe::new().eval(
249            &[Value::Str("LATENCY".into()), Value::Str("RECALL".into())],
250            &mut out,
251        );
252        assert_eq!(out[0].as_u64(), 1);
253        StrNe::new().eval(&[Value::Str("a".into()), Value::Str("a".into())], &mut out);
254        assert_eq!(out[0].as_u64(), 0);
255    }
256
257    #[test]
258    fn select_str_picks_by_cond() {
259        let mut out = vec![Value::Str(String::new().into())];
260        SelectStr::default().eval(
261            &[
262                Value::U64(1),
263                Value::Str("yes".into()),
264                Value::Str("no".into()),
265            ],
266            &mut out,
267        );
268        assert_eq!(out[0].as_str(), "yes");
269        SelectStr::default().eval(
270            &[
271                Value::U64(0),
272                Value::Str("yes".into()),
273                Value::Str("no".into()),
274            ],
275            &mut out,
276        );
277        assert_eq!(out[0].as_str(), "no");
278    }
279}