From b86985deaf98bc3351741a0a121f70ded7e3a9a9 Mon Sep 17 00:00:00 2001 From: Moritz Gmeiner Date: Mon, 30 Jan 2023 17:41:48 +0100 Subject: [PATCH] Finished up to and including chapter 16 --- Cargo.lock | 85 ++++++++++ Cargo.toml | 5 +- frontend/src/parser/expr.rs | 17 +- interpreter/Cargo.toml | 1 + interpreter/src/class.rs | 12 +- interpreter/src/environment.rs | 83 +++++++--- interpreter/src/interpret.rs | 231 ++++++++++++++-------------- interpreter/src/lib.rs | 2 +- interpreter/src/object.rs | 7 +- interpreter/src/resolver/resolve.rs | 15 +- interpreter/src/run.rs | 30 +--- interpreter/src/runtime.rs | 9 +- src/main.rs | 70 +++++++-- vm/Cargo.toml | 17 ++ vm/src/chunk.rs | 68 ++++++++ vm/src/compile.rs | 6 + vm/src/debug.rs | 76 +++++++++ vm/src/disassemble.rs | 61 ++++++++ vm/src/error.rs | 58 +++++++ vm/src/lib.rs | 17 ++ vm/src/opcode.rs | 35 +++++ vm/src/run.rs | 97 ++++++++++++ vm/src/value.rs | 22 +++ vm/src/vm.rs | 225 +++++++++++++++++++++++++++ 24 files changed, 1051 insertions(+), 198 deletions(-) create mode 100644 vm/Cargo.toml create mode 100644 vm/src/chunk.rs create mode 100644 vm/src/compile.rs create mode 100644 vm/src/debug.rs create mode 100644 vm/src/disassemble.rs create mode 100644 vm/src/error.rs create mode 100644 vm/src/lib.rs create mode 100644 vm/src/opcode.rs create mode 100644 vm/src/run.rs create mode 100644 vm/src/value.rs create mode 100644 vm/src/vm.rs diff --git a/Cargo.lock b/Cargo.lock index 3e686db..c877797 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "bitflags" version = "1.3.2" @@ -124,6 +139,12 @@ dependencies = [ "either", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.139" @@ -136,6 +157,32 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.17.0" @@ -247,6 +294,23 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + [[package]] name = "rlox2" version = "0.1.0" @@ -254,6 +318,7 @@ dependencies = [ "clap", "rlox2-frontend", "rlox2-interpreter", + "rlox2-vm", ] [[package]] @@ -271,9 +336,29 @@ version = "0.1.0" dependencies = [ "itertools", "rlox2-frontend", + "rustc-hash", "thiserror", ] +[[package]] +name = "rlox2-vm" +version = "0.1.0" +dependencies = [ + "itertools", + "lazy_static", + "num-derive", + "num-traits", + "regex", + "rlox2-frontend", + "thiserror", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustix" version = "0.36.7" diff --git a/Cargo.toml b/Cargo.toml index d051e43..97455b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ name = "rlox" path = "src/main.rs" [workspace] -members = ["frontend", "interpreter"] +members = ["frontend", "interpreter", "vm"] [dependencies.rlox2-frontend] path = "frontend" @@ -16,5 +16,8 @@ path = "frontend" [dependencies.rlox2-interpreter] path = "interpreter" +[dependencies.rlox2-vm] +path = "vm" + [dependencies] clap = { version = "4.1.4", features = ["derive"] } diff --git a/frontend/src/parser/expr.rs b/frontend/src/parser/expr.rs index d0d0f22..110b401 100644 --- a/frontend/src/parser/expr.rs +++ b/frontend/src/parser/expr.rs @@ -55,9 +55,9 @@ pub enum Expr { value: Box, }, Function { - name: Box, - param_names: Box>, - closure_vars: Box>, + name: String, + param_names: Vec, + closure_vars: Vec<(String, usize)>, body: Box, }, Class { @@ -137,10 +137,11 @@ impl Expr { } pub fn function(name: String, param_names: Vec, body: Stmt) -> Self { - let name = Box::new(name); - let param_names = Box::new(param_names); + // let name = Box::new(name); + // let param_names = Box::new(param_names); #[allow(clippy::box_default)] - let closure_vars = Box::new(Vec::new()); + // let closure_vars = Box::new(Vec::new()); + let closure_vars = Vec::new(); let body = Box::new(body); Self::Function { name, @@ -258,7 +259,7 @@ impl Display for UnaryOp { /*====================================================================================================================*/ -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[rustfmt::skip] pub enum BinaryOp { // arithmetic @@ -289,7 +290,7 @@ impl Display for BinaryOp { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum LogicalOp { Or, And, diff --git a/interpreter/Cargo.toml b/interpreter/Cargo.toml index cecaef4..7659945 100644 --- a/interpreter/Cargo.toml +++ b/interpreter/Cargo.toml @@ -11,3 +11,4 @@ path = "../frontend" [dependencies] thiserror = "1.0.38" itertools = "0.10.5" +rustc-hash = "1.1.0" diff --git a/interpreter/src/class.rs b/interpreter/src/class.rs index 304ecce..bb67dac 100644 --- a/interpreter/src/class.rs +++ b/interpreter/src/class.rs @@ -1,8 +1,8 @@ -use std::collections::HashMap; use std::fmt::Display; use std::rc::Rc; use rlox2_frontend::parser::{Expr, Stmt}; +use rustc_hash::FxHashMap; use crate::{LoxFunction, LoxReference, Value}; @@ -12,12 +12,16 @@ pub struct LoxClass { name: String, - methods: HashMap, + methods: FxHashMap, } /// Representation of a class in Lox. Always behind an Rc to ensure uniqueness. Should never be handled raw. impl LoxClass { - pub fn new(name: impl Into, methods: HashMap, superclass: Option>) -> Rc { + pub fn new( + name: impl Into, + methods: FxHashMap, + superclass: Option>, + ) -> Rc { let name = name.into(); let mut methods = methods; @@ -42,7 +46,7 @@ impl LoxClass { } if let Some(ref superclass) = superclass { - let mut new_methods: HashMap = HashMap::new(); + let mut new_methods: FxHashMap = FxHashMap::default(); // Rc is immutable, so we need to drain, change, and replace for (name, value) in methods { diff --git a/interpreter/src/environment.rs b/interpreter/src/environment.rs index 11809a6..339f44e 100644 --- a/interpreter/src/environment.rs +++ b/interpreter/src/environment.rs @@ -1,48 +1,94 @@ -use std::collections::HashMap; use std::fmt::Display; use std::io::{Read, Write}; +use rustc_hash::FxHashMap; + use crate::error::RuntimeError; use super::value::HeapedValue; use super::{Runtime, Value}; -pub type Scope = HashMap; +pub type Scope = FxHashMap; #[derive(Debug)] pub struct Environment<'a> { - local_scopes: Vec, + scope_stack: Vec, + scope_top: usize, runtime: &'a mut Runtime, + + arg_stack: Vec, } impl<'a> Environment<'a> { pub fn new(runtime: &'a mut Runtime) -> Self { Environment { - // globals: HashMap::new(), - // frames: vec![Frame::new_global()], - local_scopes: Vec::new(), + scope_stack: Vec::new(), + scope_top: 0, runtime, + + arg_stack: Vec::new(), } } - pub fn globals(&self) -> &HashMap { + pub fn push_arg(&mut self, arg: Value) { + self.arg_stack.push(arg); + } + + pub fn num_args(&self) -> usize { + self.arg_stack.len() + } + + pub fn collect_args(&mut self) -> Vec { + std::mem::take(&mut self.arg_stack) + } + + pub fn globals(&self) -> &FxHashMap { self.runtime.globals() } + pub fn scopes(&self) -> &[Scope] { + &self.scope_stack[..self.scope_top] + } + + pub fn scopes_mut(&mut self) -> &mut [Scope] { + &mut self.scope_stack[..self.scope_top] + } + + pub fn has_scope(&self) -> bool { + !self.scope_stack.is_empty() + } + + pub fn current_scope(&self) -> &Scope { + &self.scope_stack[self.scope_top - 1] + } + + pub fn current_scope_mut(&mut self) -> &mut Scope { + &mut self.scope_stack[self.scope_top - 1] + } + pub fn enter_scope(&mut self) { // self.current_frame_mut().enter_scope(); - self.local_scopes.push(Scope::new()); + // self.local_scopes.push(Scope::new()); + + if self.scope_top == self.scope_stack.len() { + self.scope_stack.push(Scope::default()); + } else { + self.scope_stack[self.scope_top].clear(); + } + + self.scope_top += 1; } pub fn exit_scope(&mut self) { // self.current_frame_mut().exit_scope(); - self.local_scopes.pop().expect("Tried to pop global scope"); + // self.local_scopes.pop().expect("Tried to pop global scope"); + self.scope_top -= 1; } pub fn insert_closure(&mut self, closure: Scope) { - let scope = self.local_scopes.last_mut().unwrap(); + let scope = self.current_scope_mut(); for (name, value) in closure { scope.insert(name, value); @@ -54,9 +100,10 @@ impl<'a> Environment<'a> { } pub fn get_local(&self, name: &str, level: usize) -> Result { - let len = self.local_scopes.len(); + let len = self.scopes().len(); + if level < len { - if let Some(heap_value) = self.local_scopes[len - level - 1].get(name) { + if let Some(heap_value) = self.scopes()[len - level - 1].get(name) { return Ok(heap_value.get_clone()); } } @@ -66,9 +113,9 @@ impl<'a> Environment<'a> { } pub fn define(&mut self, name: impl Into, value: Value) { - if let Some(scope) = self.local_scopes.last_mut() { + if self.has_scope() { let name = name.into(); - scope.insert(name, HeapedValue::new(value)); + self.current_scope_mut().insert(name, HeapedValue::new(value)); } else { self.runtime.define_global(name, value) } @@ -79,7 +126,7 @@ impl<'a> Environment<'a> { } pub fn assign(&mut self, name: &str, value: Value, level: usize) -> Result<(), RuntimeError> { - if let Some(scope) = self.local_scopes.iter_mut().rev().nth(level) { + if let Some(scope) = self.scopes_mut().iter_mut().rev().nth(level) { if let Some(heap_value) = scope.get_mut(name) { heap_value.replace(value); return Ok(()); @@ -99,7 +146,7 @@ impl<'a> Environment<'a> { } */ pub fn collect_closure(&self, closure_vars: &[(String, usize)]) -> Scope { - let mut closure_scope = Scope::new(); + let mut closure_scope = Scope::default(); for (name, level) in closure_vars { // special injected variables @@ -108,7 +155,7 @@ impl<'a> Environment<'a> { } let heap_value = self - .local_scopes + .scopes() .iter() .rev() .nth(*level) @@ -130,7 +177,7 @@ impl Display for Environment<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.runtime)?; - for (level, scope) in self.local_scopes.iter().enumerate() { + for (level, scope) in self.scopes().iter().enumerate() { write!(f, "\nScope {level}:")?; for (name, value) in scope.iter() { diff --git a/interpreter/src/interpret.rs b/interpreter/src/interpret.rs index 61065f2..d12ead7 100644 --- a/interpreter/src/interpret.rs +++ b/interpreter/src/interpret.rs @@ -1,7 +1,7 @@ -use std::collections::HashMap; use std::rc::Rc; use rlox2_frontend::parser::{BinaryOp, Expr, Literal, LogicalOp, Stmt, UnaryOp}; +use rustc_hash::FxHashMap; use crate::error::RuntimeError; use crate::function::LoxExternFunction; @@ -29,8 +29,7 @@ trait Eval { } impl Eval for Literal { - fn eval(&self, env: &mut Environment) -> EvalResult { - let _ = env; + fn eval(&self, _env: &mut Environment) -> EvalResult { match self { Literal::String(s) => Ok(Value::String(Rc::clone(s))), Literal::Number(num) => Ok(Value::Number(*num)), @@ -57,57 +56,60 @@ impl Eval for Expr { Expr::Binary { left, op, right } => { use Value::{Bool, Number, String}; + let op = *op; + let left = left.eval(env)?; let right = right.eval(env)?; - match (left, *op, right) { - (Number(left), BinaryOp::Add, Number(right)) => Ok(Number(left + right)), - (Number(left), BinaryOp::Subtract, Number(right)) => Ok(Number(left - right)), - (Number(left), BinaryOp::Multiply, Number(right)) => Ok(Number(left * right)), - (Number(left), BinaryOp::Divide, Number(right)) => { - if right == 0.0 { - return Err(RuntimeError::DivisionByZero); + if op == BinaryOp::Equal { + return Ok(Bool(left == right)); + } + + if op == BinaryOp::NotEqual { + return Ok(Bool(left != right)); + } + + match (left, right) { + (Number(left), Number(right)) => match op { + BinaryOp::Add => Ok(Number(left + right)), + BinaryOp::Subtract => Ok(Number(left - right)), + BinaryOp::Multiply => Ok(Number(left * right)), + BinaryOp::Divide => Ok(Number(left / right)), + BinaryOp::Less => Ok(Bool(left < right)), + BinaryOp::LessEqual => Ok(Bool(left <= right)), + BinaryOp::Greater => Ok(Bool(left > right)), + BinaryOp::GreaterEqual => Ok(Bool(left >= right)), + _ => unreachable!(), + }, + (String(left), String(right)) => match op { + BinaryOp::Add => { + let mut s = std::string::String::with_capacity(left.capacity() + right.capacity()); + s += &left; + s += &right; + Ok(String(Rc::new(s))) } - Ok(Number(left / right)) - } - - (String(left), BinaryOp::Add, String(right)) => { - let mut s = std::string::String::with_capacity(left.capacity() + right.capacity()); - s += &left; - s += &right; - Ok(String(Rc::new(s))) - } - - (left, BinaryOp::Equal, right) => Ok(Bool(left == right)), - (left, BinaryOp::NotEqual, right) => Ok(Bool(left != right)), - - (Number(left), BinaryOp::Less, Number(right)) => Ok(Bool(left < right)), - (Number(left), BinaryOp::LessEqual, Number(right)) => Ok(Bool(left <= right)), - (Number(left), BinaryOp::Greater, Number(right)) => Ok(Bool(left > right)), - (Number(left), BinaryOp::GreaterEqual, Number(right)) => Ok(Bool(left >= right)), - - (String(left), BinaryOp::Less, String(right)) => Ok(Bool(left < right)), - (String(left), BinaryOp::LessEqual, String(right)) => Ok(Bool(left <= right)), - (String(left), BinaryOp::Greater, String(right)) => Ok(Bool(left > right)), - (String(left), BinaryOp::GreaterEqual, String(right)) => Ok(Bool(left >= right)), - - (left, op, right) => Err(RuntimeError::BinaryOpInvalidArguments { left, op, right }), + BinaryOp::Less => Ok(Bool(left < right)), + BinaryOp::LessEqual => Ok(Bool(left <= right)), + BinaryOp::Greater => Ok(Bool(left > right)), + BinaryOp::GreaterEqual => Ok(Bool(left >= right)), + BinaryOp::Equal | BinaryOp::NotEqual => unreachable!(), + _ => Err(RuntimeError::BinaryOpInvalidArguments { + left: String(left), + op, + right: String(right), + }), + }, + (left, right) => Err(RuntimeError::BinaryOpInvalidArguments { left, op, right }), } } Expr::Logical { left, op, right } => { let left = left.eval(env)?; - match op { - LogicalOp::Or => { - if left.is_truthy() { - return Ok(left); - } - } - LogicalOp::And => { - if !left.is_truthy() { - return Ok(left); - } - } + + // shortcircuit + if *op == LogicalOp::Or && left.is_truthy() || *op == LogicalOp::And && !left.is_truthy() { + return Ok(left); } + let right = right.eval(env)?; Ok(right) } @@ -127,15 +129,20 @@ impl Eval for Expr { } Expr::Call { callee, args } => { let callee = callee.eval(env)?; - let args = args - .iter() - .map(|arg| arg.eval(env)) - .collect::>>()?; + /* let args = args + .iter() + .map(|arg| arg.eval(env)) + .collect::>>()?; */ + + for arg in args { + let arg = arg.eval(env)?; + env.push_arg(arg); + } match callee { - Value::Function(fun) => LoxFunction::call(fun, args, env), - Value::ExternFunction(ext_fun) => ext_fun.call(args, env), - Value::Class(class) => LoxClass::call(class, args, env), + Value::Function(fun) => call_fun(fun, env), + Value::ExternFunction(ext_fun) => call_extfun(ext_fun, env), + Value::Class(class) => call_class(class, env), _ => Err(RuntimeError::NotCallable { callee }), } } @@ -169,9 +176,9 @@ impl Eval for Expr { closure_vars, body, } => Ok(Value::function(LoxFunction::new( - name.as_ref(), + name, env.collect_closure(closure_vars), - param_names.as_ref().clone(), + param_names.clone(), body.as_ref().clone(), ))), Expr::Class { @@ -186,7 +193,7 @@ impl Eval for Expr { None => None, }; - let mut methods: HashMap = HashMap::new(); + let mut methods: FxHashMap = FxHashMap::default(); // this is the scope "this" will get injected in env.enter_scope(); @@ -285,71 +292,71 @@ impl Eval for Stmt { /*====================================================================================================================*/ -impl LoxFunction { - pub fn call(fun: Rc, args: Vec, env: &mut Environment) -> EvalResult { - if args.len() != fun.arity() { - return Err(RuntimeError::WrongArity { - name: fun.name().to_owned(), - arity: fun.arity(), - given: args.len(), - }); - } +pub fn call_fun(fun: Rc, env: &mut Environment) -> EvalResult { + let args = env.collect_args(); - env.enter_scope(); - - env.define(fun.name(), Value::Function(fun.clone())); - - env.insert_closure(fun.closure().clone()); - - for (name, value) in std::iter::zip(fun.param_names(), args) { - env.define(name, value); - } - - let ret_val = match fun.body().eval(env) { - Ok(_) => Ok(Value::Nil), - Err(RuntimeError::Return { value }) => Ok(value), - Err(err) => Err(err), - }; - - env.exit_scope(); - - ret_val + if args.len() != fun.arity() { + return Err(RuntimeError::WrongArity { + name: fun.name().to_owned(), + arity: fun.arity(), + given: args.len(), + }); } + + env.enter_scope(); + + env.define(fun.name(), Value::Function(fun.clone())); + + env.insert_closure(fun.closure().clone()); + + for (name, value) in std::iter::zip(fun.param_names(), args) { + env.define(name, value); + } + + let ret_val = match fun.body().eval(env) { + Ok(_) => Ok(Value::Nil), + Err(RuntimeError::Return { value }) => Ok(value), + Err(err) => Err(err), + }; + + env.exit_scope(); + + ret_val } -impl LoxExternFunction { - pub fn call(&self, args: Vec, env: &mut Environment) -> EvalResult { - if args.len() != self.arity() { - return Err(RuntimeError::WrongArity { - name: self.name().to_owned(), - arity: self.arity(), - given: args.len(), - }); - } +pub fn call_extfun(ext_fun: Rc, env: &mut Environment) -> EvalResult { + let args = env.collect_args(); - (self.closure())(args, env) + if args.len() != ext_fun.arity() { + return Err(RuntimeError::WrongArity { + name: ext_fun.name().to_owned(), + arity: ext_fun.arity(), + given: args.len(), + }); } + + (ext_fun.closure())(args, env) } -impl LoxClass { - // has to take class as an argument instead of as self to leave it behind its Rc - pub fn call(class: Rc, args: Vec, env: &mut Environment) -> EvalResult { - if args.len() != class.arity() { - return Err(RuntimeError::WrongArity { - name: class.name().to_owned(), - arity: class.arity(), - given: args.len(), - }); - } +// has to take class as an argument instead of as self to leave it behind its Rc +pub fn call_class(class: Rc, env: &mut Environment) -> EvalResult { + // let args = env.collect_args(); - let object = LoxReference::new(class); - - // object.init(args, env)?; - - if let Some(Value::Function(method)) = object.get("init") { - LoxFunction::call(method, args, env)?; - } - - Ok(Value::Object(object)) + if env.num_args() != class.arity() { + return Err(RuntimeError::WrongArity { + name: class.name().to_owned(), + arity: class.arity(), + given: env.num_args(), + }); } + + let object = LoxReference::new(class); + + // object.init(args, env)?; + + if let Some(Value::Function(method)) = object.get("init") { + call_fun(method, env)?; + } + + Ok(Value::Object(object)) } diff --git a/interpreter/src/lib.rs b/interpreter/src/lib.rs index e41dd4d..3eef28a 100644 --- a/interpreter/src/lib.rs +++ b/interpreter/src/lib.rs @@ -15,6 +15,6 @@ pub use error::{LoxError, RuntimeError}; pub use function::LoxFunction; pub use object::LoxReference; pub use resolver::{resolve, ResolverError}; -pub use run::{run_file, run_repl}; +pub use run::{run, run_repl}; pub use runtime::Runtime; pub use value::Value; diff --git a/interpreter/src/object.rs b/interpreter/src/object.rs index 5d45be2..5148221 100644 --- a/interpreter/src/object.rs +++ b/interpreter/src/object.rs @@ -1,8 +1,9 @@ use std::cell::UnsafeCell; -use std::collections::HashMap; use std::fmt::Display; use std::rc::Rc; +use rustc_hash::FxHashMap; + use crate::{LoxClass, Value}; /// This struct is private, since *nothing* is supposed to be handling an object directly, @@ -11,14 +12,14 @@ use crate::{LoxClass, Value}; struct LoxObject { class: Rc, - attrs: HashMap, + attrs: FxHashMap, } impl LoxObject { fn new(class: Rc) -> Self { LoxObject { class, - attrs: HashMap::new(), + attrs: FxHashMap::default(), } } diff --git a/interpreter/src/resolver/resolve.rs b/interpreter/src/resolver/resolve.rs index 7ae89cf..d1e74ac 100644 --- a/interpreter/src/resolver/resolve.rs +++ b/interpreter/src/resolver/resolve.rs @@ -1,13 +1,12 @@ -use std::collections::HashMap; - use rlox2_frontend::parser::{Expr, Stmt}; +use rustc_hash::FxHashMap; use crate::Runtime; use crate::{LoxError, ResolverError}; /*====================================================================================================================*/ -type ResolverScope = HashMap; +type ResolverScope = FxHashMap; type ResolverFrame = Vec; /*====================================================================================================================*/ @@ -36,12 +35,12 @@ struct Resolver { // local_scopes: Vec, frames: Vec, - closure_vars: HashMap, + closure_vars: FxHashMap, } impl Resolver { fn new(global_names: impl Iterator) -> Self { - let mut global_scope = ResolverScope::new(); + let mut global_scope = ResolverScope::default(); for name in global_names { global_scope.insert(name, ResolveStatus::Defined); @@ -50,7 +49,7 @@ impl Resolver { Resolver { global_scope, frames: vec![ResolverFrame::new()], - closure_vars: HashMap::new(), + closure_vars: FxHashMap::default(), } } @@ -63,7 +62,7 @@ impl Resolver { } fn enter_scope(&mut self) { - self.local_scopes_mut().push(ResolverScope::new()); + self.local_scopes_mut().push(ResolverScope::default()); } fn exit_scope(&mut self) { @@ -73,7 +72,7 @@ impl Resolver { } fn push_frame(&mut self) { - self.frames.push(vec![ResolverScope::new()]); + self.frames.push(vec![ResolverScope::default()]); } fn pop_frame(&mut self) { diff --git a/interpreter/src/run.rs b/interpreter/src/run.rs index 5828ed3..f804841 100644 --- a/interpreter/src/run.rs +++ b/interpreter/src/run.rs @@ -9,32 +9,6 @@ use crate::resolver::resolve; use super::Runtime; -pub fn run_file(runtime: &mut Runtime, script_path: &str) { - let source_code = std::fs::read_to_string(script_path).unwrap_or_else(|err| { - eprintln!("Reading script file {script_path} failed: {err}"); - std::process::exit(66); - }); - - /* if let Err(err) = run(&source_code) { - eprintln!("{}", err); - std::process::exit(65); - } */ - - match run(&source_code, runtime) { - Ok(()) => {} - Err(err) => { - eprintln!("{err}"); - match err { - LoxError::LexerError { .. } | LoxError::ParserError { .. } | LoxError::ResolverError { .. } => { - std::process::exit(65) - } - LoxError::RuntimeError { .. } => std::process::exit(70), - LoxError::Exit { exit_code } => std::process::exit(exit_code), - } - } - } -} - pub fn run_repl(runtime: &mut Runtime) { let stdin = std::io::stdin(); @@ -87,8 +61,8 @@ pub fn run_repl(runtime: &mut Runtime) { } } -fn run(code_string: &str, runtime: &mut Runtime) -> Result<(), LoxError> { - let tokens: Vec = scan_tokens(code_string)?; +pub fn run(source: &str, runtime: &mut Runtime) -> Result<(), LoxError> { + let tokens: Vec = scan_tokens(source)?; /* let token_str = tokens .iter() diff --git a/interpreter/src/runtime.rs b/interpreter/src/runtime.rs index e0d3fe5..73a0dd0 100644 --- a/interpreter/src/runtime.rs +++ b/interpreter/src/runtime.rs @@ -1,14 +1,15 @@ -use std::collections::HashMap; use std::fmt::Display; use std::io::{stdin, stdout, Read, Write}; +use rustc_hash::FxHashMap; + use crate::error::RuntimeError; use super::lox_std::init_std; use super::Value; pub struct Runtime { - globals: HashMap, + globals: FxHashMap, in_stream: Box, out_stream: Box, @@ -18,7 +19,7 @@ impl Runtime { pub fn new(in_stream: Box, out_stream: Box) -> Self { let in_stream = Box::new(std::io::BufReader::new(in_stream)); let mut runtime = Runtime { - globals: HashMap::new(), + globals: FxHashMap::default(), in_stream, out_stream, }; @@ -28,7 +29,7 @@ impl Runtime { runtime } - pub fn globals(&self) -> &HashMap { + pub fn globals(&self) -> &FxHashMap { &self.globals } diff --git a/src/main.rs b/src/main.rs index fc2709b..1bb5db7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,4 @@ use clap::{ArgAction, Parser}; -use rlox2_interpreter::{run_file, run_repl, Runtime}; #[derive(Parser, Debug)] struct CliArgs { @@ -8,24 +7,73 @@ struct CliArgs { #[arg(short, action = ArgAction::SetTrue)] interactive: bool, + + #[arg(long, action = ArgAction::SetTrue)] + vm: bool, } +/* +let source_code = std::fs::read_to_string(script_path).unwrap_or_else(|err| { + eprintln!("Reading script file {script_path} failed: {err}"); + std::process::exit(66); +}); */ + fn main() { - interpreter_main(); -} - -pub fn interpreter_main() { let cli_args = CliArgs::parse(); - let mut runtime = Runtime::default(); + if cli_args.vm { + use rlox2_vm::InterpretError; - if let Some(file_name) = cli_args.file_name { - run_file(&mut runtime, &file_name); + let mut vm = rlox2_vm::VM::default(); - if cli_args.interactive { - run_repl(&mut runtime); + if let Some(file_name) = cli_args.file_name { + let source = std::fs::read_to_string(&file_name).unwrap_or_else(|err| { + eprintln!("Reading script file {file_name} failed: {err}"); + std::process::exit(66); + }); + + if let Err(err) = rlox2_vm::run(&source, &mut vm) { + eprintln!("{err}"); + match err { + InterpretError::LexerError { .. } | InterpretError::CompileError { .. } => std::process::exit(65), + InterpretError::RuntimeError { .. } => std::process::exit(70), + InterpretError::Exit { exit_code } => std::process::exit(exit_code), + } + } + + if !cli_args.interactive { + return; + } } + + rlox2_vm::run_repl(&mut vm); } else { - run_repl(&mut runtime); + use rlox2_interpreter::LoxError; + + let mut runtime = rlox2_interpreter::Runtime::default(); + + if let Some(file_name) = cli_args.file_name { + let source = std::fs::read_to_string(&file_name).unwrap_or_else(|err| { + eprintln!("Reading script file {file_name} failed: {err}"); + std::process::exit(66); + }); + + if let Err(err) = rlox2_interpreter::run(&source, &mut runtime) { + eprintln!("{err}"); + match err { + LoxError::LexerError { .. } | LoxError::ParserError { .. } | LoxError::ResolverError { .. } => { + std::process::exit(65) + } + LoxError::RuntimeError { .. } => std::process::exit(70), + LoxError::Exit { exit_code } => std::process::exit(exit_code), + } + } + + if !cli_args.interactive { + return; + } + } + + rlox2_interpreter::run_repl(&mut runtime); } } diff --git a/vm/Cargo.toml b/vm/Cargo.toml new file mode 100644 index 0000000..884ad15 --- /dev/null +++ b/vm/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rlox2-vm" +version = "0.1.0" +edition = "2021" + + +[dependencies.rlox2-frontend] +path = "../frontend" + + +[dependencies] +itertools = "0.10.5" +lazy_static = "1.4.0" +num-derive = "0.3.3" +num-traits = "0.2.15" +regex = "1.7.1" +thiserror = "1.0.38" diff --git a/vm/src/chunk.rs b/vm/src/chunk.rs new file mode 100644 index 0000000..a3288d5 --- /dev/null +++ b/vm/src/chunk.rs @@ -0,0 +1,68 @@ +use crate::debug::ChunkDebugInfo; +use crate::{Opcode, Value}; + +#[derive(Debug, Default)] +pub struct Chunk { + code: Vec, + + constants: Vec, + + debug_info: Option, +} + +impl Chunk { + pub fn new() -> Self { + Chunk { + code: Vec::new(), + constants: Vec::new(), + debug_info: Some(ChunkDebugInfo::new()), + } + } + + pub fn code(&self) -> &[u8] { + &self.code + } + + pub fn debug_info(&self) -> Option<&ChunkDebugInfo> { + self.debug_info.as_ref() + } + + pub fn debug_info_mut(&mut self) -> Option<&mut ChunkDebugInfo> { + self.debug_info.as_mut() + } + + pub fn write_byte(&mut self, byte: T, line: u32) + where + T: TryInto + std::fmt::Debug, + >::Error: std::fmt::Debug, + { + let byte = byte.try_into().unwrap(); + + self.code.push(byte); + + if let Some(ref mut debug_info) = self.debug_info { + debug_info.write_line(line, self.code.len() - 1); + } + } + + pub fn write_bytes(&mut self, bytes: &[u8], line: u32) { + for byte in bytes { + self.write_byte(*byte, line); + } + } + + pub fn write_opcode(&mut self, opcode: Opcode, line: u32) { + let byte = opcode as u8; + self.write_byte(byte, line); + } + + pub fn add_constant(&mut self, value: Value) -> usize { + self.constants.push(value); + + self.constants.len() - 1 + } + + pub fn get_constant(&self, offset: usize) -> &Value { + &self.constants[offset] + } +} diff --git a/vm/src/compile.rs b/vm/src/compile.rs new file mode 100644 index 0000000..561de7c --- /dev/null +++ b/vm/src/compile.rs @@ -0,0 +1,6 @@ +use itertools::Itertools; +use rlox2_frontend::lexer::Token; + +pub fn compile(tokens: Vec) { + println!("{}", tokens.iter().map(|token| token.to_string()).join(" ")); +} diff --git a/vm/src/debug.rs b/vm/src/debug.rs new file mode 100644 index 0000000..e8f47f1 --- /dev/null +++ b/vm/src/debug.rs @@ -0,0 +1,76 @@ +#[derive(Debug)] +struct LineInfo { + start_offset: usize, + line: u32, +} + +#[derive(Debug, Default)] +pub struct ChunkDebugInfo { + line_infos: Vec, +} + +impl ChunkDebugInfo { + pub fn new() -> Self { + ChunkDebugInfo { line_infos: Vec::new() } + } + + pub fn write_line(&mut self, line: u32, offset: usize) { + let line_info = || LineInfo { + start_offset: offset, + line, + }; + + if self.line_infos.is_empty() { + self.line_infos.push(line_info()) + } + + match line.cmp(&self.line_infos.last().unwrap().line) { + // assert increasing line numbers + std::cmp::Ordering::Less => panic!( + "Tried to write_byte for line {line}, but line {} is already written", + self.line_infos.last().unwrap().line + ), + // line same as for last byte -> do nothing + std::cmp::Ordering::Equal => {} + // line greater than for last byte -> insert new line marker at current position + std::cmp::Ordering::Greater => self.line_infos.push(line_info()), + } + } + + pub fn line_number(&self, offset: usize) -> u32 { + /* if self.line_infos.len() > 2 && offset < self.line_infos[1].start_offset { + assert!(offset >= self.line_infos[0].start_offset); + + return self.line_infos[0].line; + } + + let mut low = 0; // will be max { line_offset : line_offset < offset } + let mut high = self.line_infos.len() - 1; // will be min { line_offset : line_offset >= offset } + + // bisect line_numbers + while high > low + 1 { + // as high >= low + 2, never high or low + let mid = (low + high) / 2; + + match offset.cmp(&self.line_infos[mid].start_offset) { + std::cmp::Ordering::Less => high = mid, + std::cmp::Ordering::Equal => return self.line_infos[mid].line, + std::cmp::Ordering::Greater => low = mid, + } + } + + self.line_infos[high].line */ + + // special case: all start_offsets are <= than offset: + // need to manually return the last line number as slice::partition_point doesn't like that + if self.line_infos.last().unwrap().start_offset <= offset { + return self.line_infos.last().unwrap().line; + } + + let idx = self + .line_infos + .partition_point(|line_info| line_info.start_offset < offset); + + self.line_infos[idx].line + } +} diff --git a/vm/src/disassemble.rs b/vm/src/disassemble.rs new file mode 100644 index 0000000..104f628 --- /dev/null +++ b/vm/src/disassemble.rs @@ -0,0 +1,61 @@ +use num_traits::FromPrimitive; + +use crate::Chunk; +use crate::Opcode; + +impl Chunk { + pub fn disassemble(&self, name: &str) { + println!("==== begin {name} ====\n"); + + let mut offset = 0; + + while offset < self.code().len() { + offset = self.disassemble_instruction(offset) + } + + println!("\n==== end {name} ===="); + } + + pub fn disassemble_instruction(&self, offset: usize) -> usize { + use Opcode::*; + + let mut offset = offset; + + print!("{offset:04} "); + + if let Some(debug_info) = self.debug_info() { + if offset == 0 || debug_info.line_number(offset) != debug_info.line_number(offset - 1) { + let line = debug_info.line_number(offset); + print!("{line:4} "); + } else { + print!(" | "); + }; + } + + let opcode: Opcode = FromPrimitive::from_u8(self.code()[offset]).unwrap(); + offset += 1; + + print!("{opcode:<16} "); + + match opcode { + LoadConst => { + let constant_idx = self.code()[offset]; + let value = self.get_constant(constant_idx as usize); + print!("{constant_idx:4} '{value}'"); + offset += 1; + } + LoadConstLong => { + let bytes = &self.code()[offset..offset + 3]; + let constant_idx = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], 0]); + let value = self.get_constant(constant_idx as usize); + print!("{constant_idx:4} '{value}'"); + offset += 3; + } + Add | Subtract | Multiply | Divide | Negate | Return => {} + } + + println!(); + + offset + } +} diff --git a/vm/src/error.rs b/vm/src/error.rs new file mode 100644 index 0000000..d19e63a --- /dev/null +++ b/vm/src/error.rs @@ -0,0 +1,58 @@ +use itertools::Itertools; +use rlox2_frontend::lexer::LexerError; +use thiserror::Error; + +use crate::{Opcode, Value}; + +#[derive(Error, Debug)] +pub enum CompileError {} + +#[derive(Error, Debug)] +pub enum RuntimeError { + #[error("Opcopde {opcode} had invalid operand {operand}")] + UnaryInvalidOperand { opcode: Opcode, operand: Value }, + #[error("Opcopde {opcode} had invalid operands {left} and {right}")] + BinaryInvalidOperand { opcode: Opcode, left: Value, right: Value }, + #[error("Division by zero")] + DivisionByZero, +} + +#[derive(Error, Debug)] +pub enum InterpretError { + #[error("{0}", format_multiple_errors(inner))] + LexerError { inner: Vec }, + #[error("{inner}")] + CompileError { inner: CompileError }, + #[error("{inner}")] + RuntimeError { inner: RuntimeError }, + #[error("Called exit() with exit code {exit_code}")] + Exit { exit_code: i32 }, +} + +impl From> for InterpretError { + fn from(lexer_errs: Vec) -> Self { + InterpretError::LexerError { inner: lexer_errs } + } +} + +impl From for InterpretError { + fn from(compile_err: CompileError) -> Self { + InterpretError::CompileError { inner: compile_err } + } +} + +impl From for InterpretError { + fn from(runtime_err: RuntimeError) -> Self { + InterpretError::RuntimeError { inner: runtime_err } + } +} + +fn format_multiple_errors(errs: &Vec) -> String { + let msg = if errs.len() == 1 { + errs[0].to_string() + } else { + errs.iter().map(|err| err.to_string()).join("\n") + }; + + msg +} diff --git a/vm/src/lib.rs b/vm/src/lib.rs new file mode 100644 index 0000000..58f44e2 --- /dev/null +++ b/vm/src/lib.rs @@ -0,0 +1,17 @@ +mod chunk; +mod compile; +mod debug; +mod disassemble; +mod error; +mod opcode; +mod run; +mod value; +mod vm; + +pub use chunk::Chunk; +pub use compile::compile; +pub use error::InterpretError; +pub use opcode::Opcode; +pub use run::{run, run_repl}; +pub use value::Value; +pub use vm::VM; diff --git a/vm/src/opcode.rs b/vm/src/opcode.rs new file mode 100644 index 0000000..e809654 --- /dev/null +++ b/vm/src/opcode.rs @@ -0,0 +1,35 @@ +use std::fmt::Display; + +use itertools::Itertools; +use lazy_static::lazy_static; +use num_derive::FromPrimitive; +use regex::Regex; + +lazy_static! { + static ref CAMEL_CASE_REGEX: Regex = Regex::new(r"[A-Z][a-z]*").unwrap(); +} + +#[repr(u8)] +#[derive(Debug, FromPrimitive, Clone, Copy)] +#[allow(non_camel_case_types)] // hack so Constant_Long will be printed as CONSTANT_LONG rather than CONSTANTLONG +pub enum Opcode { + LoadConst, + LoadConstLong, + Add, + Subtract, + Multiply, + Divide, + Negate, + Return, +} + +impl Display for Opcode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = CAMEL_CASE_REGEX + .captures_iter(&format!("{self:?}")) + .map(|cap| cap[0].to_ascii_uppercase()) + .join("_"); + + f.pad(&name) + } +} diff --git a/vm/src/run.rs b/vm/src/run.rs new file mode 100644 index 0000000..98c7093 --- /dev/null +++ b/vm/src/run.rs @@ -0,0 +1,97 @@ +use std::io::Write; + +use rlox2_frontend::lexer::{scan_tokens, Token}; + +use crate::{compile, InterpretError, VM}; + +/* pub fn vm_main() { + let mut chunk = Chunk::new(); + + let constant1 = chunk.add_constant(Value::Number(1.2)); + let constant2 = chunk.add_constant(Value::Number(3.4)); + let constant3 = chunk.add_constant(Value::Number(5.6)); + + chunk.write_opcode(Opcode::LoadConst, 123); + chunk.write_byte(constant1, 123); + + chunk.write_opcode(Opcode::LoadConst, 123); + chunk.write_byte(constant2, 123); + + chunk.write_opcode(Opcode::Add, 123); + + chunk.write_opcode(Opcode::LoadConst, 124); + chunk.write_byte(constant3, 124); + + chunk.write_opcode(Opcode::Divide, 124); + chunk.write_opcode(Opcode::Negate, 124); + + chunk.write_opcode(Opcode::Return, 125); + + println!(); + chunk.disassemble("test chunk"); + println!(); + + let mut vm = VM::new(); + + vm.interpret(&chunk).unwrap(); +} */ + +pub fn run_repl(vm: &mut VM) { + let stdin = std::io::stdin(); + + loop { + let mut input_buf = String::new(); + + print!("> "); + std::io::stdout().flush().unwrap(); + + 'inner: loop { + stdin.read_line(&mut input_buf).unwrap_or_else(|err| { + eprintln!("Could not read from stdin: {err}"); + std::process::exit(66); + }); + + let num_open_braces = (input_buf.matches('{').count() as i64) - (input_buf.matches('}').count() as i64); + let num_open_parens = (input_buf.matches('(').count() as i64) - (input_buf.matches(')').count() as i64); + let num_open_brackets = (input_buf.matches('[').count() as i64) - (input_buf.matches(']').count() as i64); + + // all braces/parens/brackets closed => break + if num_open_braces == 0 && num_open_parens == 0 && num_open_brackets == 0 { + break 'inner; + } + + // any braces/parens/brackets more closing than opening => break (will be parse error) + if num_open_braces < 0 || num_open_parens < 0 || num_open_brackets < 0 { + break 'inner; + } + + print!("< "); + + // let indentation = " ".repeat((num_open_braces + num_open_brackets + num_open_parens) as usize); + + // print!("{indentation}"); + + std::io::stdout().flush().unwrap(); + } + + let input_buf = input_buf.trim(); + + if input_buf.is_empty() || input_buf == "exit" || input_buf == "quit" { + std::process::exit(0); + } + + match run(input_buf, vm) { + Ok(()) => {} + Err(InterpretError::Exit { exit_code }) => std::process::exit(exit_code), + Err(err) => eprintln!("{err}"), + } + } +} + +pub fn run(source: &str, _vm: &mut VM) -> Result<(), InterpretError> { + let tokens: Vec = scan_tokens(source)?; + + compile(tokens); + + Ok(()) +} diff --git a/vm/src/value.rs b/vm/src/value.rs new file mode 100644 index 0000000..53fc080 --- /dev/null +++ b/vm/src/value.rs @@ -0,0 +1,22 @@ +use std::fmt::Display; + +#[derive(Debug, Clone, PartialEq)] +pub enum Value { + Nil, + Number(f64), +} + +impl Default for Value { + fn default() -> Self { + Value::Nil + } +} + +impl Display for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Value::Nil => write!(f, "nil"), + Value::Number(num) => write!(f, "{num}"), + } + } +} diff --git a/vm/src/vm.rs b/vm/src/vm.rs new file mode 100644 index 0000000..215ed5a --- /dev/null +++ b/vm/src/vm.rs @@ -0,0 +1,225 @@ +use std::ptr; + +use num_traits::FromPrimitive; + +use crate::error::RuntimeError; +use crate::{Chunk, Opcode}; +use crate::{InterpretError, Value}; + +/*====================================================================================================================*/ + +const STACK_MAX: usize = 256; +const DEBUG_TRACE_EXECUTION: bool = true; + +/*====================================================================================================================*/ + +#[derive(Debug)] +pub struct VM { + chunk_ptr: *const Chunk, + + ip: *const u8, + + stack: [Value; STACK_MAX], + stack_top: *mut Value, +} + +/* macro_rules! debug_println { + ($($arg:tt)*) => { + if cfg!(debug_assertions) { + println!($($arg)*); + } + }; +} */ + +impl VM { + pub fn new() -> Self { + const NIL: Value = Value::Nil; + let stack = [NIL; STACK_MAX]; + + let mut vm = VM { + chunk_ptr: ptr::null(), + ip: ptr::null(), + stack, + stack_top: ptr::null_mut(), + }; + + vm.stack_top = vm.stack.as_mut_ptr(); + + vm + } + + unsafe fn chunk(&self) -> &Chunk { + &*self.chunk_ptr + } + + unsafe fn offset(&self) -> usize { + let offset = self.ip.offset_from(self.chunk().code().as_ptr()); + + debug_assert!(offset >= 0); + + offset as usize + } + + unsafe fn stack_len(&self) -> usize { + let offset = self.stack_top.offset_from(self.stack.as_ptr()); + + debug_assert!(offset >= 0); + + offset as usize + } + + unsafe fn push_value(&mut self, value: Value) { + // ptr::write(self.stack_top, value); + debug_assert!(self.stack_len() < STACK_MAX); + let old_value = std::mem::replace(&mut *self.stack_top, value); + debug_assert_eq!(old_value, Value::Nil); + self.stack_top = self.stack_top.add(1); + } + + unsafe fn pop_value(&mut self) -> Value { + assert!(self.stack_len() < STACK_MAX); + + self.stack_top = self.stack_top.sub(1); + // ptr::read(self.stack_top) + std::mem::take(&mut *self.stack_top) + } + + pub fn interpret(&mut self, chunk: &Chunk) -> Result<(), InterpretError> { + self.chunk_ptr = chunk; + self.ip = chunk.code().as_ptr(); + + unsafe { self.run()? } + + Ok(()) + } + + unsafe fn read_byte(&mut self) -> u8 { + debug_assert!(self.offset() < self.chunk().code().len()); + + let byte = *self.ip; + self.ip = self.ip.add(1); + byte + } + + unsafe fn read_constant(&mut self) -> &Value { + let constant_idx = self.read_byte() as usize; + + self.chunk().get_constant(constant_idx) + } + + unsafe fn read_constant_long(&mut self) -> &Value { + let bytes = [self.read_byte(), self.read_byte(), self.read_byte(), 0]; + let constant_idx = u32::from_le_bytes(bytes) as usize; + + self.chunk().get_constant(constant_idx) + } + + unsafe fn run(&mut self) -> Result<(), RuntimeError> { + loop { + if DEBUG_TRACE_EXECUTION { + println!(); + self.print_stack(); + self.chunk().disassemble_instruction(self.offset()); + println!(); + } + + let opcode: Opcode = FromPrimitive::from_u8(self.read_byte()).unwrap(); + + match opcode { + Opcode::LoadConst => { + let value = self.read_constant().clone(); + println!("Constant: {value}"); + self.push_value(value); + } + Opcode::LoadConstLong => { + let value = self.read_constant_long().clone(); + println!("LongConstant: {value}"); + self.push_value(value); + } + + Opcode::Add => { + let right = self.pop_value(); + let left = self.pop_value(); + match (left, right) { + (Value::Number(left), Value::Number(right)) => { + self.push_value(Value::Number(left + right)); + } + // (Value::String(left), Value::String(right)) => todo!(), + (left, right) => return Err(RuntimeError::BinaryInvalidOperand { opcode, left, right }), + } + } + Opcode::Subtract => { + let right = self.pop_value(); + let left = self.pop_value(); + match (left, right) { + (Value::Number(left), Value::Number(right)) => { + self.push_value(Value::Number(left - right)); + } + (left, right) => return Err(RuntimeError::BinaryInvalidOperand { opcode, left, right }), + } + } + Opcode::Multiply => { + let right = self.pop_value(); + let left = self.pop_value(); + match (left, right) { + (Value::Number(left), Value::Number(right)) => { + self.push_value(Value::Number(left * right)); + } + (left, right) => return Err(RuntimeError::BinaryInvalidOperand { opcode, left, right }), + } + } + Opcode::Divide => { + let right = self.pop_value(); + let left = self.pop_value(); + match (left, right) { + (Value::Number(left), Value::Number(right)) => { + if right == 0.0 { + return Err(RuntimeError::DivisionByZero); + } + self.push_value(Value::Number(left / right)); + } + (left, right) => return Err(RuntimeError::BinaryInvalidOperand { opcode, left, right }), + } + } + Opcode::Negate => { + let value = self.pop_value(); + println!("Negate: {value}"); + if let Value::Number(num) = value { + self.push_value(Value::Number(-num)); + } else { + return Err(RuntimeError::UnaryInvalidOperand { opcode, operand: value }); + } + } + Opcode::Return => { + let value = self.pop_value(); + debug_assert_eq!(self.stack_len(), 0); + println!("Return: {value}"); + return Ok(()); + } + } + } + } + + unsafe fn print_stack(&self) { + /* let s = self.stack[0..self.stack_len()] + .iter() + .map(|value| value.to_string()) + .join(", "); + + print!("{s}"); */ + + print!("Stack: "); + + for value in self.stack[0..self.stack_len()].iter() { + print!("[ {value} ]"); + } + + println!() + } +} + +impl Default for VM { + fn default() -> Self { + VM::new() + } +}