stix_pattern/ir/mod.rs
1//! A three-address intermediate representation of a parsed pattern.
2//!
3//! [`lower`] turns a [`Pattern`](crate::ast::Pattern) into a [`Program`]: a set
4//! of comparison blocks, one per `[...]` observation, plus a `main` block that
5//! combines their results. Each comparison block is exactly the unit the matcher
6//! enumerates binding sets over.
7//!
8//! The IR is in SSA form — an instruction's [`InstrId`] names the value it
9//! produces, and there is no separate destination field.
10//!
11//! # Example
12//!
13//! ```
14//! use stix_pattern::{parse, ir};
15//!
16//! let pattern = parse("[file:size > 1024]").unwrap();
17//! let program = ir::lower(&pattern);
18//! program.validate().expect("lowered programs are always valid");
19//! assert_eq!(ir::render(&program), "[file:size > 1024]");
20//! ```
21//!
22//! # Deserialization
23//!
24//! Deserializing a [`Program`] does **not** check its invariants. Call
25//! [`Program::validate`] before using one that came from outside this process:
26//! it is what rejects the shapes that would make [`render`] produce text that
27//! does not parse or grow exponentially. Nesting depth is not limited:
28//! [`render`], [`Program::validate`] and [`Program::span_of`] are iterative, so
29//! a deeply nested program cannot make any of them overflow the stack.
30//!
31//! Validation also bounds rendered size. Each value may have at most one
32//! consumer ([`IrError::MultipleUses`]) and each comparison block at most one
33//! `Observe` ([`IrError::BlockObservedTwice`]), so every instruction is rendered
34//! at most once and output is linear in the size of the program.
35
36mod index;
37mod instr;
38mod lower;
39mod print;
40mod render;
41#[cfg(test)]
42mod scale_tests;
43mod span;
44mod validate;
45
46pub use instr::{
47 Block, BlockId, BlockKind, InstrId, Instruction, Op, Operand, Program, SCHEMA_VERSION,
48};
49pub use lower::lower;
50pub use render::render;
51pub use validate::IrError;