1
0

Pass all unit tests

This commit is contained in:
Jeff 2024-08-23 16:33:38 -04:00
parent 8b14d74eba
commit ab53df56bc
8 changed files with 316 additions and 52 deletions

View File

@ -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<Vec<Type>>,
actual: Vec<Expression>,
},
UndefinedFieldIdentifier {
identifier: Node<Identifier>,
@ -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 {
analysis_errors: vec![AnalysisError::TypeConflict {
actual_expression: Expression::literal(2, (56, 57)),
type_conflict: TypeConflict {
expected: Type::Float,
actual: Type::Integer,
actual_expression: Expression::literal(2, (52, 53)),
},
}],
source,
})

View File

@ -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<TypeEvaluation, AstError> {
let return_type = match self {
let evaluation = match self {
Expression::Block(block_expression) => {
block_expression.inner.type_evaluation(context)?
}
@ -287,41 +287,63 @@ 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) => {
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,
@ -331,6 +353,7 @@ impl Expression {
TypeEvaluation::Return(type_option)
}
}
Expression::If(if_expression) => match if_expression.inner.as_ref() {
IfExpression::If { .. } => TypeEvaluation::Return(None),
IfExpression::IfElse { if_block, .. } => if_block.inner.type_evaluation(context)?,
@ -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),
}
}
}

View File

@ -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)
}

View File

@ -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<Vec<Type>>,

View File

@ -127,6 +127,26 @@ impl Context {
}
}
/// Returns the constructor type associated with the identifier.
pub fn get_constructor_type(
&self,
identifier: &Identifier,
) -> Result<Option<StructType>, 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(())

View File

@ -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<Value>),
Constructor(Constructor),
@ -23,9 +24,10 @@ impl Evaluation {
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypeEvaluation {
Break(Option<Type>),
Constructor(Type),
Constructor(StructType),
Return(Option<Type>),
}

View File

@ -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<Type> {
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 {

View File

@ -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) => {