1
0

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",
]
[[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"

View File

@ -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"

View File

@ -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)
})
)
}

View File

@ -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)

View File

@ -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};

View File

@ -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}"),
}
}
}

View File

@ -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<BuiltInFunctionError> 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 } => {

View File

@ -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<Identifier, Value>) {
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()),
}
}

1
rust-toolchain Normal file
View File

@ -0,0 +1 @@
nightly