Pass analyzer test

This commit is contained in:
Jeff 2024-08-20 18:48:25 -04:00
parent 08a9b265ec
commit e22d0254f5
7 changed files with 225 additions and 99 deletions

View File

@ -13,8 +13,9 @@ use crate::{
ast::{ ast::{
AbstractSyntaxTree, AstError, BlockExpression, CallExpression, ElseExpression, AbstractSyntaxTree, AstError, BlockExpression, CallExpression, ElseExpression,
FieldAccessExpression, IfExpression, LetStatement, ListExpression, ListIndexExpression, FieldAccessExpression, IfExpression, LetStatement, ListExpression, ListIndexExpression,
LoopExpression, MapExpression, Node, OperatorExpression, RangeExpression, Span, Statement, LiteralExpression, LoopExpression, MapExpression, Node, OperatorExpression,
StructDefinition, StructExpression, TupleAccessExpression, PrimitiveValueExpression, RangeExpression, Span, Statement, StructDefinition,
StructExpression, TupleAccessExpression,
}, },
core_library, parse, Context, ContextError, DustError, Expression, Identifier, StructType, core_library, parse, Context, ContextError, DustError, Expression, Identifier, StructType,
Type, Type,
@ -224,6 +225,56 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
Expression::ListIndex(list_index_expression) => { Expression::ListIndex(list_index_expression) => {
let ListIndexExpression { list, index } = list_index_expression.inner.as_ref(); let ListIndexExpression { list, index } = list_index_expression.inner.as_ref();
let list_type = list.return_type(&self.context)?;
let literal_type = if let Expression::Literal(Node { inner, .. }) = index {
Some(inner.as_ref().clone())
} else {
None
};
if let Some(Type::List { length, .. }) = list_type {
if let Some(LiteralExpression::Primitive(PrimitiveValueExpression::Integer(
integer,
))) = literal_type
{
if integer < 0 || integer >= length as i64 {
return Err(AnalysisError::IndexOutOfBounds {
index: index.clone(),
length,
list: list.clone(),
index_value: integer,
});
}
}
}
if let Some(Type::String {
length: Some(length),
}) = list_type
{
if let Some(LiteralExpression::Primitive(PrimitiveValueExpression::Integer(
integer,
))) = literal_type
{
if integer < 0 || integer >= length as i64 {
return Err(AnalysisError::IndexOutOfBounds {
index: index.clone(),
length,
list: list.clone(),
index_value: integer,
});
}
}
}
if list_type.is_none() {
return Err(AnalysisError::ExpectedValueFromExpression {
expression: list.clone(),
found_type: list_type,
});
}
self.analyze_expression(list)?; self.analyze_expression(list)?;
self.analyze_expression(index)?; self.analyze_expression(index)?;
} }
@ -302,10 +353,60 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
OperatorExpression::Math { left, right, .. } => { OperatorExpression::Math { left, right, .. } => {
self.analyze_expression(left)?; self.analyze_expression(left)?;
self.analyze_expression(right)?; self.analyze_expression(right)?;
let left_type = left.return_type(&self.context)?;
let right_type = right.return_type(&self.context)?;
if left_type.is_none() {
return Err(AnalysisError::ExpectedValueFromExpression {
expression: left.clone(),
found_type: left_type,
});
}
if right_type.is_none() {
return Err(AnalysisError::ExpectedValueFromExpression {
expression: right.clone(),
found_type: right_type,
});
}
if left_type != right_type {
return Err(AnalysisError::ExpectedType {
expected: left_type.unwrap(),
actual: right_type.unwrap(),
actual_expression: right.clone(),
});
}
} }
OperatorExpression::Logic { left, right, .. } => { OperatorExpression::Logic { left, right, .. } => {
self.analyze_expression(left)?; self.analyze_expression(left)?;
self.analyze_expression(right)?; self.analyze_expression(right)?;
let left_type = left.return_type(&self.context)?;
let right_type = right.return_type(&self.context)?;
if left_type.is_none() {
return Err(AnalysisError::ExpectedValueFromExpression {
expression: left.clone(),
found_type: left_type,
});
}
if right_type.is_none() {
return Err(AnalysisError::ExpectedValueFromExpression {
expression: right.clone(),
found_type: right_type,
});
}
if left_type != right_type {
return Err(AnalysisError::ExpectedType {
expected: left_type.unwrap(),
actual: right_type.unwrap(),
actual_expression: right.clone(),
});
}
} }
}, },
Expression::Range(range_expression) => match range_expression.inner.as_ref() { Expression::Range(range_expression) => match range_expression.inner.as_ref() {
@ -325,7 +426,7 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
.map_err(|error| AnalysisError::ContextError { .map_err(|error| AnalysisError::ContextError {
error, error,
position: name.position, position: name.position,
}); })?;
for (_, expression) in fields { for (_, expression) in fields {
self.analyze_expression(expression)?; self.analyze_expression(expression)?;
@ -401,23 +502,21 @@ pub enum AnalysisError {
error: ContextError, error: ContextError,
position: Span, position: Span,
}, },
ExpectedBoolean { ExpectedType {
actual: Statement, expected: Type,
actual: Type,
actual_expression: Expression,
},
ExpectedTypeMultiple {
expected: Vec<Type>,
actual: Type,
actual_expression: Expression,
}, },
ExpectedIdentifier { ExpectedIdentifier {
actual: Statement, actual: Expression,
}, },
ExpectedIdentifierOrString { ExpectedIdentifierOrString {
actual: Statement, actual: Expression,
},
ExpectedIntegerOrRange {
actual: Statement,
},
ExpectedList {
actual: Statement,
},
ExpectedMap {
actual: Statement,
}, },
ExpectedValueFromStatement { ExpectedValueFromStatement {
actual: Statement, actual: Statement,
@ -432,9 +531,9 @@ pub enum AnalysisError {
position: Span, position: Span,
}, },
IndexOutOfBounds { IndexOutOfBounds {
list: Statement, list: Expression,
index: Statement, index: Expression,
index_value: usize, index_value: i64,
length: usize, length: usize,
}, },
TypeConflict { TypeConflict {
@ -443,8 +542,8 @@ pub enum AnalysisError {
expected: Type, expected: Type,
}, },
UndefinedField { UndefinedField {
identifier: Statement, identifier: Expression,
statement: Statement, statement: Expression,
}, },
UndefinedType { UndefinedType {
identifier: Node<Identifier>, identifier: Node<Identifier>,
@ -453,7 +552,7 @@ pub enum AnalysisError {
identifier: Node<Identifier>, identifier: Node<Identifier>,
}, },
UnexectedString { UnexectedString {
actual: Statement, actual: Expression,
}, },
UndefinedVariable { UndefinedVariable {
identifier: Node<Identifier>, identifier: Node<Identifier>,
@ -471,12 +570,14 @@ impl AnalysisError {
match self { match self {
AnalysisError::AstError(ast_error) => ast_error.position(), AnalysisError::AstError(ast_error) => ast_error.position(),
AnalysisError::ContextError { position, .. } => *position, AnalysisError::ContextError { position, .. } => *position,
AnalysisError::ExpectedBoolean { actual } => actual.position(), AnalysisError::ExpectedType {
actual_expression, ..
} => actual_expression.position(),
AnalysisError::ExpectedTypeMultiple {
actual_expression, ..
} => actual_expression.position(),
AnalysisError::ExpectedIdentifier { actual } => actual.position(), AnalysisError::ExpectedIdentifier { actual } => actual.position(),
AnalysisError::ExpectedIdentifierOrString { actual } => actual.position(), AnalysisError::ExpectedIdentifierOrString { actual } => actual.position(),
AnalysisError::ExpectedIntegerOrRange { actual } => actual.position(),
AnalysisError::ExpectedList { actual } => actual.position(),
AnalysisError::ExpectedMap { actual } => actual.position(),
AnalysisError::ExpectedValueFromExpression { expression, .. } => expression.position(), AnalysisError::ExpectedValueFromExpression { expression, .. } => expression.position(),
AnalysisError::ExpectedValueFromStatement { actual } => actual.position(), AnalysisError::ExpectedValueFromStatement { actual } => actual.position(),
AnalysisError::ExpectedValueArgumentCount { position, .. } => *position, AnalysisError::ExpectedValueArgumentCount { position, .. } => *position,
@ -499,23 +600,37 @@ impl Display for AnalysisError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
AnalysisError::AstError(ast_error) => write!(f, "{}", ast_error), AnalysisError::AstError(ast_error) => write!(f, "{}", ast_error),
Self::ContextError { error, position } => { AnalysisError::ContextError { error, .. } => write!(f, "{}", error),
write!(f, "Context error at {:?}: {}", position, error)
AnalysisError::ExpectedType {
expected,
actual,
actual_expression,
} => {
write!(
f,
"Expected type {:?}, found {:?} in {}",
expected, actual, actual_expression
)
} }
AnalysisError::ExpectedBoolean { actual, .. } => { AnalysisError::ExpectedTypeMultiple {
write!(f, "Expected boolean, found {}", actual) expected,
actual,
actual_expression,
} => {
write!(
f,
"Expected one of {:?}, found {:?} in {}",
expected, actual, actual_expression
)
} }
AnalysisError::ExpectedIdentifier { actual, .. } => { AnalysisError::ExpectedIdentifier { actual, .. } => {
write!(f, "Expected identifier, found {}", actual) write!(f, "Expected identifier, found {}", actual)
} }
AnalysisError::ExpectedIdentifierOrString { actual } => { AnalysisError::ExpectedIdentifierOrString { actual } => {
write!(f, "Expected identifier or string, found {}", actual) write!(f, "Expected identifier or string, found {}", actual)
} }
AnalysisError::ExpectedIntegerOrRange { actual, .. } => {
write!(f, "Expected integer or range, found {}", actual)
}
AnalysisError::ExpectedList { actual } => write!(f, "Expected list, found {}", actual),
AnalysisError::ExpectedMap { actual } => write!(f, "Expected map, found {}", actual),
AnalysisError::ExpectedValueFromExpression { AnalysisError::ExpectedValueFromExpression {
expression, expression,
found_type, found_type,
@ -623,18 +738,46 @@ mod tests {
#[test] #[test]
fn tuple_struct_with_wrong_field_types() { fn tuple_struct_with_wrong_field_types() {
let source = " let source = "
struct Foo(int, float) struct Foo(int, float);
Foo(1, 2) Foo(1, 2)
"; ";
assert_eq!(analyze(source), todo!()); assert_eq!(
analyze(source),
Err(DustError::Analysis {
analysis_error: AnalysisError::TypeConflict {
actual_expression: Expression::literal(2, (52, 53)),
actual_type: Type::Integer,
expected: Type::Float,
},
source,
})
);
} }
#[test] #[test]
fn constant_list_index_out_of_bounds() { fn constant_list_index_out_of_bounds() {
let source = "[1, 2, 3][3]"; let source = "[1, 2, 3][3]";
assert_eq!(analyze(source), todo!()); assert_eq!(
analyze(source),
Err(DustError::Analysis {
analysis_error: AnalysisError::IndexOutOfBounds {
list: Expression::list(
vec![
Expression::literal(1, (1, 2)),
Expression::literal(2, (4, 5)),
Expression::literal(3, (7, 8)),
],
(0, 9)
),
index: Expression::literal(3, (10, 11)),
index_value: 3,
length: 3,
},
source,
})
);
} }
#[test] #[test]
@ -665,18 +808,21 @@ mod tests {
assert_eq!(analyze(source), todo!()); assert_eq!(analyze(source), todo!());
} }
#[test]
fn length_no_arguments() {
let source = "length()";
assert_eq!(analyze(source), todo!());
}
#[test] #[test]
fn float_plus_integer() { fn float_plus_integer() {
let source = "42.0 + 2"; let source = "42.0 + 2";
assert_eq!(analyze(source), todo!()); assert_eq!(
analyze(source),
Err(DustError::Analysis {
analysis_error: AnalysisError::ExpectedType {
expected: Type::Float,
actual: Type::Integer,
actual_expression: Expression::literal(2, (7, 8)),
},
source,
})
);
} }
#[test] #[test]

View File

@ -381,7 +381,9 @@ impl Expression {
PrimitiveValueExpression::Integer(_) => Some(Type::Integer), PrimitiveValueExpression::Integer(_) => Some(Type::Integer),
PrimitiveValueExpression::Float(_) => Some(Type::Float), PrimitiveValueExpression::Float(_) => Some(Type::Float),
}, },
LiteralExpression::String(_) => Some(Type::String), LiteralExpression::String(string) => Some(Type::String {
length: Some(string.len()),
}),
}, },
Expression::Loop(loop_expression) => match loop_expression.inner.as_ref() { Expression::Loop(loop_expression) => match loop_expression.inner.as_ref() {
LoopExpression::For { block, .. } => block.inner.return_type(context)?, LoopExpression::For { block, .. } => block.inner.return_type(context)?,

View File

@ -14,15 +14,11 @@ use crate::{Identifier, Type, Value};
pub enum BuiltInFunction { pub enum BuiltInFunction {
// String tools // String tools
ToString, ToString,
LengthString,
// Integer and float tools // Integer and float tools
IsEven, IsEven,
IsOdd, IsOdd,
// List tools
LengthList,
// I/O // I/O
ReadLine, ReadLine,
WriteLine, WriteLine,
@ -33,8 +29,6 @@ impl BuiltInFunction {
match self { match self {
BuiltInFunction::IsEven => "is_even", BuiltInFunction::IsEven => "is_even",
BuiltInFunction::IsOdd => "is_odd", BuiltInFunction::IsOdd => "is_odd",
BuiltInFunction::LengthList => "length",
BuiltInFunction::LengthString => "length",
BuiltInFunction::ReadLine => "read_line", BuiltInFunction::ReadLine => "read_line",
BuiltInFunction::ToString { .. } => "to_string", BuiltInFunction::ToString { .. } => "to_string",
BuiltInFunction::WriteLine => "write_line", BuiltInFunction::WriteLine => "write_line",
@ -46,8 +40,6 @@ impl BuiltInFunction {
BuiltInFunction::ToString { .. } => None, BuiltInFunction::ToString { .. } => None,
BuiltInFunction::IsEven => None, BuiltInFunction::IsEven => None,
BuiltInFunction::IsOdd => None, BuiltInFunction::IsOdd => None,
BuiltInFunction::LengthList => None,
BuiltInFunction::LengthString => None,
BuiltInFunction::ReadLine => None, BuiltInFunction::ReadLine => None,
BuiltInFunction::WriteLine => None, BuiltInFunction::WriteLine => None,
} }
@ -58,13 +50,6 @@ impl BuiltInFunction {
BuiltInFunction::ToString { .. } => Some(vec![("value".into(), Type::Any)]), BuiltInFunction::ToString { .. } => Some(vec![("value".into(), Type::Any)]),
BuiltInFunction::IsEven => Some(vec![("value".into(), Type::Number)]), BuiltInFunction::IsEven => Some(vec![("value".into(), Type::Number)]),
BuiltInFunction::IsOdd => Some(vec![("value".into(), Type::Number)]), BuiltInFunction::IsOdd => Some(vec![("value".into(), Type::Number)]),
BuiltInFunction::LengthList => Some(vec![(
"value".into(),
Type::ListOf {
item_type: Box::new(Type::Any),
},
)]),
BuiltInFunction::LengthString => Some(vec![("value".into(), Type::String)]),
BuiltInFunction::ReadLine => None, BuiltInFunction::ReadLine => None,
BuiltInFunction::WriteLine => Some(vec![("value".into(), Type::Any)]), BuiltInFunction::WriteLine => Some(vec![("value".into(), Type::Any)]),
} }
@ -72,12 +57,10 @@ impl BuiltInFunction {
pub fn return_type(&self) -> Option<Type> { pub fn return_type(&self) -> Option<Type> {
match self { match self {
BuiltInFunction::ToString { .. } => Some(Type::String), BuiltInFunction::ToString { .. } => Some(Type::String { length: None }),
BuiltInFunction::IsEven => Some(Type::Boolean), BuiltInFunction::IsEven => Some(Type::Boolean),
BuiltInFunction::IsOdd => Some(Type::Boolean), BuiltInFunction::IsOdd => Some(Type::Boolean),
BuiltInFunction::LengthList => Some(Type::Number), BuiltInFunction::ReadLine => Some(Type::String { length: None }),
BuiltInFunction::LengthString => Some(Type::Number),
BuiltInFunction::ReadLine => Some(Type::String),
BuiltInFunction::WriteLine => None, BuiltInFunction::WriteLine => None,
} }
} }
@ -117,20 +100,6 @@ impl BuiltInFunction {
Err(BuiltInFunctionError::ExpectedInteger) Err(BuiltInFunctionError::ExpectedInteger)
} }
} }
BuiltInFunction::LengthList => {
if let Value::List(list) = &value_arguments.unwrap()[0] {
Ok(Some(Value::Integer(list.len() as i64)))
} else {
Err(BuiltInFunctionError::ExpectedList)
}
}
BuiltInFunction::LengthString => {
if let Value::String(string) = &value_arguments.unwrap()[0] {
Ok(Some(Value::Integer(string.len() as i64)))
} else {
Err(BuiltInFunctionError::ExpectedString)
}
}
BuiltInFunction::ReadLine => { BuiltInFunction::ReadLine => {
let mut input = String::new(); let mut input = String::new();

View File

@ -34,15 +34,6 @@ pub fn core_library<'a>() -> &'a Context {
(0, 0), (0, 0),
), ),
), ),
(
Identifier::new("length"),
(
ContextData::VariableValue(Value::Function(Function::BuiltIn(
BuiltInFunction::LengthList,
))),
(0, 0),
),
),
( (
Identifier::new("read_line"), Identifier::new("read_line"),
( (

View File

@ -54,7 +54,9 @@ pub enum Type {
Range { Range {
r#type: RangeableType, r#type: RangeableType,
}, },
String, String {
length: Option<usize>,
},
Struct(StructType), Struct(StructType),
Tuple(Vec<Type>), Tuple(Vec<Type>),
} }
@ -83,7 +85,7 @@ impl Type {
| (Type::Character, Type::Character) | (Type::Character, Type::Character)
| (Type::Float, Type::Float) | (Type::Float, Type::Float)
| (Type::Integer, Type::Integer) | (Type::Integer, Type::Integer)
| (Type::String, Type::String) => return Ok(()), | (Type::String { .. }, Type::String { .. }) => return Ok(()),
( (
Type::Generic { Type::Generic {
concrete_type: left, concrete_type: left,
@ -272,7 +274,7 @@ impl Display for Type {
} }
Type::Number => write!(f, "num"), Type::Number => write!(f, "num"),
Type::Range { r#type } => write!(f, "{type} range"), Type::Range { r#type } => write!(f, "{type} range"),
Type::String => write!(f, "str"), Type::String { .. } => write!(f, "str"),
Type::Struct(struct_type) => write!(f, "{struct_type}"), Type::Struct(struct_type) => write!(f, "{struct_type}"),
Type::Tuple(fields) => { Type::Tuple(fields) => {
write!(f, "(")?; write!(f, "(")?;
@ -358,8 +360,8 @@ impl Ord for Type {
left_type.cmp(right_type) left_type.cmp(right_type)
} }
(Type::Range { .. }, _) => Ordering::Greater, (Type::Range { .. }, _) => Ordering::Greater,
(Type::String, Type::String) => Ordering::Equal, (Type::String { length: left }, Type::String { length: right }) => left.cmp(right),
(Type::String, _) => Ordering::Greater, (Type::String { .. }, _) => Ordering::Greater,
(Type::Struct(left_struct), Type::Struct(right_struct)) => { (Type::Struct(left_struct), Type::Struct(right_struct)) => {
left_struct.cmp(right_struct) left_struct.cmp(right_struct)
} }
@ -638,7 +640,7 @@ mod tests {
#[test] #[test]
fn errors() { fn errors() {
let foo = Type::Integer; let foo = Type::Integer;
let bar = Type::String; let bar = Type::String { length: None };
assert_eq!( assert_eq!(
foo.check(&bar), foo.check(&bar),
@ -666,7 +668,7 @@ mod tests {
Type::Range { Type::Range {
r#type: RangeableType::Integer, r#type: RangeableType::Integer,
}, },
Type::String, Type::String { length: None },
]; ];
for left in types.clone() { for left in types.clone() {

View File

@ -200,7 +200,9 @@ impl Value {
r#type: rangeable_type, r#type: rangeable_type,
} }
} }
Value::String(_) => Type::String, Value::String(string) => Type::String {
length: Some(string.len()),
},
Value::Struct(r#struct) => match r#struct { Value::Struct(r#struct) => match r#struct {
Struct::Unit { name } => Type::Struct(StructType::Unit { name: name.clone() }), Struct::Unit { name } => Type::Struct(StructType::Unit { name: name.clone() }),
Struct::Tuple { name, fields } => { Struct::Tuple { name, fields } => {

View File

@ -1685,12 +1685,26 @@ mod tests {
} }
#[test] #[test]
fn length() { fn list_length() {
let input = "length([1, 2, 3])"; let input = "[1, 2, 3].length";
assert_eq!(run(input), Ok(Some(Value::Integer(3)))); assert_eq!(run(input), Ok(Some(Value::Integer(3))));
} }
#[test]
fn string_length() {
let input = "\"hello\".length";
assert_eq!(run(input), Ok(Some(Value::Integer(5))));
}
#[test]
fn map_length() {
let input = "map { a = 42, b = 4.0 }.length";
assert_eq!(run(input), Ok(Some(Value::Integer(2))));
}
#[test] #[test]
fn add() { fn add() {
let input = "1 + 2"; let input = "1 + 2";