commit - 37c978c740aa2c23c2be7f6165ee14b888c150b5
commit + 862cec691012ceb800f90f956da2b62c8811e4f6
blob - fca54dff3512cbf3bac7237083caa50df0523e85
blob + 880b6fa2587ba1fb9699f81e65fb64eab69bf956
--- src/compiler/opcode.rs
+++ src/compiler/opcode.rs
Div,
Mod,
Negate,
+ Not,
+ Equal,
+ NotEqual,
+ Less,
+ LessEqual,
+ Greater,
+ GreaterEqual,
GetLocal,
SetLocal,
+ JumpIfFalse,
+ Jump,
+ Loop,
+ Call,
Print,
Return,
}
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<u8>,
pub constants: Vec<Value>,
pub lines: Vec<usize>,
+ 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<String>,
+ 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
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<Chunk>,
+ frames: Vec<CallFrame>,
stack: Vec<Value>,
}
impl VM {
- pub fn new(chunk: Chunk) -> Self {
- Self {
- chunk,
+ pub fn new(
+ functions: Vec<Chunk>,
+ 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);
}
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 => {
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);
}
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);
}
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);
}
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);
}
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<Value> =
- self.stack.drain(start..).collect();
- let output: Vec<String> =
- args.iter().map(|v| format!("{v}")).collect();
+ let count =
+ self.read_byte() as usize;
+ let start =
+ self.stack.len() - count;
+ let args: Vec<Value> = self
+ .stack
+ .drain(start..)
+ .collect();
+ let output: Vec<String> = args
+ .iter()
+ .map(|v| format!("{v}"))
+ .collect();
println!("{}", output.join(" "));
}
- OpCode::Return => {
- return Ok(());
- }
}
}
}