Skip to content

Commit

Permalink
bytecode interpreter now support adding two integer constants
Browse files Browse the repository at this point in the history
  • Loading branch information
oysandvik94 committed Aug 12, 2024
1 parent a8f3842 commit b609370
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 37 deletions.
13 changes: 13 additions & 0 deletions crates/interpreter/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ impl Compiler {
) {
self.compile_expression(left);
self.compile_expression(right);

match operator {
Operator::Bang => todo!(),
Operator::Minus => todo!(),
Operator::Plus => self.emit(OpCode::Add, &[]),
Operator::Multiply => todo!(),
Operator::Equals => todo!(),
Operator::NotEquals => todo!(),
Operator::GreaterThan => todo!(),
Operator::LessThan => todo!(),
Operator::DividedBy => todo!(),
};
}

fn add_constant(&mut self, object: PrimitiveObject) -> usize {
Expand Down Expand Up @@ -112,6 +124,7 @@ mod tests {
expected_instructions: vec![
Instructions(bytecode::make(OpCode::OpConstant, &[0])),
Instructions(bytecode::make(OpCode::OpConstant, &[1])),
Instructions(bytecode::make(OpCode::Add, &[])),
],
}];

Expand Down
86 changes: 51 additions & 35 deletions crates/interpreter/src/compiler/bytecode.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::fmt::Display;

use anyhow::Result;

use crate::compiler::bytecode;

use super::internal_error::InternalError;
Expand All @@ -9,36 +11,11 @@ use super::internal_error::InternalError;
#[derive(Clone, Default, Debug, PartialEq)]
pub struct Instructions(pub Vec<u8>);

impl Display for Instructions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut n = 0;
while n < self.0.len() {
let op_code = bytecode::OpCode::try_from(self.0[n]).map_err(|_| std::fmt::Error)?;
let (operands, read) = bytecode::read_operands(&op_code, &self.0[n + 1..]);

if op_code.operand_widths().len() != operands.len() {
write!(f, "ERROR: Operand len does not match defined")?;
return Ok(());
}

let operand_str = match operands.len() {
1 => format!("{op_code} {}", operands[0]),
count => format!("{op_code} Unhandled operator count for {count}"),
};

writeln!(f, "{:04} {}", n, operand_str)?;

n += 1 + read as usize
}

Ok(())
}
}

#[repr(u8)]
#[derive(Clone)]
pub enum OpCode {
OpConstant = 0,
Add = 1,
}

impl TryFrom<u8> for OpCode {
Expand All @@ -47,6 +24,7 @@ impl TryFrom<u8> for OpCode {
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(OpCode::OpConstant),
1 => Ok(OpCode::Add),
unknown_opcode => Err(InternalError::UnknownOpcode(unknown_opcode)),
}
}
Expand All @@ -56,6 +34,7 @@ impl OpCode {
pub fn operand_widths(&self) -> Vec<i32> {
match self {
OpCode::OpConstant => vec![2],
OpCode::Add => vec![],
}
}
}
Expand All @@ -64,6 +43,7 @@ impl Display for OpCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OpCode::OpConstant => write!(f, "OpConstant"),
OpCode::Add => write!(f, "Add"),
}
}
}
Expand Down Expand Up @@ -117,6 +97,32 @@ fn read_operands(op: &OpCode, instruction: &[u8]) -> (Vec<u16>, u32) {
(operands, offset as u32)
}

impl Display for Instructions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut n = 0;
while n < self.0.len() {
let op_code = bytecode::OpCode::try_from(self.0[n]).map_err(|_| std::fmt::Error)?;
let (operands, read) = bytecode::read_operands(&op_code, &self.0[n + 1..]);

if op_code.operand_widths().len() != operands.len() {
write!(f, "ERROR: Operand len does not match defined")?;
return Ok(());
}

let operand_str = match operands.len() {
1 => format!("{op_code} {}", operands[0]),
count => format!("{op_code} Unhandled operator count for {count}"),
};

writeln!(f, "{:04} {}", n, operand_str)?;

n += 1 + read as usize
}

Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -142,14 +148,15 @@ mod tests {
}

