Finished up to and including chapter 16

This commit is contained in:
Moritz Gmeiner 2023-01-30 17:41:48 +01:00
commit b86985deaf
24 changed files with 1051 additions and 198 deletions

85
Cargo.lock generated
View file

@ -2,6 +2,21 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 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]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@ -124,6 +139,12 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.139" version = "0.2.139"
@ -136,6 +157,32 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" 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]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.17.0" version = "1.17.0"
@ -247,6 +294,23 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 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]] [[package]]
name = "rlox2" name = "rlox2"
version = "0.1.0" version = "0.1.0"
@ -254,6 +318,7 @@ dependencies = [
"clap", "clap",
"rlox2-frontend", "rlox2-frontend",
"rlox2-interpreter", "rlox2-interpreter",
"rlox2-vm",
] ]
[[package]] [[package]]
@ -271,9 +336,29 @@ version = "0.1.0"
dependencies = [ dependencies = [
"itertools", "itertools",
"rlox2-frontend", "rlox2-frontend",
"rustc-hash",
"thiserror", "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]] [[package]]
name = "rustix" name = "rustix"
version = "0.36.7" version = "0.36.7"

View file

@ -8,7 +8,7 @@ name = "rlox"
path = "src/main.rs" path = "src/main.rs"
[workspace] [workspace]
members = ["frontend", "interpreter"] members = ["frontend", "interpreter", "vm"]
[dependencies.rlox2-frontend] [dependencies.rlox2-frontend]
path = "frontend" path = "frontend"
@ -16,5 +16,8 @@ path = "frontend"
[dependencies.rlox2-interpreter] [dependencies.rlox2-interpreter]
path = "interpreter" path = "interpreter"
[dependencies.rlox2-vm]
path = "vm"
[dependencies] [dependencies]
clap = { version = "4.1.4", features = ["derive"] } clap = { version = "4.1.4", features = ["derive"] }

View file

@ -55,9 +55,9 @@ pub enum Expr {
value: Box<Expr>, value: Box<Expr>,
}, },
Function { Function {
name: Box<String>, name: String,
param_names: Box<Vec<String>>, param_names: Vec<String>,
closure_vars: Box<Vec<(String, usize)>>, closure_vars: Vec<(String, usize)>,
body: Box<Stmt>, body: Box<Stmt>,
}, },
Class { Class {
@ -137,10 +137,11 @@ impl Expr {
} }
pub fn function(name: String, param_names: Vec<String>, body: Stmt) -> Self { pub fn function(name: String, param_names: Vec<String>, body: Stmt) -> Self {
let name = Box::new(name); // let name = Box::new(name);
let param_names = Box::new(param_names); // let param_names = Box::new(param_names);
#[allow(clippy::box_default)] #[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); let body = Box::new(body);
Self::Function { Self::Function {
name, name,
@ -258,7 +259,7 @@ impl Display for UnaryOp {
/*====================================================================================================================*/ /*====================================================================================================================*/
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[rustfmt::skip] #[rustfmt::skip]
pub enum BinaryOp { pub enum BinaryOp {
// arithmetic // arithmetic
@ -289,7 +290,7 @@ impl Display for BinaryOp {
} }
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogicalOp { pub enum LogicalOp {
Or, Or,
And, And,

View file

@ -11,3 +11,4 @@ path = "../frontend"
[dependencies] [dependencies]
thiserror = "1.0.38" thiserror = "1.0.38"
itertools = "0.10.5" itertools = "0.10.5"
rustc-hash = "1.1.0"

View file

@ -1,8 +1,8 @@
use std::collections::HashMap;
use std::fmt::Display; use std::fmt::Display;
use std::rc::Rc; use std::rc::Rc;
use rlox2_frontend::parser::{Expr, Stmt}; use rlox2_frontend::parser::{Expr, Stmt};
use rustc_hash::FxHashMap;
use crate::{LoxFunction, LoxReference, Value}; use crate::{LoxFunction, LoxReference, Value};
@ -12,12 +12,16 @@ pub struct LoxClass {
name: String, name: String,
methods: HashMap<String, Value>, methods: FxHashMap<String, Value>,
} }
/// Representation of a class in Lox. Always behind an Rc to ensure uniqueness. Should never be handled raw. /// Representation of a class in Lox. Always behind an Rc to ensure uniqueness. Should never be handled raw.
impl LoxClass { impl LoxClass {
pub fn new(name: impl Into<String>, methods: HashMap<String, Value>, superclass: Option<Rc<LoxClass>>) -> Rc<Self> { pub fn new(
name: impl Into<String>,
methods: FxHashMap<String, Value>,
superclass: Option<Rc<LoxClass>>,
) -> Rc<Self> {
let name = name.into(); let name = name.into();
let mut methods = methods; let mut methods = methods;
@ -42,7 +46,7 @@ impl LoxClass {
} }
if let Some(ref superclass) = superclass { if let Some(ref superclass) = superclass {
let mut new_methods: HashMap<String, Value> = HashMap::new(); let mut new_methods: FxHashMap<String, Value> = FxHashMap::default();
// Rc<LoxFunction> is immutable, so we need to drain, change, and replace // Rc<LoxFunction> is immutable, so we need to drain, change, and replace
for (name, value) in methods { for (name, value) in methods {

View file

@ -1,48 +1,94 @@
use std::collections::HashMap;
use std::fmt::Display; use std::fmt::Display;
use std::io::{Read, Write}; use std::io::{Read, Write};
use rustc_hash::FxHashMap;
use crate::error::RuntimeError; use crate::error::RuntimeError;
use super::value::HeapedValue; use super::value::HeapedValue;
use super::{Runtime, Value}; use super::{Runtime, Value};
pub type Scope = HashMap<String, HeapedValue>; pub type Scope = FxHashMap<String, HeapedValue>;
#[derive(Debug)] #[derive(Debug)]
pub struct Environment<'a> { pub struct Environment<'a> {
local_scopes: Vec<Scope>, scope_stack: Vec<Scope>,
scope_top: usize,
runtime: &'a mut Runtime, runtime: &'a mut Runtime,
arg_stack: Vec<Value>,
} }
impl<'a> Environment<'a> { impl<'a> Environment<'a> {
pub fn new(runtime: &'a mut Runtime) -> Self { pub fn new(runtime: &'a mut Runtime) -> Self {
Environment { Environment {
// globals: HashMap::new(), scope_stack: Vec::new(),
// frames: vec![Frame::new_global()], scope_top: 0,
local_scopes: Vec::new(),
runtime, runtime,
arg_stack: Vec::new(),
} }
} }
pub fn globals(&self) -> &HashMap<String, Value> { 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<Value> {
std::mem::take(&mut self.arg_stack)
}
pub fn globals(&self) -> &FxHashMap<String, Value> {
self.runtime.globals() 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) { pub fn enter_scope(&mut self) {
// self.current_frame_mut().enter_scope(); // 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) { pub fn exit_scope(&mut self) {
// self.current_frame_mut().exit_scope(); // 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) { 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 { for (name, value) in closure {
scope.insert(name, value); scope.insert(name, value);
@ -54,9 +100,10 @@ impl<'a> Environment<'a> {
} }
pub fn get_local(&self, name: &str, level: usize) -> Result<Value, RuntimeError> { pub fn get_local(&self, name: &str, level: usize) -> Result<Value, RuntimeError> {
let len = self.local_scopes.len(); let len = self.scopes().len();
if level < 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()); return Ok(heap_value.get_clone());
} }
} }
@ -66,9 +113,9 @@ impl<'a> Environment<'a> {
} }
pub fn define(&mut self, name: impl Into<String>, value: Value) { pub fn define(&mut self, name: impl Into<String>, value: Value) {
if let Some(scope) = self.local_scopes.last_mut() { if self.has_scope() {
let name = name.into(); let name = name.into();
scope.insert(name, HeapedValue::new(value)); self.current_scope_mut().insert(name, HeapedValue::new(value));
} else { } else {
self.runtime.define_global(name, value) 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> { 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) { if let Some(heap_value) = scope.get_mut(name) {
heap_value.replace(value); heap_value.replace(value);
return Ok(()); return Ok(());
@ -99,7 +146,7 @@ impl<'a> Environment<'a> {
} */ } */
pub fn collect_closure(&self, closure_vars: &[(String, usize)]) -> Scope { 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 { for (name, level) in closure_vars {
// special injected variables // special injected variables
@ -108,7 +155,7 @@ impl<'a> Environment<'a> {
} }
let heap_value = self let heap_value = self
.local_scopes .scopes()
.iter() .iter()
.rev() .rev()
.nth(*level) .nth(*level)
@ -130,7 +177,7 @@ impl Display for Environment<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.runtime)?; 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}:")?; write!(f, "\nScope {level}:")?;
for (name, value) in scope.iter() { for (name, value) in scope.iter() {

View file

@ -1,7 +1,7 @@
use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use rlox2_frontend::parser::{BinaryOp, Expr, Literal, LogicalOp, Stmt, UnaryOp}; use rlox2_frontend::parser::{BinaryOp, Expr, Literal, LogicalOp, Stmt, UnaryOp};
use rustc_hash::FxHashMap;
use crate::error::RuntimeError; use crate::error::RuntimeError;
use crate::function::LoxExternFunction; use crate::function::LoxExternFunction;
@ -29,8 +29,7 @@ trait Eval {
} }
impl Eval for Literal { impl Eval for Literal {
fn eval(&self, env: &mut Environment) -> EvalResult<Value> { fn eval(&self, _env: &mut Environment) -> EvalResult<Value> {
let _ = env;
match self { match self {
Literal::String(s) => Ok(Value::String(Rc::clone(s))), Literal::String(s) => Ok(Value::String(Rc::clone(s))),
Literal::Number(num) => Ok(Value::Number(*num)), Literal::Number(num) => Ok(Value::Number(*num)),
@ -57,57 +56,60 @@ impl Eval for Expr {
Expr::Binary { left, op, right } => { Expr::Binary { left, op, right } => {
use Value::{Bool, Number, String}; use Value::{Bool, Number, String};
let op = *op;
let left = left.eval(env)?; let left = left.eval(env)?;
let right = right.eval(env)?; let right = right.eval(env)?;
match (left, *op, right) { if op == BinaryOp::Equal {
(Number(left), BinaryOp::Add, Number(right)) => Ok(Number(left + right)), return Ok(Bool(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);
}
Ok(Number(left / right))
} }
(String(left), BinaryOp::Add, String(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()); let mut s = std::string::String::with_capacity(left.capacity() + right.capacity());
s += &left; s += &left;
s += &right; s += &right;
Ok(String(Rc::new(s))) Ok(String(Rc::new(s)))
} }
BinaryOp::Less => Ok(Bool(left < right)),
(left, BinaryOp::Equal, right) => Ok(Bool(left == right)), BinaryOp::LessEqual => Ok(Bool(left <= right)),
(left, BinaryOp::NotEqual, right) => Ok(Bool(left != right)), BinaryOp::Greater => Ok(Bool(left > right)),
BinaryOp::GreaterEqual => Ok(Bool(left >= right)),
(Number(left), BinaryOp::Less, Number(right)) => Ok(Bool(left < right)), BinaryOp::Equal | BinaryOp::NotEqual => unreachable!(),
(Number(left), BinaryOp::LessEqual, Number(right)) => Ok(Bool(left <= right)), _ => Err(RuntimeError::BinaryOpInvalidArguments {
(Number(left), BinaryOp::Greater, Number(right)) => Ok(Bool(left > right)), left: String(left),
(Number(left), BinaryOp::GreaterEqual, Number(right)) => Ok(Bool(left >= right)), op,
right: String(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)), (left, right) => Err(RuntimeError::BinaryOpInvalidArguments { left, op, right }),
(String(left), BinaryOp::GreaterEqual, String(right)) => Ok(Bool(left >= right)),
(left, op, right) => Err(RuntimeError::BinaryOpInvalidArguments { left, op, right }),
} }
} }
Expr::Logical { left, op, right } => { Expr::Logical { left, op, right } => {
let left = left.eval(env)?; let left = left.eval(env)?;
match op {
LogicalOp::Or => { // shortcircuit
if left.is_truthy() { if *op == LogicalOp::Or && left.is_truthy() || *op == LogicalOp::And && !left.is_truthy() {
return Ok(left); return Ok(left);
} }
}
LogicalOp::And => {
if !left.is_truthy() {
return Ok(left);
}
}
}
let right = right.eval(env)?; let right = right.eval(env)?;
Ok(right) Ok(right)
} }
@ -127,15 +129,20 @@ impl Eval for Expr {
} }
Expr::Call { callee, args } => { Expr::Call { callee, args } => {
let callee = callee.eval(env)?; let callee = callee.eval(env)?;
let args = args /* let args = args
.iter() .iter()
.map(|arg| arg.eval(env)) .map(|arg| arg.eval(env))
.collect::<EvalResult<Vec<Value>>>()?; .collect::<EvalResult<Vec<Value>>>()?; */
for arg in args {
let arg = arg.eval(env)?;
env.push_arg(arg);
}
match callee { match callee {
Value::Function(fun) => LoxFunction::call(fun, args, env), Value::Function(fun) => call_fun(fun, env),
Value::ExternFunction(ext_fun) => ext_fun.call(args, env), Value::ExternFunction(ext_fun) => call_extfun(ext_fun, env),
Value::Class(class) => LoxClass::call(class, args, env), Value::Class(class) => call_class(class, env),
_ => Err(RuntimeError::NotCallable { callee }), _ => Err(RuntimeError::NotCallable { callee }),
} }
} }
@ -169,9 +176,9 @@ impl Eval for Expr {
closure_vars, closure_vars,
body, body,
} => Ok(Value::function(LoxFunction::new( } => Ok(Value::function(LoxFunction::new(
name.as_ref(), name,
env.collect_closure(closure_vars), env.collect_closure(closure_vars),
param_names.as_ref().clone(), param_names.clone(),
body.as_ref().clone(), body.as_ref().clone(),
))), ))),
Expr::Class { Expr::Class {
@ -186,7 +193,7 @@ impl Eval for Expr {
None => None, None => None,
}; };
let mut methods: HashMap<String, Value> = HashMap::new(); let mut methods: FxHashMap<String, Value> = FxHashMap::default();
// this is the scope "this" will get injected in // this is the scope "this" will get injected in
env.enter_scope(); env.enter_scope();
@ -285,8 +292,9 @@ impl Eval for Stmt {
/*====================================================================================================================*/ /*====================================================================================================================*/
impl LoxFunction { pub fn call_fun(fun: Rc<LoxFunction>, env: &mut Environment) -> EvalResult<Value> {
pub fn call(fun: Rc<LoxFunction>, args: Vec<Value>, env: &mut Environment) -> EvalResult<Value> { let args = env.collect_args();
if args.len() != fun.arity() { if args.len() != fun.arity() {
return Err(RuntimeError::WrongArity { return Err(RuntimeError::WrongArity {
name: fun.name().to_owned(), name: fun.name().to_owned(),
@ -314,31 +322,31 @@ impl LoxFunction {
env.exit_scope(); env.exit_scope();
ret_val ret_val
}
} }
impl LoxExternFunction { pub fn call_extfun(ext_fun: Rc<LoxExternFunction>, env: &mut Environment) -> EvalResult<Value> {
pub fn call(&self, args: Vec<Value>, env: &mut Environment) -> EvalResult<Value> { let args = env.collect_args();
if args.len() != self.arity() {
if args.len() != ext_fun.arity() {
return Err(RuntimeError::WrongArity { return Err(RuntimeError::WrongArity {
name: self.name().to_owned(), name: ext_fun.name().to_owned(),
arity: self.arity(), arity: ext_fun.arity(),
given: args.len(), given: args.len(),
}); });
} }
(self.closure())(args, env) (ext_fun.closure())(args, env)
}
} }
impl LoxClass { // has to take class as an argument instead of as self to leave it behind its Rc
// has to take class as an argument instead of as self to leave it behind its Rc pub fn call_class(class: Rc<LoxClass>, env: &mut Environment) -> EvalResult<Value> {
pub fn call(class: Rc<LoxClass>, args: Vec<Value>, env: &mut Environment) -> EvalResult<Value> { // let args = env.collect_args();
if args.len() != class.arity() {
if env.num_args() != class.arity() {
return Err(RuntimeError::WrongArity { return Err(RuntimeError::WrongArity {
name: class.name().to_owned(), name: class.name().to_owned(),
arity: class.arity(), arity: class.arity(),
given: args.len(), given: env.num_args(),
}); });
} }
@ -347,9 +355,8 @@ impl LoxClass {
// object.init(args, env)?; // object.init(args, env)?;
if let Some(Value::Function(method)) = object.get("init") { if let Some(Value::Function(method)) = object.get("init") {
LoxFunction::call(method, args, env)?; call_fun(method, env)?;
} }
Ok(Value::Object(object)) Ok(Value::Object(object))
}
} }

View file

@ -15,6 +15,6 @@ pub use error::{LoxError, RuntimeError};
pub use function::LoxFunction; pub use function::LoxFunction;
pub use object::LoxReference; pub use object::LoxReference;
pub use resolver::{resolve, ResolverError}; pub use resolver::{resolve, ResolverError};
pub use run::{run_file, run_repl}; pub use run::{run, run_repl};
pub use runtime::Runtime; pub use runtime::Runtime;
pub use value::Value; pub use value::Value;

View file

@ -1,8 +1,9 @@
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::fmt::Display; use std::fmt::Display;
use std::rc::Rc; use std::rc::Rc;
use rustc_hash::FxHashMap;
use crate::{LoxClass, Value}; use crate::{LoxClass, Value};
/// This struct is private, since *nothing* is supposed to be handling an object directly, /// This struct is private, since *nothing* is supposed to be handling an object directly,
@ -11,14 +12,14 @@ use crate::{LoxClass, Value};
struct LoxObject { struct LoxObject {
class: Rc<LoxClass>, class: Rc<LoxClass>,
attrs: HashMap<String, Value>, attrs: FxHashMap<String, Value>,
} }
impl LoxObject { impl LoxObject {
fn new(class: Rc<LoxClass>) -> Self { fn new(class: Rc<LoxClass>) -> Self {
LoxObject { LoxObject {
class, class,
attrs: HashMap::new(), attrs: FxHashMap::default(),
} }
} }

View file

@ -1,13 +1,12 @@
use std::collections::HashMap;
use rlox2_frontend::parser::{Expr, Stmt}; use rlox2_frontend::parser::{Expr, Stmt};
use rustc_hash::FxHashMap;
use crate::Runtime; use crate::Runtime;
use crate::{LoxError, ResolverError}; use crate::{LoxError, ResolverError};
/*====================================================================================================================*/ /*====================================================================================================================*/
type ResolverScope = HashMap<String, ResolveStatus>; type ResolverScope = FxHashMap<String, ResolveStatus>;
type ResolverFrame = Vec<ResolverScope>; type ResolverFrame = Vec<ResolverScope>;
/*====================================================================================================================*/ /*====================================================================================================================*/
@ -36,12 +35,12 @@ struct Resolver {
// local_scopes: Vec<ResolverScope>, // local_scopes: Vec<ResolverScope>,
frames: Vec<ResolverFrame>, frames: Vec<ResolverFrame>,
closure_vars: HashMap<String, usize>, closure_vars: FxHashMap<String, usize>,
} }
impl Resolver { impl Resolver {
fn new(global_names: impl Iterator<Item = String>) -> Self { fn new(global_names: impl Iterator<Item = String>) -> Self {
let mut global_scope = ResolverScope::new(); let mut global_scope = ResolverScope::default();
for name in global_names { for name in global_names {
global_scope.insert(name, ResolveStatus::Defined); global_scope.insert(name, ResolveStatus::Defined);
@ -50,7 +49,7 @@ impl Resolver {
Resolver { Resolver {
global_scope, global_scope,
frames: vec![ResolverFrame::new()], frames: vec![ResolverFrame::new()],
closure_vars: HashMap::new(), closure_vars: FxHashMap::default(),
} }
} }
@ -63,7 +62,7 @@ impl Resolver {
} }
fn enter_scope(&mut self) { fn enter_scope(&mut self) {
self.local_scopes_mut().push(ResolverScope::new()); self.local_scopes_mut().push(ResolverScope::default());
} }
fn exit_scope(&mut self) { fn exit_scope(&mut self) {
@ -73,7 +72,7 @@ impl Resolver {
} }
fn push_frame(&mut self) { fn push_frame(&mut self) {
self.frames.push(vec![ResolverScope::new()]); self.frames.push(vec![ResolverScope::default()]);
} }
fn pop_frame(&mut self) { fn pop_frame(&mut self) {

View file

@ -9,32 +9,6 @@ use crate::resolver::resolve;
use super::Runtime; 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) { pub fn run_repl(runtime: &mut Runtime) {
let stdin = std::io::stdin(); 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> { pub fn run(source: &str, runtime: &mut Runtime) -> Result<(), LoxError> {
let tokens: Vec<Token> = scan_tokens(code_string)?; let tokens: Vec<Token> = scan_tokens(source)?;
/* let token_str = tokens /* let token_str = tokens
.iter() .iter()

View file

@ -1,14 +1,15 @@
use std::collections::HashMap;
use std::fmt::Display; use std::fmt::Display;
use std::io::{stdin, stdout, Read, Write}; use std::io::{stdin, stdout, Read, Write};
use rustc_hash::FxHashMap;
use crate::error::RuntimeError; use crate::error::RuntimeError;
use super::lox_std::init_std; use super::lox_std::init_std;
use super::Value; use super::Value;
pub struct Runtime { pub struct Runtime {
globals: HashMap<String, Value>, globals: FxHashMap<String, Value>,
in_stream: Box<dyn std::io::BufRead>, in_stream: Box<dyn std::io::BufRead>,
out_stream: Box<dyn std::io::Write>, out_stream: Box<dyn std::io::Write>,
@ -18,7 +19,7 @@ impl Runtime {
pub fn new(in_stream: Box<dyn std::io::Read>, out_stream: Box<dyn std::io::Write>) -> Self { pub fn new(in_stream: Box<dyn std::io::Read>, out_stream: Box<dyn std::io::Write>) -> Self {
let in_stream = Box::new(std::io::BufReader::new(in_stream)); let in_stream = Box::new(std::io::BufReader::new(in_stream));
let mut runtime = Runtime { let mut runtime = Runtime {
globals: HashMap::new(), globals: FxHashMap::default(),
in_stream, in_stream,
out_stream, out_stream,
}; };
@ -28,7 +29,7 @@ impl Runtime {
runtime runtime
} }
pub fn globals(&self) -> &HashMap<String, Value> { pub fn globals(&self) -> &FxHashMap<String, Value> {
&self.globals &self.globals
} }

View file

@ -1,5 +1,4 @@
use clap::{ArgAction, Parser}; use clap::{ArgAction, Parser};
use rlox2_interpreter::{run_file, run_repl, Runtime};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct CliArgs { struct CliArgs {
@ -8,24 +7,73 @@ struct CliArgs {
#[arg(short, action = ArgAction::SetTrue)] #[arg(short, action = ArgAction::SetTrue)]
interactive: bool, 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() { fn main() {
interpreter_main();
}
pub fn interpreter_main() {
let cli_args = CliArgs::parse(); let cli_args = CliArgs::parse();
let mut runtime = Runtime::default(); if cli_args.vm {
use rlox2_vm::InterpretError;
let mut vm = rlox2_vm::VM::default();
if let Some(file_name) = cli_args.file_name { if let Some(file_name) = cli_args.file_name {
run_file(&mut runtime, &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 cli_args.interactive { if let Err(err) = rlox2_vm::run(&source, &mut vm) {
run_repl(&mut runtime); 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 { } 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);
} }
} }

17
vm/Cargo.toml Normal file
View file

@ -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"

68
vm/src/chunk.rs Normal file
View file

@ -0,0 +1,68 @@
use crate::debug::ChunkDebugInfo;
use crate::{Opcode, Value};
#[derive(Debug, Default)]
pub struct Chunk {
code: Vec<u8>,
constants: Vec<Value>,
debug_info: Option<ChunkDebugInfo>,
}
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<T>(&mut self, byte: T, line: u32)
where
T: TryInto<u8> + std::fmt::Debug,
<T as std::convert::TryInto<u8>>::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]
}
}

6
vm/src/compile.rs Normal file
View file

@ -0,0 +1,6 @@
use itertools::Itertools;
use rlox2_frontend::lexer::Token;
pub fn compile(tokens: Vec<Token>) {
println!("{}", tokens.iter().map(|token| token.to_string()).join(" "));
}

76
vm/src/debug.rs Normal file
View file

@ -0,0 +1,76 @@
#[derive(Debug)]
struct LineInfo {
start_offset: usize,
line: u32,
}
#[derive(Debug, Default)]
pub struct ChunkDebugInfo {
line_infos: Vec<LineInfo>,
}
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
}
}

61
vm/src/disassemble.rs Normal file
View file

@ -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
}
}

58
vm/src/error.rs Normal file
View file

@ -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<LexerError> },
#[error("{inner}")]
CompileError { inner: CompileError },
#[error("{inner}")]
RuntimeError { inner: RuntimeError },
#[error("Called exit() with exit code {exit_code}")]
Exit { exit_code: i32 },
}
impl From<Vec<LexerError>> for InterpretError {
fn from(lexer_errs: Vec<LexerError>) -> Self {
InterpretError::LexerError { inner: lexer_errs }
}
}
impl From<CompileError> for InterpretError {
fn from(compile_err: CompileError) -> Self {
InterpretError::CompileError { inner: compile_err }
}
}
impl From<RuntimeError> for InterpretError {
fn from(runtime_err: RuntimeError) -> Self {
InterpretError::RuntimeError { inner: runtime_err }
}
}
fn format_multiple_errors(errs: &Vec<impl std::error::Error>) -> String {
let msg = if errs.len() == 1 {
errs[0].to_string()
} else {
errs.iter().map(|err| err.to_string()).join("\n")
};
msg
}

17
vm/src/lib.rs Normal file
View file

@ -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;

35
vm/src/opcode.rs Normal file
View file

@ -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)
}
}

97
vm/src/run.rs Normal file
View file

@ -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<Token> = scan_tokens(source)?;
compile(tokens);
Ok(())
}

22
vm/src/value.rs Normal file
View file

@ -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}"),
}
}
}

225
vm/src/vm.rs Normal file
View file

@ -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()
}
}