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}