ocas_eval/instruction.rs
1//! Instruction set for the stack-based evaluation VM.
2//!
3//! The instruction set has two representations:
4//!
5//! - [`Instr`]: Internal, index-based instructions used by the evaluator.
6//! All slot references are absolute stack indices.
7//! - [`Instruction`]: Public, [`Slot`]-based instructions for inspection
8//! and serialization.
9//!
10//! # Stack layout
11//!
12//! ```text
13//! [params (param_count)]
14//! [constants (const_count)]
15//! [temporaries (temp_count)]
16//! [outputs (result_count)]
17//! ```
18//!
19//! The evaluator pre-fills params from user input and constants from the
20//! compiled expression, then executes instructions to compute temporaries
21//! and results.
22
23use ocas_atom::Symbol;
24
25/// Pre-resolved builtin function operation.
26///
27/// Used by the SIMD evaluator to avoid string matching on the hot path.
28/// The compiler converts `Symbol` names to `BuiltinOp` variants once at
29/// compile time.
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub enum BuiltinOp {
32 /// $\sin(x)$
33 Sin,
34 /// $\cos(x)$
35 Cos,
36 /// $\tan(x)$
37 Tan,
38 /// $\sec(x) = 1/\cos(x)$
39 Sec,
40 /// $\csc(x) = 1/\sin(x)$
41 Csc,
42 /// $\cot(x) = 1/\tan(x)$
43 Cot,
44 /// $e^x$
45 Exp,
46 /// $\ln(x)$
47 Log,
48 /// $\sqrt{x}$
49 Sqrt,
50 /// $|x|$
51 Abs,
52}
53
54impl BuiltinOp {
55 /// Try to parse a builtin operation name (case-insensitive).
56 pub fn from_name(name: &str) -> Option<Self> {
57 match name.to_lowercase().as_str() {
58 "sin" => Some(Self::Sin),
59 "cos" => Some(Self::Cos),
60 "tan" => Some(Self::Tan),
61 "sec" => Some(Self::Sec),
62 "csc" => Some(Self::Csc),
63 "cot" => Some(Self::Cot),
64 "exp" => Some(Self::Exp),
65 "log" | "ln" => Some(Self::Log),
66 "sqrt" => Some(Self::Sqrt),
67 "abs" => Some(Self::Abs),
68 _ => None,
69 }
70 }
71}
72
73/// A named slot in the evaluator stack.
74///
75/// Used by the public [`Instruction`] type to refer to stack positions
76/// semantically rather than by raw index.
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
78pub enum Slot {
79 /// A parameter slot. Index is 0-based within the params area.
80 Param(usize),
81 /// A constant value slot. Index is 0-based within the constants area.
82 Const(usize),
83 /// A temporary value slot. Index is 0-based within the temporaries area.
84 Temp(usize),
85}
86
87/// A public, self-documenting instruction.
88///
89/// Unlike [`Instr`] (which uses raw stack indices), `Instruction` uses
90/// [`Slot`] to make the instruction stream readable and serializable.
91#[derive(Debug, Clone, PartialEq)]
92pub enum Instruction {
93 /// `dst = sum of sources`
94 Add(Slot, Vec<Slot>),
95 /// `dst = product of sources`
96 Mul(Slot, Vec<Slot>),
97 /// `dst = base^exp` where exp is an integer exponent
98 Pow(Slot, Slot, i64),
99 /// `dst = base^exp` where exp is another slot
100 Powf(Slot, Slot, Slot),
101 /// `dst = builtin_function(src)`
102 Fun(Slot, Symbol, Slot),
103 /// `dst = external_function(srcs...)`
104 ExternalFun(Slot, usize, Vec<Slot>),
105 /// `dst = src` (copy)
106 Assign(Slot, Slot),
107}
108
109/// An internal, index-based instruction executed by [`super::ExpressionEvaluator`].
110///
111/// All indices are absolute positions in the evaluator's flat stack:
112/// indices `0..param_count` are parameters, `param_count..param_count+const_count`
113/// are constants, and the remainder are temporaries and outputs.
114#[derive(Debug, Clone)]
115pub enum Instr {
116 /// `stack[dst] = sum(stack[srcs[0]], stack[srcs[1]], ...)`
117 Add { dst: usize, srcs: Vec<usize> },
118 /// `stack[dst] = product(stack[srcs[0]], stack[srcs[1]], ...)`
119 Mul { dst: usize, srcs: Vec<usize> },
120 /// `stack[dst] = stack[base]^exp` where exp is an integer
121 Pow { dst: usize, base: usize, exp: i64 },
122 /// `stack[dst] = stack[base]^stack[exp]` (floating-point exponent)
123 Powf { dst: usize, base: usize, exp: usize },
124 /// `stack[dst] = builtin(stack[src])`
125 BuiltinOp {
126 dst: usize,
127 op: BuiltinOp,
128 src: usize,
129 },
130 /// `stack[dst] = fns[fn_idx](&stack[srcs[0]..])`
131 ExternalFun {
132 dst: usize,
133 fn_idx: usize,
134 srcs: Vec<usize>,
135 },
136 /// `stack[dst] = stack[src]`
137 Copy { dst: usize, src: usize },
138}
139
140impl Instr {
141 /// Return the destination stack index of this instruction.
142 pub fn dst(&self) -> usize {
143 match self {
144 Instr::Add { dst, .. }
145 | Instr::Mul { dst, .. }
146 | Instr::Pow { dst, .. }
147 | Instr::Powf { dst, .. }
148 | Instr::BuiltinOp { dst, .. }
149 | Instr::ExternalFun { dst, .. }
150 | Instr::Copy { dst, .. } => *dst,
151 }
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn slot_equality() {
161 assert_eq!(Slot::Param(0), Slot::Param(0));
162 assert_ne!(Slot::Param(0), Slot::Const(0));
163 assert_eq!(Slot::Temp(3), Slot::Temp(3));
164 }
165
166 #[test]
167 fn instr_dst() {
168 let add = Instr::Add {
169 dst: 5,
170 srcs: vec![1, 2],
171 };
172 assert_eq!(add.dst(), 5);
173
174 let fun = Instr::BuiltinOp {
175 dst: 3,
176 op: BuiltinOp::Sin,
177 src: 2,
178 };
179 assert_eq!(fun.dst(), 3);
180 }
181}