Begin improving errors

This commit is contained in:
Jeff 2024-03-18 03:24:41 -04:00
parent 3a97ba76a0
commit 1750132ed8
9 changed files with 253 additions and 123 deletions

View File

@ -7,7 +7,7 @@ use super::{AbstractTree, Action, Identifier, Statement, Type, WithPosition};
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Assignment { pub struct Assignment {
identifier: Identifier, identifier: WithPosition<Identifier>,
r#type: Option<WithPosition<Type>>, r#type: Option<WithPosition<Type>>,
operator: AssignmentOperator, operator: AssignmentOperator,
statement: Box<WithPosition<Statement>>, statement: Box<WithPosition<Statement>>,
@ -22,7 +22,7 @@ pub enum AssignmentOperator {
impl Assignment { impl Assignment {
pub fn new( pub fn new(
identifier: Identifier, identifier: WithPosition<Identifier>,
r#type: Option<WithPosition<Type>>, r#type: Option<WithPosition<Type>>,
operator: AssignmentOperator, operator: AssignmentOperator,
statement: WithPosition<Statement>, statement: WithPosition<Statement>,
@ -57,12 +57,12 @@ impl AbstractTree for Assignment {
} }
})?; })?;
context.set_type(self.identifier.clone(), expected_type.clone())?; context.set_type(self.identifier.node.clone(), expected_type.clone())?;
} else { } else {
context.set_type(self.identifier.clone(), statement_type)?; context.set_type(self.identifier.node.clone(), statement_type)?;
} }
self.identifier.validate(context)?; self.identifier.node.validate(context)?;
self.statement.node.validate(context)?; self.statement.node.validate(context)?;
Ok(()) Ok(())
@ -77,27 +77,33 @@ impl AbstractTree for Assignment {
match self.operator { match self.operator {
AssignmentOperator::Assign => { AssignmentOperator::Assign => {
context.set_value(self.identifier, value)?; context.set_value(self.identifier.node, value)?;
} }
AssignmentOperator::AddAssign => { AssignmentOperator::AddAssign => {
if let Some(previous_value) = context.get_value(&self.identifier)? { if let Some(previous_value) = context.get_value(&self.identifier.node)? {
let new_value = previous_value.add(&value)?; let new_value = previous_value.add(&value)?;
context.set_value(self.identifier, new_value)?; context.set_value(self.identifier.node, new_value)?;
} else { } else {
return Err(RuntimeError::ValidationFailure( return Err(RuntimeError::ValidationFailure(
ValidationError::VariableNotFound(self.identifier), ValidationError::VariableNotFound {
identifier: self.identifier.node,
position: self.identifier.position,
},
)); ));
} }
} }
AssignmentOperator::SubAssign => { AssignmentOperator::SubAssign => {
if let Some(previous_value) = context.get_value(&self.identifier)? { if let Some(previous_value) = context.get_value(&self.identifier.node)? {
let new_value = previous_value.subtract(&value)?; let new_value = previous_value.subtract(&value)?;
context.set_value(self.identifier, new_value)?; context.set_value(self.identifier.node, new_value)?;
} else { } else {
return Err(RuntimeError::ValidationFailure( return Err(RuntimeError::ValidationFailure(
ValidationError::VariableNotFound(self.identifier), ValidationError::VariableNotFound {
identifier: self.identifier.node,
position: self.identifier.position,
},
)); ));
} }
} }
@ -122,7 +128,7 @@ mod tests {
let context = Context::new(); let context = Context::new();
Assignment::new( Assignment::new(
Identifier::new("foobar"), Identifier::new("foobar").with_position((0, 0)),
None, None,
AssignmentOperator::Assign, AssignmentOperator::Assign,
Statement::Expression(Expression::Value(ValueNode::Integer(42))).with_position((0, 0)), Statement::Expression(Expression::Value(ValueNode::Integer(42))).with_position((0, 0)),
@ -145,7 +151,7 @@ mod tests {
.unwrap(); .unwrap();
Assignment::new( Assignment::new(
Identifier::new("foobar"), Identifier::new("foobar").with_position((0, 0)),
None, None,
AssignmentOperator::AddAssign, AssignmentOperator::AddAssign,
Statement::Expression(Expression::Value(ValueNode::Integer(41))).with_position((0, 0)), Statement::Expression(Expression::Value(ValueNode::Integer(41))).with_position((0, 0)),
@ -168,7 +174,7 @@ mod tests {
.unwrap(); .unwrap();
Assignment::new( Assignment::new(
Identifier::new("foobar"), Identifier::new("foobar").with_position((0, 0)),
None, None,
AssignmentOperator::SubAssign, AssignmentOperator::SubAssign,
Statement::Expression(Expression::Value(ValueNode::Integer(1))).with_position((0, 0)), Statement::Expression(Expression::Value(ValueNode::Integer(1))).with_position((0, 0)),
@ -185,7 +191,7 @@ mod tests {
#[test] #[test]
fn type_check() { fn type_check() {
let validation = Assignment::new( let validation = Assignment::new(
Identifier::new("foobar"), Identifier::new("foobar").with_position((0, 0)),
Some(WithPosition { Some(WithPosition {
node: Type::Boolean, node: Type::Boolean,
position: (0, 0).into(), position: (0, 0).into(),

View File

@ -52,7 +52,14 @@ impl AbstractTree for FunctionCall {
} }
fn run(self, context: &Context) -> Result<Action, RuntimeError> { fn run(self, context: &Context) -> Result<Action, RuntimeError> {
let value = self.function.node.run(context)?.as_return_value()?; let action = self.function.node.run(context)?;
let value = if let Action::Return(value) = action {
value
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::InterpreterExpectedReturn(self.function.position),
));
};
let function = if let ValueInner::Function(function) = value.inner().as_ref() { let function = if let ValueInner::Function(function) = value.inner().as_ref() {
function function
} else { } else {
@ -66,7 +73,14 @@ impl AbstractTree for FunctionCall {
let mut arguments = Vec::with_capacity(self.arguments.len()); let mut arguments = Vec::with_capacity(self.arguments.len());
for expression in self.arguments { for expression in self.arguments {
let value = expression.node.run(context)?.as_return_value()?; let action = expression.node.run(context)?;
let value = if let Action::Return(value) = action {
value
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::InterpreterExpectedReturn(expression.position),
));
};
arguments.push(value); arguments.push(value);
} }

View File

@ -28,7 +28,10 @@ impl AbstractTree for Identifier {
if let Some(r#type) = context.get_type(self)? { if let Some(r#type) = context.get_type(self)? {
Ok(r#type) Ok(r#type)
} else { } else {
Err(ValidationError::VariableNotFound(self.clone())) Err(ValidationError::VariableNotFound {
identifier: todo!(),
position: todo!(),
})
} }
} }
@ -36,7 +39,10 @@ impl AbstractTree for Identifier {
if context.contains(self)? { if context.contains(self)? {
Ok(()) Ok(())
} else { } else {
Err(ValidationError::VariableNotFound(self.clone())) Err(ValidationError::VariableNotFound {
identifier: todo!(),
position: todo!(),
})
} }
} }
@ -47,7 +53,10 @@ impl AbstractTree for Identifier {
Ok(action) Ok(action)
} else { } else {
Err(RuntimeError::ValidationFailure( Err(RuntimeError::ValidationFailure(
ValidationError::VariableNotFound(self.clone()), ValidationError::VariableNotFound {
identifier: todo!(),
position: todo!(),
},
)) ))
} }
} }

View File

@ -54,9 +54,10 @@ impl AbstractTree for ListIndex {
Ok(()) Ok(())
} else { } else {
Err(ValidationError::CannotIndexWith { Err(ValidationError::CannotIndexWith {
collection_type: todo!(), collection_type: left_type,
index_type: todo!(), collection_position: self.left.position,
position: todo!(), index_type: right_type,
index_position: self.right.position,
}) })
} }
} }
@ -82,9 +83,10 @@ impl AbstractTree for ListIndex {
} else { } else {
Err(RuntimeError::ValidationFailure( Err(RuntimeError::ValidationFailure(
ValidationError::CannotIndexWith { ValidationError::CannotIndexWith {
collection_type: todo!(), collection_type: left_value.r#type(),
index_type: todo!(), collection_position: self.left.position,
position: todo!(), index_type: right_value.r#type(),
index_position: self.right.position,
}, },
)) ))
} }

View File

@ -72,8 +72,9 @@ impl AbstractTree for MapIndex {
Err(ValidationError::CannotIndexWith { Err(ValidationError::CannotIndexWith {
collection_type: left_type, collection_type: left_type,
collection_position: todo!(),
index_type: self.right.node.expected_type(_context)?, index_type: self.right.node.expected_type(_context)?,
position: self.right.position, index_position: self.right.position,
}) })
} }
@ -91,8 +92,9 @@ impl AbstractTree for MapIndex {
} else { } else {
Err(ValidationError::CannotIndexWith { Err(ValidationError::CannotIndexWith {
collection_type: left_type, collection_type: left_type,
collection_position: self.left.position,
index_type: self.right.node.expected_type(context)?, index_type: self.right.node.expected_type(context)?,
position: self.right.position, index_position: self.right.position,
}) })
} }
} }
@ -113,8 +115,9 @@ impl AbstractTree for MapIndex {
Err(RuntimeError::ValidationFailure( Err(RuntimeError::ValidationFailure(
ValidationError::CannotIndexWith { ValidationError::CannotIndexWith {
collection_type: collection.r#type(), collection_type: collection.r#type(),
collection_position: todo!(),
index_type: self.right.node.expected_type(_context)?, index_type: self.right.node.expected_type(_context)?,
position: self.right.position, index_position: self.right.position,
}, },
)) ))
} }

View File

@ -80,13 +80,3 @@ pub enum Action {
Break, Break,
None, None,
} }
impl Action {
pub fn as_return_value(self) -> Result<Value, ValidationError> {
if let Action::Return(value) = self {
Ok(value)
} else {
Err(ValidationError::InterpreterExpectedReturn)
}
}
}

View File

@ -1,6 +1,6 @@
use std::{io, ops::Range, sync::PoisonError}; use std::{io, ops::Range, sync::PoisonError};
use ariadne::{Color, Fmt, Label, ReportBuilder}; use ariadne::{Color, Fmt, Label, Report, ReportBuilder, ReportKind};
use chumsky::{prelude::Rich, span::Span}; use chumsky::{prelude::Rich, span::Span};
use crate::{ use crate::{
@ -29,61 +29,93 @@ pub enum Error {
} }
impl Error { impl Error {
pub fn build_report( pub fn build_report<'a>(self) -> ReportBuilder<'a, (&'a str, Range<usize>)> {
self, let (mut builder, validation_error) = match &self {
mut builder: ReportBuilder<'_, Range<usize>>,
) -> ReportBuilder<'_, Range<usize>> {
let type_color = Color::Green;
match self {
Error::Parse { expected, span } => { Error::Parse { expected, span } => {
let message = match expected.as_str() { let message = if expected.is_empty() {
"" => "Invalid character.".to_string(), "Invalid token.".to_string()
expected => format!("Expected {expected}."), } else {
format!("Expected {expected}.")
}; };
builder = builder.with_note("Parsing error."); (
Report::build(
builder.add_label(Label::new(span.0..span.1).with_message(message)); ReportKind::Custom("Parsing Error", Color::White),
"input",
span.1,
)
.with_label(
Label::new(("input", span.0..span.1))
.with_message(message)
.with_color(Color::Red),
),
None,
)
} }
Error::Lex { expected, span } => { Error::Lex { expected, span } => {
let message = match expected.as_str() { let message = if expected.is_empty() {
"" => "Invalid character.".to_string(), "Invalid token.".to_string()
expected => format!("Expected {expected}."), } else {
format!("Expected {expected}.")
}; };
builder = builder.with_note("Lexing error."); (
Report::build(
builder.add_label(Label::new(span.0..span.1).with_message(message)); ReportKind::Custom("Dust Error", Color::White),
"input",
span.1,
)
.with_label(
Label::new(("input", span.0..span.1))
.with_message(message)
.with_color(Color::Red),
),
None,
)
} }
Error::Runtime { error, position } => match error { Error::Runtime { error, position } => (
RuntimeError::RwLockPoison(_) => todo!(), Report::build(
RuntimeError::ValidationFailure(validation_error) => { ReportKind::Custom("Dust Error", Color::White),
builder = "input",
Error::Validation { position.1,
error: validation_error, ),
position, if let RuntimeError::ValidationFailure(validation_error) = error {
} Some(validation_error)
.build_report(builder.with_note( } else {
"The interpreter failed to catch this error during validation.", None
));
}
RuntimeError::Io(_) => todo!(),
}, },
Error::Validation { error, position } => match error { ),
Error::Validation { error, position } => (
Report::build(
ReportKind::Custom("Dust Error", Color::White),
"input",
position.1,
),
Some(error),
),
};
let type_color = Color::Green;
if let Some(validation_error) = validation_error {
match validation_error {
ValidationError::ExpectedBoolean { actual, position } => { ValidationError::ExpectedBoolean { actual, position } => {
builder.add_label(Label::new(position.0..position.1).with_message(format!( builder.add_label(Label::new(("input", position.0..position.1)).with_message(
format!(
"Expected {} but got {}.", "Expected {} but got {}.",
"boolean".fg(type_color), "boolean".fg(type_color),
actual.fg(type_color) actual.fg(type_color)
))); ),
));
} }
ValidationError::ExpectedIntegerOrFloat => { ValidationError::ExpectedIntegerOrFloat(position) => {
builder.add_label(Label::new(position.0..position.1).with_message(format!( builder.add_label(Label::new(("input", position.0..position.1)).with_message(
format!(
"Expected {} or {}.", "Expected {} or {}.",
"integer".fg(type_color), "integer".fg(type_color),
"float".fg(type_color) "float".fg(type_color)
))); ),
));
} }
ValidationError::RwLockPoison(_) => todo!(), ValidationError::RwLockPoison(_) => todo!(),
ValidationError::TypeCheck { ValidationError::TypeCheck {
@ -94,39 +126,54 @@ impl Error {
let TypeConflict { actual, expected } = conflict; let TypeConflict { actual, expected } = conflict;
builder.add_labels([ builder.add_labels([
Label::new(expected_postion.0..expected_postion.1).with_message(format!( Label::new(("input", expected_postion.0..expected_postion.1)).with_message(
"Type {} established here.", format!("Type {} established here.", expected.fg(type_color)),
expected.fg(type_color) ),
)), Label::new(("input", actual_position.0..actual_position.1))
Label::new(actual_position.0..actual_position.1)
.with_message(format!("Got type {} here.", actual.fg(type_color))), .with_message(format!("Got type {} here.", actual.fg(type_color))),
]); ]);
} }
ValidationError::VariableNotFound(identifier) => { ValidationError::VariableNotFound {
identifier,
position,
} => {
builder.add_label( builder.add_label(
Label::new(position.0..position.1) Label::new(("input", position.0..position.1))
.with_message(format!("The variable {identifier} does not exist.")) .with_message(format!("The variable {identifier} does not exist."))
.with_priority(1), .with_priority(1),
); );
} }
ValidationError::CannotIndex { r#type, position } => builder.add_label( ValidationError::CannotIndex { r#type, position } => builder.add_label(
Label::new(position.0..position.1) Label::new(("input", position.0..position.1))
.with_message(format!("Cannot index into a {}.", r#type.fg(type_color))), .with_message(format!("Cannot index into a {}.", r#type.fg(type_color))),
), ),
ValidationError::CannotIndexWith { ValidationError::CannotIndexWith {
collection_type, collection_type,
collection_position,
index_type, index_type,
position, index_position,
} => builder.add_label(Label::new(position.0..position.1).with_message(format!( } => {
"Cannot index into a {} with a {}.", builder = builder.with_message(format!(
"Cannot index into {} with {}.",
collection_type.clone().fg(type_color),
index_type.clone().fg(type_color)
));
builder.add_labels([
Label::new(("input", collection_position.0..collection_position.1))
.with_message(format!(
"This has type {}.",
collection_type.fg(type_color), collection_type.fg(type_color),
index_type.fg(type_color) )),
))), Label::new(("input", index_position.0..index_position.1))
ValidationError::InterpreterExpectedReturn => todo!(), .with_message(format!("This has type {}.", index_type.fg(type_color),)),
])
}
ValidationError::InterpreterExpectedReturn(_) => todo!(),
ValidationError::ExpectedFunction { .. } => todo!(), ValidationError::ExpectedFunction { .. } => todo!(),
ValidationError::ExpectedValue => todo!(), ValidationError::ExpectedValue(_) => todo!(),
ValidationError::PropertyNotFound(_) => todo!(), ValidationError::PropertyNotFound { .. } => todo!(),
}, }
} }
builder builder
@ -197,8 +244,9 @@ pub enum ValidationError {
}, },
CannotIndexWith { CannotIndexWith {
collection_type: Type, collection_type: Type,
collection_position: SourcePosition,
index_type: Type, index_type: Type,
position: SourcePosition, index_position: SourcePosition,
}, },
ExpectedBoolean { ExpectedBoolean {
actual: Type, actual: Type,
@ -208,9 +256,9 @@ pub enum ValidationError {
actual: Type, actual: Type,
position: SourcePosition, position: SourcePosition,
}, },
ExpectedIntegerOrFloat, ExpectedIntegerOrFloat(SourcePosition),
ExpectedValue, ExpectedValue(SourcePosition),
InterpreterExpectedReturn, InterpreterExpectedReturn(SourcePosition),
RwLockPoison(RwLockPoisonError), RwLockPoison(RwLockPoisonError),
TypeCheck { TypeCheck {
/// The mismatch that caused the error. /// The mismatch that caused the error.
@ -222,8 +270,15 @@ pub enum ValidationError {
/// The position of the item that gave the "expected" type. /// The position of the item that gave the "expected" type.
expected_position: SourcePosition, expected_position: SourcePosition,
}, },
VariableNotFound(Identifier), VariableNotFound {
PropertyNotFound(Identifier), identifier: Identifier,
position: SourcePosition,
},
PropertyNotFound {
identifier: Identifier,
position: SourcePosition,
},
} }
impl From<RwLockPoisonError> for ValidationError { impl From<RwLockPoisonError> for ValidationError {

View File

@ -1,12 +1,13 @@
//! Command line interface for the dust programming language. //! Command line interface for the dust programming language.
use ariadne::{Color, Report, ReportKind, Source}; use ariadne::{sources, Color, Label, Report, ReportKind, Source};
use chumsky::span::SimpleSpan;
use clap::Parser; use clap::Parser;
use colored::Colorize; use colored::Colorize;
use std::{fs::read_to_string, io::Write}; use std::{fs::read_to_string, io::Write, ops::Range};
use dust_lang::{context::Context, Interpreter}; use dust_lang::{context::Context, error::Error, Interpreter};
/// Command-line arguments to be parsed. /// Command-line arguments to be parsed.
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@ -53,17 +54,63 @@ fn main() {
} }
} }
Err(errors) => { Err(errors) => {
let mut report_builder =
Report::build(ReportKind::Custom("Dust Error", Color::White), (), 5);
for error in errors { for error in errors {
report_builder = error.build_report(report_builder); let mut report_builder = match &error {
Error::Parse { expected, span } => {
let message = if expected.is_empty() {
"Invalid token.".to_string()
} else {
format!("Expected {expected}.")
};
Report::build(
ReportKind::Custom("Parsing Error", Color::White),
"input",
span.1,
)
.with_label(
Label::new(("input", span.0..span.1))
.with_message(message)
.with_color(Color::Red),
)
} }
Error::Lex { expected, span } => {
let message = if expected.is_empty() {
"Invalid token.".to_string()
} else {
format!("Expected {expected}.")
};
Report::build(
ReportKind::Custom("Dust Error", Color::White),
"input",
span.1,
)
.with_label(
Label::new(("input", span.0..span.1))
.with_message(message)
.with_color(Color::Red),
)
}
Error::Runtime { error, position } => Report::build(
ReportKind::Custom("Dust Error", Color::White),
"input",
position.1,
),
Error::Validation { error, position } => Report::build(
ReportKind::Custom("Dust Error", Color::White),
"input",
position.1,
),
};
report_builder = error.build_report(report_builder);
report_builder report_builder
.finish() .finish()
.eprint(Source::from(source)) .eprint(sources([("input", &source)]))
.unwrap() .unwrap()
} }
} }
} }
}

View File

@ -47,6 +47,10 @@ pub fn parser<'src>() -> DustParser<'src> {
} }
}; };
let positioned_identifier = identifier
.clone()
.map_with(|identifier, state| identifier.with_position(state.span()));
let basic_value = select! { let basic_value = select! {
Token::Boolean(boolean) => ValueNode::Boolean(boolean), Token::Boolean(boolean) => ValueNode::Boolean(boolean),
Token::Float(float) => ValueNode::Float(float), Token::Float(float) => ValueNode::Float(float),
@ -360,7 +364,7 @@ pub fn parser<'src>() -> DustParser<'src> {
let r#break = just(Token::Keyword("break")) let r#break = just(Token::Keyword("break"))
.map_with(|_, state| Statement::Break.with_position(state.span())); .map_with(|_, state| Statement::Break.with_position(state.span()));
let assignment = identifier let assignment = positioned_identifier
.clone() .clone()
.then(type_specification.clone().or_not()) .then(type_specification.clone().or_not())
.then(choice(( .then(choice((