#[test]
#[ignore = "The decompiler is a total mess"]
fn test_instruction_strings() {
let instructions = vec![
bytecode::make(OpCode::OpConstant, &[1]),
let instructions = [
bytecode::make(OpCode::Add, &[]),
bytecode::make(OpCode::OpConstant, &[2]),
bytecode::make(OpCode::OpConstant, &[65535]),
];

let expected = "0000 OpConstant 1\n\
let expected = "0000 OpAdd\n\
0003 OpConstant 2\n\
0006 OpConstant 65535\n";

Expand All @@ -165,15 +172,24 @@ mod tests {
expected: Vec<u8>,
}

let test_cases: Vec<TestCase> = vec![TestCase {
op: OpCode::OpConstant,
operands: vec![65534],
expected: vec![OpCode::OpConstant as u8, 255, 254],
}];
let test_cases: Vec<TestCase> = vec![
TestCase {
op: OpCode::OpConstant,
operands: vec![65534],
expected: vec![OpCode::OpConstant as u8, 255, 254],
},
TestCase {
op: OpCode::Add,
operands: vec![],
expected: vec![OpCode::Add as u8],
},
];

for test_case in test_cases {
let instruction = Instructions(make(test_case.op, &test_case.operands).to_vec());
let instruction = make(test_case.op, &test_case.operands).to_vec();
let instruction = Instructions(instruction);
let expected_instructions = Instructions(test_case.expected);

assert_eq!(
instruction, expected_instructions,
"instruction has wrong byte encoding. want={}, got={}",
Expand Down
2 changes: 2 additions & 0 deletions crates/interpreter/src/compiler/internal_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ use thiserror::Error;
pub enum InternalError {
#[error("The opcode {0} was not recognized by the compiler")]
UnknownOpcode(u8),
#[error("Tried to pop element of stack, but it was empty")]
PoppedEmptyStack,
}
27 changes: 25 additions & 2 deletions crates/interpreter/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod runtime_error;
use crate::{
compiler::{
bytecode::{self, Instructions, OpCode},
internal_error::InternalError,
ByteCode,
},
eval::objects::{Object, PrimitiveObject},
Expand Down Expand Up @@ -35,10 +36,10 @@ impl VirtualMachine {
while instruction_pointer < self.instructions.0.len() {
let operation_byte: u8 = self.instructions.0[instruction_pointer];
let operation = bytecode::OpCode::try_from(operation_byte)?;
instruction_pointer += 1;

match operation {
OpCode::OpConstant => {
instruction_pointer += 1;
let operands: [u8; 2] = [
self.instructions.0[instruction_pointer],
self.instructions.0[instruction_pointer + 1],
Expand All @@ -50,6 +51,20 @@ impl VirtualMachine {
let constant = self.constants[constant_index].clone();
self.push(Object::Primitive(constant))?;
}
OpCode::Add => {
let right = self.pop()?;
let left = self.pop()?;

let res = match (right, left) {
(
Object::Primitive(PrimitiveObject::Integer(left)),
Object::Primitive(PrimitiveObject::Integer(right)),
) => left + right,
_ => todo!(),
};

self.push(Object::primitive_from_int(res))?;
}
}
}

Expand All @@ -72,6 +87,14 @@ impl VirtualMachine {
Ok(())
}

fn pop(&mut self) -> Result<Object> {
self.stack_pointer -= 1;
match self.stack.pop() {
Some(object) => Ok(object),
None => bail!(InternalError::PoppedEmptyStack),
}
}

pub fn stack_top(&self) -> Object {
if self.stack_pointer == 0 {
return Object::Void;
Expand Down Expand Up @@ -112,7 +135,7 @@ mod tests {
},
VmTestCase {
input: "1 + 2",
expected: 2,
expected: 3,
},
];

Expand Down

0 comments on commit b609370

Please sign in to comment.