Compare commits

...
Sign in to create a new pull request.

8 commits

18 changed files with 865 additions and 1028 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
.idea .idea
target target
Cargo.lock Cargo.lock
src/tests/main.lua

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "deimos"]
path = deimos
url = ssh://forgejo@git.mallard.gdn/PSx/Deimos.git

5
.luarc.json Normal file
View file

@ -0,0 +1,5 @@
{
"diagnostics.disable": [
"undefined-global"
]
}

37
Cargo.lock generated
View file

@ -3,12 +3,11 @@
version = 4 version = 4
[[package]] [[package]]
name = "colored" name = "deimos"
version = "3.1.1" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
dependencies = [ dependencies = [
"windows-sys", "macros",
"map",
] ]
[[package]] [[package]]
@ -17,25 +16,21 @@ version = "0.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360bd2cd76e0cd9032d42cf2922155cecea2685b0cfa4630c3246df030bcfd6" checksum = "7360bd2cd76e0cd9032d42cf2922155cecea2685b0cfa4630c3246df030bcfd6"
[[package]]
name = "macros"
version = "0.0.0"
dependencies = [
"map",
]
[[package]]
name = "map"
version = "0.1.0"
[[package]] [[package]]
name = "mars" name = "mars"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"colored", "deimos",
"glam", "glam",
] ]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]

View file

@ -4,11 +4,11 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[features] [features]
bighash = [] freelist = []
vector3 = ["dep:glam"] vector3 = ["dep:glam"]
messages = [] messages = []
assertions = [] assertions = []
[dependencies] [dependencies]
glam = { version = "0.33.3", optional = true } glam = { version = "0.33.3", optional = true }
colored = { version = "3.1.1" } deimos = { path = "./deimos" }

1
deimos Submodule

@ -0,0 +1 @@
Subproject commit 385a08b094e43ff12b60a41a90ab99b4b18cea0c

BIN
doc/.objects.autosave.xopp Normal file

Binary file not shown.

BIN
doc/objects.xopp Normal file

Binary file not shown.

View file

