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::{
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 {

View File

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

View File

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

View File

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

View File

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

View File

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