mirror of
https://github.com/MorizzG/rlox.git
synced 2025-12-06 04:12:42 +00:00
Finished up to and including chapter 16
This commit is contained in:
parent
647a095a05
commit
b86985deaf
24 changed files with 1051 additions and 198 deletions
85
Cargo.lock
generated
85
Cargo.lock
generated
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"] }
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
@ -315,30 +323,30 @@ impl LoxFunction {
|
||||||
|
|
||||||
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: Rc<LoxClass>, args: Vec<Value>, env: &mut Environment) -> EvalResult<Value> {
|
pub fn call_class(class: Rc<LoxClass>, env: &mut Environment) -> EvalResult<Value> {
|
||||||
if args.len() != class.arity() {
|
// let args = env.collect_args();
|
||||||
|
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
68
src/main.rs
68
src/main.rs
|
|
@ -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
17
vm/Cargo.toml
Normal 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
68
vm/src/chunk.rs
Normal 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
6
vm/src/compile.rs
Normal 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
76
vm/src/debug.rs
Normal 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
61
vm/src/disassemble.rs
Normal 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
58
vm/src/error.rs
Normal 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
17
vm/src/lib.rs
Normal 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
35
vm/src/opcode.rs
Normal 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
97
vm/src/run.rs
Normal 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
22
vm/src/value.rs
Normal 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
225
vm/src/vm.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue