Skip to main content

polydat_core/iteration/comprehension/predicate/
analyzer.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! Predicate analyzer entry point — spec §10.9.2.
5//!
6//! Single public function: [`analyze`]. Takes a predicate
7//! string + a coord set, dispatches through the §10.9.5
8//! pattern recognizers, and returns a [`PredicateInfo`].
9//!
10//! The analyzer's correctness contract per spec §10.9.4:
11//!
12//! 1. **Sound.** Every assertion in the returned `PredicateInfo`
13//!    is true of the predicate.
14//! 2. **Conservatively incomplete.** Unrecognized patterns →
15//!    `Opaque(UnknownPattern)`; missing an optimization is
16//!    acceptable, false assertions are not.
17//! 3. **Total.** Every well-formed boolean expression
18//!    produces a `PredicateInfo`. The trivial bundle
19//!    (everything `None` / `Opaque`) is the worst case but
20//!    never a failure.
21//! 4. **Deterministic.** Same `(predicate, coords)` always
22//!    produces the same `PredicateInfo`.
23//! 5. **Constant-time per node.** Single walk through the
24//!    predicate text; no SMT, no fixed-point iteration.
25
26use super::coordset::CoordSet;
27use super::info::PredicateInfo;
28use super::recognizers;
29
30/// Analyze a predicate string in the context of a coordinate
31/// set. Returns a structured [`PredicateInfo`] consumable by
32/// the optimizer's R5 and the deferred R8 / R9 / R10 rules.
33///
34/// The coord set's per-coord `CoordKind` short-circuits
35/// continuous-coord predicates to `Opaque(Continuous)` per
36/// spec §10.9 + F20 — continuous-coord predicate analysis is
37/// deliberately deferred.
38pub fn analyze(predicate: &str, coords: &CoordSet) -> PredicateInfo {
39    recognizers::recognize(predicate, coords)
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45    use crate::iteration::comprehension::predicate::info::{
46        Determinism, Factorization, OpaqueReason,
47    };
48
49    #[test]
50    fn empty_predicate_is_unknown() {
51        let info = analyze("", &CoordSet::all_discrete::<[&str; 0], &str>([]));
52        assert!(matches!(
53            info.factorization,
54            Factorization::Opaque(OpaqueReason::UnknownPattern)
55        ));
56    }
57
58    #[test]
59    fn deterministic_run_twice() {
60        let coords = CoordSet::all_discrete(["k", "limit"]);
61        let a = analyze("{k} > 0 && {limit} <= 100", &coords);
62        let b = analyze("{k} > 0 && {limit} <= 100", &coords);
63        assert_eq!(a, b);
64    }
65
66    #[test]
67    fn integration_per_axis_via_analyze_entry() {
68        let coords = CoordSet::all_discrete(["k"]);
69        let info = analyze("{k} >= 5", &coords);
70        assert!(matches!(info.factorization, Factorization::PerAxis(_)));
71        assert_eq!(info.determinism, Determinism::Deterministic);
72    }
73}