Pass tests

This commit is contained in:
Jeff 2024-08-20 12:24:41 -04:00
parent 4846b3f74d
commit 169c1a9e3f
4 changed files with 35 additions and 11 deletions

View File

@ -16,7 +16,8 @@ use crate::{
LoopExpression, MapExpression, Node, OperatorExpression, RangeExpression, Span, Statement, LoopExpression, MapExpression, Node, OperatorExpression, RangeExpression, Span, Statement,
StructDefinition, StructExpression, TupleAccessExpression, 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. /// Analyzes the abstract syntax tree for errors.
@ -32,7 +33,7 @@ use crate::{
/// ``` /// ```
pub fn analyze(source: &str) -> Result<(), DustError> { pub fn analyze(source: &str) -> Result<(), DustError> {
let abstract_tree = parse(source)?; let abstract_tree = parse(source)?;
let context = Context::new(); let context = core_library().create_child();
let analyzer = Analyzer::new(&abstract_tree, context); let analyzer = Analyzer::new(&abstract_tree, context);
analyzer analyzer
@ -149,7 +150,7 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
Ok(()) Ok(())
} }
fn analyze_expression(&'recovered self, expression: &Expression) -> Result<(), AnalysisError> { fn analyze_expression(&self, expression: &Expression) -> Result<(), AnalysisError> {
match expression { match expression {
Expression::Block(block_expression) => self.analyze_block(&block_expression.inner)?, Expression::Block(block_expression) => self.analyze_block(&block_expression.inner)?,
Expression::Call(call_expression) => { Expression::Call(call_expression) => {
@ -173,8 +174,15 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
self.analyze_expression(expression.inner.as_ref())?; self.analyze_expression(expression.inner.as_ref())?;
} }
Expression::Identifier(identifier) => { Expression::Identifier(identifier) => {
self.context let found = self
.context
.update_last_position(&identifier.inner, identifier.position)?; .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::If(if_expression) => self.analyze_if(&if_expression.inner)?,
Expression::List(list_expression) => match list_expression.inner.as_ref() { Expression::List(list_expression) => match list_expression.inner.as_ref() {
@ -573,7 +581,7 @@ mod tests {
#[test] #[test]
fn subtract_assign_wrong_type() { fn subtract_assign_wrong_type() {
let source = " let source = "
a = 1 let mut a = 1;
a -= 1.0 a -= 1.0
"; ";
@ -674,6 +682,14 @@ mod tests {
fn undefined_variable() { fn undefined_variable() {
let source = "foo"; 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,
})
);
} }
} }

View File

@ -72,7 +72,7 @@ pub enum AstError {
impl AstError { impl AstError {
pub fn position(&self) -> Option<Span> { pub fn position(&self) -> Option<Span> {
match self { match self {
AstError::ContextError(error) => None, AstError::ContextError(_) => None,
AstError::ExpectedType { position } => Some(*position), AstError::ExpectedType { position } => Some(*position),
AstError::ExpectedTupleType { position } => Some(*position), AstError::ExpectedTupleType { position } => Some(*position),
AstError::ExpectedNonEmptyList { position } => Some(*position), AstError::ExpectedNonEmptyList { position } => Some(*position),

View File

@ -47,7 +47,13 @@ impl Context {
/// Returns a boolean indicating whether the identifier is in the context. /// Returns a boolean indicating whether the identifier is in the context.
pub fn contains(&self, identifier: &Identifier) -> Result<bool, ContextError> { pub fn contains(&self, identifier: &Identifier) -> Result<bool, ContextError> {
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. /// 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 /// 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( pub fn update_last_position(
&self, &self,
identifier: &Identifier, identifier: &Identifier,
@ -236,6 +244,8 @@ impl Context {
log::trace!("Updating {identifier}'s last position to {position:?}"); log::trace!("Updating {identifier}'s last position to {position:?}");
Ok(true) Ok(true)
} else if let Some(parent) = &self.parent {
parent.contains(identifier)
} else { } else {
Ok(false) Ok(false)
} }

View File

@ -1462,8 +1462,6 @@ mod tests {
#[test] #[test]
fn while_loop() { fn while_loop() {
env_logger::builder().is_test(true).try_init().ok();
let input = "let mut x = 0; while x < 5 { x += 1 } x"; let input = "let mut x = 0; while x < 5 { x += 1 } x";
assert_eq!(run(input), Ok(Some(Value::mutable_from(5)))); assert_eq!(run(input), Ok(Some(Value::mutable_from(5))));