dust/dust-lang/src/context.rs

248 lines
7.7 KiB
Rust
Raw Normal View History

//! Garbage-collecting context for variables.
use std::{
collections::HashMap,
sync::{Arc, PoisonError as StdPoisonError, RwLock, RwLockWriteGuard},
};
2024-08-10 00:52:13 +00:00
2024-08-20 06:25:22 +00:00
use crate::{ast::Span, Constructor, Identifier, StructType, Type, Value};
2024-08-10 00:52:13 +00:00
2024-08-20 06:25:22 +00:00
pub type Variables = HashMap<Identifier, (ContextData, Span)>;
/// Garbage-collecting context for variables.
#[derive(Debug, Clone)]
2024-08-10 00:52:13 +00:00
pub struct Context {
variables: Arc<RwLock<Variables>>,
2024-08-10 00:52:13 +00:00
}
impl Context {
pub fn new() -> Self {
Self {
variables: Arc::new(RwLock::new(HashMap::new())),
2024-08-10 00:52:13 +00:00
}
}
/// Creates a deep copy of another context.
2024-08-20 04:15:19 +00:00
pub fn with_data_from(other: &Self) -> Self {
2024-08-12 12:54:21 +00:00
Self {
variables: Arc::new(RwLock::new(other.variables.read().unwrap().clone())),
}
}
/// Returns the number of variables in the context.
pub fn variable_count(&self) -> usize {
self.variables.read().unwrap().len()
2024-08-10 00:52:13 +00:00
}
/// Returns a boolean indicating whether the context contains the variable.
pub fn contains(&self, identifier: &Identifier) -> bool {
self.variables.read().unwrap().contains_key(identifier)
2024-08-10 00:52:13 +00:00
}
/// Returns the full VariableData and Span if the context contains the given identifier.
2024-08-20 06:25:22 +00:00
pub fn get(&self, identifier: &Identifier) -> Option<(ContextData, Span)> {
self.variables.read().unwrap().get(identifier).cloned()
2024-08-10 00:52:13 +00:00
}
/// Returns the type of the variable with the given identifier.
2024-08-20 06:25:22 +00:00
pub fn get_type(&self, identifier: &Identifier) -> Option<Type> {
match self.variables.read().unwrap().get(identifier) {
2024-08-20 06:25:22 +00:00
Some((ContextData::VariableType(r#type), _)) => Some(r#type.clone()),
Some((ContextData::VariableValue(value), _)) => Some(value.r#type()),
Some((ContextData::ConstructorType(struct_type), _)) => {
Some(Type::Struct(struct_type.clone()))
}
_ => None,
}
}
/// Returns the VariableData of the variable with the given identifier.
2024-08-20 06:25:22 +00:00
pub fn get_data(&self, identifier: &Identifier) -> Option<ContextData> {
match self.variables.read().unwrap().get(identifier) {
Some((variable_data, _)) => Some(variable_data.clone()),
_ => None,
}
}
/// Returns the value of the variable with the given identifier.
2024-08-20 04:15:19 +00:00
pub fn get_variable_value(&self, identifier: &Identifier) -> Option<Value> {
match self.variables.read().unwrap().get(identifier) {
2024-08-20 06:25:22 +00:00
Some((ContextData::VariableValue(value), _)) => Some(value.clone()),
_ => None,
}
}
/// Returns the constructor associated with the given identifier.
pub fn get_constructor(&self, identifier: &Identifier) -> Option<Constructor> {
match self.variables.read().unwrap().get(identifier) {
Some((ContextData::Constructor(constructor), _)) => Some(constructor.clone()),
2024-08-10 00:52:13 +00:00
_ => None,
}
}
/// Sets a variable to a type, with a position given for garbage collection.
2024-08-20 04:15:19 +00:00
pub fn set_variable_type(&self, identifier: Identifier, r#type: Type, position: Span) {
log::trace!("Setting {identifier} to type {type} at {position:?}");
self.variables
.write()
.unwrap()
2024-08-20 06:25:22 +00:00
.insert(identifier, (ContextData::VariableType(r#type), position));
2024-08-10 00:52:13 +00:00
}
/// Sets a variable to a value.
2024-08-20 04:15:19 +00:00
pub fn set_variable_value(&self, identifier: Identifier, value: Value) {
log::trace!("Setting {identifier} to value {value}");
let mut variables = self.variables.write().unwrap();
let last_position = variables
.get(&identifier)
.map(|(_, last_position)| *last_position)
.unwrap_or_default();
2024-08-20 06:25:22 +00:00
variables.insert(
identifier,
(ContextData::VariableValue(value), last_position),
);
}
/// Associates a constructor with an identifier.
pub fn set_constructor(&self, identifier: Identifier, constructor: Constructor) {
log::trace!("Setting {identifier} to constructor {constructor:?}");
let mut variables = self.variables.write().unwrap();
let last_position = variables
.get(&identifier)
.map(|(_, last_position)| *last_position)
.unwrap_or_default();
variables.insert(
identifier,
(ContextData::Constructor(constructor), last_position),
);
}
/// Associates a constructor type with an identifier.
pub fn set_constructor_type(
&self,
identifier: Identifier,
struct_type: StructType,
position: Span,
) {
log::trace!("Setting {identifier} to constructor of type {struct_type:?}");
let mut variables = self.variables.write().unwrap();
variables.insert(
identifier,
(ContextData::ConstructorType(struct_type), position),
);
2024-08-10 00:52:13 +00:00
}
/// Collects garbage up to the given position, removing all variables with lesser positions.
pub fn collect_garbage(&self, position: Span) {
log::trace!("Collecting garbage up to {position:?}");
let mut variables = self.variables.write().unwrap();
variables.retain(|identifier, (_, last_used)| {
let should_drop = position.0 > last_used.0 && position.1 > last_used.1;
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
/// 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 {
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
}
}
/// 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()
}
}
#[derive(Debug, Clone)]
2024-08-20 06:25:22 +00:00
pub enum ContextData {
Constructor(Constructor),
ConstructorType(StructType),
VariableValue(Value),
VariableType(Type),
2024-08-10 00:52:13 +00:00
}
pub type ContextPoisonError<'err> = StdPoisonError<RwLockWriteGuard<'err, Variables>>;
#[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() {
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
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 = "
y = 1
z = 0
2024-08-12 02:47:52 +00:00
while z < 10 {
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
assert_eq!(context.variable_count(), 0);
2024-08-12 02:47:52 +00:00
}
}