Refine error reports

This commit is contained in:
Jeff 2024-03-23 17:07:41 -04:00
parent 004b7be27a
commit 7263507e84
11 changed files with 302 additions and 90 deletions

1
Cargo.lock generated
View File

@ -384,6 +384,7 @@ dependencies = [
name = "dust-shell" name = "dust-shell"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ariadne",
"clap", "clap",
"colored", "colored",
"dust-lang", "dust-lang",

View File

@ -144,11 +144,11 @@ impl Display for Type {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
Type::Any => write!(f, "any"), Type::Any => write!(f, "any"),
Type::Boolean => write!(f, "boolean"), Type::Boolean => write!(f, "bool"),
Type::Float => write!(f, "float"), Type::Float => write!(f, "float"),
Type::Integer => write!(f, "integer"), Type::Integer => write!(f, "int"),
Type::List => write!(f, "list"), Type::List => write!(f, "list"),
Type::ListOf(item_type) => write!(f, "list of {item_type}"), Type::ListOf(item_type) => write!(f, "list({item_type})"),
Type::ListExact(item_types) => { Type::ListExact(item_types) => {
write!(f, "[")?; write!(f, "[")?;
@ -165,7 +165,7 @@ impl Display for Type {
Type::Map => write!(f, "map"), Type::Map => write!(f, "map"),
Type::None => write!(f, "none"), Type::None => write!(f, "none"),
Type::Range => write!(f, "range"), Type::Range => write!(f, "range"),
Type::String => write!(f, "string"), Type::String => write!(f, "str"),
Type::Function { Type::Function {
parameter_types, parameter_types,
return_type, return_type,

View File

@ -5,12 +5,10 @@ use std::{
time::Duration, time::Duration,
}; };
use rand::{thread_rng, Rng};
use crate::{ use crate::{
abstract_tree::{Action, Identifier, Type, WithPosition}, abstract_tree::{Action, Type},
context::Context, context::Context,
error::{RuntimeError, ValidationError}, error::RuntimeError,
value::ValueInner, value::ValueInner,
Value, Value,
}; };
@ -18,6 +16,7 @@ use crate::{
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum BuiltInFunction { pub enum BuiltInFunction {
ReadLine, ReadLine,
Sleep,
WriteLine, WriteLine,
} }
@ -25,6 +24,7 @@ impl BuiltInFunction {
pub fn name(&self) -> &'static str { pub fn name(&self) -> &'static str {
match self { match self {
BuiltInFunction::ReadLine => todo!(), BuiltInFunction::ReadLine => todo!(),
BuiltInFunction::Sleep => todo!(),
BuiltInFunction::WriteLine => todo!(), BuiltInFunction::WriteLine => todo!(),
} }
} }
@ -47,32 +47,6 @@ impl BuiltInFunction {
pub fn call(&self, arguments: Vec<Value>, context: &Context) -> Result<Action, RuntimeError> { pub fn call(&self, arguments: Vec<Value>, context: &Context) -> Result<Action, RuntimeError> {
match self { match self {
BuiltInFunction::ReadLine => {
let string = arguments.get(0).unwrap();
if let ValueInner::String(_string) = string.inner().as_ref() {
// let integer = string.parse();
todo!()
// Ok(Action::Return(Value::integer(integer)))
} else {
let mut actual = Vec::with_capacity(arguments.len());
for value in arguments {
let r#type = value.r#type(context)?;
actual.push(r#type);
}
Err(RuntimeError::ValidationFailure(
ValidationError::WrongArguments {
expected: vec![Type::String],
actual,
},
))
}
}
// "INT_RANDOM_RANGE" => { // "INT_RANDOM_RANGE" => {
// let range = arguments.get(0).unwrap(); // let range = arguments.get(0).unwrap();
@ -91,18 +65,18 @@ impl BuiltInFunction {
Ok(Action::Return(Value::string(input))) Ok(Action::Return(Value::string(input)))
} }
BuiltInFunction::Sleep => {
if let ValueInner::Integer(milliseconds) = arguments[0].inner().as_ref() {
thread::sleep(Duration::from_millis(*milliseconds as u64));
}
Ok(Action::None)
}
BuiltInFunction::WriteLine => { BuiltInFunction::WriteLine => {
println!("{}", arguments[0]); println!("{}", arguments[0]);
Ok(Action::None) Ok(Action::None)
} }
// "SLEEP" => {
// if let ValueInner::Integer(milliseconds) = arguments[0].inner().as_ref() {
// thread::sleep(Duration::from_millis(*milliseconds as u64));
// }
// Ok(Action::None)
// }
_ => { _ => {
todo!() todo!()
} }

View File

@ -1,6 +1,6 @@
use std::{io, sync::PoisonError}; use std::{fmt::Debug, hash::Hash, io, ops::Range, sync::PoisonError};
use ariadne::{sources, Color, Fmt, Label, Report, ReportKind}; use ariadne::{Color, Fmt, Label, Report, ReportKind};
use chumsky::{prelude::Rich, span::Span}; use chumsky::{prelude::Rich, span::Span};
use crate::{ use crate::{
@ -31,7 +31,10 @@ pub enum Error {
} }
impl Error { impl Error {
pub fn build_report(self, source: &str) -> Result<Vec<u8>, io::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 { let (mut builder, validation_error, error_position) = match self {
Error::Parse { Error::Parse {
expected, expected,
@ -47,12 +50,12 @@ impl Error {
( (
Report::build( Report::build(
ReportKind::Custom("Parsing Error", Color::Yellow), ReportKind::Custom("Parsing Error", Color::Yellow),
"input", source_id.clone(),
span.1, span.1,
) )
.with_message(description) .with_message(description)
.with_label( .with_label(
Label::new(("input", span.0..span.1)) Label::new((source_id.clone(), span.0..span.1))
.with_message(reason) .with_message(reason)
.with_color(Color::Red), .with_color(Color::Red),
), ),
@ -74,12 +77,12 @@ impl Error {
( (
Report::build( Report::build(
ReportKind::Custom("Lexing Error", Color::Yellow), ReportKind::Custom("Lexing Error", Color::Yellow),
"input", source_id.clone(),
span.1, span.1,
) )
.with_message(description) .with_message(description)
.with_label( .with_label(
Label::new(("input", span.0..span.1)) Label::new((source_id.clone(), span.0..span.1))
.with_message(reason) .with_message(reason)
.with_color(Color::Red), .with_color(Color::Red),
), ),
@ -90,7 +93,7 @@ impl Error {
Error::Runtime { error, position } => ( Error::Runtime { error, position } => (
Report::build( Report::build(
ReportKind::Custom("Runtime Error", Color::Red), ReportKind::Custom("Runtime Error", Color::Red),
"input", source_id.clone(),
position.1, position.1,
) )
.with_message("An error occured that forced the program to exit.") .with_message("An error occured that forced the program to exit.")
@ -110,7 +113,7 @@ impl Error {
Error::Validation { error, position } => ( Error::Validation { error, position } => (
Report::build( Report::build(
ReportKind::Custom("Validation Error", Color::Magenta), ReportKind::Custom("Validation Error", Color::Magenta),
"input", source_id.clone(),
position.1, position.1,
) )
.with_message("The syntax is valid but this code is not sound.") .with_message("The syntax is valid but this code is not sound.")
@ -126,22 +129,22 @@ impl Error {
if let Some(validation_error) = validation_error { if let Some(validation_error) = validation_error {
match validation_error { match validation_error {
ValidationError::ExpectedBoolean { actual, position } => { ValidationError::ExpectedBoolean { actual, position } => {
builder.add_label(Label::new(("input", position.0..position.1)).with_message( builder.add_label(
format!( Label::new((source_id, 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(position) => { ValidationError::ExpectedIntegerOrFloat(position) => {
builder.add_label(Label::new(("input", position.0..position.1)).with_message( builder.add_label(
format!( Label::new((source_id, 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 {
@ -154,15 +157,17 @@ impl Error {
builder = builder.with_message("A type conflict was found."); builder = builder.with_message("A type conflict was found.");
builder.add_labels([ builder.add_labels([
Label::new(("input", expected_postion.0..expected_postion.1)).with_message( Label::new((source_id.clone(), expected_postion.0..expected_postion.1))
format!("Type {} established here.", expected.fg(type_color)), .with_message(format!(
), "Type {} established here.",
Label::new(("input", actual_position.0..actual_position.1)) expected.fg(type_color)
)),
Label::new((source_id, 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) => builder.add_label( ValidationError::VariableNotFound(identifier) => builder.add_label(
Label::new(("input", error_position.0..error_position.1)).with_message( Label::new((source_id, error_position.0..error_position.1)).with_message(
format!( format!(
"Variable {} does not exist in this context.", "Variable {} does not exist in this context.",
identifier.fg(identifier_color) identifier.fg(identifier_color)
@ -170,7 +175,7 @@ impl Error {
), ),
), ),
ValidationError::CannotIndex { r#type, position } => builder.add_label( ValidationError::CannotIndex { r#type, position } => builder.add_label(
Label::new(("input", position.0..position.1)) Label::new((source_id, 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 {
@ -186,12 +191,14 @@ impl Error {
)); ));
builder.add_labels([ builder.add_labels([
Label::new(("input", collection_position.0..collection_position.1)) Label::new((
.with_message(format!( source_id.clone(),
"This has type {}.", collection_position.0..collection_position.1,
collection_type.fg(type_color), ))
)), .with_message(
Label::new(("input", index_position.0..index_position.1)) 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),)), .with_message(format!("This has type {}.", index_type.fg(type_color),)),
]) ])
} }
@ -203,13 +210,7 @@ impl Error {
} }
} }
let mut output = Vec::new(); Ok(builder.finish())
builder
.finish()
.write_for_stdout(sources([("input", source)]), &mut output)?;
Ok(output)
} }
} }

View File

@ -39,6 +39,7 @@ impl<'src> Display for Token<'src> {
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub enum BuiltInIdentifier { pub enum BuiltInIdentifier {
ReadLine, ReadLine,
Sleep,
WriteLine, WriteLine,
} }
@ -46,6 +47,7 @@ impl Display for BuiltInIdentifier {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
BuiltInIdentifier::ReadLine => write!(f, "__READ_LINE__"), BuiltInIdentifier::ReadLine => write!(f, "__READ_LINE__"),
BuiltInIdentifier::Sleep => write!(f, "__SLEEP__"),
BuiltInIdentifier::WriteLine => write!(f, "__WRITE_LINE__"), BuiltInIdentifier::WriteLine => write!(f, "__WRITE_LINE__"),
} }
} }
@ -302,6 +304,7 @@ pub fn lexer<'src>() -> impl Parser<
let built_in_identifier = choice(( let built_in_identifier = choice((
just("__READ_LINE__").to(BuiltInIdentifier::ReadLine), just("__READ_LINE__").to(BuiltInIdentifier::ReadLine),
just("__SLEEP__").to(BuiltInIdentifier::Sleep),
just("__WRITE_LINE__").to(BuiltInIdentifier::WriteLine), just("__WRITE_LINE__").to(BuiltInIdentifier::WriteLine),
)) ))
.map(Token::BuiltInIdentifier); .map(Token::BuiltInIdentifier);

View File

@ -236,10 +236,8 @@ pub fn parser<'src>() -> impl Parser<
let built_in_function = { let built_in_function = {
select! { select! {
Token::BuiltInIdentifier(built_in_identifier) => { Token::BuiltInIdentifier(built_in_identifier) => {
match built_in_identifier { match built_in_identifier {BuiltInIdentifier::ReadLine=>BuiltInFunction::ReadLine,BuiltInIdentifier::WriteLine=>BuiltInFunction::WriteLine,
BuiltInIdentifier::ReadLine => BuiltInFunction::ReadLine, BuiltInIdentifier::Sleep => BuiltInFunction::Sleep, }
BuiltInIdentifier::WriteLine => BuiltInFunction::WriteLine,
}
} }
} }
} }

View File

@ -9,6 +9,7 @@ readme.workspace = true
repository.workspace = true repository.workspace = true
[dependencies] [dependencies]
ariadne = "0.4.0"
clap = { version = "4.5.3", features = ["derive"] } clap = { version = "4.5.3", features = ["derive"] }
colored = "2.1.0" colored = "2.1.0"
dust-lang = { path = "../dust-lang" } dust-lang = { path = "../dust-lang" }

View File

@ -5,6 +5,7 @@ use std::{
process::Command, process::Command,
}; };
use ariadne::sources;
use dust_lang::{ use dust_lang::{
context::{Context, ValueData}, context::{Context, ValueData},
*, *,
@ -87,9 +88,11 @@ pub fn run_shell(context: Context) {
Ok(None) => {} Ok(None) => {}
Err(errors) => { Err(errors) => {
for error in errors { for error in errors {
let report = error.build_report(&buffer).unwrap(); let report = error.build_report(&"input").unwrap();
stderr().write_all(&report).unwrap(); report
.write_for_stdout(sources([(&"input", buffer.clone())]), stderr())
.unwrap();
} }
} }
} }

218
dust-shell/src/error.rs Normal file
View File

@ -0,0 +1,218 @@
use ariadne::{Color, Fmt, Label, Report, ReportKind};
use dust_lang::error::{Error as DustError, RuntimeError, TypeConflict, ValidationError};
use std::{fmt::Debug, io, ops::Range, path::Path, rc::Rc};
#[derive(Debug)]
pub enum Error {
Dust {
errors: Vec<dust_lang::error::Error>,
},
}
impl Error {
pub fn build_reports<'id>(
self,
source_id: Rc<String>,
source: &str,
) -> Result<Vec<Report<'id, (Rc<String>, Range<usize>)>>, io::Error> {
if let Error::Dust { errors } = self {
let mut reports = Vec::new();
for error in errors {
let (mut builder, validation_error, error_position) = match error {
DustError::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(),
)
}
DustError::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(),
)
}
DustError::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,
),
DustError::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.clone(), 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.clone(), 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.clone(),
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.clone(), 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.clone(), 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.clone(), 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!(),
}
}
let report = builder.finish();
reports.push(report);
}
return Ok(reports);
} else {
return Ok(Vec::with_capacity(0));
};
}
}

View File

@ -1,13 +1,18 @@
//! Command line interface for the dust programming language. //! Command line interface for the dust programming language.
mod cli; mod cli;
mod error;
use ariadne::sources;
use clap::Parser; use clap::Parser;
use cli::run_shell; use cli::run_shell;
use colored::Colorize; use colored::Colorize;
use error::Error;
use std::{ use std::{
fs::read_to_string, fs::read_to_string,
io::{stderr, Write}, io::{stderr, Write},
path::Path,
rc::Rc,
}; };
use dust_lang::{context::Context, interpret}; use dust_lang::{context::Context, interpret};
@ -37,11 +42,10 @@ fn main() {
let args = Args::parse(); let args = Args::parse();
let context = Context::new(); let context = Context::new();
let (source, source_id) = if let Some(path) = args.path {
let source = if let Some(path) = &args.path { (read_to_string(&path).unwrap(), Rc::new(path))
read_to_string(path).unwrap()
} else if let Some(command) = args.command { } else if let Some(command) = args.command {
command (command, Rc::new("input".to_string()))
} else { } else {
return run_shell(context); return run_shell(context);
}; };
@ -55,10 +59,14 @@ fn main() {
} }
} }
Err(errors) => { Err(errors) => {
for error in errors { let reports = Error::Dust { errors }
let report = error.build_report(&source).unwrap(); .build_reports(source_id.clone(), &source)
.unwrap();
stderr().write_all(&report).unwrap(); for report in reports {
report
.write_for_stdout(sources([(source_id.clone(), source.clone())]), stderr())
.unwrap();
} }
} }
} }

5
std/thread.ds Normal file
View File

@ -0,0 +1,5 @@
thread = {
sleep = (milliseconds: int) : none {
__SLEEP__()
}
}