Merge branch 'user-objects-and-state' into HEAD
This commit is contained in:
commit
485f7c2d66
15 changed files with 935 additions and 1008 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
|||
.idea
|
||||
target
|
||||
Cargo.lock
|
||||
src/tests/main.lua
|
||||
5
.luarc.json
Normal file
5
.luarc.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"diagnostics.disable": [
|
||||
"undefined-global"
|
||||
]
|
||||
}
|
||||
59
Cargo.lock
generated
59
Cargo.lock
generated
|
|
@ -3,12 +3,11 @@
|
|||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
|
||||
name = "deimos"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"macros",
|
||||
"map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -17,25 +16,59 @@ version = "0.33.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360bd2cd76e0cd9032d42cf2922155cecea2685b0cfa4630c3246df030bcfd6"
|
||||
|
||||
[[package]]
|
||||
name = "macros"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"map",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "map"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "mars"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"deimos",
|
||||
"glam",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ version = "0.1.0"
|
|||
edition = "2024"
|
||||
|
||||
[features]
|
||||
bighash = []
|
||||
freelist = []
|
||||
vector3 = ["dep:glam"]
|
||||
messages = []
|
||||
assertions = []
|
||||
|
||||
[dependencies]
|
||||
glam = { version = "0.33.3", optional = true }
|
||||
colored = { version = "3.1.1" }
|
||||
deimos = { path = "./deimos" }
|
||||
|
|
|
|||
BIN
doc/.objects.autosave.xopp
Normal file
BIN
doc/.objects.autosave.xopp
Normal file
Binary file not shown.
BIN
doc/objects.xopp
Normal file
BIN
doc/objects.xopp
Normal file
Binary file not shown.
119
src/compiler.rs
119
src/compiler.rs
|
|
@ -4,15 +4,15 @@ use crate::parser::{
|
|||
Args, Block, Call, Chunk, Constructor, Expression, Function, LoadError, Name, Piece, Prefix,
|
||||
Statement, VarArg, Variable,
|
||||
};
|
||||
use crate::table::{Equivalent, Hashed, Hashes, Map, TableEntry};
|
||||
use crate::vm::{Code, Index, Machine, Offset, Prototype, Reference, Value};
|
||||
use deimos::map::Map;
|
||||
use std::cmp::PartialEq;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub(crate) struct Traversal<'a> {
|
||||
pub(crate) struct Traversal<'a, S> {
|
||||
// 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
|
||||
}
|
||||
|
||||
|
|
@ -20,40 +20,10 @@ pub(crate) struct Context {
|
|||
// Everything known about a closure at a time
|
||||
scopes: Vec<Scope>,
|
||||
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,
|
||||
}
|
||||
|
||||
#[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)]
|
||||
enum BlindJump {
|
||||
Break(usize),
|
||||
|
|
@ -70,7 +40,7 @@ pub(crate) struct Scope {
|
|||
upvalue: bool,
|
||||
}
|
||||
|
||||
impl Traversal<'_> {
|
||||
impl<S> Traversal<'_, S> {
|
||||
fn compile<T>(&mut self, it: &T) -> Result<(), LoadError>
|
||||
where
|
||||
T: Compile,
|
||||
|
|
@ -132,7 +102,7 @@ impl Traversal<'_> {
|
|||
| Code::RangeLoop(_)
|
||||
| Code::RangeBegin
|
||||
| Code::IteratorLoop(_)
|
||||
| Code::Comment(_) => {}
|
||||
| Code::Message(_) => {}
|
||||
}
|
||||
let prototype = &mut self.contexts.last_mut().unwrap().prototype;
|
||||
prototype.code.push(it.it);
|
||||
|
|
@ -147,14 +117,8 @@ impl Traversal<'_> {
|
|||
fn push_constant(&mut self, it: Value) -> Index {
|
||||
// todo: constants need pieces too!!!
|
||||
// don't use constants that already exist
|
||||
if let Some(entry) = &mut self
|
||||
.contexts
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.constants
|
||||
.get_table(it.clone())
|
||||
{
|
||||
entry.1
|
||||
if let Some(entry) = self.contexts.last_mut().unwrap().constants.get(it.clone()) {
|
||||
*entry
|
||||
} else {
|
||||
let index = {
|
||||
let constants = &mut self.contexts.last_mut().unwrap().prototype.constants;
|
||||
|
|
@ -163,11 +127,7 @@ impl Traversal<'_> {
|
|||
}
|
||||
.try_into()
|
||||
.unwrap();
|
||||
self.contexts
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.constants
|
||||
.set_table(ConstantEntry(it, index));
|
||||
self.contexts.last_mut().unwrap().constants.set(it, index);
|
||||
index
|
||||
}
|
||||
}
|
||||
|
|
@ -247,7 +207,7 @@ impl Traversal<'_> {
|
|||
}
|
||||
Ok(context.prototype)
|
||||
}
|
||||
pub(crate) fn new(machine: &mut Machine) -> Traversal {
|
||||
pub(crate) fn new(machine: &mut Machine<S>) -> Traversal<S> {
|
||||
Traversal {
|
||||
machine,
|
||||
contexts: vec![Context {
|
||||
|
|
@ -277,12 +237,12 @@ impl Traversal<'_> {
|
|||
|
||||
// todo: not all impl Compile for Piece<T> needs to be &T
|
||||
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>,
|
||||
traversal: &mut Traversal,
|
||||
traversal: &mut Traversal<S>,
|
||||
method: bool,
|
||||
) -> Result<(), LoadError> {
|
||||
let mut self_arguments = Vec::new();
|
||||
|
|
@ -341,9 +301,9 @@ enum Access {
|
|||
Get,
|
||||
}
|
||||
|
||||
fn compile_identifier(
|
||||
fn compile_identifier<S>(
|
||||
name: Piece<String>,
|
||||
traversal: &mut Traversal,
|
||||
traversal: &mut Traversal<S>,
|
||||
access: Access,
|
||||
) -> Result<(), LoadError> {
|
||||
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_code(name.swap(Code::UpvalueGet(0)));
|
||||
traversal.push_code(name.swap(match access {
|
||||
|
|
@ -426,9 +386,9 @@ fn compile_identifier(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_variable(
|
||||
fn compile_variable<S>(
|
||||
variable: Piece<&Variable>,
|
||||
traversal: &mut Traversal,
|
||||
traversal: &mut Traversal<S>,
|
||||
access: Access,
|
||||
) -> Result<(), LoadError> {
|
||||
match &variable.it {
|
||||
|
|
@ -455,13 +415,13 @@ fn compile_variable(
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
if let Some(expression) = expressions_iter.next() {
|
||||
let piece = expression.unit();
|
||||
|
|
@ -491,7 +451,7 @@ impl Compile for Piece<&Args> {
|
|||
}
|
||||
|
||||
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
|
||||
traversal.compile(&self.it.args.inner_ref())?; // arguments
|
||||
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> {
|
||||
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 {
|
||||
Prefix::Call(call) => {
|
||||
let index = traversal.index().unwrap();
|
||||
|
|
@ -528,7 +488,7 @@ impl Compile for Piece<&Prefix> {
|
|||
|
||||
// for the sake of erroring alone
|
||||
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 {
|
||||
Some(piece) => match &piece.it {
|
||||
Some(that_name) => match &self.it.0 {
|
||||
|
|
@ -576,7 +536,7 @@ impl Compile for Piece<&VarArg> {
|
|||
}
|
||||
|
||||
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));
|
||||
let index_table = traversal.index().unwrap();
|
||||
for field in self.it.0.iter() {
|
||||
|
|
@ -626,7 +586,7 @@ impl Compile for Piece<&Constructor> {
|
|||
}
|
||||
|
||||
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 {
|
||||
Expression::Nil => {
|
||||
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)
|
||||
}
|
||||
|
||||
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()) {
|
||||
// ?
|
||||
match jump {
|
||||
|
|
@ -700,8 +664,8 @@ fn resolve_break(traversal: &mut Traversal, begin: usize, end: usize) -> Result<
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_assignment(
|
||||
traversal: &mut Traversal,
|
||||
fn compile_assignment<S>(
|
||||
traversal: &mut Traversal<S>,
|
||||
variables: &Vec<Piece<Variable>>,
|
||||
expressions: &Vec<Piece<Expression>>,
|
||||
) -> Result<(), LoadError> {
|
||||
|
|
@ -709,7 +673,7 @@ fn compile_assignment(
|
|||
let mut expressions_iter = expressions.iter().peekable();
|
||||
while let Some(expression) = expressions_iter.next() {
|
||||
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>>,
|
||||
expression: &Piece<&Expression>|
|
||||
-> Result<(), LoadError> {
|
||||
|
|
@ -784,7 +748,7 @@ fn compile_assignment(
|
|||
}
|
||||
|
||||
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 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
|
||||
|
|
@ -1016,7 +980,7 @@ impl Compile for Piece<&Block> {
|
|||
Statement::Declaration(names, expressions) => {
|
||||
let mut expressions_iter = expressions.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>,
|
||||
name: &Piece<Name>|
|
||||
-> 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
|
||||
let return_index = traversal.index().unwrap_or(0);
|
||||
if let Some(return_expressions) = &self.it.return_exp {
|
||||
// arguments are backwards
|
||||
for return_expression in return_expressions.iter().rev() {
|
||||
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 {
|
||||
traversal.close_scope();
|
||||
|
|
@ -1140,12 +1113,12 @@ impl Compile for Piece<&Block> {
|
|||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
traversal.compile(chunk)?;
|
||||
traversal.close_context()
|
||||
|
|
|
|||
18
src/core.rs
Normal file
18
src/core.rs
Normal 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
154
src/gc.rs
|
|
@ -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
|
||||
()
|
||||
}
|
||||
}
|
||||
13
src/lib.rs
13
src/lib.rs
|
|
@ -1,26 +1,31 @@
|
|||
#![warn(clippy::cast_lossless)]
|
||||
|
||||
pub use crate::vm::*;
|
||||
pub use deimos;
|
||||
|
||||
mod compiler;
|
||||
mod gc;
|
||||
mod parser;
|
||||
mod table;
|
||||
mod vm;
|
||||
mod core;
|
||||
mod tasks;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::vm::Machine;
|
||||
use deimos::gc::Allocates;
|
||||
|
||||
#[test]
|
||||
fn simple() {
|
||||
let mut machine = match Machine::new() {
|
||||
let mut machine = match Machine::new(|_| ()) {
|
||||
Err(error) => panic!("{}", error),
|
||||
Ok(machine) => machine,
|
||||
};
|
||||
let closure = match machine.load(include_str!("tests/script.lua")) {
|
||||
Err(error) => panic!("{}", error),
|
||||
Ok(closure) => closure,
|
||||
_ => panic!(),
|
||||
};
|
||||
println!("{:?}", closure.borrow().prototype);
|
||||
println!("{:?}", machine.freelist().get(&closure).prototype);
|
||||
match machine.enter(closure) {
|
||||
Err(error) => panic!("{}", error),
|
||||
_ => {}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// PARSER
|
||||
|
||||
use crate::vm::{Index, RunError};
|
||||
use colored::*;
|
||||
use std::cmp::{max, min};
|
||||
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
|
||||
|
|
|
|||
350
src/table.rs
350
src/table.rs
|
|
@ -1,56 +1,6 @@
|
|||
use crate::gc::{Gc, Traverse};
|
||||
use crate::vm::{Callable, RunError, Value};
|
||||
use std::rc::Rc;
|
||||
|
||||
// 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;
|
||||
}
|
||||
use crate::vm::{Callable, MarsAllocator, RunError, Value};
|
||||
use deimos::gc::{Id, MaybeId, Traverse};
|
||||
use deimos::map::{DisplacementSet, Equivalent, Hashed, Hashes, TableEntry};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct KeyValue {
|
||||
|
|
@ -71,12 +21,10 @@ impl Hashes for KeyValue {
|
|||
}
|
||||
|
||||
impl TableEntry for KeyValue {
|
||||
fn new_vacant() -> Self {
|
||||
KeyValue {
|
||||
index: Value::Nil,
|
||||
value: Value::Nil,
|
||||
}
|
||||
}
|
||||
const VACANT: Self = KeyValue {
|
||||
index: Value::Nil,
|
||||
value: Value::Nil,
|
||||
};
|
||||
fn is_vacant(&self) -> bool {
|
||||
matches!(
|
||||
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 {
|
||||
fn hashed(&self) -> Hashed {
|
||||
match self {
|
||||
|
|
@ -314,11 +47,11 @@ impl Hashes for Value {
|
|||
0
|
||||
}
|
||||
}
|
||||
Value::CachedString(cached) => cached.1,
|
||||
Value::String(_string, hash) => *hash,
|
||||
Value::CachedString(hashed) => hashed.1,
|
||||
Value::String(hashed) => hashed.1,
|
||||
Value::Function(callable) => match callable {
|
||||
Callable::Rust(native) => Rc::as_ptr(native).addr() as Hashed,
|
||||
Callable::Mars(closure) => closure.addr() as Hashed,
|
||||
Callable::Rust(native) => native.hashed(),
|
||||
Callable::Mars(closure) => closure.hashed(),
|
||||
},
|
||||
Value::Integer(integer) => *integer as Hashed,
|
||||
Value::Number(number) => {
|
||||
|
|
@ -328,8 +61,8 @@ impl Hashes for Value {
|
|||
number.to_bits() as Hashed
|
||||
}
|
||||
}
|
||||
Value::Table(table) => table.addr() as Hashed,
|
||||
Value::Object(object) => object.addr() as Hashed,
|
||||
Value::Table(table) => table.hashed(),
|
||||
Value::Object(object, ..) => object.hashed(),
|
||||
#[cfg(feature = "vector3")]
|
||||
Value::Vector(vec) => {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
|
|
@ -341,23 +74,31 @@ impl Hashes for Value {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
table: Map<KeyValue>,
|
||||
table: DisplacementSet<KeyValue>,
|
||||
array: Vec<Value>,
|
||||
pub meta: Option<Gc<Table>>,
|
||||
pub meta: MaybeId<Table>,
|
||||
}
|
||||
|
||||
impl Traverse for Table {
|
||||
fn traverse(&self) {
|
||||
for Entry { entry, .. } in self.table.map.iter() {
|
||||
impl<C> Traverse<C> for Table
|
||||
where
|
||||
C: MarsAllocator,
|
||||
{
|
||||
fn collect(self) {
|
||||
todo!()
|
||||
}
|
||||
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() {
|
||||
entry.index.traverse();
|
||||
entry.value.traverse();
|
||||
entry.index.traverse(context);
|
||||
entry.value.traverse(context);
|
||||
}
|
||||
}
|
||||
for item in self.array.iter() {
|
||||
item.traverse()
|
||||
for i in 0..context.freelist().get(id).array.len() {
|
||||
let item = context.freelist().get(id).array[i].clone();
|
||||
item.traverse(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -378,24 +119,24 @@ impl Table {
|
|||
// This is an integer index, try the array first
|
||||
match value {
|
||||
Value::Nil => {
|
||||
if (0..self.array.len() + 1).contains(&(index as usize)) {
|
||||
if self.array.len() == index as usize {
|
||||
if (0..self.array.len() + 1).contains(&(index as usize - 1)) {
|
||||
if self.array.len() == index as usize - 1 {
|
||||
self.array.pop();
|
||||
} 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 => {
|
||||
if (0..self.array.len() + 1).contains(&(index as usize)) {
|
||||
if self.array.len() == index as usize {
|
||||
if (0..self.array.len() + 1).contains(&(index as usize - 1)) {
|
||||
if self.array.len() == index as usize - 1 {
|
||||
self.array.push(item)
|
||||
} else {
|
||||
self.array[index as usize + 1] = item;
|
||||
self.array[index as usize - 1] = item;
|
||||
}
|
||||
} else {
|
||||
self.table.set_table(KeyValue {
|
||||
self.table.set(KeyValue {
|
||||
index: Value::Integer(index),
|
||||
value: item,
|
||||
})
|
||||
|
|
@ -407,7 +148,7 @@ impl Table {
|
|||
Value::Nil => Err(RunError(
|
||||
"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> {
|
||||
|
|
@ -422,11 +163,8 @@ impl Table {
|
|||
// todo: move this into the Map
|
||||
Ok(self
|
||||
.table
|
||||
.get_table(index)
|
||||
.unwrap_or(&KeyValue {
|
||||
index: Value::Nil,
|
||||
value: Value::Nil,
|
||||
})
|
||||
.get(index)
|
||||
.unwrap_or(&KeyValue::VACANT)
|
||||
.value
|
||||
.clone())
|
||||
}
|
||||
|
|
@ -437,9 +175,9 @@ impl Table {
|
|||
}
|
||||
pub fn new() -> Self {
|
||||
Table {
|
||||
table: Map::new(),
|
||||
table: DisplacementSet::new(),
|
||||
array: Vec::new(),
|
||||
meta: None,
|
||||
meta: MaybeId::NULL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
0
src/tasks.rs
Normal file
0
src/tasks.rs
Normal file
|
|
@ -1,9 +1,5 @@
|
|||
local var = 2
|
||||
local function thing()
|
||||
print(var)
|
||||
var = 3
|
||||
local tab = {}
|
||||
for i = 1, 1000000 do
|
||||
tab[i] = true
|
||||
end
|
||||
|
||||
print(var)
|
||||
thing()
|
||||
thing()
|
||||
debug(tab)
|
||||
Loading…
Add table
Add a link
Reference in a new issue