From 13c95dd12fae8856d846ba29d197be9b2083f4ea Mon Sep 17 00:00:00 2001 From: Jeff Date: Sat, 23 Mar 2024 17:51:40 -0400 Subject: [PATCH] Improve errors --- dust-lang/src/abstract_tree/function_call.rs | 4 + dust-lang/src/abstract_tree/math.rs | 53 +++++- dust-lang/src/error.rs | 188 +------------------ dust-shell/src/cli.rs | 12 +- dust-shell/src/error.rs | 29 ++- examples/hello_world.ds | 5 + 6 files changed, 95 insertions(+), 196 deletions(-) diff --git a/dust-lang/src/abstract_tree/function_call.rs b/dust-lang/src/abstract_tree/function_call.rs index 8cce27f..1416c2f 100644 --- a/dust-lang/src/abstract_tree/function_call.rs +++ b/dust-lang/src/abstract_tree/function_call.rs @@ -39,6 +39,10 @@ impl AbstractNode for FunctionCall { } 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)?; if let Type::Function { .. } = function_node_type { diff --git a/dust-lang/src/abstract_tree/math.rs b/dust-lang/src/abstract_tree/math.rs index 506f6b5..211662c 100644 --- a/dust-lang/src/abstract_tree/math.rs +++ b/dust-lang/src/abstract_tree/math.rs @@ -19,18 +19,50 @@ pub enum Math { impl AbstractNode for Math { fn expected_type(&self, _context: &Context) -> Result { match self { - Math::Add(left, _) - | Math::Subtract(left, _) - | Math::Multiply(left, _) - | Math::Divide(left, _) - | Math::Modulo(left, _) => left.node.expected_type(_context), + Math::Add(left, right) + | Math::Subtract(left, right) + | Math::Multiply(left, right) + | Math::Divide(left, right) + | 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> { match self { - Math::Add(left, right) - | Math::Subtract(left, right) + Math::Add(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::Divide(left, right) | Math::Modulo(left, right) => { @@ -91,6 +123,13 @@ impl AbstractNode for Math { 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(_), _) => { return Err(RuntimeError::ValidationFailure( ValidationError::ExpectedIntegerOrFloat(right.position), diff --git a/dust-lang/src/error.rs b/dust-lang/src/error.rs index c5baa6e..d5fafc4 100644 --- a/dust-lang/src/error.rs +++ b/dust-lang/src/error.rs @@ -30,190 +30,6 @@ pub enum Error { }, } -impl Error { - pub fn build_report<'id, Id: Debug + Hash + Eq + Clone>( - self, - source_id: Id, - ) -> Result)>, 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> for Error { fn from(error: Rich<'_, char>) -> Self { Error::Lex { @@ -299,6 +115,10 @@ pub enum ValidationError { position: SourcePosition, }, ExpectedIntegerOrFloat(SourcePosition), + ExpectedIntegerFloatOrString { + actual: Type, + position: SourcePosition, + }, ExpectedValue(SourcePosition), InterpreterExpectedReturn(SourcePosition), RwLockPoison(RwLockPoisonError), diff --git a/dust-shell/src/cli.rs b/dust-shell/src/cli.rs index 126ac27..dd11449 100644 --- a/dust-shell/src/cli.rs +++ b/dust-shell/src/cli.rs @@ -3,6 +3,7 @@ use std::{ io::{stderr, Write}, path::PathBuf, process::Command, + rc::Rc, }; use ariadne::sources; @@ -17,6 +18,8 @@ use reedline::{ SqliteBackedHistory, Suggestion, }; +use crate::error::Error; + pub fn run_shell(context: Context) { let mut interpreter = Interpreter::new(context.clone()); let mut keybindings = default_emacs_keybindings(); @@ -87,11 +90,14 @@ pub fn run_shell(context: Context) { } Ok(None) => {} Err(errors) => { - for error in errors { - let report = error.build_report(&"input").unwrap(); + let source_id = Rc::new("input".to_string()); + let reports = Error::Dust { errors } + .build_reports(source_id.clone(), &buffer) + .unwrap(); + for report in reports { report - .write_for_stdout(sources([(&"input", buffer.clone())]), stderr()) + .write_for_stdout(sources([(source_id.clone(), &buffer)]), stderr()) .unwrap(); } } diff --git a/dust-shell/src/error.rs b/dust-shell/src/error.rs index 3601a59..eb3447b 100644 --- a/dust-shell/src/error.rs +++ b/dust-shell/src/error.rs @@ -1,12 +1,23 @@ 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}; +use clap::error; +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)] pub enum Error { Dust { errors: Vec, }, + Io(io::Error), } impl Error { @@ -203,6 +214,20 @@ impl Error { ValidationError::ExpectedValue(_) => todo!(), ValidationError::PropertyNotFound { .. } => 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(); diff --git a/examples/hello_world.ds b/examples/hello_world.ds index c07ac03..a61968b 100644 --- a/examples/hello_world.ds +++ b/examples/hello_world.ds @@ -1 +1,6 @@ io.write_line("Hello, world!") +io.write_line("Enter your name...") + +message = io.read_line() + +io.write_line("Hello " + message + "!")