Skip to main content

vyre_libs/
rule.rs

1//! Rule-engine dialect: typed conditions, formulas, and program builder.
2
3/// Typed condition / formula AST consumed by every rule-set builder.
4pub mod ast;
5/// Rule-set IR program builder  -  walks a `[(RuleFormula, rule_id)]` table
6/// and emits one `Node::Store` per rule into the shared `verdicts` buffer.
7pub mod builder;
8/// Shared Cat-A helpers for scalar rule-condition ops (Tier-3 plumbing).
9pub mod condition_op;
10
11macro_rules! define_file_size_condition {
12    ($module:ident, $type_name:ident, $op_id:literal, $predicate:ident) => {
13        /// File-size rule predicate operation.
14        pub mod $module {
15            use vyre_foundation::ir::{Expr, Program};
16
17            /// File-size condition operation marker.
18            #[derive(Debug, Clone, Copy, Default)]
19            pub struct $type_name;
20
21            impl $type_name {
22                /// Build the canonical IR program.
23                #[must_use]
24                pub fn program() -> Program {
25                    crate::rule::condition_op::condition_program(OP_ID, || {
26                        Expr::$predicate(
27                            crate::rule::condition_op::file_size(),
28                            crate::rule::condition_op::threshold(),
29                        )
30                    })
31                }
32            }
33
34            /// Stable operation id for this file-size predicate.
35            pub const OP_ID: &str = $op_id;
36
37            /// Execution contract annotation for the standard catalog.
38            pub const CONTRACT: vyre_spec::OperationContract =
39                crate::contracts::RULE_PREDICATE_CHEAP;
40        }
41    };
42}
43
44define_file_size_condition!(
45    file_size_eq,
46    FileSizeEq,
47    "vyre-libs::rule::file_size_eq",
48    eq
49);
50define_file_size_condition!(
51    file_size_gt,
52    FileSizeGt,
53    "vyre-libs::rule::file_size_gt",
54    gt
55);
56define_file_size_condition!(
57    file_size_gte,
58    FileSizeGte,
59    "vyre-libs::rule::file_size_gte",
60    ge
61);
62define_file_size_condition!(
63    file_size_lt,
64    FileSizeLt,
65    "vyre-libs::rule::file_size_lt",
66    lt
67);
68define_file_size_condition!(
69    file_size_lte,
70    FileSizeLte,
71    "vyre-libs::rule::file_size_lte",
72    le
73);
74define_file_size_condition!(
75    file_size_ne,
76    FileSizeNe,
77    "vyre-libs::rule::file_size_ne",
78    ne
79);
80
81macro_rules! define_pattern_count_condition {
82    ($module:ident, $type_name:ident, $op_id:literal, $predicate:ident) => {
83        /// Pattern-count rule predicate operation.
84        pub mod $module {
85            use vyre_foundation::ir::{Expr, Program};
86
87            /// Pattern-count condition operation marker.
88            #[derive(Debug, Clone, Copy, Default)]
89            pub struct $type_name;
90
91            impl $type_name {
92                /// Build the canonical IR program.
93                #[must_use]
94                pub fn program() -> Program {
95                    crate::rule::condition_op::condition_program(OP_ID, || {
96                        Expr::$predicate(
97                            crate::rule::condition_op::pattern_count(),
98                            crate::rule::condition_op::threshold(),
99                        )
100                    })
101                }
102            }
103
104            /// Stable operation id for this pattern-count predicate.
105            pub const OP_ID: &str = $op_id;
106
107            /// Execution contract annotation for the standard catalog.
108            pub const CONTRACT: vyre_spec::OperationContract =
109                crate::contracts::RULE_PREDICATE_CHEAP;
110        }
111    };
112}
113
114define_pattern_count_condition!(
115    pattern_count_gt,
116    PatternCountGt,
117    "vyre-libs::rule::pattern_count_gt",
118    gt
119);
120define_pattern_count_condition!(
121    pattern_count_gte,
122    PatternCountGte,
123    "vyre-libs::rule::pattern_count_gte",
124    ge
125);
126
127/// Cat-A op: constant-false rule leaf.
128pub mod literal_false;
129/// Cat-A op: constant-true rule leaf.
130pub mod literal_true;
131/// Cat-A op: pattern-existence rule predicate.
132pub mod pattern_exists;
133/// Reference evaluator for `RuleCondition` / `RuleFormula` trees.
134/// Mirror of the GPU lowering for parity checks, CI gates, and unit
135/// tests that need deterministic rule outcomes without backend dispatch.
136pub mod reference_eval;
137
138pub use ast::{RuleCondition, RuleFormula};
139pub use builder::build_rule_program;
140pub use reference_eval::{evaluate_condition, evaluate_formula, RuleEvaluationContext};
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn file_size_condition_family_builds_unique_programs() {
148        let programs = [
149            (file_size_eq::OP_ID, file_size_eq::FileSizeEq::program()),
150            (file_size_gt::OP_ID, file_size_gt::FileSizeGt::program()),
151            (file_size_gte::OP_ID, file_size_gte::FileSizeGte::program()),
152            (file_size_lt::OP_ID, file_size_lt::FileSizeLt::program()),
153            (file_size_lte::OP_ID, file_size_lte::FileSizeLte::program()),
154            (file_size_ne::OP_ID, file_size_ne::FileSizeNe::program()),
155        ];
156        for (op_id, program) in programs {
157            assert!(
158                !program.entry().is_empty(),
159                "Fix: generated file-size condition `{op_id}` must emit a non-empty rule program"
160            );
161            assert!(
162                program
163                    .entry()
164                    .iter()
165                    .any(|node| format!("{node:?}").contains(op_id)),
166                "Fix: generated file-size condition `{op_id}` must preserve its op id in the IR"
167            );
168        }
169    }
170
171    #[test]
172    fn file_size_condition_family_uses_rule_predicate_contract() {
173        assert_eq!(
174            file_size_eq::CONTRACT.cost_hint,
175            crate::contracts::RULE_PREDICATE_CHEAP.cost_hint
176        );
177        assert_eq!(
178            file_size_ne::CONTRACT.determinism,
179            crate::contracts::RULE_PREDICATE_CHEAP.determinism
180        );
181    }
182
183    #[test]
184    fn pattern_count_condition_family_builds_unique_programs() {
185        let programs = [
186            (
187                pattern_count_gt::OP_ID,
188                pattern_count_gt::PatternCountGt::program(),
189            ),
190            (
191                pattern_count_gte::OP_ID,
192                pattern_count_gte::PatternCountGte::program(),
193            ),
194        ];
195        for (op_id, program) in programs {
196            assert!(
197                !program.entry().is_empty(),
198                "Fix: generated pattern-count condition `{op_id}` must emit a non-empty rule program"
199            );
200            assert!(
201                program
202                    .entry()
203                    .iter()
204                    .any(|node| format!("{node:?}").contains(op_id)),
205                "Fix: generated pattern-count condition `{op_id}` must preserve its op id in the IR"
206            );
207        }
208    }
209
210    #[test]
211    fn pattern_count_condition_family_uses_rule_predicate_contract() {
212        assert_eq!(
213            pattern_count_gt::CONTRACT.cost_hint,
214            crate::contracts::RULE_PREDICATE_CHEAP.cost_hint
215        );
216        assert_eq!(
217            pattern_count_gte::CONTRACT.determinism,
218            crate::contracts::RULE_PREDICATE_CHEAP.determinism
219        );
220    }
221}