Compare commits
No commits in common. "user-objects-and-state" and "main" have entirely different histories.
user-objec
...
main
18 changed files with 1053 additions and 854 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,4 +1,3 @@
|
||||||
.idea
|
.idea
|
||||||
target
|
target
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
src/tests/main.lua
|
|
||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -1,3 +0,0 @@
|
||||||
[submodule "deimos"]
|
|
||||||
path = deimos
|
|
||||||
url = ssh://forgejo@git.mallard.gdn/PSx/Deimos.git
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"diagnostics.disable": [
|
|
||||||
"undefined-global"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
41
Cargo.lock
generated
Normal file
41
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colored"
|
||||||
|
version = "3.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glam"
|
||||||
|
version = "0.33.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7360bd2cd76e0cd9032d42cf2922155cecea2685b0cfa4630c3246df030bcfd6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mars"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"colored",
|
||||||
|
"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",
|
||||||
|
]
|
||||||
|
|
@ -4,11 +4,11 @@ version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
freelist = []
|
bighash = []
|
||||||
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 }
|
||||||
deimos = { path = "./deimos" }
|
colored = { version = "3.1.1" }
|
||||||
1
deimos
1
deimos
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 385a08b094e43ff12b60a41a90ab99b4b18cea0c
|
|
||||||
Binary file not shown.
BIN
doc/objects.xopp
BIN
doc/objects.xopp
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,
|
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, S> {
|
pub(crate) struct Traversal<'a> {
|
||||||
// A 'cursor' of where we are in assumed execution
|
// A 'cursor' of where we are in assumed execution
|
||||||
machine: &'a mut Machine<S>,
|
machine: &'a mut Machine,
|
||||||
contexts: Vec<Context>, // Stack of function prototypes being compiled
|
contexts: Vec<Context>, // Stack of function prototypes being compiled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -20,10 +20,40 @@ 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<Value, Index>, // map some constant value to an index for not repeating constants
|
constants: Map<ConstantEntry>, // 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),
|
||||||
|
|
@ -40,7 +70,7 @@ pub(crate) struct Scope {
|
||||||
upvalue: bool,
|
upvalue: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> Traversal<'_, S> {
|
impl Traversal<'_> {
|
||||||
fn compile<T>(&mut self, it: &T) -> Result<(), LoadError>
|
fn compile<T>(&mut self, it: &T) -> Result<(), LoadError>
|
||||||
where
|
where
|
||||||
T: Compile,
|
T: Compile,
|
||||||
|
|
@ -102,7 +132,7 @@ impl<S> Traversal<'_, S> {
|
||||||
| Code::RangeLoop(_)
|
| Code::RangeLoop(_)
|
||||||
| Code::RangeBegin
|
| Code::RangeBegin
|
||||||
| Code::IteratorLoop(_)
|
| Code::IteratorLoop(_)
|
||||||
| Code::Message(_) => {}
|
| Code::Comment(_) => {}
|
||||||
}
|
}
|
||||||
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);
|
||||||
|
|
@ -117,8 +147,14 @@ impl<S> Traversal<'_, S> {
|
||||||
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) = self.contexts.last_mut().unwrap().constants.get(it.clone()) {
|
if let Some(entry) = &mut self
|
||||||
*entry
|
.contexts
|
||||||
|
.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;
|
||||||
|
|
@ -127,7 +163,11 @@ impl<S> Traversal<'_, S> {
|
||||||
}
|
}
|
||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
self.contexts.last_mut().unwrap().constants.set(it, index);
|
self.contexts
|
||||||
|
.last_mut()
|
||||||
|
.unwrap()
|
||||||
|
.constants
|
||||||
|
.set_table(ConstantEntry(it, index));
|
||||||
index
|
index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -207,7 +247,7 @@ impl<S> Traversal<'_, S> {
|
||||||
}
|
}
|
||||||
Ok(context.prototype)
|
Ok(context.prototype)
|
||||||
}
|
}
|
||||||
pub(crate) fn new(machine: &mut Machine<S>) -> Traversal<S> {
|
pub(crate) fn new(machine: &mut Machine) -> Traversal {
|
||||||
Traversal {
|
Traversal {
|
||||||
machine,
|
machine,
|
||||||
contexts: vec![Context {
|
contexts: vec![Context {
|
||||||
|
|
@ -237,12 +277,12 @@ impl<S> Traversal<'_, S> {
|
||||||
|
|
||||||
// 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<S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError>;
|
fn compile(&self, traversal: &mut Traversal) -> Result<(), LoadError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_function<S>(
|
fn compile_function(
|
||||||
this: &Piece<&Function>,
|
this: &Piece<&Function>,
|
||||||
traversal: &mut Traversal<S>,
|
traversal: &mut Traversal,
|
||||||
method: bool,
|
method: bool,
|
||||||
) -> Result<(), LoadError> {
|
) -> Result<(), LoadError> {
|
||||||
let mut self_arguments = Vec::new();
|
let mut self_arguments = Vec::new();
|
||||||
|
|
@ -301,9 +341,9 @@ enum Access {
|
||||||
Get,
|
Get,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_identifier<S>(
|
fn compile_identifier(
|
||||||
name: Piece<String>,
|
name: Piece<String>,
|
||||||
traversal: &mut Traversal<S>,
|
traversal: &mut Traversal,
|
||||||
access: Access,
|
access: Access,
|
||||||
) -> Result<(), LoadError> {
|
) -> Result<(), LoadError> {
|
||||||
let contexts_len = traversal.contexts.len();
|
let contexts_len = traversal.contexts.len();
|
||||||
|
|
@ -376,7 +416,7 @@ fn compile_identifier<S>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// At this point there were no returns so we must use _G table
|
// break 'search avoids this use of the _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 {
|
||||||
|
|
@ -386,9 +426,9 @@ fn compile_identifier<S>(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_variable<S>(
|
fn compile_variable(
|
||||||
variable: Piece<&Variable>,
|
variable: Piece<&Variable>,
|
||||||
traversal: &mut Traversal<S>,
|
traversal: &mut Traversal,
|
||||||
access: Access,
|
access: Access,
|
||||||
) -> Result<(), LoadError> {
|
) -> Result<(), LoadError> {
|
||||||
match &variable.it {
|
match &variable.it {
|
||||||
|
|
@ -415,13 +455,13 @@ fn compile_variable<S>(
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Compile for Piece<&Function> {
|
impl Compile for Piece<&Function> {
|
||||||
fn compile<S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError> {
|
fn compile(&self, traversal: &mut Traversal) -> Result<(), LoadError> {
|
||||||
compile_function(self, traversal, false)
|
compile_function(self, traversal, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Compile for Piece<&Args> {
|
impl Compile for Piece<&Args> {
|
||||||
fn compile<S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError> {
|
fn compile(&self, traversal: &mut Traversal) -> 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();
|
||||||
|
|
@ -451,7 +491,7 @@ impl Compile for Piece<&Args> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Compile for Piece<&Call> {
|
impl Compile for Piece<&Call> {
|
||||||
fn compile<S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError> {
|
fn compile(&self, traversal: &mut Traversal) -> 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
|
||||||
|
|
@ -468,7 +508,7 @@ impl Compile for Piece<&Call> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Compile for Piece<&Prefix> {
|
impl Compile for Piece<&Prefix> {
|
||||||
fn compile<'a, 'b, S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError> {
|
fn compile<'a, 'b>(&self, traversal: &mut Traversal) -> Result<(), LoadError> {
|
||||||
match &self.it {
|
match &self.it {
|
||||||
Prefix::Call(call) => {
|
Prefix::Call(call) => {
|
||||||
let index = traversal.index().unwrap();
|
let index = traversal.index().unwrap();
|
||||||
|
|
@ -488,7 +528,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, S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError> {
|
fn compile<'a, 'b>(&self, traversal: &mut Traversal) -> 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 {
|
||||||
|
|
@ -536,7 +576,7 @@ impl Compile for Piece<&VarArg> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Compile for Piece<&Constructor> {
|
impl Compile for Piece<&Constructor> {
|
||||||
fn compile<'a, 'b, S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError> {
|
fn compile<'a, 'b>(&self, traversal: &mut Traversal) -> 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() {
|
||||||
|
|
@ -586,7 +626,7 @@ impl Compile for Piece<&Constructor> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Compile for Piece<&Expression> {
|
impl Compile for Piece<&Expression> {
|
||||||
fn compile<'a, S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError> {
|
fn compile<'a>(&self, traversal: &mut Traversal) -> 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());
|
||||||
|
|
@ -644,11 +684,7 @@ 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<S>(
|
fn resolve_break(traversal: &mut Traversal, begin: usize, end: usize) -> Result<(), LoadError> {
|
||||||
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 {
|
||||||
|
|
@ -664,8 +700,8 @@ fn resolve_break<S>(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_assignment<S>(
|
fn compile_assignment(
|
||||||
traversal: &mut Traversal<S>,
|
traversal: &mut Traversal,
|
||||||
variables: &Vec<Piece<Variable>>,
|
variables: &Vec<Piece<Variable>>,
|
||||||
expressions: &Vec<Piece<Expression>>,
|
expressions: &Vec<Piece<Expression>>,
|
||||||
) -> Result<(), LoadError> {
|
) -> Result<(), LoadError> {
|
||||||
|
|
@ -673,7 +709,7 @@ fn compile_assignment<S>(
|
||||||
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<S>,
|
traversal: &mut Traversal,
|
||||||
variable: Option<&Piece<Variable>>,
|
variable: Option<&Piece<Variable>>,
|
||||||
expression: &Piece<&Expression>|
|
expression: &Piece<&Expression>|
|
||||||
-> Result<(), LoadError> {
|
-> Result<(), LoadError> {
|
||||||
|
|
@ -748,7 +784,7 @@ fn compile_assignment<S>(
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Compile for Piece<&Block> {
|
impl Compile for Piece<&Block> {
|
||||||
fn compile<S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError> {
|
fn compile(&self, traversal: &mut Traversal) -> 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
|
||||||
|
|
@ -980,7 +1016,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<S>,
|
let compile_declaration = |traversal: &mut Traversal,
|
||||||
expression: &Piece<Expression>,
|
expression: &Piece<Expression>,
|
||||||
name: &Piece<Name>|
|
name: &Piece<Name>|
|
||||||
-> Result<(), LoadError> {
|
-> Result<(), LoadError> {
|
||||||
|
|
@ -1089,20 +1125,11 @@ 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();
|
||||||
|
|
@ -1113,12 +1140,12 @@ impl Compile for Piece<&Block> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Compile for Chunk {
|
impl Compile for Chunk {
|
||||||
fn compile<S>(&self, traversal: &mut Traversal<S>) -> Result<(), LoadError> {
|
fn compile(&self, traversal: &mut Traversal) -> Result<(), LoadError> {
|
||||||
traversal.compile(&self.0.inner_ref())
|
traversal.compile(&self.0.inner_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn compile<S>(chunk: &Chunk, machine: &mut Machine<S>) -> Result<Prototype, LoadError> {
|
pub(crate) fn compile(chunk: &Chunk, machine: &mut Machine) -> 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
18
src/core.rs
|
|
@ -1,18 +0,0 @@
|
||||||
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
Normal file
154
src/gc.rs
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
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
|
||||||
|
()
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/lib.rs
53
src/lib.rs
|
|
@ -1,52 +1,29 @@
|
||||||
#![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;
|
||||||
use crate::{RunError, Needle};
|
#[test]
|
||||||
|
fn simple() {
|
||||||
macro_rules! unwrap {
|
let mut machine = match Machine::new() {
|
||||||
($e:expr) => {
|
|
||||||
match $e {
|
|
||||||
Err(error) => panic!("{}", error),
|
Err(error) => panic!("{}", error),
|
||||||
Ok(result) => result,
|
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);
|
||||||
|
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// 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
|
||||||
|
|
|
||||||
343
src/table.rs
343
src/table.rs
|
|
@ -1,6 +1,56 @@
|
||||||
use crate::vm::{Callable, MarsAllocator, RunError, Value};
|
use crate::gc::{Gc, Traverse};
|
||||||
use deimos::gc::{Id, MaybeId, Traverse};
|
use crate::vm::{Callable, RunError, Value};
|
||||||
use deimos::map::{DisplacementSet, Equivalent, Hashed, Hashes, TableEntry};
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct KeyValue {
|
struct KeyValue {
|
||||||
|
|
@ -21,10 +71,12 @@ impl Hashes for KeyValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableEntry for KeyValue {
|
impl TableEntry for KeyValue {
|
||||||
const VACANT: Self = KeyValue {
|
fn new_vacant() -> Self {
|
||||||
|
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,
|
||||||
|
|
@ -36,6 +88,221 @@ 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 {
|
||||||
|
|
@ -47,11 +314,11 @@ impl Hashes for Value {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::CachedString(hashed) => hashed.1,
|
Value::CachedString(cached) => cached.1,
|
||||||
Value::String(hashed) => hashed.1,
|
Value::String(_string, hash) => *hash,
|
||||||
Value::Function(callable) => match callable {
|
Value::Function(callable) => match callable {
|
||||||
Callable::Rust(native) => native.hashed(),
|
Callable::Rust(native) => Rc::as_ptr(native).addr() as Hashed,
|
||||||
Callable::Mars(closure) => closure.hashed(),
|
Callable::Mars(closure) => closure.addr() as Hashed,
|
||||||
},
|
},
|
||||||
Value::Integer(integer) => *integer as Hashed,
|
Value::Integer(integer) => *integer as Hashed,
|
||||||
Value::Number(number) => {
|
Value::Number(number) => {
|
||||||
|
|
@ -61,8 +328,8 @@ impl Hashes for Value {
|
||||||
number.to_bits() as Hashed
|
number.to_bits() as Hashed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Table(table) => table.hashed(),
|
Value::Table(table) => table.addr() as Hashed,
|
||||||
Value::Object(object, ..) => object.hashed(),
|
Value::Object(object) => object.addr() as Hashed,
|
||||||
#[cfg(feature = "vector3")]
|
#[cfg(feature = "vector3")]
|
||||||
Value::Vector(vec) => {
|
Value::Vector(vec) => {
|
||||||
let mut hasher = DefaultHasher::new();
|
let mut hasher = DefaultHasher::new();
|
||||||
|
|
@ -74,28 +341,23 @@ 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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Table {
|
pub struct Table {
|
||||||
table: DisplacementSet<KeyValue>,
|
table: Map<KeyValue>,
|
||||||
array: Vec<Value>,
|
array: Vec<Value>,
|
||||||
pub meta: MaybeId<Table>,
|
pub meta: Option<Gc<Table>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C> Traverse<C> for Table
|
impl Traverse for Table {
|
||||||
where
|
fn traverse(&self) {
|
||||||
C: MarsAllocator,
|
for Entry { entry, .. } in self.table.map.iter() {
|
||||||
{
|
|
||||||
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(context);
|
entry.index.traverse();
|
||||||
entry.value.traverse(context);
|
entry.value.traverse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i in 0..context.freelist().get(id).array.len() {
|
for item in self.array.iter() {
|
||||||
let item = context.freelist().get(id).array[i].clone();
|
item.traverse()
|
||||||
item.traverse(context);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -116,24 +378,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 - 1)) {
|
if (0..self.array.len() + 1).contains(&(index as usize)) {
|
||||||
if self.array.len() == index as usize - 1 {
|
if self.array.len() == index as usize {
|
||||||
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.remove(Value::Integer(index));
|
self.table.rem_table(Value::Integer(index));
|
||||||
}
|
}
|
||||||
item => {
|
item => {
|
||||||
if (0..self.array.len() + 1).contains(&(index as usize - 1)) {
|
if (0..self.array.len() + 1).contains(&(index as usize)) {
|
||||||
if self.array.len() == index as usize - 1 {
|
if self.array.len() == index as usize {
|
||||||
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(KeyValue {
|
self.table.set_table(KeyValue {
|
||||||
index: Value::Integer(index),
|
index: Value::Integer(index),
|
||||||
value: item,
|
value: item,
|
||||||
})
|
})
|
||||||
|
|
@ -145,7 +407,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(KeyValue { index, value })),
|
index => Ok(self.table.set_table(KeyValue { index, value })),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get(&mut self, index: Value) -> Result<Value, RunError> {
|
pub fn get(&mut self, index: Value) -> Result<Value, RunError> {
|
||||||
|
|
@ -160,8 +422,11 @@ impl Table {
|
||||||
// todo: move this into the Map
|
// todo: move this into the Map
|
||||||
Ok(self
|
Ok(self
|
||||||
.table
|
.table
|
||||||
.get(index)
|
.get_table(index)
|
||||||
.unwrap_or(&KeyValue::VACANT)
|
.unwrap_or(&KeyValue {
|
||||||
|
index: Value::Nil,
|
||||||
|
value: Value::Nil,
|
||||||
|
})
|
||||||
.value
|
.value
|
||||||
.clone())
|
.clone())
|
||||||
}
|
}
|
||||||
|
|
@ -172,9 +437,9 @@ impl Table {
|
||||||
}
|
}
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Table {
|
Table {
|
||||||
table: DisplacementSet::new(),
|
table: Map::new(),
|
||||||
array: Vec::new(),
|
array: Vec::new(),
|
||||||
meta: MaybeId::NULL,
|
meta: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
output "hello"
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
local tab = {}
|
local var = 2
|
||||||
for i = 1, 1000000 do
|
local function thing()
|
||||||
tab[i] = true
|
print(var)
|
||||||
|
var = 3
|
||||||
end
|
end
|
||||||
output(#tab)
|
|
||||||
|
print(var)
|
||||||
|
thing()
|
||||||
|
thing()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue