Improve error layout
This commit is contained in:
parent
3b0c74010e
commit
3a2dd28efb
@ -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 {
|
||||||
|
@ -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>>,
|
||||||
|
@ -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}"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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, .. } => {
|
||||||
|
@ -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()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user