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-20 15:07:13 +00:00
|
|
|
fmt::{self, Display, Formatter},
|
|
|
|
sync::{Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard},
|
2024-08-12 08:10:07 +00:00
|
|
|
};
|
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 15:07:13 +00:00
|
|
|
pub type Associations = HashMap<Identifier, (ContextData, Span)>;
|
2024-08-14 02:57:37 +00:00
|
|
|
|
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-20 15:07:13 +00:00
|
|
|
associations: Arc<RwLock<Associations>>,
|
2024-08-20 15:40:37 +00:00
|
|
|
parent: Option<Box<Context>>,
|
2024-08-10 00:52:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Context {
|
|
|
|
pub fn new() -> Self {
|
2024-08-20 15:07:13 +00:00
|
|
|
Self::with_data(HashMap::new())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_data(data: Associations) -> Self {
|
2024-08-10 00:52:13 +00:00
|
|
|
Self {
|
2024-08-20 15:07:13 +00:00
|
|
|
associations: Arc::new(RwLock::new(data)),
|
2024-08-20 15:40:37 +00:00
|
|
|
parent: None,
|
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-20 15:07:13 +00:00
|
|
|
pub fn with_data_from(other: &Self) -> Result<Self, ContextError> {
|
|
|
|
Ok(Self::with_data(other.associations.read()?.clone()))
|
2024-08-12 12:54:21 +00:00
|
|
|
}
|
|
|
|
|
2024-08-20 15:40:37 +00:00
|
|
|
pub fn create_child(&self) -> Self {
|
|
|
|
Self {
|
|
|
|
associations: Arc::new(RwLock::new(HashMap::new())),
|
|
|
|
parent: Some(Box::new(self.clone())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
/// Returns the number of associated identifiers in the context.
|
|
|
|
pub fn association_count(&self) -> Result<usize, ContextError> {
|
|
|
|
Ok(self.associations.read()?.len())
|
2024-08-10 00:52:13 +00:00
|
|
|
}
|
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
/// Returns a boolean indicating whether the identifier is in the context.
|
|
|
|
pub fn contains(&self, identifier: &Identifier) -> Result<bool, ContextError> {
|
2024-08-20 16:24:41 +00:00
|
|
|
if self.associations.read()?.contains_key(identifier) {
|
|
|
|
Ok(true)
|
|
|
|
} else if let Some(parent) = &self.parent {
|
|
|
|
parent.contains(identifier)
|
|
|
|
} else {
|
|
|
|
Ok(false)
|
|
|
|
}
|
2024-08-10 00:52:13 +00:00
|
|
|
}
|
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
/// Returns the full ContextData and Span if the context contains the given identifier.
|
|
|
|
pub fn get(
|
|
|
|
&self,
|
|
|
|
identifier: &Identifier,
|
|
|
|
) -> Result<Option<(ContextData, Span)>, ContextError> {
|
|
|
|
let associations = self.associations.read()?;
|
|
|
|
|
|
|
|
Ok(associations.get(identifier).cloned())
|
2024-08-10 00:52:13 +00:00
|
|
|
}
|
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
/// Returns the type associated with the given identifier.
|
|
|
|
pub fn get_type(&self, identifier: &Identifier) -> Result<Option<Type>, ContextError> {
|
2024-08-20 15:40:37 +00:00
|
|
|
match self.associations.read()?.get(identifier) {
|
|
|
|
Some((ContextData::VariableType(r#type), _)) => return Ok(Some(r#type.clone())),
|
|
|
|
Some((ContextData::VariableValue(value), _)) => return Ok(Some(value.r#type())),
|
2024-08-20 06:25:22 +00:00
|
|
|
Some((ContextData::ConstructorType(struct_type), _)) => {
|
2024-08-20 15:40:37 +00:00
|
|
|
return Ok(Some(Type::Struct(struct_type.clone())))
|
2024-08-20 06:25:22 +00:00
|
|
|
}
|
2024-08-20 15:40:37 +00:00
|
|
|
_ => {}
|
|
|
|
}
|
2024-08-20 15:07:13 +00:00
|
|
|
|
2024-08-20 15:40:37 +00:00
|
|
|
if let Some(parent) = &self.parent {
|
|
|
|
parent.get_type(identifier)
|
|
|
|
} else {
|
|
|
|
Ok(None)
|
|
|
|
}
|
2024-08-12 02:41:40 +00:00
|
|
|
}
|
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
/// Returns the ContextData associated with the identifier.
|
|
|
|
pub fn get_data(&self, identifier: &Identifier) -> Result<Option<ContextData>, ContextError> {
|
2024-08-20 15:40:37 +00:00
|
|
|
if let Some((variable_data, _)) = self.associations.read()?.get(identifier) {
|
|
|
|
Ok(Some(variable_data.clone()))
|
|
|
|
} else if let Some(parent) = &self.parent {
|
|
|
|
parent.get_data(identifier)
|
|
|
|
} else {
|
|
|
|
Ok(None)
|
2024-08-12 02:41:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
/// Returns the value associated with the identifier.
|
|
|
|
pub fn get_variable_value(
|
|
|
|
&self,
|
|
|
|
identifier: &Identifier,
|
|
|
|
) -> Result<Option<Value>, ContextError> {
|
2024-08-20 15:40:37 +00:00
|
|
|
if let Some((ContextData::VariableValue(value), _)) =
|
|
|
|
self.associations.read()?.get(identifier)
|
|
|
|
{
|
|
|
|
Ok(Some(value.clone()))
|
|
|
|
} else if let Some(parent) = &self.parent {
|
|
|
|
parent.get_variable_value(identifier)
|
|
|
|
} else {
|
|
|
|
Ok(None)
|
2024-08-20 06:25:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
/// Returns the constructor associated with the identifier.
|
|
|
|
pub fn get_constructor(
|
|
|
|
&self,
|
|
|
|
identifier: &Identifier,
|
|
|
|
) -> Result<Option<Constructor>, ContextError> {
|
2024-08-20 15:40:37 +00:00
|
|
|
if let Some((ContextData::Constructor(constructor), _)) =
|
|
|
|
self.associations.read()?.get(identifier)
|
|
|
|
{
|
|
|
|
Ok(Some(constructor.clone()))
|
|
|
|
} else if let Some(parent) = &self.parent {
|
|
|
|
parent.get_constructor(identifier)
|
|
|
|
} else {
|
|
|
|
Ok(None)
|
2024-08-10 00:52:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
/// Associates an identifier with a variable type, with a position given for garbage collection.
|
|
|
|
pub fn set_variable_type(
|
|
|
|
&self,
|
|
|
|
identifier: Identifier,
|
|
|
|
r#type: Type,
|
|
|
|
position: Span,
|
|
|
|
) -> Result<(), ContextError> {
|
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-20 15:07:13 +00:00
|
|
|
self.associations
|
|
|
|
.write()?
|
2024-08-20 06:25:22 +00:00
|
|
|
.insert(identifier, (ContextData::VariableType(r#type), position));
|
2024-08-20 15:07:13 +00:00
|
|
|
|
|
|
|
Ok(())
|
2024-08-10 00:52:13 +00:00
|
|
|
}
|
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
/// Associates an identifier with a variable value.
|
|
|
|
pub fn set_variable_value(
|
|
|
|
&self,
|
|
|
|
identifier: Identifier,
|
|
|
|
value: Value,
|
|
|
|
) -> Result<(), ContextError> {
|
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-20 15:07:13 +00:00
|
|
|
let mut associations = self.associations.write()?;
|
2024-08-12 08:10:07 +00:00
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
let last_position = associations
|
2024-08-12 08:10:07 +00:00
|
|
|
.get(&identifier)
|
|
|
|
.map(|(_, last_position)| *last_position)
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
associations.insert(
|
2024-08-20 06:25:22 +00:00
|
|
|
identifier,
|
|
|
|
(ContextData::VariableValue(value), last_position),
|
|
|
|
);
|
2024-08-20 15:07:13 +00:00
|
|
|
|
|
|
|
Ok(())
|
2024-08-20 06:25:22 +00:00
|
|
|
}
|
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
/// Associates an identifier with a constructor.
|
|
|
|
pub fn set_constructor(
|
|
|
|
&self,
|
|
|
|
identifier: Identifier,
|
|
|
|
constructor: Constructor,
|
|
|
|
) -> Result<(), ContextError> {
|
2024-08-20 07:28:13 +00:00
|
|
|
log::trace!("Setting {identifier} to constructor {constructor}");
|
2024-08-20 06:25:22 +00:00
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
let mut associations = self.associations.write()?;
|
2024-08-20 06:25:22 +00:00
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
let last_position = associations
|
2024-08-20 06:25:22 +00:00
|
|
|
.get(&identifier)
|
|
|
|
.map(|(_, last_position)| *last_position)
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
associations.insert(
|
2024-08-20 06:25:22 +00:00
|
|
|
identifier,
|
|
|
|
(ContextData::Constructor(constructor), last_position),
|
|
|
|
);
|
2024-08-20 15:07:13 +00:00
|
|
|
|
|
|
|
Ok(())
|
2024-08-20 06:25:22 +00:00
|
|
|
}
|
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
/// Associates an identifier with a constructor type, with a position given for garbage
|
|
|
|
/// collection.
|
2024-08-20 06:25:22 +00:00
|
|
|
pub fn set_constructor_type(
|
|
|
|
&self,
|
|
|
|
identifier: Identifier,
|
|
|
|
struct_type: StructType,
|
|
|
|
position: Span,
|
2024-08-20 15:07:13 +00:00
|
|
|
) -> Result<(), ContextError> {
|
2024-08-20 07:28:13 +00:00
|
|
|
log::trace!("Setting {identifier} to constructor of type {struct_type}");
|
2024-08-20 06:25:22 +00:00
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
let mut variables = self.associations.write()?;
|
2024-08-20 06:25:22 +00:00
|
|
|
|
|
|
|
variables.insert(
|
|
|
|
identifier,
|
|
|
|
(ContextData::ConstructorType(struct_type), position),
|
|
|
|
);
|
2024-08-20 15:07:13 +00:00
|
|
|
|
|
|
|
Ok(())
|
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-20 16:09:17 +00:00
|
|
|
pub fn collect_garbage(&self, position_end: usize) -> Result<(), ContextError> {
|
|
|
|
log::trace!("Collecting garbage up to {position_end}");
|
2024-08-12 08:10:07 +00:00
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
let mut variables = self.associations.write()?;
|
2024-08-12 08:10:07 +00:00
|
|
|
|
|
|
|
variables.retain(|identifier, (_, last_used)| {
|
2024-08-20 16:09:17 +00:00
|
|
|
let should_drop = position_end >= 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-20 15:07:13 +00:00
|
|
|
|
|
|
|
Ok(())
|
2024-08-10 00:52:13 +00:00
|
|
|
}
|
2024-08-10 08:45:30 +00:00
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
/// Updates an associated identifier's last known position, allowing it to live longer in the
|
2024-08-20 16:24:41 +00:00
|
|
|
/// program. Returns a boolean indicating whether the identifier was found. If the identifier is
|
|
|
|
/// not found in the current context, the parent context is searched but parent context's
|
|
|
|
/// position is not updated.
|
2024-08-20 15:07:13 +00:00
|
|
|
pub fn update_last_position(
|
|
|
|
&self,
|
|
|
|
identifier: &Identifier,
|
|
|
|
position: Span,
|
|
|
|
) -> Result<bool, ContextError> {
|
|
|
|
if let Some((_, last_position)) = self.associations.write()?.get_mut(identifier) {
|
2024-08-12 08:10:07 +00:00
|
|
|
*last_position = position;
|
|
|
|
|
|
|
|
log::trace!("Updating {identifier}'s last position to {position:?}");
|
2024-08-10 08:45:30 +00:00
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
Ok(true)
|
2024-08-20 16:24:41 +00:00
|
|
|
} else if let Some(parent) = &self.parent {
|
|
|
|
parent.contains(identifier)
|
2024-08-10 08:45:30 +00:00
|
|
|
} else {
|
2024-08-20 15:07:13 +00:00
|
|
|
Ok(false)
|
2024-08-10 08:45:30 +00:00
|
|
|
}
|
|
|
|
}
|
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.
|
2024-08-20 15:07:13 +00:00
|
|
|
pub fn recover_from_poison(&mut self, error: &ContextError) {
|
2024-08-14 02:57:37 +00:00
|
|
|
log::debug!("Context is recovering from poison error");
|
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
let ContextError::PoisonErrorRecovered(recovered) = error;
|
|
|
|
|
|
|
|
let mut new_associations = HashMap::new();
|
2024-08-14 02:57:37 +00:00
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
for (identifier, (context_data, position)) in recovered.as_ref() {
|
|
|
|
new_associations.insert(identifier.clone(), (context_data.clone(), *position));
|
2024-08-14 02:57:37 +00:00
|
|
|
}
|
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
self.associations = Arc::new(RwLock::new(new_associations));
|
2024-08-14 02:57:37 +00:00
|
|
|
}
|
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-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
|
|
|
}
|
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub enum ContextError {
|
|
|
|
PoisonErrorRecovered(Arc<Associations>),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<PoisonError<RwLockWriteGuard<'_, Associations>>> for ContextError {
|
|
|
|
fn from(error: PoisonError<RwLockWriteGuard<'_, Associations>>) -> Self {
|
|
|
|
let associations = error.into_inner().clone();
|
|
|
|
|
|
|
|
Self::PoisonErrorRecovered(Arc::new(associations))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<PoisonError<RwLockReadGuard<'_, Associations>>> for ContextError {
|
|
|
|
fn from(error: PoisonError<RwLockReadGuard<'_, Associations>>) -> Self {
|
|
|
|
let associations = error.into_inner().clone();
|
|
|
|
|
|
|
|
Self::PoisonErrorRecovered(Arc::new(associations))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialEq for ContextError {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
match (self, other) {
|
|
|
|
(Self::PoisonErrorRecovered(left), Self::PoisonErrorRecovered(right)) => {
|
|
|
|
Arc::ptr_eq(left, right)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Display for ContextError {
|
|
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
Self::PoisonErrorRecovered(associations) => {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"Context poisoned with {} associations recovered",
|
|
|
|
associations.len()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-08-14 02:57:37 +00:00
|
|
|
|
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 02:47:52 +00:00
|
|
|
let source = "
|
2024-08-20 15:40:37 +00:00
|
|
|
let x = 5;
|
|
|
|
let y = 10;
|
|
|
|
let z = x + y;
|
2024-08-12 02:47:52 +00:00
|
|
|
z
|
|
|
|
";
|
2024-08-12 12:54:21 +00:00
|
|
|
let context = Context::new();
|
2024-08-12 02:47:52 +00:00
|
|
|
|
2024-08-20 16:09:17 +00:00
|
|
|
assert_eq!(
|
|
|
|
run_with_context(source, context.clone()),
|
|
|
|
Ok(Some(Value::Integer(15)))
|
|
|
|
);
|
2024-08-12 02:47:52 +00:00
|
|
|
|
2024-08-20 15:07:13 +00:00
|
|
|
assert_eq!(context.association_count().unwrap(), 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-20 15:40:37 +00:00
|
|
|
let y = 1;
|
|
|
|
let mut 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-20 15:07:13 +00:00
|
|
|
assert_eq!(context.association_count().unwrap(), 0);
|
2024-08-12 02:47:52 +00:00
|
|
|
}
|
2024-08-12 02:41:40 +00:00
|
|
|
}
|