Begin adding fancy errors

This commit is contained in:
Jeff 2024-08-08 21:47:49 -04:00
parent 4805a53269
commit 77134e5292
9 changed files with 134 additions and 56 deletions

17
Cargo.lock generated
View File

@ -11,6 +11,16 @@ dependencies = [
"memchr", "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]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.13" version = "0.6.13"
@ -140,6 +150,7 @@ checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
name = "dust-lang" name = "dust-lang"
version = "0.5.0" version = "0.5.0"
dependencies = [ dependencies = [
"annotate-snippets",
"env_logger", "env_logger",
"rand", "rand",
"rayon", "rayon",
@ -394,6 +405,12 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-width"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.1" version = "0.2.1"

View File

@ -9,6 +9,7 @@ readme.workspace = true
repository.workspace = true repository.workspace = true
[dependencies] [dependencies]
annotate-snippets = "0.11.4"
env_logger = "0.11.3" env_logger = "0.11.3"
rand = "0.8.5" rand = "0.8.5"
rayon = "1.9.0" rayon = "1.9.0"

View File

@ -85,11 +85,13 @@ impl<'a> Analyzer<'a> {
(Some(Type::Integer), _) | (Some(Type::Float), _) | (Some(Type::String), _) => { (Some(Type::Integer), _) | (Some(Type::Float), _) | (Some(Type::String), _) => {
return Err(AnalyzerError::ExpectedIntegerFloatOrString { return Err(AnalyzerError::ExpectedIntegerFloatOrString {
actual: right.as_ref().clone(), actual: right.as_ref().clone(),
position: right.position,
}); });
} }
_ => { _ => {
return Err(AnalyzerError::ExpectedIntegerFloatOrString { return Err(AnalyzerError::ExpectedIntegerFloatOrString {
actual: left.as_ref().clone(), actual: left.as_ref().clone(),
position: left.position,
}); });
} }
} }
@ -103,6 +105,7 @@ impl<'a> Analyzer<'a> {
} else { } else {
return Err(AnalyzerError::ExpectedIdentifier { return Err(AnalyzerError::ExpectedIdentifier {
actual: left.as_ref().clone(), actual: left.as_ref().clone(),
position: left.position,
}); });
} }
@ -116,12 +119,14 @@ impl<'a> Analyzer<'a> {
} else { } else {
return Err(AnalyzerError::ExpectedIdentifier { return Err(AnalyzerError::ExpectedIdentifier {
actual: function.as_ref().clone(), actual: function.as_ref().clone(),
position: function.position,
}); });
} }
} }
Statement::Identifier(_) => { Statement::Identifier(_) => {
return Err(AnalyzerError::UnexpectedIdentifier { return Err(AnalyzerError::UnexpectedIdentifier {
identifier: node.clone(), identifier: node.clone(),
position: node.position,
}); });
} }
Statement::List(statements) => { Statement::List(statements) => {
@ -136,6 +141,7 @@ impl<'a> Analyzer<'a> {
} else { } else {
return Err(AnalyzerError::ExpectedIntegerOrFloat { return Err(AnalyzerError::ExpectedIntegerOrFloat {
actual: left.as_ref().clone(), actual: left.as_ref().clone(),
position: left.position,
}); });
} }
@ -145,6 +151,7 @@ impl<'a> Analyzer<'a> {
} else { } else {
return Err(AnalyzerError::ExpectedIntegerOrFloat { return Err(AnalyzerError::ExpectedIntegerOrFloat {
actual: right.as_ref().clone(), actual: right.as_ref().clone(),
position: right.position,
}); });
} }
@ -159,6 +166,7 @@ impl<'a> Analyzer<'a> {
} else { } else {
return Err(AnalyzerError::ExpectedIdentifierOrValue { return Err(AnalyzerError::ExpectedIdentifierOrValue {
actual: left.as_ref().clone(), actual: left.as_ref().clone(),
position: left.position,
}); });
} }
@ -168,6 +176,7 @@ impl<'a> Analyzer<'a> {
} else { } else {
return Err(AnalyzerError::ExpectedIntegerOrFloat { return Err(AnalyzerError::ExpectedIntegerOrFloat {
actual: left.as_ref().clone(), actual: left.as_ref().clone(),
position: left.position,
}); });
} }
} }
@ -183,13 +192,13 @@ impl<'a> Analyzer<'a> {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum AnalyzerError { pub enum AnalyzerError {
ExpectedBoolean { actual: Node }, ExpectedBoolean { actual: Node, position: Span },
ExpectedFunction { actual: Node }, ExpectedFunction { actual: Node, position: Span },
ExpectedIdentifier { actual: Node }, ExpectedIdentifier { actual: Node, position: Span },
ExpectedIdentifierOrValue { actual: Node }, ExpectedIdentifierOrValue { actual: Node, position: Span },
ExpectedIntegerOrFloat { actual: Node }, ExpectedIntegerOrFloat { actual: Node, position: Span },
ExpectedIntegerFloatOrString { actual: Node }, ExpectedIntegerFloatOrString { actual: Node, position: Span },
UnexpectedIdentifier { identifier: Node }, UnexpectedIdentifier { identifier: Node, position: Span },
} }
impl Error for AnalyzerError {} impl Error for AnalyzerError {}
@ -197,25 +206,25 @@ impl Error for AnalyzerError {}
impl Display for AnalyzerError { impl Display for AnalyzerError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
AnalyzerError::ExpectedBoolean { actual } => { AnalyzerError::ExpectedBoolean { actual, .. } => {
write!(f, "Expected boolean, found {}", actual) write!(f, "Expected boolean, found {}", actual)
} }
AnalyzerError::ExpectedFunction { actual } => { AnalyzerError::ExpectedFunction { actual, .. } => {
write!(f, "Expected function, found {}", actual) write!(f, "Expected function, found {}", actual)
} }
AnalyzerError::ExpectedIdentifier { actual } => { AnalyzerError::ExpectedIdentifier { actual, .. } => {
write!(f, "Expected identifier, found {}", actual) write!(f, "Expected identifier, found {}", actual)
} }
AnalyzerError::ExpectedIdentifierOrValue { actual } => { AnalyzerError::ExpectedIdentifierOrValue { actual, .. } => {
write!(f, "Expected identifier or value, found {}", actual) write!(f, "Expected identifier or value, found {}", actual)
} }
AnalyzerError::ExpectedIntegerOrFloat { actual } => { AnalyzerError::ExpectedIntegerOrFloat { actual, .. } => {
write!(f, "Expected integer or float, found {}", actual) write!(f, "Expected integer or float, found {}", actual)
} }
AnalyzerError::ExpectedIntegerFloatOrString { actual } => { AnalyzerError::ExpectedIntegerFloatOrString { actual, .. } => {
write!(f, "Expected integer, float, or string, found {}", actual) write!(f, "Expected integer, float, or string, found {}", actual)
} }
AnalyzerError::UnexpectedIdentifier { identifier } => { AnalyzerError::UnexpectedIdentifier { identifier, .. } => {
write!(f, "Unexpected identifier {}", identifier) write!(f, "Unexpected identifier {}", identifier)
} }
} }
@ -246,7 +255,8 @@ mod tests {
assert_eq!( assert_eq!(
analyzer.analyze(), analyzer.analyze(),
Err(AnalyzerError::ExpectedIntegerFloatOrString { 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!( assert_eq!(
analyzer.analyze(), analyzer.analyze(),
Err(AnalyzerError::ExpectedIntegerFloatOrString { 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!( assert_eq!(
analyzer.analyze(), analyzer.analyze(),
Err(AnalyzerError::ExpectedIntegerOrFloat { 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!( assert_eq!(
analyzer.analyze(), analyzer.analyze(),
Err(AnalyzerError::ExpectedIntegerOrFloat { 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!( assert_eq!(
analyzer.analyze(), analyzer.analyze(),
Err(AnalyzerError::ExpectedIntegerOrFloat { 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!( assert_eq!(
analyzer.analyze(), analyzer.analyze(),
Err(AnalyzerError::ExpectedIdentifier { 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!( assert_eq!(
analyzer.analyze(), analyzer.analyze(),
Err(AnalyzerError::UnexpectedIdentifier { 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)
}) })
) )
} }

View File

@ -1,6 +1,7 @@
use annotate_snippets::{Level, Renderer, Snippet};
use std::{error::Error, fmt::Display}; use std::{error::Error, fmt::Display};
use crate::VmError; use crate::{AnalyzerError, VmError};
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct DustError<'src> { pub struct DustError<'src> {
@ -8,6 +9,51 @@ pub struct DustError<'src> {
source: &'src str, 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<'_> { impl Error for DustError<'_> {
fn source(&self) -> Option<&(dyn Error + 'static)> { fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.vm_error) Some(&self.vm_error)

View File

@ -20,6 +20,7 @@ pub mod vm;
pub use abstract_tree::{AbstractSyntaxTree, Node, Statement}; pub use abstract_tree::{AbstractSyntaxTree, Node, Statement};
pub use analyzer::{analyze, Analyzer, AnalyzerError}; pub use analyzer::{analyze, Analyzer, AnalyzerError};
pub use built_in_function::{BuiltInFunction, BuiltInFunctionError}; pub use built_in_function::{BuiltInFunction, BuiltInFunctionError};
pub use dust_error::DustError;
pub use identifier::Identifier; pub use identifier::Identifier;
pub use lex::{lex, LexError, Lexer}; pub use lex::{lex, LexError, Lexer};
pub use parse::{parse, ParseError, Parser}; pub use parse::{parse, ParseError, Parser};

View File

@ -235,7 +235,7 @@ impl<'src> Parser<'src> {
} else { } else {
Err(ParseError::ExpectedClosingParenthesis { Err(ParseError::ExpectedClosingParenthesis {
actual: self.current.0.to_owned(), actual: self.current.0.to_owned(),
span: self.current.1, position: self.current.1,
}) })
} }
} }
@ -265,7 +265,7 @@ impl<'src> Parser<'src> {
} else { } else {
return Err(ParseError::ExpectedClosingSquareBrace { return Err(ParseError::ExpectedClosingSquareBrace {
actual: self.current.0.to_owned(), actual: self.current.0.to_owned(),
span: self.current.1, position: self.current.1,
}); });
} }
} }
@ -290,7 +290,7 @@ impl<'src> Parser<'src> {
} else { } else {
return Err(ParseError::ExpectedOpeningParenthesis { return Err(ParseError::ExpectedOpeningParenthesis {
actual: self.current.0.to_owned(), actual: self.current.0.to_owned(),
span: self.current.1, position: self.current.1,
}); });
} }
@ -316,7 +316,7 @@ impl<'src> Parser<'src> {
} else { } else {
return Err(ParseError::ExpectedClosingParenthesis { return Err(ParseError::ExpectedClosingParenthesis {
actual: self.current.0.to_owned(), 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 { pub enum ParseError {
LexError(LexError), LexError(LexError),
ExpectedClosingParenthesis { actual: TokenOwned, span: Span }, ExpectedClosingParenthesis { actual: TokenOwned, position: Span },
ExpectedClosingSquareBrace { actual: TokenOwned, span: Span }, ExpectedClosingSquareBrace { actual: TokenOwned, position: Span },
ExpectedOpeningParenthesis { actual: TokenOwned, span: Span }, ExpectedOpeningParenthesis { actual: TokenOwned, position: Span },
UnexpectedToken(TokenOwned), UnexpectedToken(TokenOwned),
} }
@ -368,22 +368,16 @@ impl Display for ParseError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
Self::LexError(error) => write!(f, "{}", error), Self::LexError(error) => write!(f, "{}", error),
Self::ExpectedClosingParenthesis { actual, span } => write!( Self::ExpectedClosingParenthesis { actual, .. } => {
f, write!(f, "Expected closing parenthesis, found {actual}",)
"Expected closing parenthesis, found {} at {:?}", }
actual, span Self::ExpectedClosingSquareBrace { actual, .. } => {
), write!(f, "Expected closing square brace, found {actual}",)
Self::ExpectedClosingSquareBrace { actual, span } => write!( }
f, Self::ExpectedOpeningParenthesis { actual, .. } => {
"Expected closing square brace, found {:?} at {:?}", write!(f, "Expected opening parenthesis, found {actual}",)
actual, span }
), Self::UnexpectedToken(actual) => write!(f, "Unexpected token {actual}"),
Self::ExpectedOpeningParenthesis { actual, span } => write!(
f,
"Expected opening parenthesis, found {:?} at {:?}",
actual, span
),
Self::UnexpectedToken(actual) => write!(f, "Unexpected token {:?}", actual),
} }
} }
} }

View File

@ -250,7 +250,7 @@ pub enum VmError {
// Anaylsis Failures // Anaylsis Failures
// These should be prevented by running the analyzer before the VM // These should be prevented by running the analyzer before the VM
BuiltInFunctionCallFailed(BuiltInFunctionError), BuiltInFunctionCallError(BuiltInFunctionError),
ExpectedIdentifier { position: Span }, ExpectedIdentifier { position: Span },
ExpectedIdentifierOrInteger { position: Span }, ExpectedIdentifierOrInteger { position: Span },
ExpectedInteger { position: Span }, ExpectedInteger { position: Span },
@ -261,7 +261,7 @@ pub enum VmError {
impl From<BuiltInFunctionError> for VmError { impl From<BuiltInFunctionError> for VmError {
fn from(v: BuiltInFunctionError) -> Self { 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::AnaylyzerError(analyzer_error) => Some(analyzer_error),
Self::ParseError(parse_error) => Some(parse_error), Self::ParseError(parse_error) => Some(parse_error),
Self::ValueError(value_error) => Some(value_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) Some(built_in_function_error)
} }
_ => None, _ => None,
@ -303,7 +303,7 @@ impl Display for VmError {
Self::AnaylyzerError(analyzer_error) => write!(f, "{}", analyzer_error), Self::AnaylyzerError(analyzer_error) => write!(f, "{}", analyzer_error),
Self::ParseError(parse_error) => write!(f, "{}", parse_error), Self::ParseError(parse_error) => write!(f, "{}", parse_error),
Self::ValueError(value_error) => write!(f, "{}", value_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) write!(f, "{}", built_in_function_error)
} }
Self::ExpectedFunction { actual, position } => { Self::ExpectedFunction { actual, position } => {

View File

@ -1,7 +1,7 @@
use std::{collections::HashMap, fs::read_to_string}; use std::{collections::HashMap, fs::read_to_string};
use clap::Parser; use clap::Parser;
use dust_lang::run; use dust_lang::{run, DustError, Identifier, Value};
#[derive(Parser)] #[derive(Parser)]
struct Cli { struct Cli {
@ -15,22 +15,24 @@ fn main() {
let args = Cli::parse(); let args = Cli::parse();
let mut variables = HashMap::new(); let mut variables = HashMap::new();
let result = if let Some(command) = &args.command { if let Some(command) = &args.command {
run(command, &mut variables) run_and_display_errors(command, &mut variables);
} else if let Some(path) = &args.path { } 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 { } else {
panic!("No command or path provided"); panic!("No command or path provided");
}; };
}
match result { fn run_and_display_errors(source: &str, variables: &mut HashMap<Identifier, Value>) {
match run(source, variables) {
Ok(return_value) => { Ok(return_value) => {
if let Some(value) = return_value { if let Some(value) = return_value {
println!("{}", value); println!("{}", value);
} }
} }
Err(error) => eprintln!("{}", error), Err(error) => eprintln!("{}", DustError::new(error, source).report()),
} }
} }

1
rust-toolchain Normal file
View File

@ -0,0 +1 @@
nightly