Begin adding fancy errors
This commit is contained in:
parent
4805a53269
commit
77134e5292
17
Cargo.lock
generated
17
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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};
|
||||
|
@ -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}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 } => {
|
||||
|
@ -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
1
rust-toolchain
Normal file
@ -0,0 +1 @@
|
||||
nightly
|
Loading…
Reference in New Issue
Block a user