From ab53df56bc3484b32077cee0cfec44eb48d77713 Mon Sep 17 00:00:00 2001 From: Jeff Date: Fri, 23 Aug 2024 16:33:38 -0400 Subject: [PATCH] Pass all unit tests --- dust-lang/src/analyzer.rs | 176 +++++++++++++++++++++++++++-- dust-lang/src/ast/expression.rs | 83 +++++++++----- dust-lang/src/ast/mod.rs | 7 ++ dust-lang/src/built_in_function.rs | 11 +- dust-lang/src/context.rs | 53 ++++++++- dust-lang/src/evaluation.rs | 6 +- dust-lang/src/type.rs | 22 +++- dust-lang/src/value.rs | 10 +- 8 files changed, 316 insertions(+), 52 deletions(-) diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index e791fb9..2abac46 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -18,7 +18,7 @@ use crate::{ StructExpression, TupleAccessExpression, }, core_library, parse, Context, ContextError, DustError, Expression, Identifier, RangeableType, - StructType, Type, + StructType, Type, TypeConflict, TypeEvaluation, }; /// Analyzes the abstract syntax tree for errors. @@ -97,6 +97,24 @@ impl<'a> Analyzer<'a> { return; } + Ok(TypeEvaluation::Constructor(StructType::Unit { name })) => { + let set_type = self.context.set_variable_type( + identifier.inner.clone(), + Type::Struct(StructType::Unit { name }), + statement.position(), + ); + + if let Err(context_error) = set_type { + self.errors.push(AnalysisError::ContextError { + error: context_error, + position: identifier.position, + }); + } + + self.analyze_expression(value, statement.position()); + + return; + } Ok(evaluation) => evaluation.r#type(), }; @@ -104,7 +122,7 @@ impl<'a> Analyzer<'a> { let set_type = self.context.set_variable_type( identifier.inner.clone(), r#type.clone(), - identifier.position, + statement.position(), ); if let Err(context_error) = set_type { @@ -188,6 +206,110 @@ impl<'a> Analyzer<'a> { self.analyze_expression(invoker, statement_position); + let invoker_evaluation = match invoker.type_evaluation(&self.context) { + Ok(evaluation) => evaluation, + Err(ast_error) => { + self.errors.push(AnalysisError::AstError(ast_error)); + + return; + } + }; + + if let TypeEvaluation::Constructor(StructType::Tuple { fields, .. }) = + invoker_evaluation + { + for (expected_type, argument) in fields.iter().zip(arguments.iter()) { + let actual_type = match argument.type_evaluation(&self.context) { + Ok(evaluation) => evaluation.r#type(), + Err(ast_error) => { + self.errors.push(AnalysisError::AstError(ast_error)); + + return; + } + }; + + if let Some(r#type) = actual_type { + let check = expected_type.check(&r#type); + + if let Err(type_conflict) = check { + self.errors.push(AnalysisError::TypeConflict { + actual_expression: argument.clone(), + type_conflict, + }); + } + } + } + + return; + } + + let invoked_type = if let Some(r#type) = invoker_evaluation.r#type() { + r#type + } else { + self.errors + .push(AnalysisError::ExpectedValueFromExpression { + expression: invoker.clone(), + }); + + return; + }; + let function_type = if let Type::Function(function_type) = invoked_type { + function_type + } else { + self.errors.push(AnalysisError::ExpectedFunction { + actual: invoked_type, + actual_expression: invoker.clone(), + }); + + return; + }; + + let value_parameters = + if let Some(value_parameters) = &function_type.value_parameters { + value_parameters + } else { + if !arguments.is_empty() { + self.errors.push(AnalysisError::ExpectedValueArgumentCount { + expected: 0, + actual: arguments.len(), + position: invoker.position(), + }); + } + + return; + }; + + for ((_, expected_type), argument) in value_parameters.iter().zip(arguments) { + self.analyze_expression(argument, statement_position); + + let argument_evaluation = match argument.type_evaluation(&self.context) { + Ok(evaluation) => evaluation, + Err(error) => { + self.errors.push(AnalysisError::AstError(error)); + + continue; + } + }; + + let actual_type = if let Some(r#type) = argument_evaluation.r#type() { + r#type + } else { + self.errors + .push(AnalysisError::ExpectedValueFromExpression { + expression: argument.clone(), + }); + + continue; + }; + + if let Err(type_conflict) = expected_type.check(&actual_type) { + self.errors.push(AnalysisError::TypeConflict { + type_conflict, + actual_expression: argument.clone(), + }); + } + } + for argument in arguments { self.analyze_expression(argument, statement_position); } @@ -766,6 +888,10 @@ pub enum AnalysisError { error: ContextError, position: Span, }, + ExpectedFunction { + actual: Type, + actual_expression: Expression, + }, ExpectedType { expected: Type, actual: Type, @@ -812,8 +938,11 @@ pub enum AnalysisError { }, TypeConflict { actual_expression: Expression, - actual_type: Type, - expected: Type, + type_conflict: TypeConflict, + }, + UnexpectedArguments { + expected: Option>, + actual: Vec, }, UndefinedFieldIdentifier { identifier: Node, @@ -844,6 +973,9 @@ impl AnalysisError { match self { AnalysisError::AstError(ast_error) => ast_error.position(), AnalysisError::ContextError { position, .. } => *position, + AnalysisError::ExpectedFunction { + actual_expression, .. + } => actual_expression.position(), AnalysisError::ExpectedType { actual_expression, .. } => actual_expression.position(), @@ -864,6 +996,7 @@ impl AnalysisError { AnalysisError::UndefinedFieldIdentifier { identifier, .. } => identifier.position, AnalysisError::UndefinedType { identifier } => identifier.position, AnalysisError::UndefinedVariable { identifier } => identifier.position, + AnalysisError::UnexpectedArguments { actual, .. } => actual[0].position(), AnalysisError::UnexpectedIdentifier { identifier } => identifier.position, AnalysisError::UnexectedString { actual } => actual.position(), } @@ -877,6 +1010,16 @@ impl Display for AnalysisError { match self { AnalysisError::AstError(ast_error) => write!(f, "{}", ast_error), AnalysisError::ContextError { error, .. } => write!(f, "{}", error), + AnalysisError::ExpectedFunction { + actual, + actual_expression, + } => { + write!( + f, + "Expected function, found {} in {}", + actual, actual_expression + ) + } AnalysisError::ExpectedType { expected, actual, @@ -952,16 +1095,23 @@ impl Display for AnalysisError { ), AnalysisError::TypeConflict { actual_expression: actual_statement, - actual_type, - expected, + type_conflict: TypeConflict { expected, actual }, } => { write!( f, "Expected type {}, found {}, which has type {}", - expected, actual_statement, actual_type + expected, actual_statement, actual + ) + } + AnalysisError::UnexpectedArguments { + actual, expected, .. + } => { + write!( + f, + "Unexpected arguments {:?}, expected {:?}", + actual, expected ) } - AnalysisError::UndefinedFieldIdentifier { identifier, container, @@ -1070,10 +1220,12 @@ mod tests { assert_eq!( analyze(source), Err(DustError::Analysis { - analysis_errors: vec![AnalysisError::ExpectedType { - expected: Type::Float, - actual: Type::Integer, - actual_expression: Expression::literal(2, (52, 53)), + analysis_errors: vec![AnalysisError::TypeConflict { + actual_expression: Expression::literal(2, (56, 57)), + type_conflict: TypeConflict { + expected: Type::Float, + actual: Type::Integer, + }, }], source, }) diff --git a/dust-lang/src/ast/expression.rs b/dust-lang/src/ast/expression.rs index d76467e..0e6366d 100644 --- a/dust-lang/src/ast/expression.rs +++ b/dust-lang/src/ast/expression.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use crate::{ BuiltInFunction, Context, FunctionType, Identifier, RangeableType, StructType, Type, - TypeEvaluation, + TypeEvaluation, ValueData, }; use super::{AstError, Node, Span, Statement}; @@ -272,7 +272,7 @@ impl Expression { } pub fn type_evaluation(&self, context: &Context) -> Result { - let return_type = match self { + let evaluation = match self { Expression::Block(block_expression) => { block_expression.inner.type_evaluation(context)? } @@ -287,49 +287,72 @@ impl Expression { } Expression::Call(call_expression) => { let CallExpression { invoker, .. } = call_expression.inner.as_ref(); + let invoker_evaluation = invoker.type_evaluation(context)?; - let invoker_type = invoker.type_evaluation(context)?.r#type(); - - let return_type = - if let Some(Type::Function(FunctionType { return_type, .. })) = invoker_type { - return_type.map(|r#type| *r#type) - } else if let Some(Type::Struct(_)) = invoker_type { - invoker_type - } else { - None - }; - - TypeEvaluation::Return(return_type) + match invoker_evaluation { + TypeEvaluation::Return(Some(Type::Function(FunctionType { + return_type, + .. + }))) => TypeEvaluation::Return(return_type.map(|boxed| *boxed)), + TypeEvaluation::Constructor(struct_type) => { + TypeEvaluation::Return(Some(Type::Struct(struct_type))) + } + _ => { + return Err(AstError::ExpectedFunctionOrConstructor { + position: invoker.position(), + }); + } + } } Expression::FieldAccess(field_access_expression) => { let FieldAccessExpression { container, field } = field_access_expression.inner.as_ref(); - let container_type = container.type_evaluation(context)?.r#type(); + let container_type = match container.type_evaluation(context)?.r#type() { + Some(r#type) => r#type, + None => { + return Err(AstError::ExpectedNonEmptyEvaluation { + position: container.position(), + }) + } + }; - if let Some(Type::Struct(StructType::Fields { fields, .. })) = container_type { + if let Type::Struct(StructType::Fields { fields, .. }) = container_type { let found_type = fields .into_iter() .find(|(name, _)| name == &field.inner) .map(|(_, r#type)| r#type); TypeEvaluation::Return(found_type) + } else if let Some(field_type) = container_type.get_field_type(&field.inner) { + TypeEvaluation::Return(Some(field_type)) } else { - return Err(AstError::ExpectedStructFieldsType { + return Err(AstError::ExpectedNonEmptyEvaluation { position: container.position(), }); } } Expression::Grouped(expression) => expression.inner.type_evaluation(context)?, Expression::Identifier(identifier) => { - let type_option = context.get_type(&identifier.inner).map_err(|error| { - AstError::ContextError { - error, - position: identifier.position, - } - })?; + if let Some(struct_type) = + context + .get_constructor_type(&identifier.inner) + .map_err(|error| AstError::ContextError { + error, + position: identifier.position, + })? + { + TypeEvaluation::Constructor(struct_type) + } else { + let type_option = context.get_type(&identifier.inner).map_err(|error| { + AstError::ContextError { + error, + position: identifier.position, + } + })?; - TypeEvaluation::Return(type_option) + TypeEvaluation::Return(type_option) + } } Expression::If(if_expression) => match if_expression.inner.as_ref() { IfExpression::If { .. } => TypeEvaluation::Return(None), @@ -540,7 +563,7 @@ impl Expression { } }; - Ok(return_type) + Ok(evaluation) } pub fn position(&self) -> Span { @@ -734,10 +757,12 @@ pub enum PrimitiveValueExpression { impl Display for PrimitiveValueExpression { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - PrimitiveValueExpression::Boolean(boolean) => write!(f, "{boolean}"), - PrimitiveValueExpression::Character(character) => write!(f, "'{character}'"), - PrimitiveValueExpression::Float(float) => write!(f, "{float}"), - PrimitiveValueExpression::Integer(integer) => write!(f, "{integer}"), + PrimitiveValueExpression::Boolean(boolean) => ValueData::Boolean(*boolean).fmt(f), + PrimitiveValueExpression::Character(character) => { + ValueData::Character(*character).fmt(f) + } + PrimitiveValueExpression::Float(float) => ValueData::Float(*float).fmt(f), + PrimitiveValueExpression::Integer(integer) => ValueData::Integer(*integer).fmt(f), } } } diff --git a/dust-lang/src/ast/mod.rs b/dust-lang/src/ast/mod.rs index 805f4f0..377e03a 100644 --- a/dust-lang/src/ast/mod.rs +++ b/dust-lang/src/ast/mod.rs @@ -67,6 +67,9 @@ pub enum AstError { error: ContextError, position: Span, }, + ExpectedFunctionOrConstructor { + position: Span, + }, ExpectedInteger { position: Span, }, @@ -98,6 +101,7 @@ impl AstError { pub fn position(&self) -> Span { match self { AstError::ContextError { position, .. } => *position, + AstError::ExpectedFunctionOrConstructor { position } => *position, AstError::ExpectedInteger { position } => *position, AstError::ExpectedListType { position } => *position, AstError::ExpectedNonEmptyEvaluation { position } => *position, @@ -116,6 +120,9 @@ impl Display for AstError { AstError::ContextError { error, position } => { write!(f, "Context error at {:?}: {}", position, error) } + AstError::ExpectedFunctionOrConstructor { position } => { + write!(f, "Expected a function or constructor at {:?}", position) + } AstError::ExpectedInteger { position } => { write!(f, "Expected an integer at {:?}", position) } diff --git a/dust-lang/src/built_in_function.rs b/dust-lang/src/built_in_function.rs index a43293d..da7bea4 100644 --- a/dust-lang/src/built_in_function.rs +++ b/dust-lang/src/built_in_function.rs @@ -7,7 +7,7 @@ use std::{ use serde::{Deserialize, Serialize}; -use crate::{Identifier, Type, Value, ValueData, ValueError}; +use crate::{FunctionType, Identifier, Type, Value, ValueData, ValueError}; /// Integrated function that can be called from Dust code. #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] @@ -65,6 +65,15 @@ impl BuiltInFunction { } } + pub fn r#type(&self) -> Type { + Type::Function(FunctionType { + name: Identifier::new(self.name()), + type_parameters: self.type_parameters(), + value_parameters: self.value_parameters(), + return_type: self.return_type().map(Box::new), + }) + } + pub fn call( &self, _type_arguments: Option>, diff --git a/dust-lang/src/context.rs b/dust-lang/src/context.rs index f549758..ed32ad1 100644 --- a/dust-lang/src/context.rs +++ b/dust-lang/src/context.rs @@ -127,6 +127,26 @@ impl Context { } } + /// Returns the constructor type associated with the identifier. + pub fn get_constructor_type( + &self, + identifier: &Identifier, + ) -> Result, ContextError> { + let read_associations = self.associations.read()?; + + if let Some((context_data, _)) = read_associations.get(identifier) { + match context_data { + ContextData::Constructor(constructor) => Ok(Some(constructor.struct_type.clone())), + ContextData::ConstructorType(struct_type) => Ok(Some(struct_type.clone())), + _ => Ok(None), + } + } else if let Some(parent) = &self.parent { + parent.get_constructor_type(identifier) + } else { + Ok(None) + } + } + /// Associates an identifier with a variable type, with a position given for garbage collection. pub fn set_variable_type( &self, @@ -136,9 +156,22 @@ impl Context { ) -> Result<(), ContextError> { log::trace!("Setting {identifier} to type {type} at {position:?}"); - self.associations - .write()? - .insert(identifier, (ContextData::VariableType(r#type), position)); + let mut associations = self.associations.write()?; + let last_position = associations + .get(&identifier) + .map(|(_, last_position)| { + if last_position.1 > position.1 { + *last_position + } else { + position + } + }) + .unwrap_or_default(); + + associations.insert( + identifier, + (ContextData::VariableType(r#type), last_position), + ); Ok(()) } @@ -152,7 +185,6 @@ impl Context { log::trace!("Setting {identifier} to value {value}"); let mut associations = self.associations.write()?; - let last_position = associations .get(&identifier) .map(|(_, last_position)| *last_position) @@ -175,7 +207,6 @@ impl Context { log::trace!("Setting {identifier} to constructor {constructor:?}"); let mut associations = self.associations.write()?; - let last_position = associations .get(&identifier) .map(|(_, last_position)| *last_position) @@ -200,10 +231,20 @@ impl Context { log::trace!("Setting {identifier} to constructor of type {struct_type}"); let mut variables = self.associations.write()?; + let last_position = variables + .get(&identifier) + .map(|(_, last_position)| { + if last_position.1 > position.1 { + *last_position + } else { + position + } + }) + .unwrap_or_default(); variables.insert( identifier, - (ContextData::ConstructorType(struct_type), position), + (ContextData::ConstructorType(struct_type), last_position), ); Ok(()) diff --git a/dust-lang/src/evaluation.rs b/dust-lang/src/evaluation.rs index 29b605b..29649d7 100644 --- a/dust-lang/src/evaluation.rs +++ b/dust-lang/src/evaluation.rs @@ -1,5 +1,6 @@ -use crate::{Constructor, RuntimeError, Span, Type, Value}; +use crate::{Constructor, RuntimeError, Span, StructType, Type, Value}; +#[derive(Debug, Clone, PartialEq)] pub enum Evaluation { Break(Option), Constructor(Constructor), @@ -23,9 +24,10 @@ impl Evaluation { } } +#[derive(Debug, Clone, PartialEq)] pub enum TypeEvaluation { Break(Option), - Constructor(Type), + Constructor(StructType), Return(Option), } diff --git a/dust-lang/src/type.rs b/dust-lang/src/type.rs index 87f22d6..ba14443 100644 --- a/dust-lang/src/type.rs +++ b/dust-lang/src/type.rs @@ -17,7 +17,7 @@ use std::{ use serde::{Deserialize, Serialize}; -use crate::{constructor::Constructor, Identifier}; +use crate::{constructor::Constructor, BuiltInFunction, Identifier}; /// Description of a kind of value. /// @@ -255,6 +255,26 @@ impl Type { }, } } + + pub fn get_field_type(&self, field: &Identifier) -> Option { + match field.as_str() { + "to_string" => Some(BuiltInFunction::ToString.r#type()), + "length" => match self { + Type::List { .. } => Some(Type::Integer), + Type::ListOf { .. } => Some(Type::Integer), + Type::ListEmpty => Some(Type::Integer), + Type::Map { .. } => Some(Type::Integer), + Type::String { .. } => Some(Type::Integer), + _ => None, + }, + "is_even" | "is_odd" => Some(Type::Boolean), + _ => match self { + Type::Struct(StructType::Fields { fields, .. }) => fields.get(field).cloned(), + Type::Map { pairs } => pairs.get(field).cloned(), + _ => None, + }, + } + } } impl Display for Type { diff --git a/dust-lang/src/value.rs b/dust-lang/src/value.rs index bc016ef..d1fea48 100644 --- a/dust-lang/src/value.rs +++ b/dust-lang/src/value.rs @@ -1173,7 +1173,15 @@ impl Display for ValueData { ValueData::Byte(byte) => write!(f, "{byte}"), ValueData::Character(character) => write!(f, "{character}"), ValueData::Enum(r#enum) => write!(f, "{enum}"), - ValueData::Float(float) => write!(f, "{float}"), + ValueData::Float(float) => { + write!(f, "{float}")?; + + if float.fract() == 0.0 { + write!(f, ".0")?; + } + + Ok(()) + } ValueData::Function(function) => write!(f, "{function}"), ValueData::Integer(integer) => write!(f, "{integer}"), ValueData::Map(pairs) => {