1
0

Improve errors

This commit is contained in:
Jeff 2024-08-20 17:01:30 -04:00
parent 80bf09d807
commit 08a9b265ec
8 changed files with 242 additions and 120 deletions

View File

@ -90,11 +90,16 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
let r#type = value.return_type(&self.context)?;
if let Some(r#type) = r#type {
self.context.set_variable_type(
identifier.inner.clone(),
r#type,
identifier.position,
)?;
self.context
.set_variable_type(
identifier.inner.clone(),
r#type,
identifier.position,
)
.map_err(|error| AnalysisError::ContextError {
error,
position: identifier.position,
})?;
} else {
return Err(AnalysisError::ExpectedValueFromExpression {
expression: value.clone(),
@ -114,7 +119,7 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
name: name.inner.clone(),
},
name.position,
)?,
),
StructDefinition::Tuple { name, items } => {
let fields = items.iter().map(|item| item.inner.clone()).collect();
@ -125,7 +130,7 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
fields,
},
name.position,
)?;
)
}
StructDefinition::Fields { name, fields } => {
let fields = fields
@ -142,9 +147,13 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
fields,
},
name.position,
)?;
)
}
},
}
.map_err(|error| AnalysisError::ContextError {
error,
position: struct_definition.position,
})?,
}
Ok(())
@ -172,7 +181,11 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
field_access_expression.inner.as_ref();
self.context
.update_last_position(&field.inner, field.position)?;
.update_last_position(&field.inner, field.position)
.map_err(|error| AnalysisError::ContextError {
error,
position: field.position,
})?;
self.analyze_expression(container)?;
}
Expression::Grouped(expression) => {
@ -181,7 +194,11 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
Expression::Identifier(identifier) => {
let found = self
.context
.update_last_position(&identifier.inner, identifier.position)?;
.update_last_position(&identifier.inner, identifier.position)
.map_err(|error| AnalysisError::ContextError {
error,
position: identifier.position,
})?;
if !found {
return Err(AnalysisError::UndefinedVariable {
@ -304,7 +321,11 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
Expression::Struct(struct_expression) => match struct_expression.inner.as_ref() {
StructExpression::Fields { name, fields } => {
self.context
.update_last_position(&name.inner, name.position)?;
.update_last_position(&name.inner, name.position)
.map_err(|error| AnalysisError::ContextError {
error,
position: name.position,
});
for (_, expression) in fields {
self.analyze_expression(expression)?;
@ -376,7 +397,10 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> {
#[derive(Clone, Debug, PartialEq)]
pub enum AnalysisError {
AstError(AstError),
ContextError(ContextError),
ContextError {
error: ContextError,
position: Span,
},
ExpectedBoolean {
actual: Statement,
},
@ -442,18 +466,11 @@ impl From<AstError> for AnalysisError {
}
}
impl From<ContextError> for AnalysisError {
fn from(context_error: ContextError) -> Self {
Self::ContextError(context_error)
}
}
impl AnalysisError {
pub fn position(&self) -> Option<Span> {
let position = match self {
AnalysisError::AstError(ast_error) => return ast_error.position(),
AnalysisError::ContextError(_) => return None,
pub fn position(&self) -> Span {
match self {
AnalysisError::AstError(ast_error) => ast_error.position(),
AnalysisError::ContextError { position, .. } => *position,
AnalysisError::ExpectedBoolean { actual } => actual.position(),
AnalysisError::ExpectedIdentifier { actual } => actual.position(),
AnalysisError::ExpectedIdentifierOrString { actual } => actual.position(),
@ -472,9 +489,7 @@ impl AnalysisError {
AnalysisError::UndefinedVariable { identifier } => identifier.position,
AnalysisError::UnexpectedIdentifier { identifier } => identifier.position,
AnalysisError::UnexectedString { actual } => actual.position(),
};
Some(position)
}
}
}
@ -484,7 +499,9 @@ impl Display for AnalysisError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
AnalysisError::AstError(ast_error) => write!(f, "{}", ast_error),
AnalysisError::ContextError(context_error) => write!(f, "{}", context_error),
Self::ContextError { error, position } => {
write!(f, "Context error at {:?}: {}", position, error)
}
AnalysisError::ExpectedBoolean { actual, .. } => {
write!(f, "Expected boolean, found {}", actual)
}

View File

@ -307,7 +307,14 @@ impl Expression {
}
}
Expression::Grouped(expression) => expression.inner.return_type(context)?,
Expression::Identifier(identifier) => context.get_type(&identifier.inner)?,
Expression::Identifier(identifier) => {
context
.get_type(&identifier.inner)
.map_err(|error| AstError::ContextError {
error,
position: identifier.position,
})?
}
Expression::If(if_expression) => match if_expression.inner.as_ref() {
IfExpression::If { .. } => None,
IfExpression::IfElse { if_block, .. } => if_block.inner.return_type(context)?,

View File

@ -62,7 +62,7 @@ impl<T: Display> Display for Node<T> {
#[derive(Debug, Clone, PartialEq)]
pub enum AstError {
ContextError(ContextError),
ContextError { error: ContextError, position: Span },
ExpectedType { position: Span },
ExpectedTupleType { position: Span },
ExpectedNonEmptyList { position: Span },
@ -70,27 +70,23 @@ pub enum AstError {
}
impl AstError {
pub fn position(&self) -> Option<Span> {
pub fn position(&self) -> Span {
match self {
AstError::ContextError(_) => None,
AstError::ExpectedType { position } => Some(*position),
AstError::ExpectedTupleType { position } => Some(*position),
AstError::ExpectedNonEmptyList { position } => Some(*position),
AstError::ExpectedRangeableType { position } => Some(*position),
AstError::ContextError { position, .. } => *position,
AstError::ExpectedType { position } => *position,
AstError::ExpectedTupleType { position } => *position,
AstError::ExpectedNonEmptyList { position } => *position,
AstError::ExpectedRangeableType { position } => *position,
}
}
}
impl From<ContextError> for AstError {
fn from(v: ContextError) -> Self {
Self::ContextError(v)
}
}
impl Display for AstError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
AstError::ContextError(error) => write!(f, "{}", error),
AstError::ContextError { error, position } => {
write!(f, "Context error at {:?}: {}", position, error)
}
AstError::ExpectedType { position } => write!(f, "Expected a type at {:?}", position),
AstError::ExpectedTupleType { position } => {
write!(f, "Expected a tuple type at {:?}", position)

View File

@ -14,13 +14,14 @@ use crate::{Identifier, Type, Value};
pub enum BuiltInFunction {
// String tools
ToString,
LengthString,
// Integer and float tools
IsEven,
IsOdd,
// List tools
Length,
LengthList,
// I/O
ReadLine,
@ -32,7 +33,8 @@ impl BuiltInFunction {
match self {
BuiltInFunction::IsEven => "is_even",
BuiltInFunction::IsOdd => "is_odd",
BuiltInFunction::Length => "length",
BuiltInFunction::LengthList => "length",
BuiltInFunction::LengthString => "length",
BuiltInFunction::ReadLine => "read_line",
BuiltInFunction::ToString { .. } => "to_string",
BuiltInFunction::WriteLine => "write_line",
@ -44,7 +46,8 @@ impl BuiltInFunction {
BuiltInFunction::ToString { .. } => None,
BuiltInFunction::IsEven => None,
BuiltInFunction::IsOdd => None,
BuiltInFunction::Length => None,
BuiltInFunction::LengthList => None,
BuiltInFunction::LengthString => None,
BuiltInFunction::ReadLine => None,
BuiltInFunction::WriteLine => None,
}
@ -55,14 +58,15 @@ impl BuiltInFunction {
BuiltInFunction::ToString { .. } => Some(vec![("value".into(), Type::Any)]),
BuiltInFunction::IsEven => Some(vec![("value".into(), Type::Number)]),
BuiltInFunction::IsOdd => Some(vec![("value".into(), Type::Number)]),
BuiltInFunction::Length => Some(vec![(
BuiltInFunction::LengthList => Some(vec![(
"value".into(),
Type::ListOf {
item_type: Box::new(Type::Any),
},
)]),
BuiltInFunction::LengthString => Some(vec![("value".into(), Type::String)]),
BuiltInFunction::ReadLine => None,
BuiltInFunction::WriteLine => Some(vec![("output".into(), Type::Any)]),
BuiltInFunction::WriteLine => Some(vec![("value".into(), Type::Any)]),
}
}
@ -71,7 +75,8 @@ impl BuiltInFunction {
BuiltInFunction::ToString { .. } => Some(Type::String),
BuiltInFunction::IsEven => Some(Type::Boolean),
BuiltInFunction::IsOdd => Some(Type::Boolean),
BuiltInFunction::Length => Some(Type::Number),
BuiltInFunction::LengthList => Some(Type::Number),
BuiltInFunction::LengthString => Some(Type::Number),
BuiltInFunction::ReadLine => Some(Type::String),
BuiltInFunction::WriteLine => None,
}
@ -112,13 +117,20 @@ impl BuiltInFunction {
Err(BuiltInFunctionError::ExpectedInteger)
}
}
BuiltInFunction::Length => {
BuiltInFunction::LengthList => {
if let Value::List(list) = &value_arguments.unwrap()[0] {
Ok(Some(Value::Integer(list.len() as i64)))
} else {
Err(BuiltInFunctionError::ExpectedList)
}
}
BuiltInFunction::LengthString => {
if let Value::String(string) = &value_arguments.unwrap()[0] {
Ok(Some(Value::Integer(string.len() as i64)))
} else {
Err(BuiltInFunctionError::ExpectedString)
}
}
BuiltInFunction::ReadLine => {
let mut input = String::new();

View File

@ -38,7 +38,7 @@ pub fn core_library<'a>() -> &'a Context {
Identifier::new("length"),
(
ContextData::VariableValue(Value::Function(Function::BuiltIn(
BuiltInFunction::Length,
BuiltInFunction::LengthList,
))),
(0, 0),
),

View File

@ -61,12 +61,12 @@ impl<'src> DustError<'src> {
}
}
pub fn position(&self) -> Option<Span> {
pub fn position(&self) -> Span {
match self {
DustError::Runtime { runtime_error, .. } => runtime_error.position(),
DustError::Analysis { analysis_error, .. } => analysis_error.position(),
DustError::Parse { parse_error, .. } => Some(parse_error.position()),
DustError::Lex { lex_error, .. } => Some(lex_error.position()),
DustError::Parse { parse_error, .. } => parse_error.position(),
DustError::Lex { lex_error, .. } => lex_error.position(),
}
}
@ -79,25 +79,49 @@ impl<'src> DustError<'src> {
}
}
pub fn report(&self) -> String {
let title = self.title();
let span = self.position();
let label = self.to_string();
pub fn primary_error_data(&self) -> (&'static str, Span, String) {
(self.title(), self.position(), self.to_string())
}
let message = if let Some(span) = span {
Level::Error.title(title).snippet(
Snippet::source(self.source())
.annotation(Level::Info.span(span.0..span.1).label(&label)),
)
pub fn secondary_error_data(&self) -> Option<(&'static str, Span, String)> {
if let DustError::Runtime { runtime_error, .. } = self {
match runtime_error {
RuntimeError::Expression { error, .. } => {
Some(("Expression error", error.position(), error.to_string()))
}
RuntimeError::Statement { error, .. } => {
Some(("Statement error", error.position(), error.to_string()))
}
_ => None,
}
} else {
Level::Error
.title(title)
.snippet(Snippet::source(self.source()))
.footer(Level::Info.title("No position information available"))
};
None
}
}
pub fn report(&self) -> String {
let mut report = String::new();
let renderer = Renderer::styled();
format!("{}", renderer.render(message))
let (title, span, label) = self.primary_error_data();
let message = Level::Error.title(title).snippet(
Snippet::source(self.source())
.annotation(Level::Info.span(span.0..span.1).label(&label)),
);
report.push_str(&format!("{}", renderer.render(message)));
if let Some((title, span, label)) = self.secondary_error_data() {
let message = Level::Error.title(title).snippet(
Snippet::source(self.source())
.annotation(Level::Info.span(span.0..span.1).label(&label)),
);
report.push_str(&format!("{}", renderer.render(message)));
}
report
}
}

View File

@ -11,8 +11,8 @@ use std::{
use serde::{de::Visitor, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
use crate::{
AbstractSyntaxTree, BuiltInFunction, Context, EnumType, FunctionType, Identifier,
RangeableType, RuntimeError, StructType, Type, Vm,
AbstractSyntaxTree, BuiltInFunction, BuiltInFunctionError, Context, ContextError, EnumType,
FunctionType, Identifier, RangeableType, RuntimeError, StructType, Type, Vm,
};
/// Dust value representation
@ -232,18 +232,27 @@ impl Value {
}
pub fn get_field(&self, field: &Identifier) -> Option<Value> {
if let "to_string" = field.as_str() {
return Some(Value::Function(Function::BuiltIn(
BuiltInFunction::ToString,
)));
}
let built_in_function = match field.as_str() {
"to_string" => BuiltInFunction::ToString,
"length" => {
return match self {
Value::List(values) => Some(Value::Integer(values.len() as i64)),
Value::String(string) => Some(Value::Integer(string.len() as i64)),
Value::Map(map) => Some(Value::Integer(map.len() as i64)),
_ => None,
}
}
_ => {
return match self {
Value::Mutable(inner) => inner.read().unwrap().get_field(field),
Value::Struct(Struct::Fields { fields, .. }) => fields.get(field).cloned(),
Value::Map(pairs) => pairs.get(field).cloned(),
_ => None,
};
}
};
match self {
Value::Mutable(inner) => inner.read().unwrap().get_field(field),
Value::Struct(Struct::Fields { fields, .. }) => fields.get(field).cloned(),
Value::Map(pairs) => pairs.get(field).cloned(),
_ => None,
}
Some(Value::Function(Function::BuiltIn(built_in_function)))
}
pub fn get_index(&self, index: Value) -> Result<Option<Value>, ValueError> {
@ -1118,25 +1127,29 @@ impl Function {
_type_arguments: Option<Vec<Type>>,
value_arguments: Option<Vec<Value>>,
context: &Context,
) -> Result<Option<Value>, RuntimeError> {
) -> Result<Option<Value>, FunctionCallError> {
match self {
Function::BuiltIn(built_in_function) => built_in_function
.call(_type_arguments, value_arguments)
.map_err(|error| RuntimeError::BuiltInFunctionError { error }),
.map_err(FunctionCallError::BuiltInFunction),
Function::Parsed { r#type, body, .. } => {
let new_context = Context::with_data_from(context)?;
let new_context =
Context::with_data_from(context).map_err(FunctionCallError::Context)?;
if let (Some(value_parameters), Some(value_arguments)) =
(&r#type.value_parameters, value_arguments)
{
for ((identifier, _), value) in value_parameters.iter().zip(value_arguments) {
new_context.set_variable_value(identifier.clone(), value)?;
new_context
.set_variable_value(identifier.clone(), value)
.map_err(FunctionCallError::Context)?;
}
}
let mut vm = Vm::new(body, new_context);
vm.run()
.map_err(|error| FunctionCallError::Runtime(Box::new(error)))
}
}
}
@ -1187,6 +1200,23 @@ impl Display for Function {
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum FunctionCallError {
BuiltInFunction(BuiltInFunctionError),
Context(ContextError),
Runtime(Box<RuntimeError>),
}
impl Display for FunctionCallError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
FunctionCallError::BuiltInFunction(error) => write!(f, "{}", error),
FunctionCallError::Context(error) => write!(f, "{}", error),
FunctionCallError::Runtime(error) => write!(f, "{}", error),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum Struct {
Unit {

View File

@ -21,8 +21,8 @@ use crate::{
StructDefinition, StructExpression,
},
core_library, parse, Analyzer, BuiltInFunctionError, Constructor, Context, ContextData,
ContextError, DustError, Expression, Function, Identifier, ParseError, StructType, Type, Value,
ValueError,
ContextError, DustError, Expression, Function, FunctionCallError, Identifier, ParseError,
StructType, Type, Value, ValueError,
};
/// Run the source code and return the result.
@ -167,14 +167,21 @@ impl Vm {
};
let constructor = struct_type.constructor();
self.context.set_constructor(name, constructor)?;
self.context
.set_constructor(name, constructor)
.map_err(|error| RuntimeError::ContextError {
error,
position: struct_definition.position,
})?;
Ok(None)
}
};
if collect_garbage {
self.context.collect_garbage(position.1)?;
self.context
.collect_garbage(position.1)
.map_err(|error| RuntimeError::ContextError { error, position })?;
}
result.map_err(|error| RuntimeError::Statement {
@ -190,24 +197,27 @@ impl Vm {
) -> Result<(), RuntimeError> {
match let_statement {
LetStatement::Let { identifier, value } => {
let value_position = value.position();
let position = value.position();
let value = self
.run_expression(value, collect_garbage)?
.expect_value(value_position)?;
.expect_value(position)?;
self.context.set_variable_value(identifier.inner, value)?;
self.context
.set_variable_value(identifier.inner, value)
.map_err(|error| RuntimeError::ContextError { error, position })?;
Ok(())
}
LetStatement::LetMut { identifier, value } => {
let value_position = value.position();
let position = value.position();
let mutable_value = self
.run_expression(value, collect_garbage)?
.expect_value(value_position)?
.expect_value(position)?
.into_mutable();
self.context
.set_variable_value(identifier.inner, mutable_value)?;
.set_variable_value(identifier.inner, mutable_value)
.map_err(|error| RuntimeError::ContextError { error, position })?;
Ok(())
}
@ -250,7 +260,7 @@ impl Vm {
Expression::Grouped(expression) => {
self.run_expression(*expression.inner, collect_garbage)
}
Expression::Identifier(identifier) => self.run_identifier(identifier.inner),
Expression::Identifier(identifier) => self.run_identifier(identifier),
Expression::If(if_expression) => self.run_if(*if_expression.inner, collect_garbage),
Expression::List(list_expression) => {
self.run_list(*list_expression.inner, collect_garbage)
@ -279,10 +289,15 @@ impl Vm {
})
}
fn run_identifier(&self, identifier: Identifier) -> Result<Evaluation, RuntimeError> {
fn run_identifier(&self, identifier: Node<Identifier>) -> Result<Evaluation, RuntimeError> {
log::debug!("Running identifier: {}", identifier);
let get_data = self.context.get_data(&identifier)?;
let get_data = self.context.get_data(&identifier.inner).map_err(|error| {
RuntimeError::ContextError {
error,
position: identifier.position,
}
})?;
if let Some(ContextData::VariableValue(value)) = get_data {
return Ok(Evaluation::Return(Some(value)));
@ -296,7 +311,10 @@ impl Vm {
return Ok(Evaluation::Constructor(constructor));
}
Err(RuntimeError::UnassociatedIdentifier { identifier })
Err(RuntimeError::UnassociatedIdentifier {
identifier: identifier.inner,
position: identifier.position,
})
}
fn run_struct(
@ -309,7 +327,10 @@ impl Vm {
let StructExpression::Fields { name, fields } = struct_expression;
let position = name.position;
let constructor = self.context.get_constructor(&name.inner)?;
let constructor = self
.context
.get_constructor(&name.inner)
.map_err(|error| RuntimeError::ContextError { error, position })?;
if let Some(constructor) = constructor {
if let Constructor::Fields(fields_constructor) = constructor {
@ -696,6 +717,7 @@ impl Vm {
log::debug!("Running call expression: {call_expression}");
let CallExpression { invoker, arguments } = call_expression;
let invoker_position = invoker.position();
if let Expression::FieldAccess(field_access) = invoker {
let FieldAccessExpression { container, field } = *field_access.inner;
@ -738,7 +760,11 @@ impl Vm {
return function
.call(None, Some(value_arguments), &context)
.map(Evaluation::Return);
.map(Evaluation::Return)
.map_err(|error| RuntimeError::FunctionCall {
error,
position: invoker_position,
});
}
let invoker_position = invoker.position();
@ -801,6 +827,10 @@ impl Vm {
function
.call(None, value_arguments, &context)
.map(Evaluation::Return)
.map_err(|error| RuntimeError::FunctionCall {
error,
position: invoker_position,
})
}
_ => Err(RuntimeError::ExpectedValueOrConstructor {
position: invoker_position,
@ -1010,7 +1040,14 @@ impl Evaluation {
#[derive(Clone, Debug, PartialEq)]
pub enum RuntimeError {
ContextError(ContextError),
ContextError {
error: ContextError,
position: Span,
},
FunctionCall {
error: FunctionCallError,
position: Span,
},
ParseError(ParseError),
Expression {
error: Box<RuntimeError>,
@ -1030,6 +1067,7 @@ pub enum RuntimeError {
// These should be prevented by running the analyzer before the VM
BuiltInFunctionError {
error: BuiltInFunctionError,
position: Span,
},
EnumVariantNotFound {
identifier: Identifier,
@ -1086,6 +1124,7 @@ pub enum RuntimeError {
},
UnassociatedIdentifier {
identifier: Identifier,
position: Span,
},
UndefinedType {
identifier: Identifier,
@ -1099,19 +1138,13 @@ pub enum RuntimeError {
},
}
impl From<ContextError> for RuntimeError {
fn from(error: ContextError) -> Self {
Self::ContextError(error)
}
}
impl RuntimeError {
pub fn position(&self) -> Option<Span> {
let position = match self {
Self::ContextError(_) => return None,
Self::BuiltInFunctionError { .. } => return None,
Self::UnassociatedIdentifier { .. } => return None,
pub fn position(&self) -> Span {
match self {
Self::ContextError { position, .. } => *position,
Self::BuiltInFunctionError { position, .. } => *position,
Self::FunctionCall { position, .. } => *position,
Self::UnassociatedIdentifier { position, .. } => *position,
Self::ParseError(parse_error) => parse_error.position(),
Self::Expression { position, .. } => *position,
Self::Statement { position, .. } => *position,
@ -1145,9 +1178,7 @@ impl RuntimeError {
Self::UndefinedProperty {
property_position, ..
} => *property_position,
};
Some(position)
}
}
}
@ -1160,7 +1191,12 @@ impl From<ParseError> for RuntimeError {
impl Display for RuntimeError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::ContextError(context_error) => write!(f, "{}", context_error),
Self::ContextError { error, position } => {
write!(f, "Context error at {:?}: {}", position, error)
}
Self::FunctionCall { error, position } => {
write!(f, "Function call error at {:?}: {}", position, error)
}
Self::ParseError(parse_error) => write!(f, "{}", parse_error),
Self::Expression { error, position } => {
write!(
@ -1284,7 +1320,7 @@ impl Display for RuntimeError {
start_position, end_position
)
}
Self::UnassociatedIdentifier { identifier } => {
Self::UnassociatedIdentifier { identifier, .. } => {
write!(
f,
"Identifier \"{identifier}\" is not associated with a value or constructor"