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: Sized { fn matches(&self, other: &T) -> bool; } impl Equivalent for T where T: PartialEq, { fn matches(&self, other: &T) -> bool { self == other } } pub trait Hashes { fn hashed(&self) -> Hashed; } pub trait TableEntry: Equivalent + Hashes { fn new_vacant() -> Self; fn is_vacant(&self) -> bool; } #[derive(Debug, Clone)] struct KeyValue { index: Value, value: Value, } impl Equivalent for KeyValue { fn matches(&self, other: &Self) -> bool { self.index == other.index } } impl Hashes for KeyValue { fn hashed(&self) -> Hashed { self.index.hashed() } } impl TableEntry for KeyValue { fn new_vacant() -> Self { KeyValue { index: Value::Nil, value: Value::Nil, } } fn is_vacant(&self) -> bool { matches!( self, KeyValue { index: Value::Nil, .. } ) } } #[derive(Debug, Clone)] struct Entry { entry: E, home: usize, displacement: Displacement, } #[derive(Clone, Debug)] pub struct Map { // default representing vacancy! map: Vec>, map_bounds: std::ops::Range, map_count: usize, // number of elements in table } impl Map { pub(crate) fn string_cache() { // todo: this isn't a clean way, maybe make a generic Map and use a specific one for the cache and the rest of the language } 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 ], ); 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) } self.map_bounds = self.table_lower()..self.table_upper() } 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 = 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(&mut self, index: K) where E: Equivalent, 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]; 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(&mut self, index: K) -> Option<&E> where E: Equivalent, 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]; if neighbour.entry.matches(&index) { return Some(&neighbour.entry); } } None } } pub(crate) fn new() -> Map { Map { map: Vec::new(), map_count: 0, map_bounds: (0..0).into(), } } } impl Hashes for Value { fn hashed(&self) -> Hashed { match self { Value::Nil => 0, Value::Bool(boolean) => { if *boolean { 1 } else { 0 } } Value::CachedString(cached) => cached.1, Value::String(_string, hash) => *hash, Value::Function(callable) => match callable { Callable::Rust(native) => Rc::as_ptr(native).addr() as Hashed, Callable::Mars(closure) => closure.addr() as Hashed, }, Value::Integer(integer) => *integer as Hashed, Value::Number(number) => { if number.is_nan() { 0 } else { number.to_bits() as Hashed } } Value::Table(table) => table.addr() as Hashed, Value::Object(object) => object.addr() as Hashed, #[cfg(feature = "vector3")] Value::Vector(vec) => { let mut hasher = DefaultHasher::new(); vec.as_u64vec3().hash(&mut hasher); hasher.finish() as usize } } } } // todo: displaced items don't ever get shuffled closer to their homes unless the table is resized #[derive(Debug, Clone)] pub struct Table { table: Map, array: Vec, pub meta: Option>, } impl Traverse for Table { fn traverse(&self) { for Entry { entry, .. } in self.table.map.iter() { if !entry.is_vacant() { entry.index.traverse(); entry.value.traverse(); } } for item in self.array.iter() { item.traverse() } } } impl Equivalent for KeyValue { fn matches(&self, other: &Value) -> bool { self.index == *other } } impl Table { pub fn resize_array(&mut self, len: usize) { self.array.resize(len, Value::Nil) } pub fn set(&mut self, index: Value, value: Value) -> Result<(), RunError> { match index { Value::Integer(index) => { // 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 { self.array.pop(); } else { self.array[index as usize + 1] = Value::Nil } } self.table.rem_table(Value::Integer(index)); } item => { if (0..self.array.len() + 1).contains(&(index as usize)) { if self.array.len() == index as usize { self.array.push(item) } else { self.array[index as usize + 1] = item; } } else { self.table.set_table(KeyValue { index: Value::Integer(index), value: item, }) } } } Ok(()) } 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 })), } } pub fn get(&mut self, index: Value) -> Result { match index { Value::Integer(index) => Ok(self .array .get(index as usize - 1) .unwrap_or(&Value::Nil) .clone()), Value::Nil => Err(RunError("Attempt to index table with key: nil".to_string())), index => { // todo: move this into the Map Ok(self .table .get_table(index) .unwrap_or(&KeyValue { index: Value::Nil, value: Value::Nil, }) .value .clone()) } } } pub fn append(&mut self, item: Value) { self.array.push(item) } pub fn new() -> Self { Table { table: Map::new(), array: Vec::new(), meta: None, } } }