2024-08-10 01:12:36 +00:00
|
|
|
//! Garbage-collecting context for variables.
|
2024-08-12 08:10:07 +00:00
|
|
|
use std::{
|
|
|
|
collections::HashMap,
|
2024-08-14 02:57:37 +00:00
|
|
|
sync::{Arc, PoisonError as StdPoisonError, RwLock, RwLockWriteGuard},
|
2024-08-12 08:10:07 +00:00
|
|
|
};
|
2024-08-10 00:52:13 +00:00
|
|
|
|
2024-08-17 08:06:13 +00:00
|
|
|
use crate::{ast::Span, Identifier, Type, Value};
|
2024-08-10 00:52:13 +00:00
|
|
|
|
2024-08-14 02:57:37 +00:00
|
|
|
pub type Variables = HashMap<Identifier, (VariableData, Span)>;
|
|
|
|
|
2024-08-10 01:12:36 +00:00
|
|
|
/// Garbage-collecting context for variables.
|
|
|
|
#[derive(Debug, Clone)]
|
2024-08-10 00:52:13 +00:00
|
|
|
pub struct Context {
|
2024-08-14 02:57:37 +00:00
|
|
|
variables: Arc<RwLock<Variables>>,
|
2024-08-10 00:52:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Context {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
2024-08-12 08:10:07 +00:00
|
|
|
variables: Arc::new(RwLock::new(HashMap::new())),
|
2024-08-10 00:52:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-14 02:57:37 +00:00
|
|
|
/// Creates a deep copy of another context.
|
2024-08-12 12:54:21 +00:00
|
|
|
pub fn with_variables_from(other: &Self) -> Self {
|
|
|
|
Self {
|
|
|
|
variables: Arc::new(RwLock::new(other.variables.read().unwrap().clone())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-14 02:57:37 +00:00
|
|
|
/// Returns the number of variables in the context.
|
2024-08-12 08:10:07 +00:00
|
|
|
pub fn variable_count(&self) -> usize {
|
|
|
|
self.variables.read().unwrap().len()
|
2024-08-10 00:52:13 +00:00
|
|
|
}
|
|
|
|
|
2024-08-14 02:57:37 +00:00
|
|
|
/// Returns a boolean indicating whether the context contains the variable.
|
2024-08-12 08:10:07 +00:00
|
|
|
pub fn contains(&self, identifier: &Identifier) -> bool {
|
|
|
|
self.variables.read().unwrap().contains_key(identifier)
|
2024-08-10 00:52:13 +00:00
|
|
|
}
|
|
|
|
|
2024-08-14 02:57:37 +00:00
|
|
|
/// Returns the full VariableData and Span if the context contains the given identifier.
|
2024-08-12 08:10:07 +00:00
|
|
|
pub fn get(&self, identifier: &Identifier) -> Option<(VariableData, Span)> {
|
|
|
|
self.variables.read().unwrap().get(identifier).cloned()
|
2024-08-10 00:52:13 +00:00
|
|
|
}
|
|
|
|
|
2024-08-14 02:57:37 +00:00
|
|
|
/// Returns the type of the variable with the given identifier.
|
2024-08-12 08:10:07 +00:00
|
|
|
pub fn get_type(&self, identifier: &Identifier) -> Option<Type> {
|
|
|
|
match self.variables.read().unwrap().get(identifier) {
|
|
|
|
Some((VariableData::Type(r#type), _)) => Some(r#type.clone()),
|
2024-08-13 16:23:25 +00:00
|
|
|
Some((VariableData::Value(value), _)) => Some(value.r#type()),
|
2024-08-12 02:41:40 +00:00
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-14 02:57:37 +00:00
|
|
|
/// Returns the VariableData of the variable with the given identifier.
|
2024-08-12 08:10:07 +00:00
|
|
|
pub fn get_variable_data(&self, identifier: &Identifier) -> Option<VariableData> {
|
|
|
|
match self.variables.read().unwrap().get(identifier) {
|
|
|
|
Some((variable_data, _)) => Some(variable_data.clone()),
|
2024-08-12 02:41:40 +00:00
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-14 02:57:37 +00:00
|
|
|
/// Returns the value of the variable with the given identifier.
|
2024-08-12 08:10:07 +00:00
|
|
|
pub fn get_value(&self, identifier: &Identifier) -> Option<Value> {
|
|
|
|
match self.variables.read().unwrap().get(identifier) {
|
|
|
|
Some((VariableData::Value(value), _)) => Some(value.clone()),
|
2024-08-10 00:52:13 +00:00
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-14 02:57:37 +00:00
|
|
|
/// Sets a variable to a type, with a position given for garbage collection.
|
2024-08-12 12:54:21 +00:00
|
|
|
pub fn set_type(&self, identifier: Identifier, r#type: Type, position: Span) {
|
2024-08-12 08:10:07 +00:00
|
|
|
log::trace!("Setting {identifier} to type {type} at {position:?}");
|
2024-08-12 02:41:40 +00:00
|
|
|
|
2024-08-12 08:10:07 +00:00
|
|
|
self.variables
|
|
|
|
.write()
|
|
|
|
.unwrap()
|
|
|
|
.insert(identifier, (VariableData::Type(r#type), position));
|
2024-08-10 00:52:13 +00:00
|
|
|
}
|
|
|
|
|
2024-08-14 02:57:37 +00:00
|
|
|
/// Sets a variable to a value.
|
2024-08-12 12:54:21 +00:00
|
|
|
pub fn set_value(&self, identifier: Identifier, value: Value) {
|
2024-08-12 08:10:07 +00:00
|
|
|
log::trace!("Setting {identifier} to value {value}");
|
2024-08-12 02:41:40 +00:00
|
|
|
|
2024-08-12 08:10:07 +00:00
|
|
|
let mut variables = self.variables.write().unwrap();
|
|
|
|
|
|
|
|
let last_position = variables
|
|
|
|
.get(&identifier)
|
|
|
|
.map(|(_, last_position)| *last_position)
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
|
|
variables.insert(identifier, (VariableData::Value(value), last_position));
|
2024-08-10 00:52:13 +00:00
|
|
|
}
|
|
|
|
|
2024-08-14 02:57:37 +00:00
|
|
|
/// Collects garbage up to the given position, removing all variables with lesser positions.
|
2024-08-17 09:32:18 +00:00
|
|
|
pub fn collect_garbage(&self, position: Span) {
|
|
|
|
log::trace!("Collecting garbage up to {position:?}");
|
2024-08-12 08:10:07 +00:00
|
|
|
|
|
|
|
let mut variables = self.variables.write().unwrap();
|
|
|
|
|
|
|
|
variables.retain(|identifier, (_, last_used)| {
|
2024-08-17 09:32:18 +00:00
|
|
|
let should_drop = position.0 > last_used.0 && position.1 > last_used.1;
|
2024-08-12 08:10:07 +00:00
|
|
|
|
|
|
|
if should_drop {
|
|
|
|
log::trace!("Removing {identifier}");
|
|
|
|
}
|
|
|
|
|
|
|
|
!should_drop
|
|
|
|
});
|
|
|
|
variables.shrink_to_fit();
|
2024-08-10 00:52:13 +00:00
|
|
|
}
|
2024-08-10 08:45:30 +00:00
|
|
|
|
2024-08-14 02:57:37 +00:00
|
|
|
/// Updates a variable's last known position, allowing it to live longer in the program.
|
|
|
|
/// Returns a boolean indicating whether the variable was found.
|
2024-08-12 12:54:21 +00:00
|
|
|
pub fn update_last_position(&self, identifier: &Identifier, position: Span) -> bool {
|
2024-08-12 08:10:07 +00:00
|
|
|
if let Some((_, last_position)) = self.variables.write().unwrap().get_mut(identifier) {
|
|
|
|
*last_position = position;
|
|
|
|
|
|
|
|
log::trace!("Updating {identifier}'s last position to {position:?}");
|
2024-08-10 08:45:30 +00:00
|
|
|
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2024-08-14 02:57:37 +00:00
|
|
|
|
|
|
|
/// Recovers the context from a poisoned state by recovering data from an error.
|
|
|
|
///
|
|
|
|
/// This method is not used. The context's other methods do not return poison errors because
|
|
|
|
/// they are infallible.
|
|
|
|
pub fn _recover_from_poison(&mut self, error: &ContextPoisonError) {
|
|
|
|
log::debug!("Context is recovering from poison error");
|
|
|
|
|
|
|
|
let recovered = error.get_ref();
|
|
|
|
let mut new_variables = HashMap::new();
|
|
|
|
|
|
|
|
for (identifier, (variable_data, position)) in recovered.iter() {
|
|
|
|
new_variables.insert(identifier.clone(), (variable_data.clone(), *position));
|
|
|
|
}
|
|
|
|
|
|
|
|
self.variables = Arc::new(RwLock::new(new_variables));
|
|
|
|
}
|
2024-08-10 00:52:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Context {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-10 01:12:36 +00:00
|
|
|
#[derive(Debug, Clone)]
|
2024-08-10 00:52:13 +00:00
|
|
|
pub enum VariableData {
|
|
|
|
Value(Value),
|
|
|
|
Type(Type),
|
|
|
|
}
|
|
|
|
|
2024-08-14 02:57:37 +00:00
|
|
|
pub type ContextPoisonError<'err> = StdPoisonError<RwLockWriteGuard<'err, Variables>>;
|
|
|
|
|
2024-08-12 02:41:40 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use crate::vm::run_with_context;
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
2024-08-12 02:47:52 +00:00
|
|
|
#[test]
|
2024-08-12 09:44:05 +00:00
|
|
|
fn context_removes_variables() {
|
2024-08-12 08:10:07 +00:00
|
|
|
env_logger::builder().is_test(true).try_init().unwrap();
|
|
|
|
|
2024-08-12 02:47:52 +00:00
|
|
|
let source = "
|
|
|
|
x = 5
|
|
|
|
y = 10
|
|
|
|
z = x + y
|
|
|
|
z
|
|
|
|
";
|
2024-08-12 12:54:21 +00:00
|
|
|
let context = Context::new();
|
2024-08-12 02:47:52 +00:00
|
|
|
|
2024-08-12 12:54:21 +00:00
|
|
|
run_with_context(source, context.clone()).unwrap();
|
2024-08-12 02:47:52 +00:00
|
|
|
|
2024-08-12 08:10:07 +00:00
|
|
|
assert_eq!(context.variable_count(), 0);
|
2024-08-12 02:47:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2024-08-12 09:44:05 +00:00
|
|
|
fn garbage_collector_does_not_break_loops() {
|
2024-08-12 02:47:52 +00:00
|
|
|
let source = "
|
2024-08-12 08:10:07 +00:00
|
|
|
y = 1
|
|
|
|
z = 0
|
2024-08-12 02:47:52 +00:00
|
|
|
while z < 10 {
|
2024-08-12 08:10:07 +00:00
|
|
|
z = z + y
|
2024-08-12 02:47:52 +00:00
|
|
|
}
|
|
|
|
";
|
2024-08-12 12:54:21 +00:00
|
|
|
let context = Context::new();
|
2024-08-12 02:47:52 +00:00
|
|
|
|
2024-08-12 12:54:21 +00:00
|
|
|
run_with_context(source, context.clone()).unwrap();
|
2024-08-12 02:47:52 +00:00
|
|
|
|
2024-08-12 08:10:07 +00:00
|
|
|
assert_eq!(context.variable_count(), 0);
|
2024-08-12 02:47:52 +00:00
|
|
|
}
|
2024-08-12 02:41:40 +00:00
|
|
|
}
|