Pass analyzer tests
This commit is contained in:
parent
e84bb2ea70
commit
de3c83a6f5
@ -35,14 +35,15 @@ use crate::{
|
||||
pub fn analyze(source: &str) -> Result<(), DustError> {
|
||||
let abstract_tree = parse(source)?;
|
||||
let context = core_library().create_child();
|
||||
let analyzer = Analyzer::new(&abstract_tree, context);
|
||||
let mut analyzer = Analyzer::new(&abstract_tree, context);
|
||||
|
||||
analyzer
|
||||
.analyze()
|
||||
.map_err(|analysis_error| DustError::Analysis {
|
||||
analysis_error,
|
||||
source,
|
||||
})
|
||||
analyzer.analyze();
|
||||
|
||||
if analyzer.errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(DustError::analysis(analyzer.errors, source))
|
||||
}
|
||||
}
|
||||
|
||||
/// Static analyzer that checks for potential runtime errors.
|
||||
@ -61,58 +62,69 @@ pub fn analyze(source: &str) -> Result<(), DustError> {
|
||||
pub struct Analyzer<'a> {
|
||||
abstract_tree: &'a AbstractSyntaxTree,
|
||||
context: Context,
|
||||
pub errors: Vec<AnalysisError>,
|
||||
}
|
||||
|
||||
impl<'recovered, 'a: 'recovered> Analyzer<'a> {
|
||||
impl<'a> Analyzer<'a> {
|
||||
pub fn new(abstract_tree: &'a AbstractSyntaxTree, context: Context) -> Self {
|
||||
Self {
|
||||
abstract_tree,
|
||||
context,
|
||||
errors: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn analyze(&'recovered self) -> Result<(), AnalysisError> {
|
||||
pub fn analyze(&mut self) {
|
||||
for statement in &self.abstract_tree.statements {
|
||||
self.analyze_statement(statement)?;
|
||||
self.analyze_statement(statement);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn analyze_statement(&'recovered self, statement: &Statement) -> Result<(), AnalysisError> {
|
||||
fn analyze_statement(&mut self, statement: &Statement) {
|
||||
match statement {
|
||||
Statement::Expression(expression) => self.analyze_expression(expression)?,
|
||||
Statement::Expression(expression) => self.analyze_expression(expression),
|
||||
Statement::ExpressionNullified(expression_node) => {
|
||||
self.analyze_expression(&expression_node.inner)?;
|
||||
self.analyze_expression(&expression_node.inner);
|
||||
}
|
||||
Statement::Let(let_statement) => match &let_statement.inner {
|
||||
LetStatement::Let { identifier, value }
|
||||
| LetStatement::LetMut { identifier, value } => {
|
||||
let r#type = value.return_type(&self.context)?;
|
||||
let r#type = match value.return_type(&self.context) {
|
||||
Ok(type_option) => type_option,
|
||||
Err(ast_error) => {
|
||||
self.errors.push(AnalysisError::AstError(ast_error));
|
||||
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(r#type) = r#type {
|
||||
self.context
|
||||
.set_variable_type(
|
||||
let set_type = self.context.set_variable_type(
|
||||
identifier.inner.clone(),
|
||||
r#type,
|
||||
r#type.clone(),
|
||||
identifier.position,
|
||||
)
|
||||
.map_err(|error| AnalysisError::ContextError {
|
||||
error,
|
||||
);
|
||||
|
||||
if let Err(context_error) = set_type {
|
||||
self.errors.push(AnalysisError::ContextError {
|
||||
error: context_error,
|
||||
position: identifier.position,
|
||||
})?;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return Err(AnalysisError::LetExpectedValueFromStatement {
|
||||
self.errors
|
||||
.push(AnalysisError::LetExpectedValueFromStatement {
|
||||
actual: value.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
self.analyze_expression(value)?;
|
||||
self.analyze_expression(value);
|
||||
}
|
||||
LetStatement::LetType { .. } => todo!(),
|
||||
LetStatement::LetMutType { .. } => todo!(),
|
||||
},
|
||||
Statement::StructDefinition(struct_definition) => match &struct_definition.inner {
|
||||
Statement::StructDefinition(struct_definition) => {
|
||||
let set_constructor_type = match &struct_definition.inner {
|
||||
StructDefinition::Unit { name } => self.context.set_constructor_type(
|
||||
name.inner.clone(),
|
||||
StructType::Unit {
|
||||
@ -149,93 +161,114 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
|
||||
name.position,
|
||||
)
|
||||
}
|
||||
}
|
||||
.map_err(|error| AnalysisError::ContextError {
|
||||
error,
|
||||
};
|
||||
|
||||
if let Err(context_error) = set_constructor_type {
|
||||
self.errors.push(AnalysisError::ContextError {
|
||||
error: context_error,
|
||||
position: struct_definition.position,
|
||||
})?,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn analyze_expression(&self, expression: &Expression) -> Result<(), AnalysisError> {
|
||||
fn analyze_expression(&mut self, expression: &Expression) {
|
||||
match expression {
|
||||
Expression::Block(block_expression) => self.analyze_block(&block_expression.inner)?,
|
||||
Expression::Block(block_expression) => self.analyze_block(&block_expression.inner),
|
||||
Expression::Break(break_node) => {
|
||||
if let Some(expression) = &break_node.inner {
|
||||
self.analyze_expression(expression)?;
|
||||
self.analyze_expression(expression);
|
||||
}
|
||||
}
|
||||
Expression::Call(call_expression) => {
|
||||
let CallExpression { invoker, arguments } = call_expression.inner.as_ref();
|
||||
|
||||
self.analyze_expression(invoker)?;
|
||||
self.analyze_expression(invoker);
|
||||
|
||||
for argument in arguments {
|
||||
self.analyze_expression(argument)?;
|
||||
self.analyze_expression(argument);
|
||||
}
|
||||
}
|
||||
Expression::FieldAccess(field_access_expression) => {
|
||||
let FieldAccessExpression { container, field } =
|
||||
let FieldAccessExpression { container, .. } =
|
||||
field_access_expression.inner.as_ref();
|
||||
|
||||
self.context
|
||||
.update_last_position(&field.inner, field.position)
|
||||
.map_err(|error| AnalysisError::ContextError {
|
||||
error,
|
||||
position: field.position,
|
||||
})?;
|
||||
self.analyze_expression(container)?;
|
||||
self.analyze_expression(container);
|
||||
}
|
||||
Expression::Grouped(expression) => {
|
||||
self.analyze_expression(expression.inner.as_ref())?;
|
||||
self.analyze_expression(expression.inner.as_ref());
|
||||
}
|
||||
Expression::Identifier(identifier) => {
|
||||
let found = self
|
||||
.context
|
||||
.update_last_position(&identifier.inner, identifier.position)
|
||||
.map_err(|error| AnalysisError::ContextError {
|
||||
.map_err(|error| {
|
||||
self.errors.push(AnalysisError::ContextError {
|
||||
error,
|
||||
position: identifier.position,
|
||||
})?;
|
||||
})
|
||||
});
|
||||
|
||||
if !found {
|
||||
return Err(AnalysisError::UndefinedVariable {
|
||||
if let Ok(false) = found {
|
||||
self.errors.push(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() {
|
||||
ListExpression::AutoFill {
|
||||
repeat_operand,
|
||||
length_operand,
|
||||
} => {
|
||||
self.analyze_expression(repeat_operand)?;
|
||||
self.analyze_expression(length_operand)?;
|
||||
self.analyze_expression(repeat_operand);
|
||||
self.analyze_expression(length_operand);
|
||||
}
|
||||
ListExpression::Ordered(expressions) => {
|
||||
for expression in expressions {
|
||||
self.analyze_expression(expression)?;
|
||||
self.analyze_expression(expression);
|
||||
}
|
||||
}
|
||||
},
|
||||
Expression::ListIndex(list_index_expression) => {
|
||||
let ListIndexExpression { list, index } = list_index_expression.inner.as_ref();
|
||||
|
||||
self.analyze_expression(list)?;
|
||||
self.analyze_expression(index)?;
|
||||
self.analyze_expression(list);
|
||||
self.analyze_expression(index);
|
||||
|
||||
let list_type = list.return_type(&self.context)?;
|
||||
let index_type = if let Some(r#type) = index.return_type(&self.context)? {
|
||||
r#type
|
||||
} else {
|
||||
return Err(AnalysisError::ExpectedValueFromExpression {
|
||||
expression: index.clone(),
|
||||
let list_type = match list.return_type(&self.context) {
|
||||
Ok(Some(r#type)) => r#type,
|
||||
Ok(None) => {
|
||||
self.errors
|
||||
.push(AnalysisError::ExpectedValueFromExpression {
|
||||
expression: list.clone(),
|
||||
});
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
Err(ast_error) => {
|
||||
self.errors.push(AnalysisError::AstError(ast_error));
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
let index_type = match list.return_type(&self.context) {
|
||||
Ok(Some(r#type)) => r#type,
|
||||
Ok(None) => {
|
||||
self.errors
|
||||
.push(AnalysisError::ExpectedValueFromExpression {
|
||||
expression: list.clone(),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
Err(ast_error) => {
|
||||
self.errors.push(AnalysisError::AstError(ast_error));
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
let literal_type = if let Expression::Literal(Node { inner, .. }) = index {
|
||||
Some(inner.as_ref().clone())
|
||||
} else {
|
||||
@ -247,39 +280,21 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
|
||||
))) = literal_type
|
||||
{
|
||||
if integer < 0 {
|
||||
return Err(AnalysisError::NegativeIndex {
|
||||
self.errors.push(AnalysisError::NegativeIndex {
|
||||
index: index.clone(),
|
||||
index_value: integer,
|
||||
list: list.clone(),
|
||||
});
|
||||
}
|
||||
} else if let Type::Range { r#type } = &index_type {
|
||||
if let RangeableType::Integer = r#type {
|
||||
// Ok
|
||||
} else {
|
||||
return Err(AnalysisError::ExpectedType {
|
||||
expected: Type::Range {
|
||||
r#type: RangeableType::Integer,
|
||||
},
|
||||
actual: index_type,
|
||||
actual_expression: index.clone(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return Err(AnalysisError::ExpectedType {
|
||||
expected: Type::Integer,
|
||||
actual: index_type,
|
||||
actual_expression: index.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(Type::List { length, .. }) = list_type {
|
||||
if let Type::List { length, .. } = list_type {
|
||||
if let Some(LiteralExpression::Primitive(PrimitiveValueExpression::Integer(
|
||||
integer,
|
||||
))) = literal_type
|
||||
{
|
||||
if integer >= length as i64 {
|
||||
return Err(AnalysisError::IndexOutOfBounds {
|
||||
self.errors.push(AnalysisError::IndexOutOfBounds {
|
||||
index: index.clone(),
|
||||
length,
|
||||
list: list.clone(),
|
||||
@ -289,16 +304,16 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(Type::String {
|
||||
if let Type::String {
|
||||
length: Some(length),
|
||||
}) = list_type
|
||||
} = list_type
|
||||
{
|
||||
if let Some(LiteralExpression::Primitive(PrimitiveValueExpression::Integer(
|
||||
integer,
|
||||
))) = literal_type
|
||||
{
|
||||
if integer >= length as i64 {
|
||||
return Err(AnalysisError::IndexOutOfBounds {
|
||||
self.errors.push(AnalysisError::IndexOutOfBounds {
|
||||
index: index.clone(),
|
||||
length,
|
||||
list: list.clone(),
|
||||
@ -307,133 +322,237 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if list_type.is_none() {
|
||||
return Err(AnalysisError::ExpectedValueFromExpression {
|
||||
expression: list.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Expression::Literal(_) => {
|
||||
// Literals don't need to be analyzed
|
||||
}
|
||||
Expression::Loop(loop_expression) => match loop_expression.inner.as_ref() {
|
||||
LoopExpression::Infinite { block } => self.analyze_block(&block.inner)?,
|
||||
LoopExpression::Infinite { block } => self.analyze_block(&block.inner),
|
||||
LoopExpression::While { condition, block } => {
|
||||
self.analyze_expression(condition)?;
|
||||
self.analyze_block(&block.inner)?;
|
||||
self.analyze_expression(condition);
|
||||
self.analyze_block(&block.inner);
|
||||
}
|
||||
LoopExpression::For {
|
||||
iterator, block, ..
|
||||
} => {
|
||||
self.analyze_expression(iterator)?;
|
||||
self.analyze_block(&block.inner)?;
|
||||
self.analyze_expression(iterator);
|
||||
self.analyze_block(&block.inner);
|
||||
}
|
||||
},
|
||||
Expression::Map(map_expression) => {
|
||||
let MapExpression { pairs } = map_expression.inner.as_ref();
|
||||
|
||||
for (_, expression) in pairs {
|
||||
self.analyze_expression(expression)?;
|
||||
self.analyze_expression(expression);
|
||||
}
|
||||
}
|
||||
Expression::Operator(operator_expression) => match operator_expression.inner.as_ref() {
|
||||
OperatorExpression::Assignment { assignee, value } => {
|
||||
self.analyze_expression(assignee)?;
|
||||
self.analyze_expression(value)?;
|
||||
self.analyze_expression(assignee);
|
||||
self.analyze_expression(value);
|
||||
}
|
||||
OperatorExpression::Comparison { left, right, .. } => {
|
||||
self.analyze_expression(left)?;
|
||||
self.analyze_expression(right)?;
|
||||
self.analyze_expression(left);
|
||||
self.analyze_expression(right);
|
||||
}
|
||||
OperatorExpression::CompoundAssignment {
|
||||
assignee, modifier, ..
|
||||
} => {
|
||||
self.analyze_expression(assignee)?;
|
||||
self.analyze_expression(modifier)?;
|
||||
self.analyze_expression(assignee);
|
||||
self.analyze_expression(modifier);
|
||||
|
||||
let expected_type = assignee.return_type(&self.context)?;
|
||||
let actual_type = modifier.return_type(&self.context)?;
|
||||
|
||||
if expected_type.is_none() {
|
||||
return Err(AnalysisError::ExpectedValueFromExpression {
|
||||
let (expected_type, actual_type) = match (
|
||||
assignee.return_type(&self.context),
|
||||
modifier.return_type(&self.context),
|
||||
) {
|
||||
(Ok(Some(expected_type)), Ok(Some(actual_type))) => {
|
||||
(expected_type, actual_type)
|
||||
}
|
||||
(Ok(None), Ok(None)) => {
|
||||
self.errors
|
||||
.push(AnalysisError::ExpectedValueFromExpression {
|
||||
expression: assignee.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
if actual_type.is_none() {
|
||||
return Err(AnalysisError::ExpectedValueFromExpression {
|
||||
self.errors
|
||||
.push(AnalysisError::ExpectedValueFromExpression {
|
||||
expression: modifier.clone(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
(Ok(None), _) => {
|
||||
self.errors
|
||||
.push(AnalysisError::ExpectedValueFromExpression {
|
||||
expression: assignee.clone(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
(_, Ok(None)) => {
|
||||
self.errors
|
||||
.push(AnalysisError::ExpectedValueFromExpression {
|
||||
expression: modifier.clone(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
(Err(ast_error), _) => {
|
||||
self.errors.push(AnalysisError::AstError(ast_error));
|
||||
return;
|
||||
}
|
||||
(_, Err(ast_error)) => {
|
||||
self.errors.push(AnalysisError::AstError(ast_error));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let (Some(expected_type), Some(actual_type)) = (expected_type, actual_type) {
|
||||
expected_type.check(&actual_type).map_err(|_| {
|
||||
AnalysisError::TypeConflict {
|
||||
actual_expression: modifier.clone(),
|
||||
actual_type,
|
||||
if actual_type != expected_type {
|
||||
self.errors.push(AnalysisError::ExpectedType {
|
||||
expected: expected_type,
|
||||
}
|
||||
})?;
|
||||
actual: actual_type,
|
||||
actual_expression: modifier.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
OperatorExpression::ErrorPropagation(_) => todo!(),
|
||||
OperatorExpression::Negation(expression) => {
|
||||
self.analyze_expression(expression)?;
|
||||
self.analyze_expression(expression);
|
||||
}
|
||||
OperatorExpression::Not(expression) => {
|
||||
self.analyze_expression(expression)?;
|
||||
self.analyze_expression(expression);
|
||||
}
|
||||
OperatorExpression::Math { left, right, .. } => {
|
||||
self.analyze_expression(left)?;
|
||||
self.analyze_expression(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 {
|
||||
let (left_type, right_type) = match (
|
||||
left.return_type(&self.context),
|
||||
right.return_type(&self.context),
|
||||
) {
|
||||
(Ok(Some(left_type)), Ok(Some(right_type))) => (left_type, right_type),
|
||||
(Ok(None), Ok(None)) => {
|
||||
self.errors
|
||||
.push(AnalysisError::ExpectedValueFromExpression {
|
||||
expression: left.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
if right_type.is_none() {
|
||||
return Err(AnalysisError::ExpectedValueFromExpression {
|
||||
self.errors
|
||||
.push(AnalysisError::ExpectedValueFromExpression {
|
||||
expression: right.clone(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
(Ok(None), _) => {
|
||||
self.errors
|
||||
.push(AnalysisError::ExpectedValueFromExpression {
|
||||
expression: left.clone(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
(_, Ok(None)) => {
|
||||
self.errors
|
||||
.push(AnalysisError::ExpectedValueFromExpression {
|
||||
expression: right.clone(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
(Err(ast_error), _) => {
|
||||
self.errors.push(AnalysisError::AstError(ast_error));
|
||||
return;
|
||||
}
|
||||
(_, Err(ast_error)) => {
|
||||
self.errors.push(AnalysisError::AstError(ast_error));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if left_type != right_type {
|
||||
return Err(AnalysisError::ExpectedType {
|
||||
expected: left_type.unwrap(),
|
||||
actual: right_type.unwrap(),
|
||||
match left_type {
|
||||
Type::Integer => {
|
||||
if right_type != Type::Integer {
|
||||
self.errors.push(AnalysisError::ExpectedType {
|
||||
expected: Type::Integer,
|
||||
actual: right_type,
|
||||
actual_expression: right.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Type::Float => {
|
||||
if right_type != Type::Float {
|
||||
self.errors.push(AnalysisError::ExpectedType {
|
||||
expected: Type::Float,
|
||||
actual: right_type,
|
||||
actual_expression: right.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Type::String { .. } => {
|
||||
if let Type::String { .. } = right_type {
|
||||
} else {
|
||||
self.errors.push(AnalysisError::ExpectedType {
|
||||
expected: Type::String { length: None },
|
||||
actual: right_type,
|
||||
actual_expression: right.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.errors.push(AnalysisError::ExpectedTypeMultiple {
|
||||
expected: vec![
|
||||
Type::Float,
|
||||
Type::Integer,
|
||||
Type::String { length: None },
|
||||
],
|
||||
actual: left_type,
|
||||
actual_expression: left.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
OperatorExpression::Logic { left, right, .. } => {
|
||||
self.analyze_expression(left)?;
|
||||
self.analyze_expression(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 {
|
||||
let (left_type, right_type) = match (
|
||||
left.return_type(&self.context),
|
||||
right.return_type(&self.context),
|
||||
) {
|
||||
(Ok(Some(left_type)), Ok(Some(right_type))) => (left_type, right_type),
|
||||
(Ok(None), Ok(None)) => {
|
||||
self.errors
|
||||
.push(AnalysisError::ExpectedValueFromExpression {
|
||||
expression: left.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
if right_type.is_none() {
|
||||
return Err(AnalysisError::ExpectedValueFromExpression {
|
||||
self.errors
|
||||
.push(AnalysisError::ExpectedValueFromExpression {
|
||||
expression: right.clone(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
(Ok(None), _) => {
|
||||
self.errors
|
||||
.push(AnalysisError::ExpectedValueFromExpression {
|
||||
expression: left.clone(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
(_, Ok(None)) => {
|
||||
self.errors
|
||||
.push(AnalysisError::ExpectedValueFromExpression {
|
||||
expression: right.clone(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
(Err(ast_error), _) => {
|
||||
self.errors.push(AnalysisError::AstError(ast_error));
|
||||
return;
|
||||
}
|
||||
(_, Err(ast_error)) => {
|
||||
self.errors.push(AnalysisError::AstError(ast_error));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if left_type != right_type {
|
||||
return Err(AnalysisError::ExpectedType {
|
||||
expected: left_type.unwrap(),
|
||||
actual: right_type.unwrap(),
|
||||
self.errors.push(AnalysisError::ExpectedType {
|
||||
expected: left_type,
|
||||
actual: right_type,
|
||||
actual_expression: right.clone(),
|
||||
});
|
||||
}
|
||||
@ -441,87 +560,84 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
|
||||
},
|
||||
Expression::Range(range_expression) => match range_expression.inner.as_ref() {
|
||||
RangeExpression::Exclusive { start, end } => {
|
||||
self.analyze_expression(start)?;
|
||||
self.analyze_expression(end)?;
|
||||
self.analyze_expression(start);
|
||||
self.analyze_expression(end);
|
||||
}
|
||||
RangeExpression::Inclusive { start, end } => {
|
||||
self.analyze_expression(start)?;
|
||||
self.analyze_expression(end)?;
|
||||
self.analyze_expression(start);
|
||||
self.analyze_expression(end);
|
||||
}
|
||||
},
|
||||
Expression::Struct(struct_expression) => match struct_expression.inner.as_ref() {
|
||||
StructExpression::Fields { name, fields } => {
|
||||
self.context
|
||||
.update_last_position(&name.inner, name.position)
|
||||
.map_err(|error| AnalysisError::ContextError {
|
||||
let update_position = self
|
||||
.context
|
||||
.update_last_position(&name.inner, name.position);
|
||||
|
||||
if let Err(error) = update_position {
|
||||
self.errors.push(AnalysisError::ContextError {
|
||||
error,
|
||||
position: name.position,
|
||||
})?;
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (_, expression) in fields {
|
||||
self.analyze_expression(expression)?;
|
||||
self.analyze_expression(expression);
|
||||
}
|
||||
}
|
||||
},
|
||||
Expression::TupleAccess(tuple_access) => {
|
||||
let TupleAccessExpression { tuple, .. } = tuple_access.inner.as_ref();
|
||||
|
||||
self.analyze_expression(tuple)?;
|
||||
self.analyze_expression(tuple);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn analyze_block(
|
||||
&'recovered self,
|
||||
block_expression: &BlockExpression,
|
||||
) -> Result<(), AnalysisError> {
|
||||
fn analyze_block(&mut self, block_expression: &BlockExpression) {
|
||||
match block_expression {
|
||||
BlockExpression::Async(statements) => {
|
||||
for statement in statements {
|
||||
self.analyze_statement(statement)?;
|
||||
self.analyze_statement(statement);
|
||||
}
|
||||
}
|
||||
BlockExpression::Sync(statements) => {
|
||||
for statement in statements {
|
||||
self.analyze_statement(statement)?;
|
||||
self.analyze_statement(statement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn analyze_if(&'recovered self, if_expression: &IfExpression) -> Result<(), AnalysisError> {
|
||||
fn analyze_if(&mut self, if_expression: &IfExpression) {
|
||||
match if_expression {
|
||||
IfExpression::If {
|
||||
condition,
|
||||
if_block,
|
||||
} => {
|
||||
self.analyze_expression(condition)?;
|
||||
self.analyze_block(&if_block.inner)?;
|
||||
self.analyze_expression(condition);
|
||||
self.analyze_block(&if_block.inner);
|
||||
}
|
||||
IfExpression::IfElse {
|
||||
condition,
|
||||
if_block,
|
||||
r#else,
|
||||
} => {
|
||||
self.analyze_expression(condition)?;
|
||||
self.analyze_block(&if_block.inner)?;
|
||||
self.analyze_expression(condition);
|
||||
self.analyze_block(&if_block.inner);
|
||||
|
||||
match r#else {
|
||||
ElseExpression::Block(block_expression) => {
|
||||
self.analyze_block(&block_expression.inner)?;
|
||||
self.analyze_block(&block_expression.inner);
|
||||
}
|
||||
ElseExpression::If(if_expression) => {
|
||||
self.analyze_if(&if_expression.inner)?;
|
||||
self.analyze_if(&if_expression.inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -577,7 +693,7 @@ pub enum AnalysisError {
|
||||
},
|
||||
UndefinedField {
|
||||
identifier: Expression,
|
||||
statement: Expression,
|
||||
expression: Expression,
|
||||
},
|
||||
UndefinedType {
|
||||
identifier: Node<Identifier>,
|
||||
@ -712,7 +828,7 @@ impl Display for AnalysisError {
|
||||
}
|
||||
AnalysisError::UndefinedField {
|
||||
identifier,
|
||||
statement: map,
|
||||
expression: map,
|
||||
} => {
|
||||
write!(f, "Undefined field {} in map {}", identifier, map)
|
||||
}
|
||||
@ -737,6 +853,30 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn multiple_errors() {
|
||||
let source = "1 + 1.0; 'a' + 1";
|
||||
|
||||
assert_eq!(
|
||||
analyze(source),
|
||||
Err(DustError::Analysis {
|
||||
analysis_errors: vec![
|
||||
AnalysisError::ExpectedType {
|
||||
expected: Type::Integer,
|
||||
actual: Type::Float,
|
||||
actual_expression: Expression::literal(1.0, (4, 7)),
|
||||
},
|
||||
AnalysisError::ExpectedTypeMultiple {
|
||||
expected: vec![Type::Float, Type::Integer, Type::String { length: None }],
|
||||
actual: Type::Character,
|
||||
actual_expression: Expression::literal('a', (9, 12)),
|
||||
}
|
||||
],
|
||||
source,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_assign_wrong_type() {
|
||||
let source = "
|
||||
@ -747,11 +887,11 @@ mod tests {
|
||||
assert_eq!(
|
||||
analyze(source),
|
||||
Err(DustError::Analysis {
|
||||
analysis_error: AnalysisError::TypeConflict {
|
||||
actual_expression: Expression::literal(1.0, (45, 48)),
|
||||
actual_type: Type::Float,
|
||||
analysis_errors: vec![AnalysisError::ExpectedType {
|
||||
expected: Type::Integer,
|
||||
},
|
||||
actual: Type::Float,
|
||||
actual_expression: Expression::literal(1.0, (45, 48)),
|
||||
}],
|
||||
source,
|
||||
})
|
||||
);
|
||||
@ -767,11 +907,11 @@ mod tests {
|
||||
assert_eq!(
|
||||
analyze(source),
|
||||
Err(DustError::Analysis {
|
||||
analysis_error: AnalysisError::TypeConflict {
|
||||
actual_expression: Expression::literal(1.0, (45, 48)),
|
||||
actual_type: Type::Float,
|
||||
analysis_errors: vec![AnalysisError::ExpectedType {
|
||||
expected: Type::Integer,
|
||||
},
|
||||
actual: Type::Float,
|
||||
actual_expression: Expression::literal(1.0, (45, 48)),
|
||||
}],
|
||||
source,
|
||||
})
|
||||
);
|
||||
@ -787,11 +927,11 @@ mod tests {
|
||||
assert_eq!(
|
||||
analyze(source),
|
||||
Err(DustError::Analysis {
|
||||
analysis_error: AnalysisError::TypeConflict {
|
||||
actual_expression: Expression::literal(2, (52, 53)),
|
||||
actual_type: Type::Integer,
|
||||
analysis_errors: vec![AnalysisError::ExpectedType {
|
||||
expected: Type::Float,
|
||||
},
|
||||
actual: Type::Integer,
|
||||
actual_expression: Expression::literal(2, (52, 53)),
|
||||
}],
|
||||
source,
|
||||
})
|
||||
);
|
||||
@ -804,7 +944,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
analyze(source),
|
||||
Err(DustError::Analysis {
|
||||
analysis_error: AnalysisError::IndexOutOfBounds {
|
||||
analysis_errors: vec![AnalysisError::IndexOutOfBounds {
|
||||
list: Expression::list(
|
||||
vec![
|
||||
Expression::literal(1, (1, 2)),
|
||||
@ -816,7 +956,7 @@ mod tests {
|
||||
index: Expression::literal(3, (10, 11)),
|
||||
index_value: 3,
|
||||
length: 3,
|
||||
},
|
||||
}],
|
||||
source,
|
||||
})
|
||||
);
|
||||
@ -826,28 +966,63 @@ mod tests {
|
||||
fn nonexistant_field_identifier() {
|
||||
let source = "{ x = 1 }.y";
|
||||
|
||||
assert_eq!(analyze(source), todo!());
|
||||
assert_eq!(
|
||||
analyze(source),
|
||||
Err(DustError::Analysis {
|
||||
analysis_errors: vec![AnalysisError::UndefinedField {
|
||||
identifier: Expression::identifier(Identifier::new("y"), (11, 12)),
|
||||
expression: Expression::map(
|
||||
[(
|
||||
Node::new(Identifier::new("x"), (2, 3)),
|
||||
Expression::literal(1, (6, 7))
|
||||
)],
|
||||
(0, 11)
|
||||
),
|
||||
}],
|
||||
source,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonexistant_field_string() {
|
||||
let source = "{ x = 1 }.'y'";
|
||||
|
||||
assert_eq!(analyze(source), todo!());
|
||||
assert_eq!(
|
||||
analyze(source),
|
||||
Err(DustError::Analysis {
|
||||
analysis_errors: vec![AnalysisError::UndefinedField {
|
||||
identifier: Expression::literal("y", (11, 14)),
|
||||
expression: Expression::map(
|
||||
[(
|
||||
Node::new(Identifier::new("x"), (2, 3)),
|
||||
Expression::literal(1, (6, 7))
|
||||
)],
|
||||
(0, 11)
|
||||
),
|
||||
}],
|
||||
source,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_list_index() {
|
||||
let source = "[1, 2, 3]['foo']";
|
||||
let source = "[1, 2, 3][\"foo\"]";
|
||||
|
||||
assert_eq!(
|
||||
analyze(source),
|
||||
Err(DustError::Analysis {
|
||||
analysis_error: AnalysisError::ExpectedType {
|
||||
expected: Type::Integer,
|
||||
analysis_errors: vec![AnalysisError::ExpectedTypeMultiple {
|
||||
expected: vec![
|
||||
Type::Integer,
|
||||
Type::Range {
|
||||
r#type: RangeableType::Integer
|
||||
}
|
||||
],
|
||||
actual: Type::String { length: Some(3) },
|
||||
actual_expression: Expression::literal("foo", (10, 15)),
|
||||
},
|
||||
}],
|
||||
source,
|
||||
})
|
||||
);
|
||||
@ -857,7 +1032,15 @@ mod tests {
|
||||
fn malformed_field_access() {
|
||||
let source = "{ x = 1 }.0";
|
||||
|
||||
assert_eq!(analyze(source), todo!());
|
||||
assert_eq!(
|
||||
analyze(source),
|
||||
Err(DustError::Analysis {
|
||||
analysis_errors: vec![AnalysisError::ExpectedIdentifierOrString {
|
||||
actual: Expression::literal(0, (10, 11))
|
||||
}],
|
||||
source,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -867,11 +1050,11 @@ mod tests {
|
||||
assert_eq!(
|
||||
analyze(source),
|
||||
Err(DustError::Analysis {
|
||||
analysis_error: AnalysisError::ExpectedType {
|
||||
analysis_errors: vec![AnalysisError::ExpectedType {
|
||||
expected: Type::Float,
|
||||
actual: Type::Integer,
|
||||
actual_expression: Expression::literal(2, (7, 8)),
|
||||
},
|
||||
}],
|
||||
source,
|
||||
})
|
||||
);
|
||||
@ -884,11 +1067,11 @@ mod tests {
|
||||
assert_eq!(
|
||||
analyze(source),
|
||||
Err(DustError::Analysis {
|
||||
analysis_error: AnalysisError::ExpectedType {
|
||||
analysis_errors: vec![AnalysisError::ExpectedType {
|
||||
expected: Type::Integer,
|
||||
actual: Type::Boolean,
|
||||
actual_expression: Expression::literal(true, (5, 9)),
|
||||
},
|
||||
}],
|
||||
source,
|
||||
})
|
||||
);
|
||||
@ -898,7 +1081,16 @@ mod tests {
|
||||
fn nonexistant_field() {
|
||||
let source = "'hello'.foo";
|
||||
|
||||
assert_eq!(analyze(source), todo!());
|
||||
assert_eq!(
|
||||
analyze(source),
|
||||
Err(DustError::Analysis {
|
||||
analysis_errors: vec![AnalysisError::UndefinedField {
|
||||
expression: Expression::literal("hello", (0, 7)),
|
||||
identifier: Expression::identifier(Identifier::new("foo"), (8, 11)),
|
||||
}],
|
||||
source,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -908,9 +1100,9 @@ mod tests {
|
||||
assert_eq!(
|
||||
analyze(source),
|
||||
Err(DustError::Analysis {
|
||||
analysis_error: AnalysisError::UndefinedVariable {
|
||||
analysis_errors: vec![AnalysisError::UndefinedVariable {
|
||||
identifier: Node::new(Identifier::new("foo"), (0, 3))
|
||||
},
|
||||
}],
|
||||
source,
|
||||
})
|
||||
);
|
||||
|
@ -13,7 +13,7 @@ pub enum DustError<'src> {
|
||||
source: &'src str,
|
||||
},
|
||||
Analysis {
|
||||
analysis_error: AnalysisError,
|
||||
analysis_errors: Vec<AnalysisError>,
|
||||
source: &'src str,
|
||||
},
|
||||
Parse {
|
||||
@ -34,9 +34,9 @@ impl<'src> DustError<'src> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn analysis(analysis_error: AnalysisError, source: &'src str) -> Self {
|
||||
pub fn analysis(analysis_errors: Vec<AnalysisError>, source: &'src str) -> Self {
|
||||
DustError::Analysis {
|
||||
analysis_error,
|
||||
analysis_errors,
|
||||
source,
|
||||
}
|
||||
}
|
||||
@ -61,15 +61,6 @@ impl<'src> DustError<'src> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position(&self) -> Span {
|
||||
match self {
|
||||
DustError::Runtime { runtime_error, .. } => runtime_error.position(),
|
||||
DustError::Analysis { analysis_error, .. } => analysis_error.position(),
|
||||
DustError::Parse { parse_error, .. } => parse_error.position(),
|
||||
DustError::Lex { lex_error, .. } => lex_error.position(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn source(&self) -> &'src str {
|
||||
match self {
|
||||
DustError::Runtime { source, .. } => source,
|
||||
@ -79,23 +70,27 @@ impl<'src> DustError<'src> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn primary_error_data(&self) -> (&'static str, Span, String) {
|
||||
(self.title(), self.position(), self.to_string())
|
||||
pub fn error_data(&self) -> Vec<(&'static str, Span, String)> {
|
||||
match self {
|
||||
DustError::Runtime { runtime_error, .. } => vec![(
|
||||
"Runtime error",
|
||||
runtime_error.position(),
|
||||
runtime_error.to_string(),
|
||||
)],
|
||||
DustError::Analysis {
|
||||
analysis_errors, ..
|
||||
} => analysis_errors
|
||||
.iter()
|
||||
.map(|error| ("Analysis error", error.position(), error.to_string()))
|
||||
.collect(),
|
||||
DustError::Parse { parse_error, .. } => vec![(
|
||||
"Parse error",
|
||||
parse_error.position(),
|
||||
parse_error.to_string(),
|
||||
)],
|
||||
DustError::Lex { lex_error, .. } => {
|
||||
vec![("Lex error", lex_error.position(), lex_error.to_string())]
|
||||
}
|
||||
|
||||
pub fn secondary_error_data(&self) -> Option<(&'static str, Span, String)> {
|
||||
if let DustError::Runtime { runtime_error, .. } = self {
|
||||
match runtime_error {
|
||||
RuntimeError::Expression { error, .. } => {
|
||||
Some(("Expression error", error.position(), error.to_string()))
|
||||
}
|
||||
RuntimeError::Statement { error, .. } => {
|
||||
Some(("Statement error", error.position(), error.to_string()))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,16 +98,7 @@ impl<'src> DustError<'src> {
|
||||
let mut report = String::new();
|
||||
let renderer = Renderer::styled();
|
||||
|
||||
let (title, span, label) = self.primary_error_data();
|
||||
|
||||
let message = Level::Error.title(title).snippet(
|
||||
Snippet::source(self.source())
|
||||
.annotation(Level::Info.span(span.0..span.1).label(&label)),
|
||||
);
|
||||
|
||||
report.push_str(&format!("{}", renderer.render(message)));
|
||||
|
||||
if let Some((title, span, label)) = self.secondary_error_data() {
|
||||
for (title, span, label) in self.error_data() {
|
||||
let message = Level::Error.title(title).snippet(
|
||||
Snippet::source(self.source())
|
||||
.annotation(Level::Info.span(span.0..span.1).label(&label)),
|
||||
@ -129,7 +115,15 @@ impl Display for DustError<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
DustError::Runtime { runtime_error, .. } => write!(f, "{runtime_error}"),
|
||||
DustError::Analysis { analysis_error, .. } => write!(f, "{analysis_error}"),
|
||||
DustError::Analysis {
|
||||
analysis_errors, ..
|
||||
} => {
|
||||
for error in analysis_errors {
|
||||
write!(f, "{error} ")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
DustError::Parse { parse_error, .. } => write!(f, "{parse_error}"),
|
||||
DustError::Lex { lex_error, .. } => write!(f, "{lex_error}"),
|
||||
}
|
||||
|
@ -57,14 +57,16 @@ pub fn run(source: &str) -> Result<Option<Value>, DustError> {
|
||||
/// ```
|
||||
pub fn run_with_context(source: &str, context: Context) -> Result<Option<Value>, DustError> {
|
||||
let abstract_syntax_tree = parse(source)?;
|
||||
let analyzer = Analyzer::new(&abstract_syntax_tree, context.clone());
|
||||
let mut analyzer = Analyzer::new(&abstract_syntax_tree, context.clone());
|
||||
|
||||
analyzer
|
||||
.analyze()
|
||||
.map_err(|analysis_error| DustError::Analysis {
|
||||
analysis_error,
|
||||
analyzer.analyze();
|
||||
|
||||
if !analyzer.errors.is_empty() {
|
||||
return Err(DustError::Analysis {
|
||||
analysis_errors: analyzer.errors,
|
||||
source,
|
||||
})?;
|
||||
});
|
||||
}
|
||||
|
||||
let mut vm = Vm::new(abstract_syntax_tree, context);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user