diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index a2e8b71..90cbf44 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -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 for AnalysisError { } } -impl From for AnalysisError { - fn from(context_error: ContextError) -> Self { - Self::ContextError(context_error) - } -} - impl AnalysisError { - pub fn position(&self) -> Option { - 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) } diff --git a/dust-lang/src/ast/expression.rs b/dust-lang/src/ast/expression.rs index 1d5a62c..74fbf31 100644 --- a/dust-lang/src/ast/expression.rs +++ b/dust-lang/src/ast/expression.rs @@ -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)?, diff --git a/dust-lang/src/ast/mod.rs b/dust-lang/src/ast/mod.rs index 89cae04..faa380d 100644 --- a/dust-lang/src/ast/mod.rs +++ b/dust-lang/src/ast/mod.rs @@ -62,7 +62,7 @@ impl Display for Node { #[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 { + 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 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) diff --git a/dust-lang/src/built_in_function.rs b/dust-lang/src/built_in_function.rs index bb839a1..4164a18 100644 --- a/dust-lang/src/built_in_function.rs +++ b/dust-lang/src/built_in_function.rs @@ -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(); diff --git a/dust-lang/src/core_library.rs b/dust-lang/src/core_library.rs index 8df3180..6cbf6b5 100644 --- a/dust-lang/src/core_library.rs +++ b/dust-lang/src/core_library.rs @@ -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), ), diff --git a/dust-lang/src/dust_error.rs b/dust-lang/src/dust_error.rs index c5b89a2..a7bc58f 100644 --- a/dust-lang/src/dust_error.rs +++ b/dust-lang/src/dust_error.rs @@ -61,12 +61,12 @@ impl<'src> DustError<'src> { } } - pub fn position(&self) -> Option { + 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 } } diff --git a/dust-lang/src/value.rs b/dust-lang/src/value.rs index 2cf7626..391fcc7 100644 --- a/dust-lang/src/value.rs +++ b/dust-lang/src/value.rs @@ -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 { - 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, ValueError> { @@ -1118,25 +1127,29 @@ impl Function { _type_arguments: Option>, value_arguments: Option>, context: &Context, - ) -> Result, RuntimeError> { + ) -> Result, 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), +} + +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 { diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index c4852fa..3c91ff8 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -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 { + fn run_identifier(&self, identifier: Node) -> Result { 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, @@ -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 for RuntimeError { - fn from(error: ContextError) -> Self { - Self::ContextError(error) - } -} - impl RuntimeError { - pub fn position(&self) -> Option { - 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 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"