feat(tazjin/rlox): Bootstrap VM for Lox bytecode
Change-Id: I479e20bf2087e5c4aa20e31b364c57ed0d961bcf Reviewed-on: https://cl.tvl.fyi/c/depot/+/2416 Tested-by: BuildkiteCI Reviewed-by: tazjin <mail@tazj.in>
This commit is contained in:
parent
b1d0e22b1f
commit
7fb93fb490
7 changed files with 89 additions and 25 deletions
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
|||
authors = ["Vincent Ambo <mail@tazj.in>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
[features]
|
||||
# Enables debugging/disassembling in the bytecode interpreter. Off by
|
||||
# default as it is quite spammy.
|
||||
disassemble = []
|
||||
|
|
|
@ -10,7 +10,7 @@ use super::value;
|
|||
// problem.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Chunk {
|
||||
code: Vec<OpCode>,
|
||||
pub code: Vec<OpCode>,
|
||||
lines: Vec<Span>,
|
||||
constants: Vec<value::Value>,
|
||||
}
|
||||
|
@ -38,6 +38,10 @@ impl Chunk {
|
|||
idx
|
||||
}
|
||||
|
||||
pub fn constant(&self, idx: usize) -> &value::Value {
|
||||
self.constants.index(idx)
|
||||
}
|
||||
|
||||
fn add_line(&mut self, line: usize) {
|
||||
match self.lines.last_mut() {
|
||||
Some(span) if span.line == line => span.count += 1,
|
||||
|
@ -58,26 +62,11 @@ impl Chunk {
|
|||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Chunk {
|
||||
type Output = OpCode;
|
||||
|
||||
fn index(&self, offset: usize) -> &Self::Output {
|
||||
self.code.index(offset)
|
||||
}
|
||||
}
|
||||
|
||||
// Disassembler
|
||||
pub fn disassemble(chunk: &Chunk, name: &str) {
|
||||
println!("== {} ==", name);
|
||||
|
||||
for (idx, _) in chunk.code.iter().enumerate() {
|
||||
disassemble_instruction(chunk, idx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Print a single disassembled instruction at the specified offset.
|
||||
/// Some instructions are printed "raw", others have special handling.
|
||||
fn disassemble_instruction(chunk: &Chunk, offset: usize) {
|
||||
pub fn disassemble_instruction(chunk: &Chunk, offset: usize) {
|
||||
print!("{:04} ", offset);
|
||||
|
||||
let line = chunk.get_line(offset);
|
||||
|
@ -87,8 +76,8 @@ fn disassemble_instruction(chunk: &Chunk, offset: usize) {
|
|||
print!("{:4} ", line);
|
||||
}
|
||||
|
||||
match &chunk[offset] {
|
||||
OpCode::OpConstant(idx) => println!("OpConstant idx '{:?}'", chunk.constants[*idx]),
|
||||
match chunk.code.index(offset) {
|
||||
OpCode::OpConstant(idx) => println!("OpConstant({}) '{:?}'", *idx, chunk.constant(*idx)),
|
||||
op => println!("{:?}", op),
|
||||
}
|
||||
}
|
||||
|
|
13
users/tazjin/rlox/src/bytecode/errors.rs
Normal file
13
users/tazjin/rlox/src/bytecode/errors.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
#[derive(Debug)]
|
||||
pub enum ErrorKind {
|
||||
// CompileError,
|
||||
// RuntimeError,
|
||||
InternalError(&'static str),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
pub kind: ErrorKind,
|
||||
}
|
||||
|
||||
pub type LoxResult<T> = Result<T, Error>;
|
|
@ -3,8 +3,10 @@
|
|||
//! https://craftinginterpreters.com/chunks-of-bytecode.html
|
||||
|
||||
mod chunk;
|
||||
mod errors;
|
||||
mod opcode;
|
||||
mod value;
|
||||
mod vm;
|
||||
|
||||
use chunk::Chunk;
|
||||
use opcode::OpCode;
|
||||
|
@ -16,5 +18,5 @@ pub fn main() {
|
|||
chunk.add_op(OpCode::OpConstant(constant), 1);
|
||||
chunk.add_op(OpCode::OpReturn, 1);
|
||||
|
||||
chunk::disassemble(&chunk, "test chunk");
|
||||
vm::interpret(chunk).expect("it should work");
|
||||
}
|
||||
|
|
59
users/tazjin/rlox/src/bytecode/vm.rs
Normal file
59
users/tazjin/rlox/src/bytecode/vm.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use super::chunk;
|
||||
use super::errors::*;
|
||||
use super::opcode::OpCode;
|
||||
use super::value::Value;
|
||||
|
||||
pub struct VM {
|
||||
chunk: chunk::Chunk,
|
||||
|
||||
// TODO(tazjin): Accessing array elements constantly is not ideal,
|
||||
// lets see if something clever can be done with iterators.
|
||||
ip: usize,
|
||||
|
||||
stack: Vec<Value>,
|
||||
}
|
||||
|
||||
impl VM {
|
||||
fn push(&mut self, value: Value) {
|
||||
self.stack.push(value)
|
||||
}
|
||||
|
||||
fn pop(&mut self) -> Value {
|
||||
self.stack.pop().expect("fatal error: stack empty!")
|
||||
}
|
||||
}
|
||||
|
||||
impl VM {
|
||||
fn run(&mut self) -> LoxResult<()> {
|
||||
loop {
|
||||
let op = &self.chunk.code[self.ip];
|
||||
|
||||
#[cfg(feature = "disassemble")]
|
||||
chunk::disassemble_instruction(&self.chunk, self.ip);
|
||||
|
||||
self.ip += 1;
|
||||
|
||||
match op {
|
||||
OpCode::OpReturn => {
|
||||
println!("{:?}", self.pop());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
OpCode::OpConstant(idx) => {
|
||||
let c = *self.chunk.constant(*idx);
|
||||
self.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interpret(chunk: chunk::Chunk) -> LoxResult<()> {
|
||||
let mut vm = VM {
|
||||
chunk,
|
||||
ip: 0,
|
||||
stack: vec![],
|
||||
};
|
||||
|
||||
vm.run()
|
||||
}
|
|
@ -2,8 +2,8 @@ use std::fmt;
|
|||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use crate::treewalk::errors::Error;
|
||||
use crate::treewalk::parser::Literal;
|
||||
use crate::treewalk::interpreter::Value;
|
||||
use crate::treewalk::parser::Literal;
|
||||
|
||||
pub trait Builtin: fmt::Debug {
|
||||
fn arity(&self) -> usize;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::*;
|
||||
|
||||
mod errors;
|
||||
pub mod interpreter;
|
||||
mod parser;
|
||||
mod resolver;
|
||||
mod scanner;
|
||||
pub mod interpreter;
|
||||
|
||||
pub fn main() {
|
||||
let mut args = env::args();
|
||||
|
|
Loading…
Reference in a new issue