@ -4,15 +4,15 @@ use crate::parser::{
Args, Block, Call, Chunk, Constructor, Expression, Function, LoadError, Name, Piece, Prefix, Args, Block, Call, Chunk, Constructor, Expression, Function, LoadError, Name, Piece, Prefix,
Statement, VarArg, Variable, Statement, VarArg, Variable,
}; };
use crate::table::{Equivalent, Hashed, Hashes, Map, TableEntry};
use crate::vm::{Code, Index, Machine, Offset, Prototype, Reference, Value}; use crate::vm::{Code, Index, Machine, Offset, Prototype, Reference, Value};
use deimos::map::Map;
use std::cmp::PartialEq; use std::cmp::PartialEq;
use std::ops::Deref; use std::ops::Deref;
use std::rc::Rc; use std::rc::Rc;
pub(crate) struct Traversal<'a> { pub(crate) struct Traversal<'a, S> {
// A 'cursor' of where we are in assumed execution // A 'cursor' of where we are in assumed execution
machine: &'a mut Machine, machine: &'a mut Machine<S>,
contexts: Vec<Context>, // Stack of function prototypes being compiled contexts: Vec<Context>, // Stack of function prototypes being compiled
} }
@ -20,40 +20,10 @@ pub(crate) struct Context {
// Everything known about a closure at a time // Everything known about a closure at a time
scopes: Vec<Scope>, scopes: Vec<Scope>,
vararg: Option<Piece<Option<String>>>, vararg: Option<Piece<Option<String>>>,
constants: Map<ConstantEntry>, // map some constant value to an index for not repeating constants constants: Map<Value, Index>, // map some constant value to an index for not repeating constants
prototype: Prototype, prototype: Prototype,
} }
#[derive(Clone, Debug)]
struct ConstantEntry(Value, Index);
impl Equivalent<Self> for ConstantEntry {
fn matches(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Equivalent<Value> for ConstantEntry {
fn matches(&self, other: &Value) -> bool {
self.0 == *other
}
}
impl Hashes for ConstantEntry {
fn hashed(&self) -> Hashed {
self.0.hashed()
}
}
impl TableEntry for ConstantEntry {
fn new_vacant() -> Self {
ConstantEntry(Value::Nil, 0)
}
fn is_vacant(&self) -> bool {
matches!(self.0, Value::Nil)
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
enum BlindJump { enum BlindJump {
Break(usize), Break(usize),
@ -70,7 +40,7 @@ pub(crate) struct Scope {
upvalue: bool, upvalue: bool,
} }
impl Traversal<'_> { impl<S> Traversal<'_, S> {
fn compile<T>(&mut self, it: &T) -> Result<(), LoadError> fn compile<T>(&mut self, it: &T) -> Result<(), LoadError>
where where
T: Compile, T: Compile,
@ -132,7 +102,7 @@ impl Traversal<'_> {
| Code::RangeLoop(_) | Code::RangeLoop(_)
| Code::RangeBegin | Code::RangeBegin
| Code::IteratorLoop(_) | Code::IteratorLoop(_)
| Code::Comment(_) => {} | Code::Message(_) => {}
} }
let prototype = &mut self.contexts.last_mut().unwrap().prototype; let prototype = &mut self.contexts.last_mut().unwrap().prototype;
prototype.code.push(it.it); prototype.code.push(it.it);
@ -147,14 +117,8 @@ impl Traversal<'_> {
fn push_constant(&mut self, it: Value) -> Index { fn push_constant(&mut self, it: Value) -> Index {
// todo: constants need pieces too!!! // todo: constants need pieces too!!!
// don't use constants that already exist // don't use constants that already exist
if let Some(entry) = &mut self if let Some(entry) = self.contexts.last_mut().unwrap().constants.get(it.clone()) {
.contexts *entry
.last_mut()
.unwrap()
.constants
.get_table(it.clone())
{
entry.1
} else { } else {
let index = { let index = {
let constants = &mut self.contexts.last_mut().unwrap().prototype.constants; let constants = &mut self.contexts.last_mut().unwrap().prototype.constants;
@ -163,11 +127,7 @@ impl Traversal<'_> {
} }
.try_into() .try_into()
.unwrap(); .unwrap();
self.contexts self.contexts.last_mut().unwrap().constants.set(it, index);
.last_mut()
.unwrap()
.constants
.set_table(ConstantEntry(it, index));
index index
} }
} }
@ -247,7 +207,7 @@ impl Traversal<'_> {
} }
Ok(context.prototype) Ok(context.prototype)
} }
pub(crate) fn new(machine: &mut Machine) -> Traversal { pub(crate) fn new(machine: &mut Machine<S>) -> Traversal<S> {
Traversal { Traversal {
machine, machine,
contexts: vec![Context { contexts: vec![Context {
@ -277,12 +237,12 @@ impl Traversal<'_> {
// todo: not all impl Compile for Piece<T> needs to be &T // todo: not all impl Compile for Piece<T> needs to be &T
trait Compile { trait Compile {
fn compile(&self, traversal: &mut Traversal) -> Result<(), LoadError>; fn compile<S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError>;
} }
fn compile_function( fn compile_function<S>(
this: &Piece<&Function>, this: &Piece<&Function>,
traversal: &mut Traversal, traversal: &mut Traversal<S>,
method: bool, method: bool,
) -> Result<(), LoadError> { ) -> Result<(), LoadError> {
let mut self_arguments = Vec::new(); let mut self_arguments = Vec::new();
@ -341,9 +301,9 @@ enum Access {
Get, Get,
} }
fn compile_identifier( fn compile_identifier<S>(
name: Piece<String>, name: Piece<String>,
traversal: &mut Traversal, traversal: &mut Traversal<S>,
access: Access, access: Access,
) -> Result<(), LoadError> { ) -> Result<(), LoadError> {
let contexts_len = traversal.contexts.len(); let contexts_len = traversal.contexts.len();
@ -416,7 +376,7 @@ fn compile_identifier(
} }
} }
} }
// break 'search avoids this use of the _G table // At this point there were no returns so we must use _G table
traversal.push_string(name.it.clone(), name.unit())?; traversal.push_string(name.it.clone(), name.unit())?;
traversal.push_code(name.swap(Code::UpvalueGet(0))); traversal.push_code(name.swap(Code::UpvalueGet(0)));
traversal.push_code(name.swap(match access { traversal.push_code(name.swap(match access {
@ -426,9 +386,9 @@ fn compile_identifier(
Ok(()) Ok(())
} }
fn compile_variable( fn compile_variable<S>(
variable: Piece<&Variable>, variable: Piece<&Variable>,
traversal: &mut Traversal, traversal: &mut Traversal<S>,
access: Access, access: Access,
) -> Result<(), LoadError> { ) -> Result<(), LoadError> {
match &variable.it { match &variable.it {
@ -455,13 +415,13 @@ fn compile_variable(
} }
impl Compile for Piece<&Function> { impl Compile for Piece<&Function> {
fn compile(&self, traversal: &mut Traversal) -> Result<(), LoadError> { fn compile<S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError> {
compile_function(self, traversal, false) compile_function(self, traversal, false)
} }
} }
impl Compile for Piece<&Args> { impl Compile for Piece<&Args> {
fn compile(&self, traversal: &mut Traversal) -> Result<(), LoadError> { fn compile<S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError> {
let mut expressions_iter = self.it.0.iter().rev(); let mut expressions_iter = self.it.0.iter().rev();
if let Some(expression) = expressions_iter.next() { if let Some(expression) = expressions_iter.next() {
let piece = expression.unit(); let piece = expression.unit();
@ -491,7 +451,7 @@ impl Compile for Piece<&Args> {
} }
impl Compile for Piece<&Call> { impl Compile for Piece<&Call> {
fn compile(&self, traversal: &mut Traversal) -> Result<(), LoadError> { fn compile<S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError> {
let index = traversal.index().map(|it| it + 1).unwrap_or(0); // there will always be at least the callable let index = traversal.index().map(|it| it + 1).unwrap_or(0); // there will always be at least the callable
traversal.compile(&self.it.args.inner_ref())?; // arguments traversal.compile(&self.it.args.inner_ref())?; // arguments
traversal.compile(&self.it.prefix.as_ref().inner_ref())?; // call prefix traversal.compile(&self.it.prefix.as_ref().inner_ref())?; // call prefix
@ -508,7 +468,7 @@ impl Compile for Piece<&Call> {
} }
impl Compile for Piece<&Prefix> { impl Compile for Piece<&Prefix> {
fn compile<'a, 'b>(&self, traversal: &mut Traversal) -> Result<(), LoadError> { fn compile<'a, 'b, S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError> {
match &self.it { match &self.it {
Prefix::Call(call) => { Prefix::Call(call) => {
let index = traversal.index().unwrap(); let index = traversal.index().unwrap();
@ -528,7 +488,7 @@ impl Compile for Piece<&Prefix> {
// for the sake of erroring alone // for the sake of erroring alone
impl Compile for Piece<&VarArg> { impl Compile for Piece<&VarArg> {
fn compile<'a, 'b>(&self, traversal: &mut Traversal) -> Result<(), LoadError> { fn compile<'a, 'b, S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError> {
match &traversal.contexts.last().unwrap().vararg { match &traversal.contexts.last().unwrap().vararg {
Some(piece) => match &piece.it { Some(piece) => match &piece.it {
Some(that_name) => match &self.it.0 { Some(that_name) => match &self.it.0 {
@ -576,7 +536,7 @@ impl Compile for Piece<&VarArg> {
} }
impl Compile for Piece<&Constructor> { impl Compile for Piece<&Constructor> {
fn compile<'a, 'b>(&self, traversal: &mut Traversal) -> Result<(), LoadError> { fn compile<'a, 'b, S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError> {
traversal.push_code(self.swap(Code::TableNew)); traversal.push_code(self.swap(Code::TableNew));
let index_table = traversal.index().unwrap(); let index_table = traversal.index().unwrap();
for field in self.it.0.iter() { for field in self.it.0.iter() {
@ -626,7 +586,7 @@ impl Compile for Piece<&Constructor> {
} }
impl Compile for Piece<&Expression> { impl Compile for Piece<&Expression> {
fn compile<'a>(&self, traversal: &mut Traversal) -> Result<(), LoadError> { fn compile<'a, S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError> {
match self.it { match self.it {
Expression::Nil => { Expression::Nil => {
traversal.push_literal(Value::Nil, self.unit()); traversal.push_literal(Value::Nil, self.unit());
@ -684,7 +644,11 @@ fn jump_from(here: usize, there: usize) -> Result<Offset, LoadError> {
Ok((there as i64 - here as i64) as Offset) Ok((there as i64 - here as i64) as Offset)
} }
fn resolve_break(traversal: &mut Traversal, begin: usize, end: usize) -> Result<(), LoadError> { fn resolve_break<S>(
traversal: &mut Traversal<S>,
begin: usize,
end: usize,
) -> Result<(), LoadError> {
for jump in std::mem::replace(&mut traversal.last_scope().jumps, Vec::new()) { for jump in std::mem::replace(&mut traversal.last_scope().jumps, Vec::new()) {
// ? // ?
match jump { match jump {
@ -700,8 +664,8 @@ fn resolve_break(traversal: &mut Traversal, begin: usize, end: usize) -> Result<
Ok(()) Ok(())
} }
fn compile_assignment( fn compile_assignment<S>(
traversal: &mut Traversal, traversal: &mut Traversal<S>,
variables: &Vec<Piece<Variable>>, variables: &Vec<Piece<Variable>>,
expressions: &Vec<Piece<Expression>>, expressions: &Vec<Piece<Expression>>,
) -> Result<(), LoadError> { ) -> Result<(), LoadError> {
@ -709,7 +673,7 @@ fn compile_assignment(
let mut expressions_iter = expressions.iter().peekable(); let mut expressions_iter = expressions.iter().peekable();
while let Some(expression) = expressions_iter.next() { while let Some(expression) = expressions_iter.next() {
let compile_variable_expression = |// Compile a variable normally with one value from an expression let compile_variable_expression = |// Compile a variable normally with one value from an expression
traversal: &mut Traversal, traversal: &mut Traversal<S>,
variable: Option<&Piece<Variable>>, variable: Option<&Piece<Variable>>,
expression: &Piece<&Expression>| expression: &Piece<&Expression>|
-> Result<(), LoadError> { -> Result<(), LoadError> {
@ -784,7 +748,7 @@ fn compile_assignment(
} }
impl Compile for Piece<&Block> { impl Compile for Piece<&Block> {
fn compile(&self, traversal: &mut Traversal) -> Result<(), LoadError> { fn compile<S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError> {
let last_scope_index = traversal.index().unwrap_or(0); let last_scope_index = traversal.index().unwrap_or(0);
let mut depth = 0; // Counter for 'dangling' scopes produced by any declarations to be closed. let mut depth = 0; // Counter for 'dangling' scopes produced by any declarations to be closed.
// todo: for some reason, register indices are called "<thing>_index" and code indices "index_<thing>"? fix it // todo: for some reason, register indices are called "<thing>_index" and code indices "index_<thing>"? fix it
@ -1016,7 +980,7 @@ impl Compile for Piece<&Block> {
Statement::Declaration(names, expressions) => { Statement::Declaration(names, expressions) => {
let mut expressions_iter = expressions.iter().peekable(); let mut expressions_iter = expressions.iter().peekable();
let mut names_iter = names.iter().peekable(); let mut names_iter = names.iter().peekable();
let compile_declaration = |traversal: &mut Traversal, let compile_declaration = |traversal: &mut Traversal<S>,
expression: &Piece<Expression>, expression: &Piece<Expression>,
name: &Piece<Name>| name: &Piece<Name>|
-> Result<(), LoadError> { -> Result<(), LoadError> {
@ -1125,11 +1089,20 @@ impl Compile for Piece<&Block> {
} }
} }
// Any 'dangling' scopes that weren't closed (declarations) will be at the end of this block // Any 'dangling' scopes that weren't closed (declarations) will be at the end of this block
let return_index = traversal.index().unwrap_or(0);
if let Some(return_expressions) = &self.it.return_exp { if let Some(return_expressions) = &self.it.return_exp {
// arguments are backwards // arguments are backwards
for return_expression in return_expressions.iter().rev() { for return_expression in return_expressions.iter().rev() {
traversal.compile(&return_expression.inner_ref())?; traversal.compile(&return_expression.inner_ref())?;
} }
if !return_expressions.is_empty() {
traversal.push_code(
return_expressions
.first()
.unwrap()
.swap(Code::Return(return_index)),
);
}
} }
for _ in 0..depth { for _ in 0..depth {
traversal.close_scope(); traversal.close_scope();
@ -1140,12 +1113,12 @@ impl Compile for Piece<&Block> {
} }
impl Compile for Chunk { impl Compile for Chunk {
fn compile(&self, traversal: &mut Traversal) -> Result<(), LoadError> { fn compile<S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError> {
traversal.compile(&self.0.inner_ref()) traversal.compile(&self.0.inner_ref())
} }
} }
pub(crate) fn compile(chunk: &Chunk, machine: &mut Machine) -> Result<Prototype, LoadError> { pub(crate) fn compile<S>(chunk: &Chunk, machine: &mut Machine<S>) -> Result<Prototype, LoadError> {
let mut traversal = Traversal::new(machine); let mut traversal = Traversal::new(machine);
traversal.compile(chunk)?; traversal.compile(chunk)?;
traversal.close_context() traversal.close_context()

18
src/core.rs Normal file
View file

@ -0,0 +1,18 @@
use crate::vm::{Machine, ForeignFunction, Needle, RunError};
pub(crate) fn load<S>(machine: &mut Machine<S>) -> Result<(),RunError> {
machine.set_global_function("print",|machine: &mut Machine<S>, mut needle: Needle| {
while let Some(value) = needle.pop() {
print!("{} ", machine.meta_to_string(needle.dispatch, value)?.unwrap_str())
}
println!();
Ok(())
})?;
machine.set_global_function("debug",|_machine: &mut Machine<S>, mut needle: Needle| {
while let Some(value) = needle.pop() {
print!("{:?} ", value)
}
println!();
Ok(())
})?;
Ok(())
}

154
src/gc.rs
View file

@ -1,154 +0,0 @@
use std::alloc::{Layout, alloc, dealloc};
use std::any::Any;
use std::cell::{Ref, RefCell, RefMut};
use std::fmt::Debug;
use std::ops::{Deref, DerefMut};
use std::ptr::NonNull;
// A simple 'stop the world' 'mark and sweep' garbage collector
// This approach is used since game engine frames provide a nice 'resting' interval for the interpreter to clean up faster
// todo! generational and naive reference counted collection
/* naive reference counting could be done as:
keep a traditional reference count =>
- field count: u8 (>256 references -> a cycle will prevent premature deallocation anyway)
if the count reaches zero, prematurely deallocate by using an optional soft 'drop' method in the VM =>
- field location: usize (location of this pointer in the big 'objects' array of the allocator)
- drop(&self,allocator) {
if count == 0 {
allocator[self.pointer->location] = nullptr; // make allocator forget this
dealloc(self.pointer); // deallocate now
}
}
in the allocator ensure location is consistent
*/
#[derive(Debug)]
pub struct Gc<T: ?Sized + Traverse> {
it: NonNull<RefCell<Header<T>>>,
}
pub struct Agc {}
impl<T: ?Sized + Traverse> Clone for Gc<T> {
fn clone(&self) -> Self {
Gc { it: self.it }
}
}
impl<T: ?Sized + Traverse> Traverse for Gc<T> {
fn traverse(&self) {
self.borrow().traverse()
}
}
impl<T: ?Sized + Traverse> PartialEq for Gc<T> {
fn eq(&self, other: &Gc<T>) -> bool {
self.it == other.it
}
}
impl<T: ?Sized + Traverse> Gc<T> {
pub fn borrow(&self) -> impl Deref<Target = T> {
Ref::map(
unsafe {
// ???
(*self.it.as_ptr()).borrow()
},
|it| &it.data,
)
}
pub fn borrow_mut(&mut self) -> impl DerefMut<Target = T> {
RefMut::map(unsafe { (*self.it.as_mut()).borrow_mut() }, |it| {
&mut it.data
})
}
pub fn replace(&mut self, new: T)
where
T: Sized,
{
unsafe {
(*self.it.as_mut()).borrow_mut().data = new;
}
}
pub fn addr(&self) -> usize {
self.it.as_ptr().addr()
}
}
#[derive(Clone, Debug, Copy)]
enum Color {
White,
Black,
}
struct Header<T: ?Sized + Traverse> {
layout: Layout,
color: Color, // NOT for incremental tri-color right now; white == mark-free, black == mark-keep
data: T,
}
pub struct Allocator {
// An array of pointers is used here since I did not want to use a compactor and larger types
// held on the GC (game objects) will most likely be massive anyway.
objects: Vec<Option<NonNull<RefCell<Header<dyn Traverse>>>>>,
}
impl Allocator {
pub fn new() -> Allocator {
Allocator {
objects: Vec::new(),
}
}
pub fn alloc<T: Traverse>(&mut self, it: T) -> Gc<T> {
let layout = Layout::new::<RefCell<Header<T>>>();
let pointer = unsafe {
let pointer = alloc(layout) as *mut RefCell<Header<T>>;
assert!(!pointer.is_null()); // lol idk how to fix this rn (gah use a result!!!)
pointer.write(RefCell::new(Header {
layout,
color: Color::Black,
data: it,
}));
pointer
}; // todo: maybe use a compacting GC
self.objects.push(NonNull::new(pointer));
Gc {
it: NonNull::new(pointer).unwrap(),
}
}
fn mark_white(&mut self) {
for i in 0..self.objects.len() {
if let Some(object) = self.objects[i] {
unsafe { (*object.as_ptr()).borrow_mut().color = Color::White };
}
}
}
fn collect(&mut self) {
self.mark_white();
self.objects.retain(|object| {
if let Some(object) = object {
let header = unsafe { (*object.as_ptr()).borrow_mut() };
match header.color {
Color::White => {
unsafe {
dealloc(object.as_ptr() as *mut u8, header.layout);
}
true
}
Color::Black => false,
}
} else {
false
}
});
}
}
pub trait Traverse: Any + Debug {
fn traverse(&self) {
// mark or grey
()
}
}

View file

@ -1,29 +1,52 @@
#![warn(clippy::cast_lossless)] #![warn(clippy::cast_lossless)]
pub use crate::vm::*;
pub use deimos;
mod compiler; mod compiler;
mod gc;
mod parser; mod parser;
mod table; mod table;
mod vm; mod vm;
mod core;
mod tasks;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::vm::Machine; use crate::vm::Machine;
#[test] use crate::{RunError, Needle};
fn simple() {
let mut machine = match Machine::new() { macro_rules! unwrap {
($e:expr) => {
match $e {
Err(error) => panic!("{}", error), Err(error) => panic!("{}", error),
Ok(machine) => machine, Ok(result) => result,
}; }
let closure = match machine.load(include_str!("tests/script.lua")) {
Err(error) => panic!("{}", error),
Ok(closure) => closure,
_ => panic!(),
};
println!("{:?}", closure.borrow().prototype);
match machine.enter(closure) {
Err(error) => panic!("{}", error),
_ => {}
}; };
} }
fn output(machine: &mut Machine<String>, mut needle: Needle) -> Result<(),RunError> {
let arg = needle.pop().ok_or(RunError::new("Expected string, got nil."))?;
let str = arg.try_string().ok_or(RunError::new("Expected string."))?;
machine.state.push_str(str.as_str());
println!("{}",str.as_str());
Ok(())
}
fn output_test(file: &str) -> Result<Machine<String>,Box<dyn std::error::Error>> {
let mut machine = Machine::new(|_| String::new())?;
machine.set_global_function("output",output)?;
let closure = machine.load(file)?;
machine.enter(closure)?;
Ok(machine)
}
#[test]
fn hello() {
assert_eq!(unwrap!(output_test(include_str!("tests/hello.lua"))).state,"hello")
}
#[test]
fn many() {
println!("{}",unwrap!(output_test(include_str!("tests/script.lua"))).state)
}
} }

View file

@ -1,7 +1,6 @@
// PARSER // PARSER
use crate::vm::{Index, RunError}; use crate::vm::{Index, RunError};
use colored::*;
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::fmt::Formatter; use std::fmt::Formatter;
// TODO: Vec<Piece<T>> is good but sometimes we either need at least one or none, current fix is just to check if there is one and if not skip it, this takes extra performance // TODO: Vec<Piece<T>> is good but sometimes we either need at least one or none, current fix is just to check if there is one and if not skip it, this takes extra performance

View file

@ -1,56 +1,6 @@
use crate::gc::{Gc, Traverse}; use crate::vm::{Callable, MarsAllocator, RunError, Value};
use crate::vm::{Callable, RunError, Value}; use deimos::gc::{Id, MaybeId, Traverse};
use std::rc::Rc; use deimos::map::{DisplacementSet, Equivalent, Hashed, Hashes, TableEntry};
// I've already spent 2 months on this interpreter, and I'm tired, so I've cut a few corners...
/* See some of Lua's notes on this table: https://www.lua.org/source/5.5/ltable.c.html
This table uses an 'open-addressed' hashmap https://en.wikipedia.org/wiki/Open_addressing
where collisions are handled by 'linear-probing' and something I call 'displacement'.
Motivation:
Afaik, without having some mechanism that records or ensures known proximity between a displaced element
that collided with other(s) in the hashmap and it's original home (at element's hash's index),
the entire array may have to be checked just to see if an element is present. How can we fix this?
Solution:
Lua uses Brent's method, which I couldn't find a concrete explanation of, here I'm just recording
the maximum displacement that will need to be linearly-probed from the home entry to be certain of
any 'home' element's presence in the greater array.
Notes:
- If this value becomes large, the load factor is probably high and there would be a resize.
- Values would only need to be shuffled around closer to their 'home' during a resize.
- It is unlikely the displacement would large value without a resize amending that problem.
*/
type Displacement = u8;
#[cfg(feature = "bighash")]
pub type Hashed = u64;
#[cfg(not(feature = "bighash"))]
pub type Hashed = u32;
pub trait Equivalent<T>: Sized {
fn matches(&self, other: &T) -> bool;
}
impl<T> Equivalent<T> for T
where
T: PartialEq,
{
fn matches(&self, other: &T) -> bool {
self == other
}
}
pub trait Hashes {
fn hashed(&self) -> Hashed;
}
pub trait TableEntry: Equivalent<Self> + Hashes {
fn new_vacant() -> Self;
fn is_vacant(&self) -> bool;
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct KeyValue { struct KeyValue {
@ -71,12 +21,10 @@ impl Hashes for KeyValue {
} }
impl TableEntry for KeyValue { impl TableEntry for KeyValue {
fn new_vacant() -> Self { const VACANT: Self = KeyValue {
KeyValue {
index: Value::Nil, index: Value::Nil,
value: Value::Nil, value: Value::Nil,
} };
}
fn is_vacant(&self) -> bool { fn is_vacant(&self) -> bool {
matches!( matches!(
self, self,
@ -88,221 +36,6 @@ impl TableEntry for KeyValue {
} }
} }
#[derive(Debug, Clone)]
struct Entry<E> {
entry: E,
home: usize,
displacement: Displacement,
}
#[derive(Clone, Debug)]
pub struct Map<E: TableEntry + Clone> {
// default representing vacancy!
map: Vec<Entry<E>>,
map_bounds: std::ops::Range<usize>,
map_count: usize, // number of elements in table
}
impl<E: TableEntry + Clone> Map<E> {
fn exchange_table(&mut self, len: usize) {
let old = std::mem::replace(
&mut self.map,
vec![
Entry {
entry: E::new_vacant(),
home: 0,
displacement: 0,
};
len
],
);
self.map_bounds = self.table_lower()..self.table_upper();
for Entry { entry, .. } in old {
if !entry.is_vacant() {
self.set_table(entry)
}
}
}
pub fn resize_table(&mut self, len: usize) {
self.exchange_table(len.max(self.map_count + (self.map_count / 3)));
self.map_bounds = 0..self.table_upper();
}
fn table_upper(&self) -> usize {
(self.map.len() / 4) * 3
}
fn table_lower(&self) -> usize {
self.map.len() / 3
}
fn ensure_table(&mut self) {
if self.map_count + 1 > self.table_upper() {
// free space is short
self.exchange_table((self.map.len() + 4) * 2);
} else if self.map_count < self.table_lower() {
// too much free space
self.exchange_table(self.map.len() / 2)
}
}
pub(crate) fn set_table(&mut self, entry: E) {
#[cfg(feature = "assertions")]
assert!(!entry.is_vacant());
if !self.map_bounds.contains(&(self.map_count + 1)) {
// make sure the table is appropriately sized
self.ensure_table();
}
let range = self.map.len();
let home = entry.hashed() as usize % range;
if self.map[home].entry.is_vacant() {
// attempt to place it directly in an empty space
// INSERT
self.map[home].home = home;
self.map[home].entry = entry;
self.map[home].displacement = self.map[home].displacement.max(0);
self.map_count += 1;
} else if self.map[home].entry.matches(&entry) {
// attempt to replace it directly
// REPLACE
self.map[home].entry = entry; // home.item.soft_drop()
} else {
// attempt to replace it in a collided neighbour location
for i in 1..self.map[home].displacement + 1 {
let neighbour = &mut self.map[(home + i as usize) % range]; // todo: is mod expensive?
if neighbour.entry.matches(&entry) {
// found where it was displaced to
// REPLACE
neighbour.entry = entry;
return;
}
}
// at this point it must be added, probe to place in an empty space
for j in self.map[home].displacement + 1..Displacement::MAX - 1 {
let neighbour = &mut self.map[(home + j as usize) % range];
if neighbour.entry.is_vacant() {
// new empty slot hooray!
// INSERT
neighbour.entry = entry;
neighbour.home = home;
self.map[home].displacement = j;
self.map_count += 1;
return;
}
}
// impossible but this still needs to be complete
#[cfg(feature = "messages")]
eprintln!("Large table collision, are the hashes ok?\n\tBrute-force probing...");
let mut free: Option<usize> = None;
for k in 0..self.map.len() {
let neighbour = &mut self.map[k];
if neighbour.entry.is_vacant() && free.is_none() {
free = Some(k);
} else if neighbour.entry.matches(&entry) {
// REPLACE
neighbour.entry = entry;
return;
}
}
if let Some(k) = free {
// INSERT
self.map[k].home = home;
self.map[k].entry = entry;
self.map[home].displacement = Displacement::MAX;
self.map_count += 1;
return;
}
// must resize
#[cfg(feature = "messages")]
eprintln!("\n\tResizing...");
self.resize_table((self.map.len() + 4) * 2);
self.set_table(entry);
}
}
pub(crate) fn rem_table<K>(&mut self, index: K)
where
E: Equivalent<K>,
K: Hashes,
{
if self.map.len() == 0 {
return;
}
let range = self.map.len();
let home = index.hashed() as usize % range;
if self.map[home].entry.matches(&index) {
self.map[home].entry = E::new_vacant();
self.map_count -= 1;
} else {
if self.map[home].displacement != Displacement::MAX {
let mut largest = 0;
for i in 1..self.map[home].displacement {
let neighbour = &mut self.map[(home + i as usize) % range]; // todo: maybe mod slows this down
if neighbour.entry.matches(&index) {
neighbour.entry = E::new_vacant();
self.map_count -= 1;
if i == self.map[home].displacement {
self.map[home].displacement = largest
}
return;
} else if !neighbour.entry.is_vacant() && neighbour.home == home {
largest = i;
}
}
} else {
let mut largest = 0;
let mut finished = false; // what
for k in 0..self.map.len() {
let neighbour = &mut self.map[k];
if neighbour.entry.matches(&index) {
neighbour.entry = E::new_vacant();
self.map_count -= 1;
#[cfg(feature = "assertions")]
assert!(!finished);
finished = true;
}
if neighbour.home == home && !neighbour.entry.is_vacant() {
largest = largest.max(if k < home {
k + self.map.len() - home - 1 // ?
} else {
k - home
})
}
}
self.map[home].displacement = if largest > Displacement::MAX as usize {
Displacement::MAX
} else {
largest as Displacement
}
}
}
}
pub(crate) fn get_table<K>(&mut self, index: K) -> Option<&E>
where
E: Equivalent<K>,
K: Hashes,
{
if self.map.len() == 0 {
return None;
}
let location = index.hashed() as usize % self.map.len();
let home = &self.map[location];
if home.entry.matches(&index) {
Some(&home.entry)
} else {
for i in 1..home.displacement as usize {
let neighbour = &self.map[(location + i) % self.map.len()];
if neighbour.entry.matches(&index) {
return Some(&neighbour.entry);
}
}
None
}
}
pub(crate) fn new() -> Map<E> {
Map {
map: Vec::new(),
map_count: 0,
map_bounds: (0..0).into(),
}
}
}
impl Hashes for Value { impl Hashes for Value {
fn hashed(&self) -> Hashed { fn hashed(&self) -> Hashed {
match self { match self {
@ -314,11 +47,11 @@ impl Hashes for Value {
0 0
} }
} }
Value::CachedString(cached) => cached.1, Value::CachedString(hashed) => hashed.1,
Value::String(_string, hash) => *hash, Value::String(hashed) => hashed.1,
Value::Function(callable) => match callable { Value::Function(callable) => match callable {
Callable::Rust(native) => Rc::as_ptr(native).addr() as Hashed, Callable::Rust(native) => native.hashed(),
Callable::Mars(closure) => closure.addr() as Hashed, Callable::Mars(closure) => closure.hashed(),
}, },
Value::Integer(integer) => *integer as Hashed, Value::Integer(integer) => *integer as Hashed,
Value::Number(number) => { Value::Number(number) => {
@ -328,8 +61,8 @@ impl Hashes for Value {
number.to_bits() as Hashed number.to_bits() as Hashed
} }
} }
Value::Table(table) => table.addr() as Hashed, Value::Table(table) => table.hashed(),
Value::Object(object) => object.addr() as Hashed, Value::Object(object, ..) => object.hashed(),
#[cfg(feature = "vector3")] #[cfg(feature = "vector3")]
Value::Vector(vec) => { Value::Vector(vec) => {
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
@ -341,23 +74,28 @@ impl Hashes for Value {
} }
// todo: displaced items don't ever get shuffled closer to their homes unless the table is resized // todo: displaced items don't ever get shuffled closer to their homes unless the table is resized
#[derive(Debug, Clone)] #[derive(Debug)]
pub struct Table { pub struct Table {
table: Map<KeyValue>, table: DisplacementSet<KeyValue>,
array: Vec<Value>, array: Vec<Value>,
pub meta: Option<Gc<Table>>, pub meta: MaybeId<Table>,
} }
impl Traverse for Table { impl<C> Traverse<C> for Table
fn traverse(&self) { where
for Entry { entry, .. } in self.table.map.iter() { C: MarsAllocator,
{
fn traverse(id: &Id<Table>, context: &mut C) {
for i in 0..context.freelist().get(id).table.map.len() {
let entry = context.freelist().get(id).table.map[i].entry.clone();
if !entry.is_vacant() { if !entry.is_vacant() {
entry.index.traverse(); entry.index.traverse(context);
entry.value.traverse(); entry.value.traverse(context);
} }
} }
for item in self.array.iter() { for i in 0..context.freelist().get(id).array.len() {
item.traverse() let item = context.freelist().get(id).array[i].clone();
item.traverse(context);
} }
} }
} }
@ -378,24 +116,24 @@ impl Table {
// This is an integer index, try the array first // This is an integer index, try the array first
match value { match value {
Value::Nil => { Value::Nil => {
if (0..self.array.len() + 1).contains(&(index as usize)) { if (0..self.array.len() + 1).contains(&(index as usize - 1)) {
if self.array.len() == index as usize { if self.array.len() == index as usize - 1 {
self.array.pop(); self.array.pop();
} else { } else {
self.array[index as usize + 1] = Value::Nil self.array[index as usize - 1] = Value::Nil
} }
} }
self.table.rem_table(Value::Integer(index)); self.table.remove(Value::Integer(index));
} }
item => { item => {
if (0..self.array.len() + 1).contains(&(index as usize)) { if (0..self.array.len() + 1).contains(&(index as usize - 1)) {
if self.array.len() == index as usize { if self.array.len() == index as usize - 1 {
self.array.push(item) self.array.push(item)
} else { } else {
self.array[index as usize + 1] = item; self.array[index as usize - 1] = item;
} }
} else { } else {
self.table.set_table(KeyValue { self.table.set(KeyValue {
index: Value::Integer(index), index: Value::Integer(index),
value: item, value: item,
}) })
@ -407,7 +145,7 @@ impl Table {
Value::Nil => Err(RunError( Value::Nil => Err(RunError(
"Attempt to set new index of table with key: nil".to_string(), "Attempt to set new index of table with key: nil".to_string(),
)), )),
index => Ok(self.table.set_table(KeyValue { index, value })), index => Ok(self.table.set(KeyValue { index, value })),
} }
} }
pub fn get(&mut self, index: Value) -> Result<Value, RunError> { pub fn get(&mut self, index: Value) -> Result<Value, RunError> {
@ -422,11 +160,8 @@ impl Table {
// todo: move this into the Map // todo: move this into the Map
Ok(self Ok(self
.table .table
.get_table(index) .get(index)
.unwrap_or(&KeyValue { .unwrap_or(&KeyValue::VACANT)
index: Value::Nil,
value: Value::Nil,
})
.value .value
.clone()) .clone())
} }
@ -437,9 +172,9 @@ impl Table {
} }
pub fn new() -> Self { pub fn new() -> Self {
Table { Table {
table: Map::new(), table: DisplacementSet::new(),
array: Vec::new(), array: Vec::new(),
meta: None, meta: MaybeId::NULL,
} }
} }
} }

0
src/tasks.rs Normal file
View file

1
src/tests/hello.lua Normal file
View file

@ -0,0 +1 @@
output "hello"

View file

@ -1,9 +1,5 @@
local var = 2 local tab = {}
local function thing() for i = 1, 1000000 do
print(var) tab[i] = true
var = 3
end end
output(#tab)
print(var)
thing()
thing()

1105
src/vm.rs

File diff suppressed because it is too large Load diff