Track down tricky context bug
This commit is contained in:
parent
5c9a8dab31
commit
c42fca496b
@ -84,6 +84,12 @@ impl<'a> Analyzer<'a> {
|
|||||||
statement: &Statement,
|
statement: &Statement,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
) -> Result<(), ContextError> {
|
) -> Result<(), ContextError> {
|
||||||
|
log::trace!(
|
||||||
|
"Analyzing statement {statement} at {:?} with context {}",
|
||||||
|
statement.position(),
|
||||||
|
context.id()
|
||||||
|
);
|
||||||
|
|
||||||
match statement {
|
match statement {
|
||||||
Statement::Expression(expression) => self.analyze_expression(expression, context)?,
|
Statement::Expression(expression) => self.analyze_expression(expression, context)?,
|
||||||
Statement::ExpressionNullified(expression_node) => {
|
Statement::ExpressionNullified(expression_node) => {
|
||||||
@ -112,9 +118,7 @@ impl<'a> Analyzer<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(r#type) = r#type {
|
if let Some(r#type) = r#type {
|
||||||
self.abstract_tree
|
context.set_variable_type(identifier.inner.clone(), r#type.clone())?;
|
||||||
.context
|
|
||||||
.set_variable_type(identifier.inner.clone(), r#type.clone())?;
|
|
||||||
} else {
|
} else {
|
||||||
self.errors
|
self.errors
|
||||||
.push(AnalysisError::LetExpectedValueFromStatement {
|
.push(AnalysisError::LetExpectedValueFromStatement {
|
||||||
@ -177,8 +181,9 @@ impl<'a> Analyzer<'a> {
|
|||||||
context: &Context,
|
context: &Context,
|
||||||
) -> Result<(), ContextError> {
|
) -> Result<(), ContextError> {
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"Analyzing expression {expression} at {:?}",
|
"Analyzing expression {expression} at {:?} with context {}",
|
||||||
expression.position()
|
expression.position(),
|
||||||
|
context.id()
|
||||||
);
|
);
|
||||||
|
|
||||||
match expression {
|
match expression {
|
||||||
@ -828,6 +833,8 @@ impl<'a> Analyzer<'a> {
|
|||||||
BlockExpression::Sync(ast) => ast,
|
BlockExpression::Sync(ast) => ast,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
log::trace!("Analyzing block with context {}", ast.context.id());
|
||||||
|
|
||||||
for statement in &ast.statements {
|
for statement in &ast.statements {
|
||||||
self.analyze_statement(statement, &ast.context)?;
|
self.analyze_statement(statement, &ast.context)?;
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,22 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt::{self, Display, Formatter},
|
fmt::{self, Display, Formatter},
|
||||||
sync::{Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak},
|
sync::{
|
||||||
|
atomic::{AtomicUsize, Ordering},
|
||||||
|
Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Constructor, Identifier, StructType, Type, Value};
|
use crate::{Constructor, Identifier, StructType, Type, Value};
|
||||||
|
|
||||||
pub type Associations = HashMap<Identifier, (ContextData, usize)>;
|
pub type Associations = HashMap<Identifier, (ContextData, usize)>;
|
||||||
|
|
||||||
|
static ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
|
fn next_id() -> usize {
|
||||||
|
ID_COUNTER.fetch_add(1, Ordering::SeqCst)
|
||||||
|
}
|
||||||
|
|
||||||
/// Garbage-collecting context for variables.
|
/// Garbage-collecting context for variables.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
@ -26,6 +35,7 @@ impl Context {
|
|||||||
associations: RwLock::new(data),
|
associations: RwLock::new(data),
|
||||||
parent: None,
|
parent: None,
|
||||||
is_immutable: false,
|
is_immutable: false,
|
||||||
|
id: next_id(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -36,6 +46,7 @@ impl Context {
|
|||||||
associations: RwLock::new(data),
|
associations: RwLock::new(data),
|
||||||
parent: None,
|
parent: None,
|
||||||
is_immutable: true,
|
is_immutable: true,
|
||||||
|
id: next_id(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,10 +68,15 @@ impl Context {
|
|||||||
associations: RwLock::new(HashMap::new()),
|
associations: RwLock::new(HashMap::new()),
|
||||||
parent: Some(Arc::downgrade(&self.inner)),
|
parent: Some(Arc::downgrade(&self.inner)),
|
||||||
is_immutable: false,
|
is_immutable: false,
|
||||||
|
id: next_id(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> usize {
|
||||||
|
self.inner.id
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the number of associated identifiers in the context.
|
/// Returns the number of associated identifiers in the context.
|
||||||
pub fn association_count(&self) -> Result<usize, ContextError> {
|
pub fn association_count(&self) -> Result<usize, ContextError> {
|
||||||
self.inner.association_count()
|
self.inner.association_count()
|
||||||
@ -183,6 +199,7 @@ impl Context {
|
|||||||
associations: RwLock::new(new_associations),
|
associations: RwLock::new(new_associations),
|
||||||
parent: None,
|
parent: None,
|
||||||
is_immutable: false,
|
is_immutable: false,
|
||||||
|
id: next_id(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,12 +212,17 @@ impl Default for Context {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ContextInner {
|
pub struct ContextInner {
|
||||||
|
id: usize,
|
||||||
associations: RwLock<Associations>,
|
associations: RwLock<Associations>,
|
||||||
parent: Option<Weak<ContextInner>>,
|
parent: Option<Weak<ContextInner>>,
|
||||||
is_immutable: bool,
|
is_immutable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextInner {
|
impl ContextInner {
|
||||||
|
fn parent(&self) -> Option<Arc<ContextInner>> {
|
||||||
|
self.parent.as_ref().and_then(|parent| parent.upgrade())
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the number of associated identifiers in the context.
|
/// Returns the number of associated identifiers in the context.
|
||||||
pub fn association_count(&self) -> Result<usize, ContextError> {
|
pub fn association_count(&self) -> Result<usize, ContextError> {
|
||||||
Ok(self.associations.read()?.len())
|
Ok(self.associations.read()?.len())
|
||||||
@ -328,7 +350,7 @@ impl ContextInner {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Associates an identifier with a variable type, with a position given for garbage collection.
|
/// Associates an identifier with a variable type.
|
||||||
pub fn set_variable_type(
|
pub fn set_variable_type(
|
||||||
&self,
|
&self,
|
||||||
identifier: Identifier,
|
identifier: Identifier,
|
||||||
@ -338,7 +360,7 @@ impl ContextInner {
|
|||||||
return Err(ContextError::CannotMutateImmutableContext);
|
return Err(ContextError::CannotMutateImmutableContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
log::trace!("Setting {identifier} to type {type}.");
|
log::trace!("Setting {identifier} to type {type} in context {}", self.id);
|
||||||
|
|
||||||
let mut associations = self.associations.write()?;
|
let mut associations = self.associations.write()?;
|
||||||
let last_position = associations
|
let last_position = associations
|
||||||
@ -364,7 +386,10 @@ impl ContextInner {
|
|||||||
return Err(ContextError::CannotMutateImmutableContext);
|
return Err(ContextError::CannotMutateImmutableContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
log::trace!("Setting {identifier} to value {value}");
|
log::trace!(
|
||||||
|
"Setting {identifier} to value {value} in context {}",
|
||||||
|
self.id
|
||||||
|
);
|
||||||
|
|
||||||
let mut associations = self.associations.write()?;
|
let mut associations = self.associations.write()?;
|
||||||
let last_position = associations
|
let last_position = associations
|
||||||
@ -390,7 +415,10 @@ impl ContextInner {
|
|||||||
return Err(ContextError::CannotMutateImmutableContext);
|
return Err(ContextError::CannotMutateImmutableContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
log::trace!("Setting {identifier} to constructor {constructor:?}");
|
log::trace!(
|
||||||
|
"Setting {identifier} to constructor {constructor:?} in context {}",
|
||||||
|
self.id
|
||||||
|
);
|
||||||
|
|
||||||
let mut associations = self.associations.write()?;
|
let mut associations = self.associations.write()?;
|
||||||
let last_position = associations
|
let last_position = associations
|
||||||
@ -417,7 +445,10 @@ impl ContextInner {
|
|||||||
return Err(ContextError::CannotMutateImmutableContext);
|
return Err(ContextError::CannotMutateImmutableContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
log::trace!("Setting {identifier} to constructor of type {struct_type}");
|
log::trace!(
|
||||||
|
"Setting {identifier} to constructor of type {struct_type} in context {}",
|
||||||
|
self.id
|
||||||
|
);
|
||||||
|
|
||||||
let mut variables = self.associations.write()?;
|
let mut variables = self.associations.write()?;
|
||||||
let last_position = variables
|
let last_position = variables
|
||||||
@ -435,7 +466,11 @@ impl ContextInner {
|
|||||||
|
|
||||||
/// Collects garbage up to the given position, removing all variables with lesser positions.
|
/// Collects garbage up to the given position, removing all variables with lesser positions.
|
||||||
pub fn collect_garbage(&self, position: usize) -> Result<(), ContextError> {
|
pub fn collect_garbage(&self, position: usize) -> Result<(), ContextError> {
|
||||||
log::trace!("Collecting garbage up to {position}");
|
if self.is_immutable {
|
||||||
|
return Err(ContextError::CannotMutateImmutableContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
log::trace!("Collecting garbage up to {position} in context {}", self.id);
|
||||||
|
|
||||||
let mut variables = self.associations.write()?;
|
let mut variables = self.associations.write()?;
|
||||||
|
|
||||||
@ -443,7 +478,7 @@ impl ContextInner {
|
|||||||
let should_drop = position >= *last_used;
|
let should_drop = position >= *last_used;
|
||||||
|
|
||||||
if should_drop {
|
if should_drop {
|
||||||
log::trace!("Removing {identifier}");
|
log::trace!("Removing {identifier} from context {}", self.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
!should_drop
|
!should_drop
|
||||||
@ -465,40 +500,60 @@ impl ContextInner {
|
|||||||
let found = self.update_position_if_found(identifier, position)?;
|
let found = self.update_position_if_found(identifier, position)?;
|
||||||
|
|
||||||
if found {
|
if found {
|
||||||
Ok(true)
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let found_in_ancestor = if let Some(parent) = &self.parent() {
|
||||||
|
if parent.is_immutable {
|
||||||
|
false
|
||||||
} else {
|
} else {
|
||||||
|
parent.update_position_if_found(identifier, position)?
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if found_in_ancestor {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
let mut associations = self.associations.write()?;
|
let mut associations = self.associations.write()?;
|
||||||
|
|
||||||
log::trace!("Updating {identifier}'s last position to {position:?}");
|
log::trace!(
|
||||||
|
"Reserving {identifier} at position {position:?} in context {}",
|
||||||
|
self.id
|
||||||
|
);
|
||||||
|
|
||||||
associations.insert(identifier.clone(), (ContextData::Reserved, position));
|
associations.insert(identifier.clone(), (ContextData::Reserved, position));
|
||||||
|
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn update_position_if_found(
|
fn update_position_if_found(
|
||||||
&self,
|
&self,
|
||||||
identifier: &Identifier,
|
identifier: &Identifier,
|
||||||
position: usize,
|
position: usize,
|
||||||
) -> Result<bool, ContextError> {
|
) -> Result<bool, ContextError> {
|
||||||
|
if self.is_immutable {
|
||||||
|
return Err(ContextError::CannotMutateImmutableContext);
|
||||||
|
}
|
||||||
|
|
||||||
let mut associations = self.associations.write()?;
|
let mut associations = self.associations.write()?;
|
||||||
|
|
||||||
if let Some((_, last_position)) = associations.get_mut(identifier) {
|
if let Some((_, last_position)) = associations.get_mut(identifier) {
|
||||||
log::trace!("Updating {identifier}'s last position to {position:?}");
|
log::trace!(
|
||||||
|
"Updating {identifier}'s last position to {position:?} in context {}",
|
||||||
|
self.id
|
||||||
|
);
|
||||||
|
|
||||||
*last_position = position;
|
*last_position = position;
|
||||||
|
|
||||||
return Ok(true);
|
Ok(true)
|
||||||
} else if let Some(parent) = &self.parent {
|
} else {
|
||||||
if let Some(parent) = parent.upgrade() {
|
|
||||||
return parent.update_position_if_found(identifier, position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ContextData {
|
pub enum ContextData {
|
||||||
|
@ -1020,10 +1020,7 @@ impl<'src> Parser<'src> {
|
|||||||
|
|
||||||
let context = context.create_child();
|
let context = context.create_child();
|
||||||
|
|
||||||
log::trace!(
|
log::trace!("Creating new block context {}", context.id());
|
||||||
"Creating new block context with {} associations",
|
|
||||||
context.association_count()?
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut ast = AbstractSyntaxTree {
|
let mut ast = AbstractSyntaxTree {
|
||||||
statements: VecDeque::new(),
|
statements: VecDeque::new(),
|
||||||
|
@ -1652,16 +1652,9 @@ mod tests {
|
|||||||
assert_eq!(run(input), Ok(Some(Value::integer(42))));
|
assert_eq!(run(input), Ok(Some(Value::integer(42))));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn built_in_function_dot_notation() {
|
|
||||||
let input = "42.to_string()";
|
|
||||||
|
|
||||||
assert_eq!(run(input), Ok(Some(Value::string("42"))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_string() {
|
fn to_string() {
|
||||||
let input = "to_string(42)";
|
let input = "42.to_string()";
|
||||||
|
|
||||||
assert_eq!(run(input), Ok(Some(Value::string("42"))));
|
assert_eq!(run(input), Ok(Some(Value::string("42"))));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user