Skip to content

Commit

Permalink
feat: implemented VM
Browse files Browse the repository at this point in the history
  • Loading branch information
ZakFarmer committed Oct 22, 2023
1 parent c0f5954 commit e239b21
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 7 deletions.
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[workspace]
members = [ "opcode", "compiler", "lexer", "parser", "evaluator", "object", "interpreter"]
members = [ "opcode", "compiler", "lexer", "parser", "evaluator", "object", "interpreter", "vm"]
3 changes: 3 additions & 0 deletions compiler/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::rc::Rc;

use anyhow::Error;
use opcode::Opcode;
use parser::ast::{Expression, IntegerLiteral, Literal, Node, Statement};

pub struct Bytecode {
Expand Down Expand Up @@ -76,6 +77,8 @@ impl Compiler {
Statement::Expr(e) => {
self.compile_expression(e)?;

self.emit(Opcode::OpPop, vec![]);

return Ok(());
}
_ => {
Expand Down
2 changes: 2 additions & 0 deletions interpreter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition = "2021"

[dependencies]
anyhow = "1.0.75"
compiler = { path = "../compiler" }
env_logger = "0.10.0"
evaluator = { path = "../evaluator" }
lazy_static = "1.4.0"
Expand All @@ -15,3 +16,4 @@ log = "0.4.20"
object = { path = "../object" }
parser = { path = "../parser" }
rustyline = { version = "12.0.0", features = ["with-file-history"] }
vm = { path = "../vm" }
19 changes: 13 additions & 6 deletions interpreter/src/repl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ use std::{cell::RefCell, rc::Rc};

use anyhow::{Error, Result};

use compiler::Compiler;
use lexer::Lexer;
use object::environment::Environment;
use parser::Parser;
use parser::{Parser, ast::Node};
use rustyline::error::ReadlineError;
use vm::Vm;

const PROMPT: &str = ">> ";

Expand All @@ -19,8 +21,6 @@ pub fn init_repl() -> Result<(), Error> {

println!("php-rs interpreter v{}", env!("CARGO_PKG_VERSION"));

let env = Rc::new(RefCell::new(Environment::new()));

loop {
let readline = rl.readline(format!("{}", PROMPT).as_str());

Expand All @@ -33,12 +33,19 @@ pub fn init_repl() -> Result<(), Error> {
let mut parser = Parser::new(lexer);

let program = parser.parse_program()?;

parser.check_errors()?;

let evaluated = evaluator::eval_statements(&program.statements, &env)?;
let mut compiler = Compiler::new();

compiler.compile(&Node::Program(program))?;

let mut vm = Vm::new(compiler.bytecode());

vm.run()?;

let last_popped = vm.last_popped_stack_elem();
println!("{}", last_popped);

println!("{}", evaluated);
Ok(())
})();

Expand Down
8 changes: 8 additions & 0 deletions opcode/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,14 @@ impl From<u8> for Opcode {
}
}

impl std::fmt::Display for Opcode {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let definition = lookup(*self);

write!(f, "{}", definition.name)
}
}

pub fn read_operands(def: &OpcodeDefinition, ins: &[u8]) -> (Vec<usize>, usize) {
let mut operands = Vec::with_capacity(def.operand_widths.len());
let mut offset = 0;
Expand Down
8 changes: 8 additions & 0 deletions parser/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ impl std::fmt::Display for Node {
#[derive(Clone, Debug, PartialEq)]
pub enum Literal {
Integer(IntegerLiteral),
Float(FloatLiteral),
Boolean(BooleanLiteral),
String(StringLiteral),
Array(ArrayLiteral),
Expand All @@ -30,6 +31,7 @@ impl std::fmt::Display for Literal {
Literal::Integer(IntegerLiteral { token: _, value }) => write!(f, "{}", value),
Literal::Boolean(BooleanLiteral { token: _, value }) => write!(f, "{}", value),
Literal::String(StringLiteral { token: _, value }) => write!(f, "{}", value),
Literal::Float(FloatLiteral { token: _, value }) => write!(f, "{}", value),
Literal::Array(ArrayLiteral { token: _, elements }) => {
let mut elements_string = String::new();

Expand Down Expand Up @@ -189,6 +191,12 @@ pub struct IntegerLiteral {
pub value: i64,
}

#[derive(Clone, Debug, PartialEq)]
pub struct FloatLiteral {
pub token: Token,
pub value: f64,
}

#[derive(Clone, Debug, PartialEq)]
pub struct StringLiteral {
pub token: Token,
Expand Down
17 changes: 17 additions & 0 deletions vm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "vm"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.75"
compiler = { path = "../compiler" }
env_logger = "0.10.0"
lexer = { path = "../lexer" }
object = { path = "../object" }
opcode = { path = "../opcode" }
parser = { path = "../parser" }
log = "0.4.20"
byteorder = "1.5.0"
140 changes: 140 additions & 0 deletions vm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use std::{rc::Rc, borrow::Borrow};

use anyhow::Error;
use byteorder::{BigEndian, ByteOrder};
use compiler::Bytecode;
use object::Object;
use opcode::Opcode;

const STACK_SIZE: usize = 2048;

pub struct Vm {
constants: Vec<Rc<Object>>,
instructions: opcode::Instructions,

stack: Vec<Rc<Object>>,
stack_pointer: usize
}

impl Vm {
pub fn new(bytecode: Bytecode) -> Self {
Self {
constants: bytecode.constants,
instructions: bytecode.instructions,

stack: vec![Rc::new(Object::Null); STACK_SIZE],
stack_pointer: 0
}
}

pub fn run(&mut self) -> Result<(), Error> {
let mut ip = 0;

while ip < self.instructions.0.len() {
let op = Opcode::from(self.instructions.0[ip]);
ip += 1;

match op {
Opcode::OpConst => {
let const_index = BigEndian::read_u16(&self.instructions.0[ip..ip + 2]) as usize;
ip += 2;

self.push(Rc::clone(&self.constants[const_index]));
}
Opcode::OpAdd => {
let right = self.pop();
let left = self.pop();

let result = match (&* left, &* right) {
(Object::Integer(l), Object::Integer(r)) => Object::Integer(l + r),
_ => {
return Err(Error::msg(format!(
"unsupported types for addition: {} + {}",
left, right
)));
}
};

self.push(Rc::new(result));
}
Opcode::OpDiv => {
let right = self.stack[self.stack_pointer - 1].borrow();
let left = self.stack[self.stack_pointer - 2].borrow();

let result = match (left, right) {
(Object::Integer(l), Object::Integer(r)) => Object::Integer(l / r),
_ => {
return Err(Error::msg(format!(
"unsupported types for division: {} / {}",
left, right
)));
}
};

self.stack_pointer -= 1;
self.stack[self.stack_pointer - 1] = Rc::new(result);
}
Opcode::OpMul => {
let right = self.stack[self.stack_pointer - 1].borrow();
let left = self.stack[self.stack_pointer - 2].borrow();

let result = match (left, right) {
(Object::Integer(l), Object::Integer(r)) => Object::Integer(l * r),
_ => {
return Err(Error::msg(format!(
"unsupported types for multiplication: {} * {}",
left, right
)));
}
};

self.stack_pointer -= 1;
self.stack[self.stack_pointer - 1] = Rc::new(result);
}
Opcode::OpSub => {
let right = self.stack[self.stack_pointer - 1].borrow();
let left = self.stack[self.stack_pointer - 2].borrow();

let result = match (left, right) {
(Object::Integer(l), Object::Integer(r)) => Object::Integer(l - r),
_ => {
return Err(Error::msg(format!(
"unsupported types for subtraction: {} - {}",
left, right
)));
}
};

self.stack_pointer -= 1;
self.stack[self.stack_pointer - 1] = Rc::new(result);
}
Opcode::OpPop => {
self.pop();
}
_ => {
return Err(Error::msg(format!("unknown opcode: {}", op)));
}
}
}

Ok(())
}

pub fn last_popped_stack_elem(&self) -> Rc<Object> {
Rc::clone(&self.stack[self.stack_pointer])
}

pub fn pop(&mut self) -> Rc<Object> {
self.stack_pointer -= 1;
Rc::clone(&self.stack[self.stack_pointer])
}

pub fn push(&mut self, obj: Rc<Object>) {
self.stack[self.stack_pointer] = obj;
self.stack_pointer += 1;
}

pub fn stack_top(&self) -> Rc<Object> {
Rc::clone(&self.stack[self.stack_pointer - 1])
}
}
69 changes: 69 additions & 0 deletions vm/tests/vm_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use anyhow::Error;
use compiler::Compiler;
use lexer::Lexer;
use parser::{Parser, ast::Node};
use vm::Vm;

struct VmTestCase {
input: String,
expected: String,
}

fn run_vm_tests(tests: Vec<VmTestCase>) -> Result<(), Error> {
for test in tests {
let mut parser = Parser::new(Lexer::new(&test.input));

let program = parser.parse_program()?;
let mut compiler = Compiler::new();

let bytecode = compiler.compile(&Node::Program(program))?;

let mut vm = Vm::new(bytecode);

vm.run()?;

let stack_elem = vm.last_popped_stack_elem();

assert_eq!(stack_elem.to_string(), test.expected);
}

Ok(())
}

#[test]
fn test_integer_arithmetic() -> Result<(), Error> {
let tests = vec![
VmTestCase {
input: "1".to_string(),
expected: "1".to_string(),
},
VmTestCase {
input: "2".to_string(),
expected: "2".to_string(),
},
VmTestCase {
input: "1 + 2".to_string(),
expected: "3".to_string(),
},
VmTestCase {
input: "1 - 2".to_string(),
expected: "-1".to_string(),
},
VmTestCase {
input: "1 * 2".to_string(),
expected: "2".to_string(),
},
VmTestCase {
input: "4 / 2".to_string(),
expected: "2".to_string(),
},
VmTestCase {
input: "50 / 2 * 2 + 10 - 5".to_string(),
expected: "55".to_string(),
},
];

run_vm_tests(tests)?;

Ok(())
}

0 comments on commit e239b21

Please sign in to comment.