diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index ae71266..f8edbb7 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -176,7 +176,10 @@ impl<'a> Analyzer<'a> { expression: &Expression, context: &Context, ) -> Result<(), ContextError> { - log::trace!("Analyzing expression {expression}"); + log::trace!( + "Analyzing expression {expression} at {:?}", + expression.position() + ); match expression { Expression::Block(block_expression) => { diff --git a/dust-lang/src/context.rs b/dust-lang/src/context.rs index f2cf5fd..fbe9c58 100644 --- a/dust-lang/src/context.rs +++ b/dust-lang/src/context.rs @@ -25,6 +25,17 @@ impl Context { inner: Arc::new(ContextInner { associations: RwLock::new(data), parent: None, + 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, }), } } @@ -45,6 +56,7 @@ impl Context { inner: Arc::new(ContextInner { associations: RwLock::new(HashMap::new()), parent: Some(Arc::downgrade(&self.inner)), + is_immutable: false, }), } } @@ -158,20 +170,19 @@ impl Context { /// Recovers the context from a poisoned state by recovering data from an error. /// /// This method is not used. - pub fn _recover_from_poison(&mut self, error: &ContextError) { + pub fn _recover_from_poison(&mut self, recovered: &RwLockReadGuard) { log::debug!("Context is recovering from poison error"); - let ContextError::PoisonErrorRecovered(recovered) = error; - let mut new_associations = HashMap::new(); - for (identifier, (context_data, position)) in recovered.as_ref() { + 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, + is_immutable: false, }); } } @@ -184,18 +195,12 @@ impl Default for Context { #[derive(Debug)] pub struct ContextInner { - pub associations: RwLock, - pub parent: Option>, + associations: RwLock, + parent: Option>, + is_immutable: bool, } impl ContextInner { - pub fn new(associations: RwLock, parent: Option>) -> Self { - Self { - associations, - parent, - } - } - /// Returns the number of associated identifiers in the context. pub fn association_count(&self) -> Result { Ok(self.associations.read()?.len()) @@ -329,6 +334,10 @@ impl ContextInner { identifier: Identifier, r#type: Type, ) -> Result<(), ContextError> { + if self.is_immutable { + return Err(ContextError::CannotMutateImmutableContext); + } + log::trace!("Setting {identifier} to type {type}."); let mut associations = self.associations.write()?; @@ -351,6 +360,10 @@ impl ContextInner { identifier: Identifier, value: Value, ) -> Result<(), ContextError> { + if self.is_immutable { + return Err(ContextError::CannotMutateImmutableContext); + } + log::trace!("Setting {identifier} to value {value}"); let mut associations = self.associations.write()?; @@ -373,6 +386,10 @@ impl ContextInner { identifier: Identifier, constructor: Constructor, ) -> Result<(), ContextError> { + if self.is_immutable { + return Err(ContextError::CannotMutateImmutableContext); + } + log::trace!("Setting {identifier} to constructor {constructor:?}"); let mut associations = self.associations.write()?; @@ -396,6 +413,10 @@ impl ContextInner { identifier: Identifier, struct_type: StructType, ) -> Result<(), ContextError> { + if self.is_immutable { + return Err(ContextError::CannotMutateImmutableContext); + } + log::trace!("Setting {identifier} to constructor of type {struct_type}"); let mut variables = self.associations.write()?; @@ -490,6 +511,7 @@ pub enum ContextData { #[derive(Debug, Clone)] pub enum ContextError { + CannotMutateImmutableContext, PoisonErrorRecovered(Arc), } @@ -512,9 +534,11 @@ impl From>> for ContextError { impl PartialEq for ContextError { fn eq(&self, other: &Self) -> bool { match (self, other) { + (Self::CannotMutateImmutableContext, Self::CannotMutateImmutableContext) => true, (Self::PoisonErrorRecovered(left), Self::PoisonErrorRecovered(right)) => { Arc::ptr_eq(left, right) } + _ => false, } } } @@ -522,6 +546,7 @@ impl PartialEq for ContextError { impl Display for ContextError { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { + Self::CannotMutateImmutableContext => write!(f, "Cannot mutate immutable context"), Self::PoisonErrorRecovered(associations) => { write!( f, diff --git a/dust-lang/src/core_library.rs b/dust-lang/src/core_library.rs index d4c75bd..13fcf01 100644 --- a/dust-lang/src/core_library.rs +++ b/dust-lang/src/core_library.rs @@ -6,7 +6,7 @@ static CORE_LIBRARY: OnceLock = OnceLock::new(); pub fn core_library<'a>() -> &'a Context { CORE_LIBRARY.get_or_init(|| { - Context::with_data(HashMap::from([ + Context::with_data_immutable(HashMap::from([ ( Identifier::new("to_string"), ( diff --git a/dust-lang/src/dust_error.rs b/dust-lang/src/dust_error.rs index 3df6b76..95fff49 100644 --- a/dust-lang/src/dust_error.rs +++ b/dust-lang/src/dust_error.rs @@ -1,8 +1,8 @@ //! Top-level error handling for the Dust language. -use annotate_snippets::{Level, Message, Renderer, Snippet}; +use annotate_snippets::{Level, Renderer, Snippet}; use std::fmt::Display; -use crate::{AnalysisError, ContextError, LexError, ParseError, RuntimeError, Span}; +use crate::{AnalysisError, ContextError, LexError, ParseError, RuntimeError}; /// An error that occurred during the execution of the Dust language and its /// corresponding source code. @@ -79,43 +79,6 @@ impl<'src> DustError<'src> { } } - fn footer(&self) -> Vec<(&'static str, Span, String)> { - match self { - DustError::ContextError(_) => vec![], - DustError::Runtime { runtime_error, .. } => { - let mut error_data = vec![( - "Runtime error", - runtime_error.position(), - runtime_error.to_string(), - )]; - - if let RuntimeError::Expression { error, position } = runtime_error { - error_data.push(( - "Error occured at this expression", - *position, - error.to_string(), - )); - } - - error_data - } - DustError::Analysis { - analysis_errors, .. - } => analysis_errors - .iter() - .map(|error| ("Analysis error", error.position(), error.to_string())) - .collect(), - DustError::Parse { parse_error, .. } => vec![( - "Parse error", - parse_error.position(), - parse_error.to_string(), - )], - DustError::Lex { lex_error, .. } => { - vec![("Lex error", lex_error.position(), lex_error.to_string())] - } - } - } - pub fn report(&self) -> String { let mut report = String::new(); let renderer = Renderer::styled(); diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 2d5cebc..eb6f9ea 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -1022,8 +1022,13 @@ impl Vm { let mut evaluation = Evaluation::Return(None); for statement in ast.statements { + let position = statement.position(); evaluation = Vm.run_statement(statement, &ast.context, collect_garbage)?; + ast.context + .collect_garbage(position.1) + .map_err(|error| RuntimeError::ContextError { error, position })?; + if let Evaluation::Break(_) = evaluation { return Ok(evaluation); } diff --git a/dust-lang/tests/scopes.rs b/dust-lang/tests/scopes.rs index 001b5fc..c27e39e 100644 --- a/dust-lang/tests/scopes.rs +++ b/dust-lang/tests/scopes.rs @@ -9,6 +9,8 @@ fn block_scope_captures_parent() { #[test] fn block_scope_does_not_capture_child() { + env_logger::builder().is_test(true).try_init().unwrap(); + let source = "{ let x = 42; } x"; assert_eq!(