diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index d040842..c217ec1 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -16,7 +16,8 @@ use crate::{ LoopExpression, MapExpression, Node, OperatorExpression, RangeExpression, Span, Statement, StructDefinition, StructExpression, TupleAccessExpression, }, - parse, Context, ContextError, DustError, Expression, Identifier, StructType, Type, + core_library, parse, Context, ContextError, DustError, Expression, Identifier, StructType, + Type, }; /// Analyzes the abstract syntax tree for errors. @@ -32,7 +33,7 @@ use crate::{ /// ``` pub fn analyze(source: &str) -> Result<(), DustError> { let abstract_tree = parse(source)?; - let context = Context::new(); + let context = core_library().create_child(); let analyzer = Analyzer::new(&abstract_tree, context); analyzer @@ -149,7 +150,7 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> { Ok(()) } - fn analyze_expression(&'recovered self, expression: &Expression) -> Result<(), AnalysisError> { + fn analyze_expression(&self, expression: &Expression) -> Result<(), AnalysisError> { match expression { Expression::Block(block_expression) => self.analyze_block(&block_expression.inner)?, Expression::Call(call_expression) => { @@ -173,8 +174,15 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> { self.analyze_expression(expression.inner.as_ref())?; } Expression::Identifier(identifier) => { - self.context + let found = self + .context .update_last_position(&identifier.inner, identifier.position)?; + + if !found { + return Err(AnalysisError::UndefinedVariable { + identifier: identifier.clone(), + }); + } } Expression::If(if_expression) => self.analyze_if(&if_expression.inner)?, Expression::List(list_expression) => match list_expression.inner.as_ref() { @@ -573,7 +581,7 @@ mod tests { #[test] fn subtract_assign_wrong_type() { let source = " - a = 1 + let mut a = 1; a -= 1.0 "; @@ -674,6 +682,14 @@ mod tests { fn undefined_variable() { let source = "foo"; - assert_eq!(analyze(source), todo!()); + assert_eq!( + analyze(source), + Err(DustError::Analysis { + analysis_error: AnalysisError::UndefinedVariable { + identifier: Node::new(Identifier::new("foo"), (0, 3)) + }, + source, + }) + ); } } diff --git a/dust-lang/src/ast/mod.rs b/dust-lang/src/ast/mod.rs index 564a48a..ed1ba1b 100644 --- a/dust-lang/src/ast/mod.rs +++ b/dust-lang/src/ast/mod.rs @@ -72,7 +72,7 @@ pub enum AstError { impl AstError { pub fn position(&self) -> Option { match self { - AstError::ContextError(error) => None, + AstError::ContextError(_) => None, AstError::ExpectedType { position } => Some(*position), AstError::ExpectedTupleType { position } => Some(*position), AstError::ExpectedNonEmptyList { position } => Some(*position), diff --git a/dust-lang/src/context.rs b/dust-lang/src/context.rs index b79e8a4..c2e3cc1 100644 --- a/dust-lang/src/context.rs +++ b/dust-lang/src/context.rs @@ -47,7 +47,13 @@ impl Context { /// Returns a boolean indicating whether the identifier is in the context. pub fn contains(&self, identifier: &Identifier) -> Result { - Ok(self.associations.read()?.contains_key(identifier)) + if self.associations.read()?.contains_key(identifier) { + Ok(true) + } else if let Some(parent) = &self.parent { + parent.contains(identifier) + } else { + Ok(false) + } } /// Returns the full ContextData and Span if the context contains the given identifier. @@ -224,7 +230,9 @@ impl Context { } /// Updates an associated identifier's last known position, allowing it to live longer in the - /// program. Returns a boolean indicating whether the identifier. + /// 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, @@ -236,6 +244,8 @@ impl Context { log::trace!("Updating {identifier}'s last position to {position:?}"); Ok(true) + } else if let Some(parent) = &self.parent { + parent.contains(identifier) } else { Ok(false) } diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index e98c1e5..96a24ce 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -1462,8 +1462,6 @@ mod tests { #[test] fn while_loop() { - env_logger::builder().is_test(true).try_init().ok(); - let input = "let mut x = 0; while x < 5 { x += 1 } x"; assert_eq!(run(input), Ok(Some(Value::mutable_from(5))));