Improve error layout

This commit is contained in:
Jeff 2024-08-11 17:59:52 -04:00
parent 3b0c74010e
commit 3a2dd28efb
6 changed files with 166 additions and 88 deletions

View File

@ -11,8 +11,8 @@ use std::{
}; };
use crate::{ use crate::{
abstract_tree::BinaryOperator, AbstractSyntaxTree, BuiltInFunction, Context, Node, Span, abstract_tree::BinaryOperator, parse, AbstractSyntaxTree, BuiltInFunction, Context, DustError,
Statement, Type, Node, Span, Statement, Type,
}; };
/// Analyzes the abstract syntax tree for errors. /// Analyzes the abstract syntax tree for errors.
@ -28,13 +28,16 @@ use crate::{
/// ///
/// assert!(result.is_err()); /// assert!(result.is_err());
/// ``` /// ```
pub fn analyze( pub fn analyze<'src>(source: &'src str, context: &mut Context) -> Result<(), DustError<'src>> {
abstract_tree: &AbstractSyntaxTree, let abstract_tree = parse(source)?;
context: &mut Context, let mut analyzer = Analyzer::new(&abstract_tree, context);
) -> Result<(), AnalyzerError> {
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. /// Static analyzer that checks for potential runtime errors.
@ -448,6 +451,34 @@ mod tests {
use super::*; 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] #[test]
fn float_plus_integer() { fn float_plus_integer() {
let abstract_tree = AbstractSyntaxTree { let abstract_tree = AbstractSyntaxTree {

View File

@ -7,7 +7,7 @@ use std::{
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{Type, Value}; use crate::{Identifier, Type, Value};
/// Integrated function that can be called from Dust code. /// Integrated function that can be called from Dust code.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] #[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( pub fn call(
&self, &self,
_type_arguments: Option<Vec<Type>>, _type_arguments: Option<Vec<Type>>,

View File

@ -1,34 +1,66 @@
//! Top-level error handling for the Dust language. //! Top-level error handling for the Dust language.
use annotate_snippets::{Level, Renderer, Snippet}; 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 /// An error that occurred during the execution of the Dust language and its
/// corresponding source code. /// corresponding source code.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct DustError<'src> { pub enum DustError<'src> {
vm_error: VmError, VmError {
source: &'src str, 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> { impl<'src> DustError<'src> {
pub fn new(vm_error: VmError, source: &'src str) -> Self { pub fn title(&self) -> &'static str {
Self { vm_error, source } 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 { pub fn report(&self) -> String {
let title = match &self.vm_error { let title = self.title();
VmError::AnaylyzerError(_) => "Analyzer error", let span = self.position();
VmError::ParseError(_) => "Parse error", let label = self.to_string();
VmError::ValueError { .. } => "Value error",
VmError::BuiltInFunctionError { .. } => "Runtime error",
_ => "Analysis Failure",
};
let span = self.vm_error.position();
let label = self.vm_error.to_string();
let message = Level::Error.title(title).snippet( 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(); 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<'_> { impl Display for DustError<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 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}"),
}
} }
} }

View File

@ -12,8 +12,8 @@ use std::{
}; };
use crate::{ use crate::{
AbstractSyntaxTree, BinaryOperator, BuiltInFunction, Identifier, LexError, Lexer, Node, Span, AbstractSyntaxTree, BinaryOperator, BuiltInFunction, DustError, Identifier, LexError, Lexer,
Statement, Token, TokenOwned, Value, Node, Span, Statement, Token, TokenOwned, Value,
}; };
/// Parses the input into an abstract syntax tree. /// 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 lexer = Lexer::new();
let mut parser = Parser::new(input, lexer); let mut parser = Parser::new(source, lexer);
let mut nodes = VecDeque::new(); let mut nodes = VecDeque::new();
loop { loop {
let node = parser.parse()?; let node = parser
.parse()
.map_err(|parse_error| DustError::ParseError {
parse_error,
source,
})?;
nodes.push_back(node); nodes.push_back(node);
@ -864,24 +869,27 @@ mod tests {
assert_eq!( assert_eq!(
parse(input), parse(input),
Err(ParseError::ExpectedAssignment { Err(DustError::ParseError {
actual: Node::new( source: input,
Statement::Nil(Box::new(Node::new( parse_error: ParseError::ExpectedAssignment {
Statement::BinaryOperation { actual: Node::new(
left: Box::new(Node::new( Statement::Nil(Box::new(Node::new(
Statement::Identifier(Identifier::new("z")), Statement::BinaryOperation {
(16, 17) left: Box::new(Node::new(
)), Statement::Identifier(Identifier::new("z")),
operator: Node::new(BinaryOperator::Assign, (18, 19)), (16, 17)
right: Box::new(Node::new( )),
Statement::Constant(Value::integer(3)), operator: Node::new(BinaryOperator::Assign, (18, 19)),
(20, 21) right: Box::new(Node::new(
)), Statement::Constant(Value::integer(3)),
}, (20, 21)
(16, 21) )),
))), },
(16, 24) (16, 21)
) ))),
(16, 24)
),
}
}) })
); );
} }
@ -989,9 +997,12 @@ mod tests {
assert_eq!( assert_eq!(
parse(input), parse(input),
Err(ParseError::UnexpectedToken { Err(DustError::ParseError {
actual: TokenOwned::Semicolon, source: input,
position: (0, 1) parse_error: ParseError::UnexpectedToken {
actual: TokenOwned::Semicolon,
position: (0, 1)
}
}) })
); );
} }

View File

@ -1,25 +1,32 @@
//! Virtual machine for running the abstract syntax tree. //! Virtual machine for running the abstract syntax tree.
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
error::Error,
fmt::{self, Display, Formatter}, fmt::{self, Display, Formatter},
}; };
use crate::{ use crate::{
abstract_tree::BinaryOperator, parse, value::ValueInner, AbstractSyntaxTree, Analyzer, abstract_tree::BinaryOperator, parse, value::ValueInner, AbstractSyntaxTree, Analyzer,
AnalyzerError, BuiltInFunctionError, Context, Node, ParseError, Span, Statement, Value, BuiltInFunctionError, Context, DustError, Node, ParseError, Span, Statement, Value, ValueError,
ValueError,
}; };
pub fn run(input: &str, context: &mut Context) -> Result<Option<Value>, VmError> { pub fn run<'src>(
let abstract_syntax_tree = parse(input)?; 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); 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); let mut vm = Vm::new(abstract_syntax_tree);
vm.run(context) vm.run(context)
.map_err(|vm_error| DustError::VmError { vm_error, source })
} }
pub struct Vm { pub struct Vm {
@ -528,7 +535,6 @@ impl Vm {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum VmError { pub enum VmError {
AnaylyzerError(AnalyzerError),
ParseError(ParseError), ParseError(ParseError),
ValueError { ValueError {
error: ValueError, error: ValueError,
@ -571,7 +577,6 @@ pub enum VmError {
impl VmError { impl VmError {
pub fn position(&self) -> Span { pub fn position(&self) -> Span {
match self { match self {
Self::AnaylyzerError(analyzer_error) => analyzer_error.position(),
Self::ParseError(parse_error) => parse_error.position(), Self::ParseError(parse_error) => parse_error.position(),
Self::ValueError { position, .. } => *position, Self::ValueError { position, .. } => *position,
Self::BuiltInFunctionError { 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 { impl From<ParseError> for VmError {
fn from(error: ParseError) -> Self { fn from(error: ParseError) -> Self {
Self::ParseError(error) 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 { impl Display for VmError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
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 { error, .. } => write!(f, "{}", error), Self::ValueError { error, .. } => write!(f, "{}", error),
Self::BuiltInFunctionError { error, .. } => { Self::BuiltInFunctionError { error, .. } => {

View File

@ -1,7 +1,7 @@
use std::fs::read_to_string; use std::fs::read_to_string;
use clap::Parser; use clap::Parser;
use dust_lang::{run, Context, DustError}; use dust_lang::{run, Context};
#[derive(Parser)] #[derive(Parser)]
struct Cli { struct Cli {
@ -33,6 +33,6 @@ fn run_and_display_errors(source: &str, variables: &mut Context) {
println!("{}", value); println!("{}", value);
} }
} }
Err(error) => eprintln!("{}", DustError::new(error, source).report()), Err(error) => eprintln!("{}", error.report()),
} }
} }