From 3a2dd28efb1db559f60eec31d24e0ccf8dbcd5c2 Mon Sep 17 00:00:00 2001 From: Jeff Date: Sun, 11 Aug 2024 17:59:52 -0400 Subject: [PATCH] Improve error layout --- dust-lang/src/analyzer.rs | 47 +++++++++++++++--- dust-lang/src/built_in_function.rs | 21 +++++++- dust-lang/src/dust_error.rs | 79 +++++++++++++++++++++--------- dust-lang/src/parser.rs | 63 ++++++++++++++---------- dust-lang/src/vm.rs | 40 +++++---------- dust-shell/src/main.rs | 4 +- 6 files changed, 166 insertions(+), 88 deletions(-) diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index c0d767c..f342b55 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -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 { diff --git a/dust-lang/src/built_in_function.rs b/dust-lang/src/built_in_function.rs index 6cfe361..bbbb736 100644 --- a/dust-lang/src/built_in_function.rs +++ b/dust-lang/src/built_in_function.rs @@ -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>, diff --git a/dust-lang/src/dust_error.rs b/dust-lang/src/dust_error.rs index ae6aa16..4b02070 100644 --- a/dust-lang/src/dust_error.rs +++ b/dust-lang/src/dust_error.rs @@ -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}"), + } } } diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index 288c2a4..032b0eb 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -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 { +pub fn parse(source: &str) -> Result { 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) + } }) ); } diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 3bb3882..d4d0224 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -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, VmError> { - let abstract_syntax_tree = parse(input)?; +pub fn run<'src>( + source: &'src str, + context: &mut Context, +) -> Result, 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 for VmError { - fn from(error: AnalyzerError) -> Self { - Self::AnaylyzerError(error) - } -} - impl From 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, .. } => { diff --git a/dust-shell/src/main.rs b/dust-shell/src/main.rs index c7fff89..d3b821c 100644 --- a/dust-shell/src/main.rs +++ b/dust-shell/src/main.rs @@ -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()), } }