dice_rust/
stack_vm.rs

1use crate::{analyzer::SemanticAnalyzer, error::RuntimeError};
2use rand::prelude::*;
3
4/// Represents the control flow result of executing an instruction
5#[derive(Debug, PartialEq)]
6enum ExecutionControl {
7    /// Continue to the next instruction
8    Continue,
9    /// Jump by the specified relative offset
10    Jump(isize),
11    /// Terminate the program execution
12    Terminate,
13}
14
15#[derive(Debug, Clone)]
16#[allow(dead_code)]
17enum Instruction {
18    // Constants
19    LdcI4(u32), // Load 32-bit integer constant
20
21    // Local variables
22    Stloc0, // Store to local variable 0
23    Stloc1, // Store to local variable 1
24    Stloc2, // Store to local variable 2
25    Ldloc0, // Load from local variable 0
26    Ldloc1, // Load from local variable 1
27    Ldloc2, // Load from local variable 2
28
29    // Stack manipulation
30    Pop, // Pop value from stack
31    Dup, // Duplicate top stack value
32
33    // Arithmetic operations
34    Add, // Pop two values, push sum
35    Sub, // Pop two values, push difference
36    Mul, // Pop two values, push product
37    Div, // Pop two values, push quotient
38    Rem, // Pop two values, push remainder
39
40    // Comparison operations
41    Ceq, // Compare equal
42    Cgt, // Compare greater than
43    Clt, // Compare less than
44
45    // Branching
46    Br(isize),      // Unconditional branch (relative offset)
47    Brtrue(isize),  // Branch if true (relative offset)
48    Brfalse(isize), // Branch if false (relative offset)
49
50    // Method calls
51    Call(String), // Call method
52    Ret,          // Return from method
53
54    // I/O operations
55    CallWriteLine,           // Write line to console (stdout)
56    CallWrite,               // Write to console (stdout)
57    CallWriteStr(String),    // Write string to console (stdout)
58    CallWriteLineErr,        // Write line to stderr
59    CallWriteStrErr(String), // Write string to stderr
60
61    // Random number generation
62    CallRandom, // Generate random number
63}
64
65type Bytecode = Vec<Instruction>;
66
67struct Compiler;
68impl Compiler {
69    pub fn compile(source: &str) -> Result<Bytecode, Box<dyn std::error::Error>> {
70        let mut bytecode = Vec::<Instruction>::new();
71        let mut analyzer = match SemanticAnalyzer::new(source) {
72            Ok(analyzer) => analyzer,
73            Err(e) => return Err(Box::new(e)),
74        };
75        let ast = match analyzer.analyze() {
76            Ok(ast) => ast,
77            Err(e) => return Err(Box::new(e)),
78        };
79        if let Some(stmt) = ast.statement {
80            match stmt.kind {
81                crate::ast::StatementKind::Expression { expr } => {
82                    let crate::ast::ExpressionKind::Dice { count, faces } = expr.kind;
83
84                    // Initialize locals: count(0), faces(1), total(2)
85                    bytecode.push(Instruction::LdcI4(count)); // 0
86                    bytecode.push(Instruction::Stloc0); // 1: local 0 = count
87
88                    bytecode.push(Instruction::LdcI4(faces)); // 2
89                    bytecode.push(Instruction::Stloc1); // 3: local 1 = faces
90
91                    bytecode.push(Instruction::LdcI4(0)); // 4
92                    bytecode.push(Instruction::Stloc2); // 5: local 2 = total = 0
93
94                    // Loop start (PC = 6)
95                    bytecode.push(Instruction::Ldloc0); // 6: load count
96                    bytecode.push(Instruction::LdcI4(0)); // 7
97                    bytecode.push(Instruction::Cgt); // 8: count > 0
98
99                    // If count <= 0, break out of loop - we'll fix this offset later
100                    let brfalse_index = bytecode.len(); // Remember index 9
101                    bytecode.push(Instruction::Brfalse(0)); // 9: placeholder
102
103                    // Generate random number
104                    bytecode.push(Instruction::Ldloc1); // 10: load faces
105                    bytecode.push(Instruction::CallRandom); // 11: generate random [1, faces]
106
107                    // Print the roll result
108                    bytecode.push(Instruction::Dup); // 12: duplicate for printing
109                    bytecode.push(Instruction::CallWriteLine); // 13
110
111                    // Add to total
112                    bytecode.push(Instruction::Ldloc2); // 14: load total
113                    bytecode.push(Instruction::Add); // 15: total + roll
114                    bytecode.push(Instruction::Stloc2); // 16: store new total
115
116                    // Decrement count
117                    bytecode.push(Instruction::Ldloc0); // 17: load count
118                    bytecode.push(Instruction::LdcI4(1)); // 18
119                    bytecode.push(Instruction::Sub); // 19: count - 1
120                    bytecode.push(Instruction::Stloc0); // 20: store new count
121
122                    // Jump back to loop start
123                    const LOOP_START_PC: isize = 6;
124                    const CURRENT_PC: isize = 21;
125                    let br_back_offset = LOOP_START_PC - CURRENT_PC; // From PC 21 back to PC 6
126                    bytecode.push(Instruction::Br(br_back_offset)); // 21
127
128                    // After loop: print total if original count > 1
129                    let loop_exit_pc = bytecode.len(); // This is PC 22
130                    if count > 1 {
131                        bytecode.push(Instruction::CallWriteStrErr("Total: ".to_string())); // 22: print "Total: " to stderr
132                        bytecode.push(Instruction::Ldloc2); // 23: load total
133                        bytecode.push(Instruction::CallWriteLineErr); // 24: print total with newline to stderr
134                    }
135
136                    // Now fix the brfalse offset to point to the correct exit location
137                    let brfalse_offset = loop_exit_pc as isize - brfalse_index as isize;
138                    bytecode[brfalse_index] = Instruction::Brfalse(brfalse_offset);
139                }
140            }
141        }
142        Ok(bytecode)
143    }
144}
145
146pub struct StackVm {
147    stack: Vec<u32>,
148    locals: [u32; 3], // Local variables 0, 1, 2
149    rng: ThreadRng,
150}
151
152impl Default for StackVm {
153    fn default() -> Self {
154        Self::new()
155    }
156}
157
158impl StackVm {
159    pub fn new() -> Self {
160        Self {
161            stack: Vec::new(),
162            locals: [0; 3],
163            rng: ThreadRng::default(),
164        }
165    }
166
167    pub fn execute(&mut self, source: &str) -> Result<(), Box<dyn std::error::Error>> {
168        let bytecode = Compiler::compile(source)?;
169        let mut pc = 0;
170
171        while pc < bytecode.len() {
172            let instruction = &bytecode[pc];
173            let control = self.execute_instruction(instruction)?;
174
175            match control {
176                ExecutionControl::Terminate => {
177                    // Ret instruction - exit execution loop
178                    break;
179                }
180                ExecutionControl::Continue => {
181                    pc += 1;
182                }
183                ExecutionControl::Jump(offset) => {
184                    // Apply relative offset for branches
185                    let new_pc = (pc as isize) + offset;
186                    if new_pc < 0 {
187                        return Err(Box::new(RuntimeError::InvalidStackState));
188                    } else if new_pc >= bytecode.len() as isize {
189                        // Jump beyond bytecode end - treat as program termination
190                        break;
191                    } else {
192                        pc = new_pc as usize;
193                    }
194                }
195            }
196        }
197
198        // Program completed successfully
199        Ok(())
200    }
201
202    fn execute_instruction(
203        &mut self,
204        instruction: &Instruction,
205    ) -> Result<ExecutionControl, Box<dyn std::error::Error>> {
206        match instruction {
207            // Constants
208            Instruction::LdcI4(value) => {
209                self.stack.push(*value);
210            }
211
212            // Local variables
213            Instruction::Stloc0 => {
214                let value = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
215                self.locals[0] = value;
216            }
217            Instruction::Stloc1 => {
218                let value = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
219                self.locals[1] = value;
220            }
221            Instruction::Stloc2 => {
222                let value = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
223                self.locals[2] = value;
224            }
225            Instruction::Ldloc0 => {
226                self.stack.push(self.locals[0]);
227            }
228            Instruction::Ldloc1 => {
229                self.stack.push(self.locals[1]);
230            }
231            Instruction::Ldloc2 => {
232                self.stack.push(self.locals[2]);
233            }
234
235            // Stack manipulation
236            Instruction::Pop => {
237                self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
238            }
239            Instruction::Dup => {
240                let value = self
241                    .stack
242                    .last()
243                    .copied()
244                    .ok_or(RuntimeError::InvalidStackState)?;
245                self.stack.push(value);
246            }
247
248            // Arithmetic operations
249            Instruction::Add => {
250                let b = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
251                let a = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
252                self.stack.push(a.wrapping_add(b));
253            }
254            Instruction::Sub => {
255                let b = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
256                let a = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
257                self.stack.push(a.wrapping_sub(b));
258            }
259            Instruction::Mul => {
260                let b = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
261                let a = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
262                self.stack.push(a.wrapping_mul(b));
263            }
264            Instruction::Div => {
265                let b = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
266                let a = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
267                if b == 0 {
268                    return Err(Box::new(RuntimeError::DivisionByZero));
269                }
270                self.stack.push(a / b);
271            }
272            Instruction::Rem => {
273                let b = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
274                let a = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
275                if b == 0 {
276                    return Err(Box::new(RuntimeError::DivisionByZero));
277                }
278                self.stack.push(a % b);
279            }
280
281            // Comparison operations
282            Instruction::Ceq => {
283                let b = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
284                let a = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
285                self.stack.push(if a == b { 1 } else { 0 });
286            }
287            Instruction::Cgt => {
288                let b = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
289                let a = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
290                self.stack.push(if a > b { 1 } else { 0 });
291            }
292            Instruction::Clt => {
293                let b = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
294                let a = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
295                self.stack.push(if a < b { 1 } else { 0 });
296            }
297
298            // Branching
299            Instruction::Br(offset) => {
300                return Ok(ExecutionControl::Jump(*offset));
301            }
302            Instruction::Brtrue(offset) => {
303                let value = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
304                if value != 0 {
305                    return Ok(ExecutionControl::Jump(*offset));
306                }
307            }
308            Instruction::Brfalse(offset) => {
309                let value = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
310                if value == 0 {
311                    return Ok(ExecutionControl::Jump(*offset));
312                }
313            }
314
315            // I/O operations
316            Instruction::CallWriteLine => {
317                let value = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
318                println!("{value}");
319            }
320            Instruction::CallWrite => {
321                let value = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
322                print!("{value}");
323            }
324            Instruction::CallWriteStr(s) => {
325                print!("{s}");
326            }
327            Instruction::CallWriteLineErr => {
328                let value = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
329                eprintln!("{value}");
330            }
331            Instruction::CallWriteStrErr(s) => {
332                eprint!("{s}");
333            }
334
335            // Random number generation
336            Instruction::CallRandom => {
337                let max = self.stack.pop().ok_or(RuntimeError::InvalidStackState)?;
338                if max == 0 {
339                    self.stack.push(0);
340                } else {
341                    // Generate random number between 1 and max (inclusive)
342                    let random_value = self.rng.random_range(1..=max);
343                    self.stack.push(random_value);
344                }
345            }
346
347            // Method calls and control flow
348            Instruction::Call(_) => {
349                return Err(Box::new(RuntimeError::InvalidStackState));
350            }
351            Instruction::Ret => {
352                // Return from method - signal to exit the execution loop
353                return Ok(ExecutionControl::Terminate);
354            }
355        };
356        Ok(ExecutionControl::Continue) // No jump, continue to next instruction
357    }
358}