Pass analyzer test
This commit is contained in:
parent
08a9b265ec
commit
e22d0254f5
@ -13,8 +13,9 @@ use crate::{
|
||||
ast::{
|
||||
AbstractSyntaxTree, AstError, BlockExpression, CallExpression, ElseExpression,
|
||||
FieldAccessExpression, IfExpression, LetStatement, ListExpression, ListIndexExpression,
|
||||
LoopExpression, MapExpression, Node, OperatorExpression, RangeExpression, Span, Statement,
|
||||
StructDefinition, StructExpression, TupleAccessExpression,
|
||||
LiteralExpression, LoopExpression, MapExpression, Node, OperatorExpression,
|
||||
PrimitiveValueExpression, RangeExpression, Span, Statement, StructDefinition,
|
||||
StructExpression, TupleAccessExpression,
|
||||
},
|
||||
core_library, parse, Context, ContextError, DustError, Expression, Identifier, StructType,
|
||||
Type,
|
||||
@ -224,6 +225,56 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
|
||||
Expression::ListIndex(list_index_expression) => {
|
||||
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(index)?;
|
||||
}
|
||||
@ -302,10 +353,60 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
|
||||
OperatorExpression::Math { left, right, .. } => {
|
||||
self.analyze_expression(left)?;
|
||||
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, .. } => {
|
||||
self.analyze_expression(left)?;
|
||||
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() {
|
||||
@ -325,7 +426,7 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
|
||||
.map_err(|error| AnalysisError::ContextError {
|
||||
error,
|
||||
position: name.position,
|
||||
});
|
||||
})?;
|
||||
|
||||
for (_, expression) in fields {
|
||||
self.analyze_expression(expression)?;
|
||||
@ -401,23 +502,21 @@ pub enum AnalysisError {
|
||||
error: ContextError,
|
||||
position: Span,
|
||||
},
|
||||
ExpectedBoolean {
|
||||
actual: Statement,
|
||||
ExpectedType {
|
||||
expected: Type,
|
||||
actual: Type,
|
||||
actual_expression: Expression,
|
||||
},
|
||||
ExpectedTypeMultiple {
|
||||
expected: Vec<Type>,
|
||||
actual: Type,
|
||||
actual_expression: Expression,
|
||||
},
|
||||
ExpectedIdentifier {
|
||||
actual: Statement,
|
||||
actual: Expression,
|
||||
},
|
||||
ExpectedIdentifierOrString {
|
||||
actual: Statement,
|
||||
},
|
||||
ExpectedIntegerOrRange {
|
||||
actual: Statement,
|
||||
},
|
||||
ExpectedList {
|
||||
actual: Statement,
|
||||
},
|
||||
ExpectedMap {
|
||||
actual: Statement,
|
||||
actual: Expression,
|
||||
},
|
||||
ExpectedValueFromStatement {
|
||||
actual: Statement,
|
||||
@ -432,9 +531,9 @@ pub enum AnalysisError {
|
||||
position: Span,
|
||||
},
|
||||
IndexOutOfBounds {
|
||||
list: Statement,
|
||||
index: Statement,
|
||||
index_value: usize,
|
||||
list: Expression,
|
||||
index: Expression,
|
||||
index_value: i64,
|
||||
length: usize,
|
||||
},
|
||||
TypeConflict {
|
||||
@ -443,8 +542,8 @@ pub enum AnalysisError {
|
||||
expected: Type,
|
||||
},
|
||||
UndefinedField {
|
||||
identifier: Statement,
|
||||
statement: Statement,
|
||||
identifier: Expression,
|
||||
statement: Expression,
|
||||
},
|
||||
UndefinedType {
|
||||
identifier: Node<Identifier>,
|
||||
@ -453,7 +552,7 @@ pub enum AnalysisError {
|
||||
identifier: Node<Identifier>,
|
||||
},
|
||||
UnexectedString {
|
||||
actual: Statement,
|
||||
actual: Expression,
|
||||
},
|
||||
UndefinedVariable {
|
||||
identifier: Node<Identifier>,
|
||||
@ -471,12 +570,14 @@ impl AnalysisError {
|
||||
match self {
|
||||
AnalysisError::AstError(ast_error) => ast_error.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::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::ExpectedValueFromStatement { actual } => actual.position(),
|
||||
AnalysisError::ExpectedValueArgumentCount { position, .. } => *position,
|
||||
@ -499,23 +600,37 @@ impl Display for AnalysisError {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
AnalysisError::AstError(ast_error) => write!(f, "{}", ast_error),
|
||||
Self::ContextError { error, position } => {
|
||||
write!(f, "Context error at {:?}: {}", position, error)
|
||||
AnalysisError::ContextError { error, .. } => write!(f, "{}", error),
|
||||
|
||||
AnalysisError::ExpectedType {
|
||||
expected,
|
||||
actual,
|
||||
actual_expression,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"Expected type {:?}, found {:?} in {}",
|
||||
expected, actual, actual_expression
|
||||
)
|
||||
}
|
||||
AnalysisError::ExpectedBoolean { actual, .. } => {
|
||||
write!(f, "Expected boolean, found {}", actual)
|
||||
AnalysisError::ExpectedTypeMultiple {
|
||||
expected,
|
||||
actual,
|
||||
actual_expression,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"Expected one of {:?}, found {:?} in {}",
|
||||
expected, actual, actual_expression
|
||||
)
|
||||
}
|
||||
|
||||
AnalysisError::ExpectedIdentifier { actual, .. } => {
|
||||
write!(f, "Expected identifier, found {}", actual)
|
||||
}
|
||||
AnalysisError::ExpectedIdentifierOrString { 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 {
|
||||
expression,
|
||||
found_type,
|
||||
@ -623,18 +738,46 @@ mod tests {
|
||||
#[test]
|
||||
fn tuple_struct_with_wrong_field_types() {
|
||||
let source = "
|
||||
struct Foo(int, float)
|
||||
struct Foo(int, float);
|
||||
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]
|
||||
fn constant_list_index_out_of_bounds() {
|
||||
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]
|
||||
@ -665,18 +808,21 @@ mod tests {
|
||||
assert_eq!(analyze(source), todo!());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn length_no_arguments() {
|
||||
let source = "length()";
|
||||
|
||||
assert_eq!(analyze(source), todo!());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_plus_integer() {
|
||||
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]
|
||||
|
@ -381,7 +381,9 @@ impl Expression {
|
||||
PrimitiveValueExpression::Integer(_) => Some(Type::Integer),
|
||||
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() {
|
||||
LoopExpression::For { block, .. } => block.inner.return_type(context)?,
|
||||
|
@ -14,15 +14,11 @@ use crate::{Identifier, Type, Value};
|
||||
pub enum BuiltInFunction {
|
||||
// String tools
|
||||
ToString,
|
||||
LengthString,
|
||||
|
||||
// Integer and float tools
|
||||
IsEven,
|
||||
IsOdd,
|
||||
|
||||
// List tools
|
||||
LengthList,
|
||||
|
||||
// I/O
|
||||
ReadLine,
|
||||
WriteLine,
|
||||
@ -33,8 +29,6 @@ impl BuiltInFunction {
|
||||
match self {
|
||||
BuiltInFunction::IsEven => "is_even",
|
||||
BuiltInFunction::IsOdd => "is_odd",
|
||||
BuiltInFunction::LengthList => "length",
|
||||
BuiltInFunction::LengthString => "length",
|
||||
BuiltInFunction::ReadLine => "read_line",
|
||||
BuiltInFunction::ToString { .. } => "to_string",
|
||||
BuiltInFunction::WriteLine => "write_line",
|
||||
@ -46,8 +40,6 @@ impl BuiltInFunction {
|
||||
BuiltInFunction::ToString { .. } => None,
|
||||
BuiltInFunction::IsEven => None,
|
||||
BuiltInFunction::IsOdd => None,
|
||||
BuiltInFunction::LengthList => None,
|
||||
BuiltInFunction::LengthString => None,
|
||||
BuiltInFunction::ReadLine => None,
|
||||
BuiltInFunction::WriteLine => None,
|
||||
}
|
||||
@ -58,13 +50,6 @@ impl BuiltInFunction {
|
||||
BuiltInFunction::ToString { .. } => Some(vec![("value".into(), Type::Any)]),
|
||||
BuiltInFunction::IsEven => 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::WriteLine => Some(vec![("value".into(), Type::Any)]),
|
||||
}
|
||||
@ -72,12 +57,10 @@ impl BuiltInFunction {
|
||||
|
||||
pub fn return_type(&self) -> Option<Type> {
|
||||
match self {
|
||||
BuiltInFunction::ToString { .. } => Some(Type::String),
|
||||
BuiltInFunction::ToString { .. } => Some(Type::String { length: None }),
|
||||
BuiltInFunction::IsEven => Some(Type::Boolean),
|
||||
BuiltInFunction::IsOdd => Some(Type::Boolean),
|
||||
BuiltInFunction::LengthList => Some(Type::Number),
|
||||
BuiltInFunction::LengthString => Some(Type::Number),
|
||||
BuiltInFunction::ReadLine => Some(Type::String),
|
||||
BuiltInFunction::ReadLine => Some(Type::String { length: None }),
|
||||
BuiltInFunction::WriteLine => None,
|
||||
}
|
||||
}
|
||||
@ -117,20 +100,6 @@ impl BuiltInFunction {
|
||||
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 => {
|
||||
let mut input = String::new();
|
||||
|
||||
|
@ -34,15 +34,6 @@ pub fn core_library<'a>() -> &'a Context {
|
||||
(0, 0),
|
||||
),
|
||||
),
|
||||
(
|
||||
Identifier::new("length"),
|
||||
(
|
||||
ContextData::VariableValue(Value::Function(Function::BuiltIn(
|
||||
BuiltInFunction::LengthList,
|
||||
))),
|
||||
(0, 0),
|
||||
),
|
||||
),
|
||||
(
|
||||
Identifier::new("read_line"),
|
||||
(
|
||||
|
@ -54,7 +54,9 @@ pub enum Type {
|
||||
Range {
|
||||
r#type: RangeableType,
|
||||
},
|
||||
String,
|
||||
String {
|
||||
length: Option<usize>,
|
||||
},
|
||||
Struct(StructType),
|
||||
Tuple(Vec<Type>),
|
||||
}
|
||||
@ -83,7 +85,7 @@ impl Type {
|
||||
| (Type::Character, Type::Character)
|
||||
| (Type::Float, Type::Float)
|
||||
| (Type::Integer, Type::Integer)
|
||||
| (Type::String, Type::String) => return Ok(()),
|
||||
| (Type::String { .. }, Type::String { .. }) => return Ok(()),
|
||||
(
|
||||
Type::Generic {
|
||||
concrete_type: left,
|
||||
@ -272,7 +274,7 @@ impl Display for Type {
|
||||
}
|
||||
Type::Number => write!(f, "num"),
|
||||
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::Tuple(fields) => {
|
||||
write!(f, "(")?;
|
||||
@ -358,8 +360,8 @@ impl Ord for Type {
|
||||
left_type.cmp(right_type)
|
||||
}
|
||||
(Type::Range { .. }, _) => Ordering::Greater,
|
||||
(Type::String, Type::String) => Ordering::Equal,
|
||||
(Type::String, _) => Ordering::Greater,
|
||||
(Type::String { length: left }, Type::String { length: right }) => left.cmp(right),
|
||||
(Type::String { .. }, _) => Ordering::Greater,
|
||||
(Type::Struct(left_struct), Type::Struct(right_struct)) => {
|
||||
left_struct.cmp(right_struct)
|
||||
}
|
||||
@ -638,7 +640,7 @@ mod tests {
|
||||
#[test]
|
||||
fn errors() {
|
||||
let foo = Type::Integer;
|
||||
let bar = Type::String;
|
||||
let bar = Type::String { length: None };
|
||||
|
||||
assert_eq!(
|
||||
foo.check(&bar),
|
||||
@ -666,7 +668,7 @@ mod tests {
|
||||
Type::Range {
|
||||
r#type: RangeableType::Integer,
|
||||
},
|
||||
Type::String,
|
||||
Type::String { length: None },
|
||||
];
|
||||
|
||||
for left in types.clone() {
|
||||
|
@ -200,7 +200,9 @@ impl Value {
|
||||
r#type: rangeable_type,
|
||||
}
|
||||
}
|
||||
Value::String(_) => Type::String,
|
||||
Value::String(string) => Type::String {
|
||||
length: Some(string.len()),
|
||||
},
|
||||
Value::Struct(r#struct) => match r#struct {
|
||||
Struct::Unit { name } => Type::Struct(StructType::Unit { name: name.clone() }),
|
||||
Struct::Tuple { name, fields } => {
|
||||
|
@ -1685,12 +1685,26 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn length() {
|
||||
let input = "length([1, 2, 3])";
|
||||
fn list_length() {
|
||||
let input = "[1, 2, 3].length";
|
||||
|
||||
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]
|
||||
fn add() {
|
||||
let input = "1 + 2";
|
||||
|
Loading…
Reference in New Issue
Block a user