commit 862cec691012ceb800f90f956da2b62c8811e4f6 from: Murilo Ijanc date: Tue Feb 10 20:20:00 2026 UTC Add control flow with if/else and while loops New opcodes for comparison (Equal, NotEqual, Less, LessEqual, Greater, GreaterEqual), logical Not, and jumps (JumpIfFalse, Jump, Loop). Compiler uses jump patching for if/else branches and backward jumps for while loops. commit - 37c978c740aa2c23c2be7f6165ee14b888c150b5 commit + 862cec691012ceb800f90f956da2b62c8811e4f6 blob - fca54dff3512cbf3bac7237083caa50df0523e85 blob + 880b6fa2587ba1fb9699f81e65fb64eab69bf956 --- src/compiler/opcode.rs +++ src/compiler/opcode.rs @@ -28,8 +28,19 @@ pub enum OpCode { Div, Mod, Negate, + Not, + Equal, + NotEqual, + Less, + LessEqual, + Greater, + GreaterEqual, GetLocal, SetLocal, + JumpIfFalse, + Jump, + Loop, + Call, Print, Return, } @@ -45,76 +56,210 @@ impl OpCode { 5 => Some(OpCode::Div), 6 => Some(OpCode::Mod), 7 => Some(OpCode::Negate), - 8 => Some(OpCode::GetLocal), - 9 => Some(OpCode::SetLocal), - 10 => Some(OpCode::Print), - 11 => Some(OpCode::Return), + 8 => Some(OpCode::Not), + 9 => Some(OpCode::Equal), + 10 => Some(OpCode::NotEqual), + 11 => Some(OpCode::Less), + 12 => Some(OpCode::LessEqual), + 13 => Some(OpCode::Greater), + 14 => Some(OpCode::GreaterEqual), + 15 => Some(OpCode::GetLocal), + 16 => Some(OpCode::SetLocal), + 17 => Some(OpCode::JumpIfFalse), + 18 => Some(OpCode::Jump), + 19 => Some(OpCode::Loop), + 20 => Some(OpCode::Call), + 21 => Some(OpCode::Print), + 22 => Some(OpCode::Return), _ => None, } } } pub struct Chunk { + pub name: String, pub code: Vec, pub constants: Vec, pub lines: Vec, + pub arity: u8, } impl Chunk { - pub fn new() -> Self { - Self { code: Vec::new(), constants: Vec::new(), lines: Vec::new() } + pub fn new( + name: impl Into, + arity: u8, + ) -> Self { + Self { + name: name.into(), + code: Vec::new(), + constants: Vec::new(), + lines: Vec::new(), + arity, + } } - pub fn emit_byte(&mut self, byte: u8, line: usize) { + pub fn emit_byte( + &mut self, + byte: u8, + line: usize, + ) { self.code.push(byte); self.lines.push(line); } - pub fn emit_op(&mut self, op: OpCode, line: usize) { + pub fn emit_op( + &mut self, + op: OpCode, + line: usize, + ) { self.emit_byte(op as u8, line); } - pub fn add_constant(&mut self, value: Value) -> u8 { + pub fn add_constant( + &mut self, + value: Value, + ) -> u8 { self.constants.push(value); (self.constants.len() - 1) as u8 } - pub fn emit_constant(&mut self, value: Value, line: usize) { + pub fn emit_constant( + &mut self, + value: Value, + line: usize, + ) { let index = self.add_constant(value); self.emit_op(OpCode::Constant, line); self.emit_byte(index, line); } + /// Emit a jump instruction with a 2-byte + /// placeholder. Returns the offset of the + /// placeholder so it can be patched later. + pub fn emit_jump( + &mut self, + op: OpCode, + line: usize, + ) -> usize { + self.emit_op(op, line); + self.emit_byte(0xFF, line); + self.emit_byte(0xFF, line); + self.code.len() - 2 + } + + /// Patch a previously emitted jump placeholder + /// with the actual offset. + pub fn patch_jump(&mut self, offset: usize) { + let jump = + self.code.len() - offset - 2; + self.code[offset] = (jump >> 8) as u8; + self.code[offset + 1] = jump as u8; + } + + /// Emit a loop instruction that jumps back + /// to `loop_start`. + pub fn emit_loop( + &mut self, + loop_start: usize, + line: usize, + ) { + self.emit_op(OpCode::Loop, line); + let offset = self.code.len() - loop_start + 2; + self.emit_byte((offset >> 8) as u8, line); + self.emit_byte(offset as u8, line); + } + pub fn disassemble(&self, name: &str) { println!("== {name} =="); let mut offset = 0; + while offset < self.code.len() { - offset = self.disassemble_instruction(offset); + offset = + self.disassemble_instruction(offset); } } - fn disassemble_instruction(&self, offset: usize) -> usize { + fn disassemble_instruction( + &self, + offset: usize, + ) -> usize { print!("{:04} ", offset); + let byte = self.code[offset]; let Some(op) = OpCode::from_byte(byte) else { println!("unknown opcode {byte}"); return offset + 1; }; + match op { OpCode::Constant => { - let index = self.code[offset + 1] as usize; + let index = + self.code[offset + 1] as usize; let value = &self.constants[index]; - println!("{:<12} {:4} ({})", "Constant", index, value); + println!( + "{:<14} {:4} ({})", + "Constant", index, value + ); offset + 2 } OpCode::GetLocal | OpCode::SetLocal => { - let slot = self.code[offset + 1] as usize; - println!("{:<12} {:4}", format!("{:?}", op), slot); + let slot = + self.code[offset + 1] as usize; + println!( + "{:<14} {:4}", + format!("{:?}", op), + slot + ); offset + 2 } + OpCode::JumpIfFalse | OpCode::Jump => { + let hi = + self.code[offset + 1] as u16; + let lo = + self.code[offset + 2] as u16; + let jump = (hi << 8) | lo; + let target = offset + 3 + jump as usize; + println!( + "{:<14} {:4} -> {:04}", + format!("{:?}", op), + jump, + target + ); + offset + 3 + } + OpCode::Loop => { + let hi = + self.code[offset + 1] as u16; + let lo = + self.code[offset + 2] as u16; + let jump = (hi << 8) | lo; + let target = offset + 3 - jump as usize; + println!( + "{:<14} {:4} -> {:04}", + format!("{:?}", op), + jump, + target + ); + offset + 3 + } + OpCode::Call => { + let fn_index = + self.code[offset + 1] as usize; + let arg_count = + self.code[offset + 2] as usize; + println!( + "{:<14} {:4} args={}", + "Call", fn_index, arg_count + ); + offset + 3 + } OpCode::Print => { - let count = self.code[offset + 1] as usize; - println!("{:<12} {:4}", "Print", count); + let count = + self.code[offset + 1] as usize; + println!( + "{:<14} {:4}", + "Print", count + ); offset + 2 } _ => { blob - 4309c4ed0f97962469ee0a7463c9f86ce883004c blob + 803a9813685a32571358dab9e713eeab70ddf457 --- src/vm/mod.rs +++ src/vm/mod.rs @@ -20,27 +20,64 @@ pub mod value; use crate::compiler::opcode::{Chunk, OpCode}; use value::Value; -pub struct VM { - chunk: Chunk, +struct CallFrame { + fn_index: usize, ip: usize, + slot_offset: usize, +} + +pub struct VM { + functions: Vec, + frames: Vec, stack: Vec, } impl VM { - pub fn new(chunk: Chunk) -> Self { - Self { - chunk, + pub fn new( + functions: Vec, + main_index: usize, + ) -> Self { + let frames = vec![CallFrame { + fn_index: main_index, ip: 0, + slot_offset: 0, + }]; + Self { + functions, + frames, stack: Vec::new(), } } + fn frame(&self) -> &CallFrame { + self.frames.last().expect("no call frame") + } + + fn frame_mut(&mut self) -> &mut CallFrame { + self.frames.last_mut().expect("no call frame") + } + fn read_byte(&mut self) -> u8 { - let byte = self.chunk.code[self.ip]; - self.ip += 1; + let frame = self.frame(); + let byte = self.functions[frame.fn_index] + .code[frame.ip]; + self.frame_mut().ip += 1; byte } + fn read_u16(&mut self) -> u16 { + let hi = self.read_byte() as u16; + let lo = self.read_byte() as u16; + (hi << 8) | lo + } + + fn read_constant(&mut self) -> Value { + let index = self.read_byte() as usize; + let fn_index = self.frame().fn_index; + self.functions[fn_index].constants[index] + .clone() + } + fn push(&mut self, value: Value) { self.stack.push(value); } @@ -61,11 +98,8 @@ impl VM { match op { OpCode::Constant => { - let index = - self.read_byte() as usize; let value = - self.chunk.constants[index] - .clone(); + self.read_constant(); self.push(value); } OpCode::Pop => { @@ -75,15 +109,29 @@ impl VM { let b = self.pop(); let a = self.pop(); let result = match (&a, &b) { - (Value::Int(a), Value::Int(b)) => - Value::Int(a + b), - (Value::Float(a), Value::Float(b)) => - Value::Float(a + b), - (Value::Str(a), Value::Str(b)) => - Value::Str(format!("{a}{b}")), - _ => return Err(format!( - "cannot add {:?} and {:?}", a, b - )), + ( + Value::Int(a), + Value::Int(b), + ) => Value::Int(a + b), + ( + Value::Float(a), + Value::Float(b), + ) => Value::Float(a + b), + ( + Value::Str(a), + Value::Str(b), + ) => { + Value::Str(format!( + "{a}{b}" + )) + } + _ => { + return Err(format!( + "cannot add {:?} \ + and {:?}", + a, b + )) + } }; self.push(result); } @@ -91,13 +139,21 @@ impl VM { let b = self.pop(); let a = self.pop(); let result = match (&a, &b) { - (Value::Int(a), Value::Int(b)) => - Value::Int(a - b), - (Value::Float(a), Value::Float(b)) => - Value::Float(a - b), - _ => return Err(format!( - "cannot subtract {:?} and {:?}", a, b - )), + ( + Value::Int(a), + Value::Int(b), + ) => Value::Int(a - b), + ( + Value::Float(a), + Value::Float(b), + ) => Value::Float(a - b), + _ => { + return Err(format!( + "cannot subtract \ + {:?} and {:?}", + a, b + )) + } }; self.push(result); } @@ -105,13 +161,21 @@ impl VM { let b = self.pop(); let a = self.pop(); let result = match (&a, &b) { - (Value::Int(a), Value::Int(b)) => - Value::Int(a * b), - (Value::Float(a), Value::Float(b)) => - Value::Float(a * b), - _ => return Err(format!( - "cannot multiply {:?} and {:?}", a, b - )), + ( + Value::Int(a), + Value::Int(b), + ) => Value::Int(a * b), + ( + Value::Float(a), + Value::Float(b), + ) => Value::Float(a * b), + _ => { + return Err(format!( + "cannot multiply \ + {:?} and {:?}", + a, b + )) + } }; self.push(result); } @@ -119,15 +183,29 @@ impl VM { let b = self.pop(); let a = self.pop(); let result = match (&a, &b) { - (Value::Int(a), Value::Int(b)) => { - if *b == 0 { return Err("division by zero".to_string()); } + ( + Value::Int(a), + Value::Int(b), + ) => { + if *b == 0 { + return Err( + "division by zero" + .to_string(), + ); + } Value::Int(a / b) } - (Value::Float(a), Value::Float(b)) => - Value::Float(a / b), - _ => return Err(format!( - "cannot divide {:?} and {:?}", a, b - )), + ( + Value::Float(a), + Value::Float(b), + ) => Value::Float(a / b), + _ => { + return Err(format!( + "cannot divide \ + {:?} and {:?}", + a, b + )) + } }; self.push(result); } @@ -135,49 +213,318 @@ impl VM { let b = self.pop(); let a = self.pop(); let result = match (&a, &b) { - (Value::Int(a), Value::Int(b)) => { - if *b == 0 { return Err("modulo by zero".to_string()); } + ( + Value::Int(a), + Value::Int(b), + ) => { + if *b == 0 { + return Err( + "modulo by zero" + .to_string(), + ); + } Value::Int(a % b) } - _ => return Err(format!( - "cannot modulo {:?} and {:?}", a, b - )), + _ => { + return Err(format!( + "cannot modulo \ + {:?} and {:?}", + a, b + )) + } }; self.push(result); } OpCode::Negate => { let v = self.pop(); let result = match v { - Value::Int(n) => Value::Int(-n), - Value::Float(n) => Value::Float(-n), - _ => return Err(format!("cannot negate {:?}", v)), + Value::Int(n) => { + Value::Int(-n) + } + Value::Float(n) => { + Value::Float(-n) + } + _ => { + return Err(format!( + "cannot negate {:?}", + v + )) + } }; self.push(result); } + OpCode::Not => { + let v = self.pop(); + let result = match v { + Value::Bool(b) => { + Value::Bool(!b) + } + _ => { + return Err(format!( + "cannot not {:?}", + v + )) + } + }; + self.push(result); + } + OpCode::Equal => { + let b = self.pop(); + let a = self.pop(); + let result = match (&a, &b) { + ( + Value::Int(a), + Value::Int(b), + ) => a == b, + ( + Value::Float(a), + Value::Float(b), + ) => a == b, + ( + Value::Str(a), + Value::Str(b), + ) => a == b, + ( + Value::Bool(a), + Value::Bool(b), + ) => a == b, + _ => { + return Err(format!( + "cannot compare \ + {:?} and {:?}", + a, b + )) + } + }; + self.push(Value::Bool(result)); + } + OpCode::NotEqual => { + let b = self.pop(); + let a = self.pop(); + let result = match (&a, &b) { + ( + Value::Int(a), + Value::Int(b), + ) => a != b, + ( + Value::Float(a), + Value::Float(b), + ) => a != b, + ( + Value::Str(a), + Value::Str(b), + ) => a != b, + ( + Value::Bool(a), + Value::Bool(b), + ) => a != b, + _ => { + return Err(format!( + "cannot compare \ + {:?} and {:?}", + a, b + )) + } + }; + self.push(Value::Bool(result)); + } + OpCode::Less => { + let b = self.pop(); + let a = self.pop(); + let result = match (&a, &b) { + ( + Value::Int(a), + Value::Int(b), + ) => a < b, + ( + Value::Float(a), + Value::Float(b), + ) => a < b, + _ => { + return Err(format!( + "cannot compare \ + {:?} and {:?}", + a, b + )) + } + }; + self.push(Value::Bool(result)); + } + OpCode::LessEqual => { + let b = self.pop(); + let a = self.pop(); + let result = match (&a, &b) { + ( + Value::Int(a), + Value::Int(b), + ) => a <= b, + ( + Value::Float(a), + Value::Float(b), + ) => a <= b, + _ => { + return Err(format!( + "cannot compare \ + {:?} and {:?}", + a, b + )) + } + }; + self.push(Value::Bool(result)); + } + OpCode::Greater => { + let b = self.pop(); + let a = self.pop(); + let result = match (&a, &b) { + ( + Value::Int(a), + Value::Int(b), + ) => a > b, + ( + Value::Float(a), + Value::Float(b), + ) => a > b, + _ => { + return Err(format!( + "cannot compare \ + {:?} and {:?}", + a, b + )) + } + }; + self.push(Value::Bool(result)); + } + OpCode::GreaterEqual => { + let b = self.pop(); + let a = self.pop(); + let result = match (&a, &b) { + ( + Value::Int(a), + Value::Int(b), + ) => a >= b, + ( + Value::Float(a), + Value::Float(b), + ) => a >= b, + _ => { + return Err(format!( + "cannot compare \ + {:?} and {:?}", + a, b + )) + } + }; + self.push(Value::Bool(result)); + } OpCode::GetLocal => { - let slot = self.read_byte() as usize; - let value = self.stack[slot].clone(); + let slot = + self.read_byte() as usize; + let offset = + self.frame().slot_offset; + let value = + self.stack[offset + slot] + .clone(); self.push(value); } OpCode::SetLocal => { - let slot = self.read_byte() as usize; - let value = self.stack.last() - .expect("stack underflow").clone(); - self.stack[slot] = value; + let slot = + self.read_byte() as usize; + let offset = + self.frame().slot_offset; + let value = self + .stack + .last() + .expect("stack underflow") + .clone(); + self.stack[offset + slot] = value; self.pop(); } + OpCode::JumpIfFalse => { + let jump = + self.read_u16() as usize; + if let Value::Bool(false) = + self.stack.last().expect( + "stack underflow", + ) + { + self.frame_mut().ip += jump; + } + } + OpCode::Jump => { + let jump = + self.read_u16() as usize; + self.frame_mut().ip += jump; + } + OpCode::Loop => { + let jump = + self.read_u16() as usize; + self.frame_mut().ip -= jump; + } + OpCode::Call => { + let fn_index = + self.read_byte() as usize; + let arg_count = + self.read_byte() as usize; + + let arity = self.functions + [fn_index] + .arity + as usize; + if arg_count != arity { + return Err(format!( + "function '{}' expects \ + {} args, got {}", + self.functions[fn_index] + .name, + arity, + arg_count + )); + } + + let slot_offset = + self.stack.len() - arg_count; + + self.frames.push(CallFrame { + fn_index, + ip: 0, + slot_offset, + }); + } + OpCode::Return => { + let result = self.pop(); + + let frame = self + .frames + .pop() + .expect("no call frame"); + + if self.frames.is_empty() { + // Returning from main + return Ok(()); + } + + // Pop locals + args from stack + self.stack + .truncate(frame.slot_offset); + + // Push return value + self.push(result); + } OpCode::Print => { - let count = self.read_byte() as usize; - let start = self.stack.len() - count; - let args: Vec = - self.stack.drain(start..).collect(); - let output: Vec = - args.iter().map(|v| format!("{v}")).collect(); + let count = + self.read_byte() as usize; + let start = + self.stack.len() - count; + let args: Vec = self + .stack + .drain(start..) + .collect(); + let output: Vec = args + .iter() + .map(|v| format!("{v}")) + .collect(); println!("{}", output.join(" ")); } - OpCode::Return => { - return Ok(()); - } } } }