Skip to main content

polydat_core/iteration/comprehension/ir/
program.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! Immutable IR Program wrapper — spec §9.1.
5//!
6//! `Program` is the public surface of the compiled IR. It's
7//! `#[non_exhaustive]` and accessible by value but cannot be
8//! mutated after construction: `ir::compile::compile` is the
9//! only path from AST to IR (the optimizer (§10) is a separate
10//! AST→AST pass callers may run first), and the resulting
11//! program is frozen.
12
13use serde::{Deserialize, Serialize};
14
15use super::op::Op;
16
17/// An immutable IR program — a finite, ordered sequence of
18/// [`Op`]s ending in [`Op::Dispense`].
19///
20/// `Program` is the load-bearing immutable public API per
21/// spec §9.1. `ir::compile::compile` is the only constructor;
22/// consumers read via `ops` and
23/// [`stack_depth`](Program::stack_depth).
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25#[non_exhaustive]
26pub struct Program {
27    ops: Vec<Op>,
28}
29
30impl Program {
31    /// Construct a Program. The compiler (`super::compile`) is
32    /// the canonical caller; external code shouldn't call this
33    /// directly except in tests.
34    pub fn new(ops: Vec<Op>) -> Self {
35        Self { ops }
36    }
37
38    /// The opcode sequence. Borrowed; the program owns the
39    /// vec.
40    pub fn ops(&self) -> &[Op] {
41        &self.ops
42    }
43
44    /// Number of opcodes.
45    pub fn len(&self) -> usize {
46        self.ops.len()
47    }
48
49    /// Whether the program has no opcode.
50    pub fn is_empty(&self) -> bool {
51        self.ops.is_empty()
52    }
53
54    /// Maximum stack depth this program reaches during
55    /// interpretation. Used by the bounds checker (§9.3's
56    /// `O(depth(C))` operator-stack term).
57    pub fn stack_depth(&self) -> usize {
58        let mut depth: i64 = 0;
59        let mut max_depth: i64 = 0;
60        for op in &self.ops {
61            let (pop, push) = op.stack_effect();
62            depth -= pop as i64;
63            depth += push as i64;
64            if depth > max_depth {
65                max_depth = depth;
66            }
67        }
68        max_depth as usize
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use crate::iteration::comprehension::source::Source;
76    use crate::iteration::comprehension::strategy::ZipMode;
77
78    fn push_clause(name: &str) -> Op {
79        Op::PushClause {
80            name: name.into(),
81            source: Source::Literal { values: vec![] },
82        }
83    }
84
85    #[test]
86    fn stack_depth_simple() {
87        // PUSH, PUSH, CARTESIAN(2), DISPENSE — max depth 2.
88        let p = Program::new(vec![
89            push_clause("a"),
90            push_clause("b"),
91            Op::Cartesian { n: 2 },
92            Op::Dispense,
93        ]);
94        assert_eq!(p.stack_depth(), 2);
95    }
96
97    #[test]
98    fn stack_depth_nested() {
99        // PUSH, PUSH, PUSH, CARTESIAN(3), DISPENSE — max depth 3.
100        let p = Program::new(vec![
101            push_clause("a"),
102            push_clause("b"),
103            push_clause("c"),
104            Op::Cartesian { n: 3 },
105            Op::Dispense,
106        ]);
107        assert_eq!(p.stack_depth(), 3);
108    }
109
110    #[test]
111    fn stack_depth_zip_then_cartesian() {
112        // PUSH a, PUSH b, ZIP(2), PUSH c, CARTESIAN(2)
113        let p = Program::new(vec![
114            push_clause("a"),
115            push_clause("b"),
116            Op::Zip {
117                n: 2,
118                mode: ZipMode::Strict,
119            },
120            push_clause("c"),
121            Op::Cartesian { n: 2 },
122            Op::Dispense,
123        ]);
124        assert_eq!(p.stack_depth(), 2);
125    }
126
127    #[test]
128    fn round_trip_serde() {
129        let p = Program::new(vec![push_clause("a"), Op::Dispense]);
130        let json = serde_json::to_string(&p).unwrap();
131        let back: Program = serde_json::from_str(&json).unwrap();
132        assert_eq!(p, back);
133    }
134}