Improve error layout
This commit is contained in:
parent
3b0c74010e
commit
3a2dd28efb
@ -11,8 +11,8 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
abstract_tree::BinaryOperator, AbstractSyntaxTree, BuiltInFunction, Context, Node, Span,
|
||||
Statement, Type,
|
||||
abstract_tree::BinaryOperator, parse, AbstractSyntaxTree, BuiltInFunction, Context, DustError,
|
||||
Node, Span, Statement, Type,
|
||||
};
|
||||
|
||||
/// Analyzes the abstract syntax tree for errors.
|
||||
@ -28,13 +28,16 @@ use crate::{
|
||||
///
|
||||
/// assert!(result.is_err());
|
||||
/// ```
|
||||
pub fn analyze(
|
||||
abstract_tree: &AbstractSyntaxTree,
|
||||
context: &mut Context,
|
||||
) -> Result<(), AnalyzerError> {
|
||||
let mut analyzer = Analyzer::new(abstract_tree, context);
|
||||
pub fn analyze<'src>(source: &'src str, context: &mut Context) -> Result<(), DustError<'src>> {
|
||||
let abstract_tree = parse(source)?;
|
||||
let mut analyzer = Analyzer::new(&abstract_tree, context);
|
||||
|
||||
analyzer.analyze()
|
||||
analyzer
|
||||
.analyze()
|
||||
.map_err(|analyzer_error| DustError::AnalyzerError {
|
||||
analyzer_error,
|
||||
source,
|
||||
})
|
||||
}
|
||||
|
||||
/// Static analyzer that checks for potential runtime errors.
|
||||
@ -448,6 +451,34 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn write_line_wrong_arguments() {
|
||||
let abstract_tree = AbstractSyntaxTree {
|
||||
nodes: [Node::new(
|
||||
Statement::BuiltInFunctionCall {
|
||||
function: BuiltInFunction::WriteLine,
|
||||
type_arguments: None,
|
||||
value_arguments: Some(vec![Node::new(
|
||||
Statement::Constant(Value::integer(1)),
|
||||
(0, 1),
|
||||
)]),
|
||||
},
|
||||
(0, 1),
|
||||
)]
|
||||
.into(),
|
||||
};
|
||||
let mut context = Context::new();
|
||||
let mut analyzer = Analyzer::new(&abstract_tree, &mut context);
|
||||
|
||||
assert_eq!(
|
||||
analyzer.analyze(),
|
||||
Err(AnalyzerError::ExpectedString {
|
||||
actual: Node::new(Statement::Constant(Value::integer(1)), (0, 1)),
|
||||
position: (0, 1)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_plus_integer() {
|
||||
let abstract_tree = AbstractSyntaxTree {
|
||||
|
@ -7,7 +7,7 @@ use std::{
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Type, Value};
|
||||
use crate::{Identifier, Type, Value};
|
||||
|
||||
/// Integrated function that can be called from Dust code.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
@ -39,6 +39,25 @@ impl BuiltInFunction {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value_parameters(&self) -> Vec<(Identifier, Type)> {
|
||||
match self {
|
||||
BuiltInFunction::ToString => vec![("value".into(), Type::Any)],
|
||||
BuiltInFunction::IsEven => vec![("value".into(), Type::Integer)],
|
||||
BuiltInFunction::IsOdd => vec![("value".into(), Type::Integer)],
|
||||
BuiltInFunction::Length => {
|
||||
vec![(
|
||||
"value".into(),
|
||||
Type::List {
|
||||
item_type: Box::new(Type::Any),
|
||||
length: 1,
|
||||
},
|
||||
)]
|
||||
}
|
||||
BuiltInFunction::ReadLine => vec![],
|
||||
BuiltInFunction::WriteLine => vec![("output".into(), Type::Any)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call(
|
||||
&self,
|
||||
_type_arguments: Option<Vec<Type>>,
|
||||
|
@ -1,34 +1,66 @@
|
||||
//! Top-level error handling for the Dust language.
|
||||
use annotate_snippets::{Level, Renderer, Snippet};
|
||||
use std::{error::Error, fmt::Display};
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::VmError;
|
||||
use crate::{AnalyzerError, LexError, ParseError, VmError};
|
||||
|
||||
/// An error that occurred during the execution of the Dust language and its
|
||||
/// corresponding source code.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DustError<'src> {
|
||||
vm_error: VmError,
|
||||
source: &'src str,
|
||||
pub enum DustError<'src> {
|
||||
VmError {
|
||||
vm_error: VmError,
|
||||
source: &'src str,
|
||||
},
|
||||
AnalyzerError {
|
||||
analyzer_error: AnalyzerError,
|
||||
source: &'src str,
|
||||
},
|
||||
ParseError {
|
||||
parse_error: ParseError,
|
||||
source: &'src str,
|
||||
},
|
||||
LexError {
|
||||
lex_error: LexError,
|
||||
source: &'src str,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'src> DustError<'src> {
|
||||
pub fn new(vm_error: VmError, source: &'src str) -> Self {
|
||||
Self { vm_error, source }
|
||||
pub fn title(&self) -> &'static str {
|
||||
match self {
|
||||
DustError::VmError { .. } => "Runtime error",
|
||||
DustError::AnalyzerError { .. } => "Analyzer error",
|
||||
DustError::ParseError { .. } => "Parse error",
|
||||
DustError::LexError { .. } => "Lex error",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position(&self) -> (usize, usize) {
|
||||
match self {
|
||||
DustError::VmError { vm_error, .. } => vm_error.position(),
|
||||
DustError::AnalyzerError { analyzer_error, .. } => analyzer_error.position(),
|
||||
DustError::ParseError { parse_error, .. } => parse_error.position(),
|
||||
DustError::LexError { lex_error, .. } => lex_error.position(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn source(&self) -> &'src str {
|
||||
match self {
|
||||
DustError::VmError { source, .. } => source,
|
||||
DustError::AnalyzerError { source, .. } => source,
|
||||
DustError::ParseError { source, .. } => source,
|
||||
DustError::LexError { source, .. } => 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::BuiltInFunctionError { .. } => "Runtime error",
|
||||
_ => "Analysis Failure",
|
||||
};
|
||||
let span = self.vm_error.position();
|
||||
let label = self.vm_error.to_string();
|
||||
let title = self.title();
|
||||
let span = self.position();
|
||||
let label = self.to_string();
|
||||
let message = Level::Error.title(title).snippet(
|
||||
Snippet::source(self.source).annotation(Level::Info.span(span.0..span.1).label(&label)),
|
||||
Snippet::source(self.source())
|
||||
.annotation(Level::Info.span(span.0..span.1).label(&label)),
|
||||
);
|
||||
let renderer = Renderer::styled();
|
||||
|
||||
@ -36,14 +68,13 @@ impl<'src> DustError<'src> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for DustError<'_> {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
Some(&self.vm_error)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DustError<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}\n{}", self.vm_error, self.source)
|
||||
match self {
|
||||
DustError::VmError { vm_error, .. } => write!(f, "{vm_error}"),
|
||||
DustError::AnalyzerError { analyzer_error, .. } => write!(f, "{analyzer_error}"),
|
||||
DustError::ParseError { parse_error, .. } => write!(f, "{parse_error}"),
|
||||
DustError::LexError { lex_error, .. } => write!(f, "{lex_error}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,8 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
AbstractSyntaxTree, BinaryOperator, BuiltInFunction, Identifier, LexError, Lexer, Node, Span,
|
||||
Statement, Token, TokenOwned, Value,
|
||||
AbstractSyntaxTree, BinaryOperator, BuiltInFunction, DustError, Identifier, LexError, Lexer,
|
||||
Node, Span, Statement, Token, TokenOwned, Value,
|
||||
};
|
||||
|
||||
/// Parses the input into an abstract syntax tree.
|
||||
@ -48,13 +48,18 @@ use crate::{
|
||||
/// },
|
||||
/// );
|
||||
/// ```
|
||||
pub fn parse(input: &str) -> Result<AbstractSyntaxTree, ParseError> {
|
||||
pub fn parse(source: &str) -> Result<AbstractSyntaxTree, DustError> {
|
||||
let lexer = Lexer::new();
|
||||
let mut parser = Parser::new(input, lexer);
|
||||
let mut parser = Parser::new(source, lexer);
|
||||
let mut nodes = VecDeque::new();
|
||||
|
||||
loop {
|
||||
let node = parser.parse()?;
|
||||
let node = parser
|
||||
.parse()
|
||||
.map_err(|parse_error| DustError::ParseError {
|
||||
parse_error,
|
||||
source,
|
||||
})?;
|
||||
|
||||
nodes.push_back(node);
|
||||
|
||||
@ -864,24 +869,27 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
parse(input),
|
||||
Err(ParseError::ExpectedAssignment {
|
||||
actual: Node::new(
|
||||
Statement::Nil(Box::new(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(Node::new(
|
||||
Statement::Identifier(Identifier::new("z")),
|
||||
(16, 17)
|
||||
)),
|
||||
operator: Node::new(BinaryOperator::Assign, (18, 19)),
|
||||
right: Box::new(Node::new(
|
||||
Statement::Constant(Value::integer(3)),
|
||||
(20, 21)
|
||||
)),
|
||||
},
|
||||
(16, 21)
|
||||
))),
|
||||
(16, 24)
|
||||
)
|
||||
Err(DustError::ParseError {
|
||||
source: input,
|
||||
parse_error: ParseError::ExpectedAssignment {
|
||||
actual: Node::new(
|
||||
Statement::Nil(Box::new(Node::new(
|
||||
Statement::BinaryOperation {
|
||||
left: Box::new(Node::new(
|
||||
Statement::Identifier(Identifier::new("z")),
|
||||
(16, 17)
|
||||
)),
|
||||
operator: Node::new(BinaryOperator::Assign, (18, 19)),
|
||||
right: Box::new(Node::new(
|
||||
Statement::Constant(Value::integer(3)),
|
||||
(20, 21)
|
||||
)),
|
||||
},
|
||||
(16, 21)
|
||||
))),
|
||||
(16, 24)
|
||||
),
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
@ -989,9 +997,12 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
parse(input),
|
||||
Err(ParseError::UnexpectedToken {
|
||||
actual: TokenOwned::Semicolon,
|
||||
position: (0, 1)
|
||||
Err(DustError::ParseError {
|
||||
source: input,
|
||||
parse_error: ParseError::UnexpectedToken {
|
||||
actual: TokenOwned::Semicolon,
|
||||
position: (0, 1)
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -1,25 +1,32 @@
|
||||
//! Virtual machine for running the abstract syntax tree.
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
error::Error,
|
||||
fmt::{self, Display, Formatter},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
abstract_tree::BinaryOperator, parse, value::ValueInner, AbstractSyntaxTree, Analyzer,
|
||||
AnalyzerError, BuiltInFunctionError, Context, Node, ParseError, Span, Statement, Value,
|
||||
ValueError,
|
||||
BuiltInFunctionError, Context, DustError, Node, ParseError, Span, Statement, Value, ValueError,
|
||||
};
|
||||
|
||||
pub fn run(input: &str, context: &mut Context) -> Result<Option<Value>, VmError> {
|
||||
let abstract_syntax_tree = parse(input)?;
|
||||
pub fn run<'src>(
|
||||
source: &'src str,
|
||||
context: &mut Context,
|
||||
) -> Result<Option<Value>, DustError<'src>> {
|
||||
let abstract_syntax_tree = parse(source)?;
|
||||
let mut analyzer = Analyzer::new(&abstract_syntax_tree, context);
|
||||
|
||||
analyzer.analyze()?;
|
||||
analyzer
|
||||
.analyze()
|
||||
.map_err(|analyzer_error| DustError::AnalyzerError {
|
||||
analyzer_error,
|
||||
source,
|
||||
})?;
|
||||
|
||||
let mut vm = Vm::new(abstract_syntax_tree);
|
||||
|
||||
vm.run(context)
|
||||
.map_err(|vm_error| DustError::VmError { vm_error, source })
|
||||
}
|
||||
|
||||
pub struct Vm {
|
||||
@ -528,7 +535,6 @@ impl Vm {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum VmError {
|
||||
AnaylyzerError(AnalyzerError),
|
||||
ParseError(ParseError),
|
||||
ValueError {
|
||||
error: ValueError,
|
||||
@ -571,7 +577,6 @@ pub enum VmError {
|
||||
impl VmError {
|
||||
pub fn position(&self) -> Span {
|
||||
match self {
|
||||
Self::AnaylyzerError(analyzer_error) => analyzer_error.position(),
|
||||
Self::ParseError(parse_error) => parse_error.position(),
|
||||
Self::ValueError { position, .. } => *position,
|
||||
Self::BuiltInFunctionError { position, .. } => *position,
|
||||
@ -587,34 +592,15 @@ impl VmError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AnalyzerError> for VmError {
|
||||
fn from(error: AnalyzerError) -> Self {
|
||||
Self::AnaylyzerError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError> for VmError {
|
||||
fn from(error: ParseError) -> Self {
|
||||
Self::ParseError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for VmError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
Self::AnaylyzerError(analyzer_error) => Some(analyzer_error),
|
||||
Self::ParseError(parse_error) => Some(parse_error),
|
||||
Self::ValueError { error, .. } => Some(error),
|
||||
Self::BuiltInFunctionError { error, .. } => Some(error),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for VmError {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::AnaylyzerError(analyzer_error) => write!(f, "{}", analyzer_error),
|
||||
Self::ParseError(parse_error) => write!(f, "{}", parse_error),
|
||||
Self::ValueError { error, .. } => write!(f, "{}", error),
|
||||
Self::BuiltInFunctionError { error, .. } => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::fs::read_to_string;
|
||||
|
||||
use clap::Parser;
|
||||
use dust_lang::{run, Context, DustError};
|
||||
use dust_lang::{run, Context};
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Cli {
|
||||
@ -33,6 +33,6 @@ fn run_and_display_errors(source: &str, variables: &mut Context) {
|
||||
println!("{}", value);
|
||||
}
|
||||
}
|
||||
Err(error) => eprintln!("{}", DustError::new(error, source).report()),
|
||||
Err(error) => eprintln!("{}", error.report()),
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user