Improve errors

This commit is contained in:
Jeff 2024-03-23 17:51:40 -04:00
parent 7263507e84
commit 13c95dd12f
6 changed files with 95 additions and 196 deletions

View File

@ -39,6 +39,10 @@ impl AbstractNode for FunctionCall {
} }
fn validate(&self, _context: &Context) -> Result<(), ValidationError> { fn validate(&self, _context: &Context) -> Result<(), ValidationError> {
for expression in &self.arguments {
expression.node.validate(_context)?;
}
let function_node_type = self.function.node.expected_type(_context)?; let function_node_type = self.function.node.expected_type(_context)?;
if let Type::Function { .. } = function_node_type { if let Type::Function { .. } = function_node_type {

View File

@ -19,18 +19,50 @@ pub enum Math {
impl AbstractNode for Math { impl AbstractNode for Math {
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> { fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
match self { match self {
Math::Add(left, _) Math::Add(left, right)
| Math::Subtract(left, _) | Math::Subtract(left, right)
| Math::Multiply(left, _) | Math::Multiply(left, right)
| Math::Divide(left, _) | Math::Divide(left, right)
| Math::Modulo(left, _) => left.node.expected_type(_context), | Math::Modulo(left, right) => {
let left_type = left.node.expected_type(_context)?;
let right_type = right.node.expected_type(_context)?;
if let Type::Float = left_type {
return Ok(Type::Float);
}
if let Type::Float = right_type {
return Ok(Type::Float);
}
Ok(left_type)
}
} }
} }
fn validate(&self, context: &Context) -> Result<(), ValidationError> { fn validate(&self, context: &Context) -> Result<(), ValidationError> {
match self { match self {
Math::Add(left, right) Math::Add(left, right) => {
| Math::Subtract(left, right) let left_type = left.node.expected_type(context)?;
let right_type = right.node.expected_type(context)?;
if let Type::Integer | Type::Float | Type::String = left_type {
if let Type::Integer | Type::Float | Type::String = right_type {
Ok(())
} else {
Err(ValidationError::ExpectedIntegerFloatOrString {
actual: right_type,
position: right.position,
})
}
} else {
Err(ValidationError::ExpectedIntegerFloatOrString {
actual: left_type,
position: left.position,
})
}
}
Math::Subtract(left, right)
| Math::Multiply(left, right) | Math::Multiply(left, right)
| Math::Divide(left, right) | Math::Divide(left, right)
| Math::Modulo(left, right) => { | Math::Modulo(left, right) => {
@ -91,6 +123,13 @@ impl AbstractNode for Math {
Value::float(sum) Value::float(sum)
} }
(ValueInner::String(left), ValueInner::String(right)) => {
let mut concatenated = String::with_capacity(left.len() + right.len());
concatenated.extend(left.chars().chain(right.chars()));
Value::string(concatenated)
}
(ValueInner::Integer(_) | ValueInner::Float(_), _) => { (ValueInner::Integer(_) | ValueInner::Float(_), _) => {
return Err(RuntimeError::ValidationFailure( return Err(RuntimeError::ValidationFailure(
ValidationError::ExpectedIntegerOrFloat(right.position), ValidationError::ExpectedIntegerOrFloat(right.position),

View File

@ -30,190 +30,6 @@ pub enum Error {
}, },
} }
impl Error {
pub fn build_report<'id, Id: Debug + Hash + Eq + Clone>(
self,
source_id: Id,
) -> Result<Report<'id, (Id, Range<usize>)>, io::Error> {
let (mut builder, validation_error, error_position) = match self {
Error::Parse {
expected,
span,
reason,
} => {
let description = if expected.is_empty() {
"Invalid token.".to_string()
} else {
format!("Expected {expected}.")
};
(
Report::build(
ReportKind::Custom("Parsing Error", Color::Yellow),
source_id.clone(),
span.1,
)
.with_message(description)
.with_label(
Label::new((source_id.clone(), span.0..span.1))
.with_message(reason)
.with_color(Color::Red),
),
None,
span.into(),
)
}
Error::Lex {
expected,
span,
reason,
} => {
let description = if expected.is_empty() {
"Invalid character.".to_string()
} else {
format!("Expected {expected}.")
};
(
Report::build(
ReportKind::Custom("Lexing Error", Color::Yellow),
source_id.clone(),
span.1,
)
.with_message(description)
.with_label(
Label::new((source_id.clone(), span.0..span.1))
.with_message(reason)
.with_color(Color::Red),
),
None,
span.into(),
)
}
Error::Runtime { error, position } => (
Report::build(
ReportKind::Custom("Runtime Error", Color::Red),
source_id.clone(),
position.1,
)
.with_message("An error occured that forced the program to exit.")
.with_note(
"There may be unexpected side-effects because the program could not finish.",
)
.with_help(
"This is the interpreter's fault. Please submit a bug with this error message.",
),
if let RuntimeError::ValidationFailure(validation_error) = error {
Some(validation_error)
} else {
None
},
position,
),
Error::Validation { error, position } => (
Report::build(
ReportKind::Custom("Validation Error", Color::Magenta),
source_id.clone(),
position.1,
)
.with_message("The syntax is valid but this code is not sound.")
.with_note("This error was detected by the interpreter before running the code."),
Some(error),
position,
),
};
let type_color = Color::Green;
let identifier_color = Color::Blue;
if let Some(validation_error) = validation_error {
match validation_error {
ValidationError::ExpectedBoolean { actual, position } => {
builder.add_label(
Label::new((source_id, position.0..position.1)).with_message(format!(
"Expected {} but got {}.",
"boolean".fg(type_color),
actual.fg(type_color)
)),
);
}
ValidationError::ExpectedIntegerOrFloat(position) => {
builder.add_label(
Label::new((source_id, position.0..position.1)).with_message(format!(
"Expected {} or {}.",
"integer".fg(type_color),
"float".fg(type_color)
)),
);
}
ValidationError::RwLockPoison(_) => todo!(),
ValidationError::TypeCheck {
conflict,
actual_position,
expected_position: expected_postion,
} => {
let TypeConflict { actual, expected } = conflict;
builder = builder.with_message("A type conflict was found.");
builder.add_labels([
Label::new((source_id.clone(), expected_postion.0..expected_postion.1))
.with_message(format!(
"Type {} established here.",
expected.fg(type_color)
)),
Label::new((source_id, actual_position.0..actual_position.1))
.with_message(format!("Got type {} here.", actual.fg(type_color))),
]);
}
ValidationError::VariableNotFound(identifier) => builder.add_label(
Label::new((source_id, error_position.0..error_position.1)).with_message(
format!(
"Variable {} does not exist in this context.",
identifier.fg(identifier_color)
),
),
),
ValidationError::CannotIndex { r#type, position } => builder.add_label(
Label::new((source_id, position.0..position.1))
.with_message(format!("Cannot index into a {}.", r#type.fg(type_color))),
),
ValidationError::CannotIndexWith {
collection_type,
collection_position,
index_type,
index_position,
} => {
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((
source_id.clone(),
collection_position.0..collection_position.1,
))
.with_message(
format!("This has type {}.", collection_type.fg(type_color),),
),
Label::new((source_id, index_position.0..index_position.1))
.with_message(format!("This has type {}.", index_type.fg(type_color),)),
])
}
ValidationError::InterpreterExpectedReturn(_) => todo!(),
ValidationError::ExpectedFunction { .. } => todo!(),
ValidationError::ExpectedValue(_) => todo!(),
ValidationError::PropertyNotFound { .. } => todo!(),
ValidationError::WrongArguments { .. } => todo!(),
}
}
Ok(builder.finish())
}
}
impl From<Rich<'_, char>> for Error { impl From<Rich<'_, char>> for Error {
fn from(error: Rich<'_, char>) -> Self { fn from(error: Rich<'_, char>) -> Self {
Error::Lex { Error::Lex {
@ -299,6 +115,10 @@ pub enum ValidationError {
position: SourcePosition, position: SourcePosition,
}, },
ExpectedIntegerOrFloat(SourcePosition), ExpectedIntegerOrFloat(SourcePosition),
ExpectedIntegerFloatOrString {
actual: Type,
position: SourcePosition,
},
ExpectedValue(SourcePosition), ExpectedValue(SourcePosition),
InterpreterExpectedReturn(SourcePosition), InterpreterExpectedReturn(SourcePosition),
RwLockPoison(RwLockPoisonError), RwLockPoison(RwLockPoisonError),

View File

@ -3,6 +3,7 @@ use std::{
io::{stderr, Write}, io::{stderr, Write},
path::PathBuf, path::PathBuf,
process::Command, process::Command,
rc::Rc,
}; };
use ariadne::sources; use ariadne::sources;
@ -17,6 +18,8 @@ use reedline::{
SqliteBackedHistory, Suggestion, SqliteBackedHistory, Suggestion,
}; };
use crate::error::Error;
pub fn run_shell(context: Context) { pub fn run_shell(context: Context) {
let mut interpreter = Interpreter::new(context.clone()); let mut interpreter = Interpreter::new(context.clone());
let mut keybindings = default_emacs_keybindings(); let mut keybindings = default_emacs_keybindings();
@ -87,11 +90,14 @@ pub fn run_shell(context: Context) {
} }
Ok(None) => {} Ok(None) => {}
Err(errors) => { Err(errors) => {
for error in errors { let source_id = Rc::new("input".to_string());
let report = error.build_report(&"input").unwrap(); let reports = Error::Dust { errors }
.build_reports(source_id.clone(), &buffer)
.unwrap();
for report in reports {
report report
.write_for_stdout(sources([(&"input", buffer.clone())]), stderr()) .write_for_stdout(sources([(source_id.clone(), &buffer)]), stderr())
.unwrap(); .unwrap();
} }
} }

View File

@ -1,12 +1,23 @@
use ariadne::{Color, Fmt, Label, Report, ReportKind}; use ariadne::{Color, Fmt, Label, Report, ReportKind};
use dust_lang::error::{Error as DustError, RuntimeError, TypeConflict, ValidationError}; use clap::error;
use std::{fmt::Debug, io, ops::Range, path::Path, rc::Rc}; use dust_lang::{
abstract_tree::Type,
error::{Error as DustError, RuntimeError, TypeConflict, ValidationError},
};
use std::{
fmt::{self, Debug, Display, Formatter},
io,
ops::Range,
path::Path,
rc::Rc,
};
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
Dust { Dust {
errors: Vec<dust_lang::error::Error>, errors: Vec<dust_lang::error::Error>,
}, },
Io(io::Error),
} }
impl Error { impl Error {
@ -203,6 +214,20 @@ impl Error {
ValidationError::ExpectedValue(_) => todo!(), ValidationError::ExpectedValue(_) => todo!(),
ValidationError::PropertyNotFound { .. } => todo!(), ValidationError::PropertyNotFound { .. } => todo!(),
ValidationError::WrongArguments { .. } => todo!(), ValidationError::WrongArguments { .. } => todo!(),
ValidationError::ExpectedIntegerFloatOrString { actual, position } => {
builder = builder.with_message(format!(
"Expected an {}, {} or {}.",
Type::Integer.fg(type_color),
Type::Float.fg(type_color),
Type::String.fg(type_color)
));
builder.add_labels([Label::new((
source_id.clone(),
position.0..position.1,
))
.with_message(format!("This has type {}.", actual.fg(type_color),))])
}
} }
} }
let report = builder.finish(); let report = builder.finish();

View File

@ -1 +1,6 @@
io.write_line("Hello, world!") io.write_line("Hello, world!")
io.write_line("Enter your name...")
message = io.read_line()
io.write_line("Hello " + message + "!")