From 77134e5292bdc4f8ba107f3ed548a52e70352297 Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 8 Aug 2024 21:47:49 -0400 Subject: [PATCH] Begin adding fancy errors --- Cargo.lock | 17 +++++++++++ dust-lang/Cargo.toml | 1 + dust-lang/src/analyzer.rs | 58 +++++++++++++++++++++++-------------- dust-lang/src/dust_error.rs | 48 +++++++++++++++++++++++++++++- dust-lang/src/lib.rs | 1 + dust-lang/src/parse.rs | 40 +++++++++++-------------- dust-lang/src/vm.rs | 8 ++--- dust-shell/src/main.rs | 16 +++++----- rust-toolchain | 1 + 9 files changed, 134 insertions(+), 56 deletions(-) create mode 100644 rust-toolchain diff --git a/Cargo.lock b/Cargo.lock index cb4f104..3f3a953 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "annotate-snippets" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e35ed54e5ea7997c14ed4c70ba043478db1112e98263b3b035907aa197d991" +dependencies = [ + "anstyle", + "unicode-width", +] + [[package]] name = "anstream" version = "0.6.13" @@ -140,6 +150,7 @@ checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" name = "dust-lang" version = "0.5.0" dependencies = [ + "annotate-snippets", "env_logger", "rand", "rayon", @@ -394,6 +405,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + [[package]] name = "utf8parse" version = "0.2.1" diff --git a/dust-lang/Cargo.toml b/dust-lang/Cargo.toml index 9c9675c..5a7f656 100644 --- a/dust-lang/Cargo.toml +++ b/dust-lang/Cargo.toml @@ -9,6 +9,7 @@ readme.workspace = true repository.workspace = true [dependencies] +annotate-snippets = "0.11.4" env_logger = "0.11.3" rand = "0.8.5" rayon = "1.9.0" diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index 5c7d922..ace66ad 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -85,11 +85,13 @@ impl<'a> Analyzer<'a> { (Some(Type::Integer), _) | (Some(Type::Float), _) | (Some(Type::String), _) => { return Err(AnalyzerError::ExpectedIntegerFloatOrString { actual: right.as_ref().clone(), + position: right.position, }); } _ => { return Err(AnalyzerError::ExpectedIntegerFloatOrString { actual: left.as_ref().clone(), + position: left.position, }); } } @@ -103,6 +105,7 @@ impl<'a> Analyzer<'a> { } else { return Err(AnalyzerError::ExpectedIdentifier { actual: left.as_ref().clone(), + position: left.position, }); } @@ -116,12 +119,14 @@ impl<'a> Analyzer<'a> { } else { return Err(AnalyzerError::ExpectedIdentifier { actual: function.as_ref().clone(), + position: function.position, }); } } Statement::Identifier(_) => { return Err(AnalyzerError::UnexpectedIdentifier { identifier: node.clone(), + position: node.position, }); } Statement::List(statements) => { @@ -136,6 +141,7 @@ impl<'a> Analyzer<'a> { } else { return Err(AnalyzerError::ExpectedIntegerOrFloat { actual: left.as_ref().clone(), + position: left.position, }); } @@ -145,6 +151,7 @@ impl<'a> Analyzer<'a> { } else { return Err(AnalyzerError::ExpectedIntegerOrFloat { actual: right.as_ref().clone(), + position: right.position, }); } @@ -159,6 +166,7 @@ impl<'a> Analyzer<'a> { } else { return Err(AnalyzerError::ExpectedIdentifierOrValue { actual: left.as_ref().clone(), + position: left.position, }); } @@ -168,6 +176,7 @@ impl<'a> Analyzer<'a> { } else { return Err(AnalyzerError::ExpectedIntegerOrFloat { actual: left.as_ref().clone(), + position: left.position, }); } } @@ -183,13 +192,13 @@ impl<'a> Analyzer<'a> { #[derive(Clone, Debug, PartialEq)] pub enum AnalyzerError { - ExpectedBoolean { actual: Node }, - ExpectedFunction { actual: Node }, - ExpectedIdentifier { actual: Node }, - ExpectedIdentifierOrValue { actual: Node }, - ExpectedIntegerOrFloat { actual: Node }, - ExpectedIntegerFloatOrString { actual: Node }, - UnexpectedIdentifier { identifier: Node }, + ExpectedBoolean { actual: Node, position: Span }, + ExpectedFunction { actual: Node, position: Span }, + ExpectedIdentifier { actual: Node, position: Span }, + ExpectedIdentifierOrValue { actual: Node, position: Span }, + ExpectedIntegerOrFloat { actual: Node, position: Span }, + ExpectedIntegerFloatOrString { actual: Node, position: Span }, + UnexpectedIdentifier { identifier: Node, position: Span }, } impl Error for AnalyzerError {} @@ -197,25 +206,25 @@ impl Error for AnalyzerError {} impl Display for AnalyzerError { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - AnalyzerError::ExpectedBoolean { actual } => { + AnalyzerError::ExpectedBoolean { actual, .. } => { write!(f, "Expected boolean, found {}", actual) } - AnalyzerError::ExpectedFunction { actual } => { + AnalyzerError::ExpectedFunction { actual, .. } => { write!(f, "Expected function, found {}", actual) } - AnalyzerError::ExpectedIdentifier { actual } => { + AnalyzerError::ExpectedIdentifier { actual, .. } => { write!(f, "Expected identifier, found {}", actual) } - AnalyzerError::ExpectedIdentifierOrValue { actual } => { + AnalyzerError::ExpectedIdentifierOrValue { actual, .. } => { write!(f, "Expected identifier or value, found {}", actual) } - AnalyzerError::ExpectedIntegerOrFloat { actual } => { + AnalyzerError::ExpectedIntegerOrFloat { actual, .. } => { write!(f, "Expected integer or float, found {}", actual) } - AnalyzerError::ExpectedIntegerFloatOrString { actual } => { + AnalyzerError::ExpectedIntegerFloatOrString { actual, .. } => { write!(f, "Expected integer, float, or string, found {}", actual) } - AnalyzerError::UnexpectedIdentifier { identifier } => { + AnalyzerError::UnexpectedIdentifier { identifier, .. } => { write!(f, "Unexpected identifier {}", identifier) } } @@ -246,7 +255,8 @@ mod tests { assert_eq!( analyzer.analyze(), Err(AnalyzerError::ExpectedIntegerFloatOrString { - actual: Node::new(Statement::Constant(Value::float(1.0)), (1, 2)) + actual: Node::new(Statement::Constant(Value::float(1.0)), (1, 2)), + position: (1, 2) }) ) } @@ -269,7 +279,8 @@ mod tests { assert_eq!( analyzer.analyze(), Err(AnalyzerError::ExpectedIntegerFloatOrString { - actual: Node::new(Statement::Constant(Value::boolean(true)), (0, 1)) + actual: Node::new(Statement::Constant(Value::boolean(true)), (0, 1)), + position: (0, 1) }) ) } @@ -299,7 +310,8 @@ mod tests { assert_eq!( analyzer.analyze(), Err(AnalyzerError::ExpectedIntegerOrFloat { - actual: Node::new(Statement::Constant(Value::boolean(true)), (0, 1)) + actual: Node::new(Statement::Constant(Value::boolean(true)), (0, 1)), + position: (0, 1) }) ) } @@ -328,7 +340,8 @@ mod tests { assert_eq!( analyzer.analyze(), Err(AnalyzerError::ExpectedIntegerOrFloat { - actual: Node::new(Statement::Constant(Value::boolean(true)), (0, 1)) + actual: Node::new(Statement::Constant(Value::boolean(true)), (0, 1)), + position: (0, 1) }) ) } @@ -354,7 +367,8 @@ mod tests { assert_eq!( analyzer.analyze(), Err(AnalyzerError::ExpectedIntegerOrFloat { - actual: Node::new(Statement::Constant(Value::boolean(false)), (1, 2)) + actual: Node::new(Statement::Constant(Value::boolean(false)), (1, 2)), + position: (1, 2) }) ) } @@ -377,7 +391,8 @@ mod tests { assert_eq!( analyzer.analyze(), Err(AnalyzerError::ExpectedIdentifier { - actual: Node::new(Statement::Constant(Value::integer(1)), (0, 1)) + actual: Node::new(Statement::Constant(Value::integer(1)), (0, 1)), + position: (0, 1) }) ) } @@ -397,7 +412,8 @@ mod tests { assert_eq!( analyzer.analyze(), Err(AnalyzerError::UnexpectedIdentifier { - identifier: Node::new(Statement::Identifier(Identifier::new("x")), (0, 1)) + identifier: Node::new(Statement::Identifier(Identifier::new("x")), (0, 1)), + position: (0, 1) }) ) } diff --git a/dust-lang/src/dust_error.rs b/dust-lang/src/dust_error.rs index 955f831..b435e4d 100644 --- a/dust-lang/src/dust_error.rs +++ b/dust-lang/src/dust_error.rs @@ -1,6 +1,7 @@ +use annotate_snippets::{Level, Renderer, Snippet}; use std::{error::Error, fmt::Display}; -use crate::VmError; +use crate::{AnalyzerError, VmError}; #[derive(Debug, Clone, PartialEq)] pub struct DustError<'src> { @@ -8,6 +9,51 @@ pub struct DustError<'src> { source: &'src str, } +impl<'src> DustError<'src> { + pub fn new(vm_error: VmError, source: &'src str) -> Self { + Self { vm_error, source } + } + + pub fn report(&self) -> String { + let title = match &self.vm_error { + VmError::AnaylyzerError(_) => "Analyzer error", + VmError::ParseError(_) => "Parse error", + VmError::ValueError(_) => "Value error", + VmError::BuiltInFunctionCallError(_) => "Runtime error", + _ => "Analysis Failure", + }; + let span = match &self.vm_error { + VmError::AnaylyzerError(analyzer_error) => match analyzer_error { + AnalyzerError::ExpectedBoolean { position, .. } => position, + AnalyzerError::ExpectedFunction { position, .. } => position, + AnalyzerError::ExpectedIdentifier { position, .. } => position, + AnalyzerError::ExpectedIdentifierOrValue { position, .. } => position, + AnalyzerError::ExpectedIntegerOrFloat { position, .. } => position, + AnalyzerError::ExpectedIntegerFloatOrString { position, .. } => position, + AnalyzerError::UnexpectedIdentifier { position, .. } => position, + }, + VmError::ParseError(_) => todo!(), + VmError::ValueError(_) => todo!(), + VmError::BuiltInFunctionCallError(_) => todo!(), + VmError::ExpectedIdentifier { position } => position, + VmError::ExpectedIdentifierOrInteger { position } => position, + VmError::ExpectedInteger { position } => position, + VmError::ExpectedFunction { position, .. } => position, + VmError::ExpectedList { position } => position, + VmError::ExpectedValue { position } => position, + }; + let label = self.vm_error.to_string(); + + let message = Level::Error.title(title).snippet( + Snippet::source(self.source).annotation(Level::Info.span(span.0..span.1).label(&label)), + ); + + let renderer = Renderer::styled(); + + format!("{}", renderer.render(message)) + } +} + impl Error for DustError<'_> { fn source(&self) -> Option<&(dyn Error + 'static)> { Some(&self.vm_error) diff --git a/dust-lang/src/lib.rs b/dust-lang/src/lib.rs index 29cd981..093a186 100644 --- a/dust-lang/src/lib.rs +++ b/dust-lang/src/lib.rs @@ -20,6 +20,7 @@ pub mod vm; pub use abstract_tree::{AbstractSyntaxTree, Node, Statement}; pub use analyzer::{analyze, Analyzer, AnalyzerError}; pub use built_in_function::{BuiltInFunction, BuiltInFunctionError}; +pub use dust_error::DustError; pub use identifier::Identifier; pub use lex::{lex, LexError, Lexer}; pub use parse::{parse, ParseError, Parser}; diff --git a/dust-lang/src/parse.rs b/dust-lang/src/parse.rs index e520651..d204179 100644 --- a/dust-lang/src/parse.rs +++ b/dust-lang/src/parse.rs @@ -235,7 +235,7 @@ impl<'src> Parser<'src> { } else { Err(ParseError::ExpectedClosingParenthesis { actual: self.current.0.to_owned(), - span: self.current.1, + position: self.current.1, }) } } @@ -265,7 +265,7 @@ impl<'src> Parser<'src> { } else { return Err(ParseError::ExpectedClosingSquareBrace { actual: self.current.0.to_owned(), - span: self.current.1, + position: self.current.1, }); } } @@ -290,7 +290,7 @@ impl<'src> Parser<'src> { } else { return Err(ParseError::ExpectedOpeningParenthesis { actual: self.current.0.to_owned(), - span: self.current.1, + position: self.current.1, }); } @@ -316,7 +316,7 @@ impl<'src> Parser<'src> { } else { return Err(ParseError::ExpectedClosingParenthesis { actual: self.current.0.to_owned(), - span: self.current.1, + position: self.current.1, }); } } @@ -349,9 +349,9 @@ impl<'src> Parser<'src> { pub enum ParseError { LexError(LexError), - ExpectedClosingParenthesis { actual: TokenOwned, span: Span }, - ExpectedClosingSquareBrace { actual: TokenOwned, span: Span }, - ExpectedOpeningParenthesis { actual: TokenOwned, span: Span }, + ExpectedClosingParenthesis { actual: TokenOwned, position: Span }, + ExpectedClosingSquareBrace { actual: TokenOwned, position: Span }, + ExpectedOpeningParenthesis { actual: TokenOwned, position: Span }, UnexpectedToken(TokenOwned), } @@ -368,22 +368,16 @@ impl Display for ParseError { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::LexError(error) => write!(f, "{}", error), - Self::ExpectedClosingParenthesis { actual, span } => write!( - f, - "Expected closing parenthesis, found {} at {:?}", - actual, span - ), - Self::ExpectedClosingSquareBrace { actual, span } => write!( - f, - "Expected closing square brace, found {:?} at {:?}", - actual, span - ), - Self::ExpectedOpeningParenthesis { actual, span } => write!( - f, - "Expected opening parenthesis, found {:?} at {:?}", - actual, span - ), - Self::UnexpectedToken(actual) => write!(f, "Unexpected token {:?}", actual), + Self::ExpectedClosingParenthesis { actual, .. } => { + write!(f, "Expected closing parenthesis, found {actual}",) + } + Self::ExpectedClosingSquareBrace { actual, .. } => { + write!(f, "Expected closing square brace, found {actual}",) + } + Self::ExpectedOpeningParenthesis { actual, .. } => { + write!(f, "Expected opening parenthesis, found {actual}",) + } + Self::UnexpectedToken(actual) => write!(f, "Unexpected token {actual}"), } } } diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index df7a724..45dcacc 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -250,7 +250,7 @@ pub enum VmError { // Anaylsis Failures // These should be prevented by running the analyzer before the VM - BuiltInFunctionCallFailed(BuiltInFunctionError), + BuiltInFunctionCallError(BuiltInFunctionError), ExpectedIdentifier { position: Span }, ExpectedIdentifierOrInteger { position: Span }, ExpectedInteger { position: Span }, @@ -261,7 +261,7 @@ pub enum VmError { impl From for VmError { fn from(v: BuiltInFunctionError) -> Self { - Self::BuiltInFunctionCallFailed(v) + Self::BuiltInFunctionCallError(v) } } @@ -289,7 +289,7 @@ impl Error for VmError { Self::AnaylyzerError(analyzer_error) => Some(analyzer_error), Self::ParseError(parse_error) => Some(parse_error), Self::ValueError(value_error) => Some(value_error), - Self::BuiltInFunctionCallFailed(built_in_function_error) => { + Self::BuiltInFunctionCallError(built_in_function_error) => { Some(built_in_function_error) } _ => None, @@ -303,7 +303,7 @@ impl Display for VmError { Self::AnaylyzerError(analyzer_error) => write!(f, "{}", analyzer_error), Self::ParseError(parse_error) => write!(f, "{}", parse_error), Self::ValueError(value_error) => write!(f, "{}", value_error), - Self::BuiltInFunctionCallFailed(built_in_function_error) => { + Self::BuiltInFunctionCallError(built_in_function_error) => { write!(f, "{}", built_in_function_error) } Self::ExpectedFunction { actual, position } => { diff --git a/dust-shell/src/main.rs b/dust-shell/src/main.rs index 66db18f..80ecf4e 100644 --- a/dust-shell/src/main.rs +++ b/dust-shell/src/main.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, fs::read_to_string}; use clap::Parser; -use dust_lang::run; +use dust_lang::{run, DustError, Identifier, Value}; #[derive(Parser)] struct Cli { @@ -15,22 +15,24 @@ fn main() { let args = Cli::parse(); let mut variables = HashMap::new(); - let result = if let Some(command) = &args.command { - run(command, &mut variables) + if let Some(command) = &args.command { + run_and_display_errors(command, &mut variables); } else if let Some(path) = &args.path { - let content = read_to_string(path).unwrap(); + let source = read_to_string(path).expect("Failed to read file"); - run(&content, &mut variables) + run_and_display_errors(&source, &mut variables) } else { panic!("No command or path provided"); }; +} - match result { +fn run_and_display_errors(source: &str, variables: &mut HashMap) { + match run(source, variables) { Ok(return_value) => { if let Some(value) = return_value { println!("{}", value); } } - Err(error) => eprintln!("{}", error), + Err(error) => eprintln!("{}", DustError::new(error, source).report()), } } diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..bf867e0 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +nightly