1
0
dust/dust-lang/src/context.rs

597 lines
18 KiB
Rust
Raw Normal View History

//! Garbage-collecting context for variables.
use std::{
collections::HashMap,
2024-08-20 15:07:13 +00:00
fmt::{self, Display, Formatter},
sync::{Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak},
};
2024-08-10 00:52:13 +00:00
2024-09-02 07:58:53 +00:00
use crate::{Constructor, Identifier, StructType, Type, Value};
2024-08-10 00:52:13 +00:00
2024-09-02 07:58:53 +00:00
pub type Associations = HashMap<Identifier, (ContextData, usize)>;
/// Garbage-collecting context for variables.
#[derive(Debug, Clone)]
2024-08-10 00:52:13 +00:00
pub struct Context {
inner: Arc<ContextInner>,
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 {
inner: Arc::new(ContextInner {
associations: RwLock::new(data),
parent: None,
2024-09-05 15:32:31 +00:00
is_immutable: false,
}),
}
}
pub fn with_data_immutable(data: Associations) -> Self {
Self {
inner: Arc::new(ContextInner {
associations: RwLock::new(data),
parent: None,
is_immutable: true,
}),
2024-08-10 00:52:13 +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> {
let mut associations = HashMap::new();
for (identifier, (context_data, position)) in other.inner.associations.read()?.iter() {
associations.insert(identifier.clone(), (context_data.clone(), *position));
}
Ok(Self::with_data(associations))
2024-08-12 12:54:21 +00:00
}
2024-08-20 15:40:37 +00:00
pub fn create_child(&self) -> Self {
Self {
inner: Arc::new(ContextInner {
associations: RwLock::new(HashMap::new()),
parent: Some(Arc::downgrade(&self.inner)),
2024-09-05 15:32:31 +00:00
is_immutable: false,
}),
2024-08-20 15:40:37 +00:00
}
}
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> {
self.inner.association_count()
}
/// Returns a boolean indicating whether the identifier is in the context.
pub fn contains(&self, identifier: &Identifier) -> Result<bool, ContextError> {
self.inner.contains(identifier)
}
/// Returns the full ContextData and Span if the context contains the given identifier.
pub fn get(
&self,
identifier: &Identifier,
) -> Result<Option<(ContextData, usize)>, ContextError> {
self.inner.get(identifier)
}
/// Returns the type associated with the given identifier.
pub fn get_type(&self, identifier: &Identifier) -> Result<Option<Type>, ContextError> {
self.inner.get_type(identifier)
}
/// Returns the ContextData associated with the identifier.
pub fn get_data(&self, identifier: &Identifier) -> Result<Option<ContextData>, ContextError> {
self.inner.get_data(identifier)
}
/// Returns the value associated with the identifier.
pub fn get_variable_value(
&self,
identifier: &Identifier,
) -> Result<Option<Value>, ContextError> {
self.inner.get_variable_value(identifier)
}
/// Returns the constructor associated with the identifier.
pub fn get_constructor(
&self,
identifier: &Identifier,
) -> Result<Option<Constructor>, ContextError> {
self.inner.get_constructor(identifier)
}
/// Returns the constructor type associated with the identifier.
pub fn get_constructor_type(
&self,
identifier: &Identifier,
) -> Result<Option<StructType>, ContextError> {
self.inner.get_constructor_type(identifier)
}
/// 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,
) -> Result<(), ContextError> {
self.inner.set_variable_type(identifier, r#type)
}
/// Associates an identifier with a variable value.
pub fn set_variable_value(
&self,
identifier: Identifier,
value: Value,
) -> Result<(), ContextError> {
self.inner.set_variable_value(identifier, value)
}
/// Associates an identifier with a constructor.
pub fn set_constructor(
&self,
identifier: Identifier,
constructor: Constructor,
) -> Result<(), ContextError> {
self.inner.set_constructor(identifier, constructor)
}
/// Associates an identifier with a constructor type, with a position given for garbage
/// collection.
pub fn set_constructor_type(
&self,
identifier: Identifier,
struct_type: StructType,
) -> Result<(), ContextError> {
self.inner.set_constructor_type(identifier, struct_type)
}
2024-09-03 04:09:32 +00:00
/// Collects garbage up to the given position, removing all variables with lesser positions.
pub fn collect_garbage(&self, position: usize) -> Result<(), ContextError> {
self.inner.collect_garbage(position)
}
/// Updates an associated identifier's last known position, allowing it to live longer in the
/// 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.
pub fn update_last_position(
&self,
identifier: &Identifier,
position: usize,
) -> Result<bool, ContextError> {
self.inner.update_last_position(identifier, position)
}
/// Recovers the context from a poisoned state by recovering data from an error.
///
/// This method is not used.
2024-09-05 15:32:31 +00:00
pub fn _recover_from_poison(&mut self, recovered: &RwLockReadGuard<Associations>) {
log::debug!("Context is recovering from poison error");
let mut new_associations = HashMap::new();
2024-09-05 15:32:31 +00:00
for (identifier, (context_data, position)) in recovered.iter() {
new_associations.insert(identifier.clone(), (context_data.clone(), *position));
}
self.inner = Arc::new(ContextInner {
associations: RwLock::new(new_associations),
parent: None,
2024-09-05 15:32:31 +00:00
is_immutable: false,
});
}
}
impl Default for Context {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct ContextInner {
2024-09-05 15:32:31 +00:00
associations: RwLock<Associations>,
parent: Option<Weak<ContextInner>>,
is_immutable: bool,
}
impl ContextInner {
/// 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)
2024-09-02 09:53:09 +00:00
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
parent.contains(identifier)
} else {
Ok(false)
}
2024-08-20 16:24:41 +00:00
} 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,
2024-09-02 07:58:53 +00:00
) -> Result<Option<(ContextData, usize)>, ContextError> {
2024-09-02 15:04:08 +00:00
if let Some((variable_data, position)) = self.associations.read()?.get(identifier) {
return Ok(Some((variable_data.clone(), *position)));
2024-09-02 15:04:08 +00:00
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get(identifier);
}
2024-09-02 15:04:08 +00:00
}
Ok(None)
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-09-02 09:53:09 +00:00
if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_type(identifier);
}
2024-08-20 15:40:37 +00:00
}
Ok(None)
}
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) {
return Ok(Some(variable_data.clone()));
2024-09-02 09:53:09 +00:00
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_data(identifier);
}
}
Ok(None)
}
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)
{
return Ok(Some(value.clone()));
2024-09-02 09:53:09 +00:00
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_variable_value(identifier);
}
2024-08-20 06:25:22 +00:00
}
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)
{
return Ok(Some(constructor.clone()));
2024-09-02 09:53:09 +00:00
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_constructor(identifier);
}
2024-08-10 00:52:13 +00:00
}
Ok(None)
2024-08-10 00:52:13 +00:00
}
2024-08-23 20:33:38 +00:00
/// Returns the constructor type associated with the identifier.
pub fn get_constructor_type(
&self,
identifier: &Identifier,
) -> Result<Option<StructType>, ContextError> {
let read_associations = self.associations.read()?;
if let Some((context_data, _)) = read_associations.get(identifier) {
return match context_data {
2024-08-23 20:33:38 +00:00
ContextData::Constructor(constructor) => Ok(Some(constructor.struct_type.clone())),
ContextData::ConstructorType(struct_type) => Ok(Some(struct_type.clone())),
_ => Ok(None),
};
2024-09-02 09:53:09 +00:00
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_constructor_type(identifier);
}
2024-08-23 20:33:38 +00:00
}
Ok(None)
2024-08-23 20:33:38 +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,
) -> Result<(), ContextError> {
2024-09-05 15:32:31 +00:00
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
2024-08-31 11:24:45 +00:00
log::trace!("Setting {identifier} to type {type}.");
2024-08-23 20:33:38 +00:00
let mut associations = self.associations.write()?;
let last_position = associations
.get(&identifier)
2024-08-31 11:24:45 +00:00
.map(|(_, last_position)| *last_position)
2024-08-23 20:33:38 +00:00
.unwrap_or_default();
associations.insert(
identifier,
(ContextData::VariableType(r#type), last_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-09-05 15:32:31 +00:00
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
log::trace!("Setting {identifier} to value {value}");
2024-08-20 15:07:13 +00:00
let mut associations = self.associations.write()?;
let last_position = associations
.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-09-05 15:32:31 +00:00
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
2024-08-23 15:44:47 +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()?;
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,
2024-08-20 15:07:13 +00:00
) -> Result<(), ContextError> {
2024-09-05 15:32:31 +00:00
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
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-23 20:33:38 +00:00
let last_position = variables
.get(&identifier)
2024-09-02 07:58:53 +00:00
.map(|(_, last_position)| *last_position)
2024-08-23 20:33:38 +00:00
.unwrap_or_default();
2024-08-20 06:25:22 +00:00
variables.insert(
identifier,
2024-08-23 20:33:38 +00:00
(ContextData::ConstructorType(struct_type), last_position),
2024-08-20 06:25:22 +00:00
);
2024-08-20 15:07:13 +00:00
Ok(())
2024-08-10 00:52:13 +00:00
}
/// Collects garbage up to the given position, removing all variables with lesser positions.
2024-09-02 07:58:53 +00:00
pub fn collect_garbage(&self, position: usize) -> Result<(), ContextError> {
log::trace!("Collecting garbage up to {position}");
2024-08-20 15:07:13 +00:00
let mut variables = self.associations.write()?;
variables.retain(|identifier, (_, last_used)| {
2024-09-02 07:58:53 +00:00
let should_drop = position >= *last_used;
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,
2024-09-02 07:58:53 +00:00
position: usize,
2024-09-02 16:57:27 +00:00
) -> Result<bool, ContextError> {
let found = self.update_position_if_found(identifier, position)?;
if found {
2024-09-03 10:08:34 +00:00
Ok(true)
} else {
let mut associations = self.associations.write()?;
2024-09-02 16:57:27 +00:00
2024-09-03 10:08:34 +00:00
log::trace!("Updating {identifier}'s last position to {position:?}");
2024-09-02 16:57:27 +00:00
2024-09-03 10:08:34 +00:00
associations.insert(identifier.clone(), (ContextData::Reserved, position));
2024-09-02 16:57:27 +00:00
2024-09-03 10:08:34 +00:00
Ok(false)
2024-09-02 16:57:27 +00:00
}
}
fn update_position_if_found(
&self,
identifier: &Identifier,
position: usize,
2024-08-20 15:07:13 +00:00
) -> Result<bool, ContextError> {
2024-08-31 11:24:45 +00:00
let mut associations = self.associations.write()?;
2024-08-31 11:24:45 +00:00
if let Some((_, last_position)) = associations.get_mut(identifier) {
2024-09-02 15:04:08 +00:00
log::trace!("Updating {identifier}'s last position to {position:?}");
2024-08-31 11:24:45 +00:00
2024-09-02 15:04:08 +00:00
*last_position = position;
2024-08-10 08:45:30 +00:00
2024-09-03 10:08:34 +00:00
return Ok(true);
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.update_position_if_found(identifier, position);
}
2024-08-10 08:45:30 +00:00
}
2024-09-03 10:08:34 +00:00
Ok(false)
2024-08-10 08:45:30 +00:00
}
2024-08-10 00:52:13 +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-31 11:24:45 +00:00
Reserved,
2024-08-10 00:52:13 +00:00
}
2024-08-20 15:07:13 +00:00
#[derive(Debug, Clone)]
pub enum ContextError {
2024-09-05 15:32:31 +00:00
CannotMutateImmutableContext,
2024-08-20 15:07:13 +00:00
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) {
2024-09-05 15:32:31 +00:00
(Self::CannotMutateImmutableContext, Self::CannotMutateImmutableContext) => true,
2024-08-20 15:07:13 +00:00
(Self::PoisonErrorRecovered(left), Self::PoisonErrorRecovered(right)) => {
Arc::ptr_eq(left, right)
}
2024-09-05 15:32:31 +00:00
_ => false,
2024-08-20 15:07:13 +00:00
}
}
}
impl Display for ContextError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
2024-09-05 15:32:31 +00:00
Self::CannotMutateImmutableContext => write!(f, "Cannot mutate immutable context"),
2024-08-20 15:07:13 +00:00
Self::PoisonErrorRecovered(associations) => {
write!(
f,
"Context poisoned with {} associations recovered",
associations.len()
)
}
}
}
}
#[cfg(test)]
mod tests {
2024-08-30 23:58:07 +00:00
use crate::{parse, Vm};
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-30 22:06:58 +00:00
let ast = parse(source).unwrap();
let context = ast.context.clone();
2024-08-12 02:47:52 +00:00
2024-08-30 22:06:58 +00:00
assert_eq!(Vm.run(ast), Ok(Some(Value::integer(15))));
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 mut z = 0;
2024-08-12 02:47:52 +00:00
while z < 10 {
2024-08-30 22:06:58 +00:00
z += 1;
2024-08-12 02:47:52 +00:00
}
";
2024-08-30 22:06:58 +00:00
let ast = parse(source).unwrap();
let context = ast.context.clone();
2024-08-12 02:47:52 +00:00
2024-08-30 22:06:58 +00:00
assert_eq!(Vm.run(ast), Ok(None));
2024-08-20 15:07:13 +00:00
assert_eq!(context.association_count().unwrap(), 0);
2024-08-12 02:47:52 +00:00
}
}