diff --git a/src/abstract_tree/assignment.rs b/src/abstract_tree/assignment.rs index c88070e..e966783 100644 --- a/src/abstract_tree/assignment.rs +++ b/src/abstract_tree/assignment.rs @@ -80,7 +80,7 @@ impl AbstractTree for Assignment { context.set_value(self.identifier, value)?; } AssignmentOperator::AddAssign => { - if let Some(previous_value) = context.use_value(&self.identifier)? { + if let Some(previous_value) = context.get_value(&self.identifier)? { let new_value = previous_value.add(&value)?; context.set_value(self.identifier, new_value)?; @@ -91,7 +91,7 @@ impl AbstractTree for Assignment { } } AssignmentOperator::SubAssign => { - if let Some(previous_value) = context.use_value(&self.identifier)? { + if let Some(previous_value) = context.get_value(&self.identifier)? { let new_value = previous_value.subtract(&value)?; context.set_value(self.identifier, new_value)?; @@ -131,7 +131,7 @@ mod tests { .unwrap(); assert_eq!( - context.use_value(&Identifier::new("foobar")), + context.get_value(&Identifier::new("foobar")), Ok(Some(Value::integer(42))) ) } @@ -154,7 +154,7 @@ mod tests { .unwrap(); assert_eq!( - context.use_value(&Identifier::new("foobar")), + context.get_value(&Identifier::new("foobar")), Ok(Some(Value::integer(42))) ) } @@ -177,7 +177,7 @@ mod tests { .unwrap(); assert_eq!( - context.use_value(&Identifier::new("foobar")), + context.get_value(&Identifier::new("foobar")), Ok(Some(Value::integer(42))) ) } diff --git a/src/abstract_tree/block.rs b/src/abstract_tree/block.rs index 9bf062b..5f19584 100644 --- a/src/abstract_tree/block.rs +++ b/src/abstract_tree/block.rs @@ -41,12 +41,7 @@ impl AbstractTree for Block { let mut previous = Action::None; for statement in self.statements { - let action = statement.node.run(_context)?; - previous = match action { - Action::Return(value) => Action::Return(value), - Action::None => Action::None, - Action::Break => return Ok(action), - }; + previous = statement.node.run(_context)?; } Ok(previous) diff --git a/src/abstract_tree/function_call.rs b/src/abstract_tree/function_call.rs index 50070fe..55cb38f 100644 --- a/src/abstract_tree/function_call.rs +++ b/src/abstract_tree/function_call.rs @@ -51,8 +51,9 @@ impl AbstractTree for FunctionCall { arguments.push(value); } - let function_context = Context::inherit_data_from(context)?; + let function_context = Context::new(); + function_context.inherit_data_from(&context)?; function.clone().call(arguments, function_context) } } diff --git a/src/abstract_tree/identifier.rs b/src/abstract_tree/identifier.rs index 0c5627a..aaaa9be 100644 --- a/src/abstract_tree/identifier.rs +++ b/src/abstract_tree/identifier.rs @@ -25,7 +25,7 @@ impl Identifier { impl AbstractTree for Identifier { fn expected_type(&self, context: &Context) -> Result { - if let Some(r#type) = context.use_type(self)? { + if let Some(r#type) = context.get_type(self)? { Ok(r#type) } else { Err(ValidationError::VariableNotFound(self.clone())) @@ -33,7 +33,7 @@ impl AbstractTree for Identifier { } fn validate(&self, context: &Context) -> Result<(), ValidationError> { - if context.add_allowance(self)? { + if context.contains(self)? { Ok(()) } else { Err(ValidationError::VariableNotFound(self.clone())) @@ -41,7 +41,7 @@ impl AbstractTree for Identifier { } fn run(self, context: &Context) -> Result { - let return_action = context.use_value(&self)?.map(|value| Action::Return(value)); + let return_action = context.get_value(&self)?.map(|value| Action::Return(value)); if let Some(action) = return_action { Ok(action) diff --git a/src/abstract_tree/if_else.rs b/src/abstract_tree/if_else.rs index d6091dd..869148b 100644 --- a/src/abstract_tree/if_else.rs +++ b/src/abstract_tree/if_else.rs @@ -32,8 +32,13 @@ impl AbstractTree for IfElse { } fn validate(&self, context: &Context) -> Result<(), ValidationError> { + self.if_expression.node.validate(context)?; + self.if_block.validate(context)?; + if let Type::Boolean = self.if_expression.node.expected_type(context)? { if let Some(else_block) = &self.else_block { + else_block.validate(context)?; + let expected = self.if_block.expected_type(context)?; let actual = else_block.expected_type(context)?; diff --git a/src/abstract_tree/loop.rs b/src/abstract_tree/loop.rs index 19a6095..2b3223c 100644 --- a/src/abstract_tree/loop.rs +++ b/src/abstract_tree/loop.rs @@ -1,3 +1,5 @@ +use std::cmp::Ordering; + use crate::{ context::Context, error::{RuntimeError, ValidationError}, @@ -5,7 +7,7 @@ use crate::{ use super::{AbstractTree, Action, Statement, Type, WithPosition}; -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Clone, Debug)] pub struct Loop { statements: Vec>, } @@ -30,38 +32,92 @@ impl AbstractTree for Loop { } fn run(self, _context: &Context) -> Result { - let mut index = 0; - loop { - if index == self.statements.len() - 1 { - index = 0; - } else { - index += 1; - } + for statement in &self.statements { + let action = statement.node.clone().run(_context)?; - let statement = self.statements[index].clone(); - let action = statement.node.run(_context)?; - - match action { - Action::Return(_) => {} - Action::None => {} - r#break => return Ok(r#break), + match action { + Action::Return(_) => {} + Action::None => {} + Action::Break => return Ok(Action::Break), + } } } } } +impl Eq for Loop {} + +impl PartialEq for Loop { + fn eq(&self, other: &Self) -> bool { + self.statements.eq(&other.statements) + } +} + +impl PartialOrd for Loop { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Loop { + fn cmp(&self, other: &Self) -> Ordering { + self.statements.cmp(&other.statements) + } +} + #[cfg(test)] mod tests { + use crate::{ + abstract_tree::{ + Assignment, AssignmentOperator, Block, Expression, Identifier, IfElse, Logic, ValueNode, + }, + Value, + }; + use super::*; #[test] fn basic_loop() { - let result = Loop { - statements: vec![Statement::Break.with_position((0, 0))], - } - .run(&Context::new()); + let result = Loop::new(vec![Statement::Break.with_position((0, 0))]).run(&Context::new()); assert_eq!(result, Ok(Action::Break)) } + + #[test] + fn complex_loop() { + let result = Block::new(vec![ + Statement::Assignment(Assignment::new( + Identifier::new("i"), + None, + AssignmentOperator::Assign, + Statement::Expression(Expression::Value(ValueNode::Integer(1))) + .with_position((0, 0)), + )) + .with_position((0, 0)), + Statement::Loop(Loop::new(vec![Statement::IfElse(IfElse::new( + Expression::Logic(Box::new(Logic::Greater( + Expression::Identifier(Identifier::new("i")).with_position((10, 11)), + Expression::Value(ValueNode::Integer(2)).with_position((14, 15)), + ))) + .with_position((10, 15)), + Block::new(vec![Statement::Break.with_position((18, 24))]), + Some(Block::new(vec![Statement::Assignment(Assignment::new( + Identifier::new("i"), + None, + AssignmentOperator::AddAssign, + Statement::Expression(Expression::Value(ValueNode::Integer(1))) + .with_position((38, 39)), + )) + .with_position((33, 39))])), + )) + .with_position((0, 0))])) + .with_position((0, 0)), + Statement::Expression(Expression::Identifier(Identifier::new("i"))) + .with_position((0, 0)), + ]) + .run(&Context::new()); + + assert_eq!(result, Ok(Action::Return(Value::integer(3)))) + } } diff --git a/src/abstract_tree/value_node.rs b/src/abstract_tree/value_node.rs index af56ea5..1a596d5 100644 --- a/src/abstract_tree/value_node.rs +++ b/src/abstract_tree/value_node.rs @@ -87,7 +87,9 @@ impl AbstractTree for ValueNode { body, } = self { - let function_context = Context::inherit_types_from(context)?; + let function_context = Context::new(); + + function_context.inherit_types_from(context)?; for (identifier, r#type) in parameters { function_context.set_type(identifier.clone(), r#type.node.clone())?; diff --git a/src/context.rs b/src/context.rs index 43fbdac..a257090 100644 --- a/src/context.rs +++ b/src/context.rs @@ -6,36 +6,13 @@ use std::{ use crate::{ abstract_tree::{Identifier, Type}, error::RwLockPoisonError, - value::{BuiltInFunction, ValueInner}, + value::BuiltInFunction, Value, }; +#[derive(Clone, Debug)] pub struct Context { - inner: Arc>>, -} - -#[derive(Clone, Debug)] -pub struct UsageData(Arc>); - -#[derive(Clone, Debug)] -pub struct UsageDataInner { - pub allowances: usize, - pub uses: usize, -} - -impl Default for UsageData { - fn default() -> Self { - UsageData(Arc::new(RwLock::new(UsageDataInner { - allowances: 0, - uses: 0, - }))) - } -} - -#[derive(Clone, Debug)] -pub enum ValueData { - Type(Type), - Value(Value), + inner: Arc>>, } impl Context { @@ -45,96 +22,47 @@ impl Context { } } - pub fn with_data(data: BTreeMap) -> Self { + pub fn with_data(data: BTreeMap) -> Self { Self { inner: Arc::new(RwLock::new(data)), } } - pub fn inherit_types_from(other: &Context) -> Result { - let mut new_data = BTreeMap::new(); + pub fn inherit_types_from(&self, other: &Context) -> Result<(), RwLockPoisonError> { + let mut self_data = self.inner.write()?; - for (identifier, (value_data, usage_data)) in other.inner.read()?.iter() { + for (identifier, value_data) in other.inner.read()?.iter() { if let ValueData::Type(r#type) = value_data { if let Type::Function { .. } = r#type { - new_data.insert(identifier.clone(), (value_data.clone(), usage_data.clone())); + self_data.insert(identifier.clone(), value_data.clone()); } } } - Ok(Self::with_data(new_data)) + Ok(()) } - pub fn inherit_data_from(other: &Context) -> Result { - let mut new_data = BTreeMap::new(); + pub fn inherit_data_from(&self, other: &Context) -> Result<(), RwLockPoisonError> { + let mut self_data = self.inner.write()?; - for (identifier, (value_data, usage_data)) in other.inner.read()?.iter() { - if let ValueData::Type(r#type) = value_data { - if let Type::Function { .. } = r#type { - new_data.insert(identifier.clone(), (value_data.clone(), usage_data.clone())); - } - } - if let ValueData::Value(value) = value_data { - if let ValueInner::Function { .. } = value.inner().as_ref() { - new_data.insert(identifier.clone(), (value_data.clone(), usage_data.clone())); - } - } + for (identifier, value_data) in other.inner.read()?.iter() { + self_data.insert(identifier.clone(), value_data.clone()); } - Ok(Self::with_data(new_data)) + Ok(()) } - pub fn add_allowance(&self, identifier: &Identifier) -> Result { - if let Some((_, usage_data)) = self.inner.read()?.get(identifier) { - usage_data.0.write()?.allowances += 1; - - Ok(true) - } else { - Ok(false) - } + pub fn contains(&self, identifier: &Identifier) -> Result { + Ok(self.inner.read()?.contains_key(identifier)) } - pub fn use_data( - &self, - identifier: &Identifier, - ) -> Result, RwLockPoisonError> { - let should_remove = - if let Some((value_data, usage_data)) = self.inner.read()?.get(identifier) { - let mut usage_data = usage_data.0.write()?; - - log::trace!("Adding use for variable: {identifier}"); - - usage_data.uses += 1; - - if usage_data.uses == usage_data.allowances { - true - } else { - return Ok(Some(value_data.clone())); - } - } else { - false + pub fn get_type(&self, identifier: &Identifier) -> Result, RwLockPoisonError> { + if let Some(value_data) = self.inner.read()?.get(identifier) { + let r#type = match value_data { + ValueData::Type(r#type) => r#type.clone(), + ValueData::Value(value) => value.r#type(), }; - if should_remove { - log::trace!("Removing varialble: {identifier}"); - - self.inner.write()?.remove(identifier); - } - - let value_data = match identifier.as_str() { - "output" => ValueData::Value(BuiltInFunction::output()), - _ => return Ok(None), - }; - - Ok(Some(value_data)) - } - - pub fn use_type(&self, identifier: &Identifier) -> Result, RwLockPoisonError> { - if let Some((ValueData::Type(r#type), usage_data)) = self.inner.read()?.get(identifier) { - log::trace!("Adding use for variable: {identifier}"); - - usage_data.0.write()?.uses += 1; - return Ok(Some(r#type.clone())); } @@ -146,43 +74,23 @@ impl Context { Ok(Some(r#type)) } - pub fn use_value(&self, identifier: &Identifier) -> Result, RwLockPoisonError> { - let should_remove = if let Some((ValueData::Value(value), usage_data)) = - self.inner.read()?.get(identifier) - { - let mut usage_data = usage_data.0.write()?; - - log::trace!("Adding use for variable: {identifier}"); - - usage_data.uses += 1; - - if usage_data.uses == usage_data.allowances { - true - } else { - return Ok(Some(value.clone())); - } + pub fn get_value(&self, identifier: &Identifier) -> Result, RwLockPoisonError> { + if let Some(ValueData::Value(value)) = self.inner.read()?.get(identifier) { + Ok(Some(value.clone())) } else { - false - }; + let value = match identifier.as_str() { + "output" => Value::built_in_function(BuiltInFunction::Output), + _ => return Ok(None), + }; - if should_remove { - log::trace!("Removing varialble: {identifier}"); - - self.inner.write()?.remove(identifier); + Ok(Some(value)) } - - let value = match identifier.as_str() { - "output" => BuiltInFunction::output(), - _ => return Ok(None), - }; - - Ok(Some(value)) } pub fn set_type(&self, identifier: Identifier, r#type: Type) -> Result<(), RwLockPoisonError> { self.inner .write()? - .insert(identifier, (ValueData::Type(r#type), UsageData::default())); + .insert(identifier, ValueData::Type(r#type)); Ok(()) } @@ -190,12 +98,14 @@ impl Context { pub fn set_value(&self, identifier: Identifier, value: Value) -> Result<(), RwLockPoisonError> { let mut inner = self.inner.write()?; - if let Some((_value_data, usage_data)) = inner.remove(&identifier) { - inner.insert(identifier, (ValueData::Value(value), usage_data)); - } else { - inner.insert(identifier, (ValueData::Value(value), UsageData::default())); - } + inner.insert(identifier, ValueData::Value(value)); Ok(()) } } + +#[derive(Clone, Debug, PartialEq)] +pub enum ValueData { + Type(Type), + Value(Value), +} diff --git a/src/error.rs b/src/error.rs index 9ce1148..adcac14 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,6 @@ -use std::sync::PoisonError; +use std::{ops::Range, sync::PoisonError}; -use ariadne::{Color, Label, Report, ReportKind}; +use ariadne::{Label, ReportBuilder}; use chumsky::{prelude::Rich, span::Span}; use crate::{ @@ -18,7 +18,10 @@ pub enum Error { expected: String, span: (usize, usize), }, - Runtime(RuntimeError), + Runtime { + error: RuntimeError, + position: SourcePosition, + }, Validation { error: ValidationError, position: SourcePosition, @@ -26,7 +29,10 @@ pub enum Error { } impl Error { - pub fn report(&self) -> Report { + pub fn build_report( + self, + mut builder: ReportBuilder<'_, Range>, + ) -> ReportBuilder<'_, Range> { match self { Error::Parse { expected, span } => { let message = match expected.as_str() { @@ -34,9 +40,7 @@ impl Error { expected => format!("Expected {expected}."), }; - Report::build(ReportKind::Custom("Lexing Error", Color::White), (), span.0) - .with_label(Label::new(span.0..span.1).with_message(message)) - .finish() + builder.add_label(Label::new(span.0..span.1).with_message(message)); } Error::Lex { expected, span } => { let message = match expected.as_str() { @@ -44,62 +48,59 @@ impl Error { expected => format!("Expected {expected}."), }; - Report::build(ReportKind::Custom("Lexing Error", Color::White), (), span.0) - .with_label(Label::new(span.0..span.1).with_message(message)) - .finish() + builder.add_label(Label::new(span.0..span.1).with_message(message)); } - Error::Runtime(_) => todo!(), - Error::Validation { error, position } => { - let mut report = Report::build( - ReportKind::Custom("Validation Error: The code was not run.", Color::White), - (), - 0, - ) - .with_label( - Label::new(position.0..position.1).with_message("Error found in this item."), - ); - - match error { - ValidationError::ExpectedBoolean => { - report = - report.with_label(Label::new(0..0).with_message("Expected boolean.")); - } - ValidationError::ExpectedIntegerOrFloat => { - report = report.with_label( - Label::new(0..0).with_message("Expected integer or float."), - ); - } - ValidationError::RwLockPoison(_) => todo!(), - ValidationError::TypeCheck { - conflict, - actual_position, - expected_position: expected_postion, - } => { - let TypeConflict { actual, expected } = conflict; - - report = report.with_labels([ - Label::new(expected_postion.0..expected_postion.1) - .with_message(format!("Type {expected} established here.")), - Label::new(actual_position.0..actual_position.1) - .with_message(format!("Got type {actual} here.")), - ]); - } - ValidationError::VariableNotFound(identifier) => { - report = report - .with_label(Label::new(0..0).with_message(format!( - "The variable {identifier} does not exist." - ))); - } - ValidationError::CannotIndex(_) => todo!(), - ValidationError::CannotIndexWith(_, _) => todo!(), - ValidationError::InterpreterExpectedReturn => todo!(), - ValidationError::ExpectedFunction => todo!(), - ValidationError::ExpectedValue => todo!(), + Error::Runtime { error, position } => match error { + RuntimeError::RwLockPoison(_) => todo!(), + RuntimeError::ValidationFailure(validation_error) => { + builder = + Error::Validation { + error: validation_error, + position, + } + .build_report(builder.with_note( + "The interpreter failed to catch this error during validation.", + )); } + }, + Error::Validation { error, position } => match error { + ValidationError::ExpectedBoolean => { + builder.add_label(Label::new(0..0).with_message("Expected boolean.")); + } + ValidationError::ExpectedIntegerOrFloat => { + builder.add_label(Label::new(0..0).with_message("Expected integer or float.")); + } + ValidationError::RwLockPoison(_) => todo!(), + ValidationError::TypeCheck { + conflict, + actual_position, + expected_position: expected_postion, + } => { + let TypeConflict { actual, expected } = conflict; - report.finish() - } + builder.add_labels([ + Label::new(expected_postion.0..expected_postion.1) + .with_message(format!("Type {expected} established here.")), + Label::new(actual_position.0..actual_position.1) + .with_message(format!("Got type {actual} here.")), + ]); + } + ValidationError::VariableNotFound(identifier) => { + builder.add_label( + Label::new(position.0..position.1) + .with_message(format!("The variable {identifier} does not exist.")) + .with_priority(1), + ); + } + ValidationError::CannotIndex(_) => todo!(), + ValidationError::CannotIndexWith(_, _) => todo!(), + ValidationError::InterpreterExpectedReturn => todo!(), + ValidationError::ExpectedFunction => todo!(), + ValidationError::ExpectedValue => todo!(), + }, } + + builder } } @@ -121,12 +122,6 @@ impl<'src> From>> for Error { } } -impl From for Error { - fn from(error: RuntimeError) -> Self { - Error::Runtime(error) - } -} - #[derive(Debug, PartialEq)] pub enum RuntimeError { RwLockPoison(RwLockPoisonError), diff --git a/src/lib.rs b/src/lib.rs index 0ff6f6a..1c4a31d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,12 @@ impl Interpreter { Action::Return(value) => Some(value), Action::None => continue, }, - Err(runtime_error) => return Err(vec![Error::Runtime(runtime_error)]), + Err(runtime_error) => { + return Err(vec![Error::Runtime { + error: runtime_error, + position: statement.position, + }]) + } } } diff --git a/src/main.rs b/src/main.rs index d17d66b..df416d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ //! Command line interface for the dust programming language. -use ariadne::Source; +use ariadne::{Color, Report, ReportKind, Source}; use clap::Parser; use colored::Colorize; @@ -53,9 +53,17 @@ fn main() { } } Err(errors) => { + let mut report_builder = + Report::build(ReportKind::Custom("Dust Error", Color::White), (), 5); + for error in errors { - error.report().eprint(Source::from(&source)).unwrap(); + report_builder = error.build_report(report_builder); } + + report_builder + .finish() + .eprint(Source::from(source)) + .unwrap() } } } diff --git a/src/parser.rs b/src/parser.rs index 6bf3890..f70392e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -656,6 +656,26 @@ mod tests { ),) .with_position((7, 9))]),) ); + assert_eq!( + parse(&lex("loop { if i > 2 { break } else { i += 1 } }").unwrap()).unwrap()[0].node, + Statement::Loop(Loop::new(vec![Statement::IfElse(IfElse::new( + Expression::Logic(Box::new(Logic::Greater( + Expression::Identifier(Identifier::new("i")).with_position((10, 11)), + Expression::Value(ValueNode::Integer(2)).with_position((14, 15)) + ))) + .with_position((10, 15)), + Block::new(vec![Statement::Break.with_position((18, 24))]), + Some(Block::new(vec![Statement::Assignment(Assignment::new( + Identifier::new("i"), + None, + AssignmentOperator::AddAssign, + Statement::Expression(Expression::Value(ValueNode::Integer(1))) + .with_position((38, 39)) + )) + .with_position((33, 39))])) + ),) + .with_position((7, 42))])) + ); } #[test] diff --git a/tests/values.rs b/tests/values.rs index 17559aa..0c13866 100644 --- a/tests/values.rs +++ b/tests/values.rs @@ -141,8 +141,8 @@ fn map_type_errors() { actual: Type::String, expected: Type::Boolean }, - actual_position: (0, 0).into(), - expected_position: (0, 0).into(), + actual_position: (15, 20).into(), + expected_position: (8, 13).into(), }, position: (0, 22).into() }]) diff --git a/tests/variables.rs b/tests/variables.rs index 8e34594..78fc7cc 100644 --- a/tests/variables.rs +++ b/tests/variables.rs @@ -30,8 +30,8 @@ fn set_variable_with_type_error() { actual: Type::Boolean, expected: Type::String }, - actual_position: (0, 0).into(), - expected_position: (0, 0).into() + actual_position: (14, 18).into(), + expected_position: (8, 12).into() }, position: (0, 18).into() }]) @@ -43,13 +43,13 @@ fn function_variable() { assert_eq!( interpret("foobar = (x: int): int { x }; foobar"), Ok(Some(Value::function( - vec![(Identifier::new("x"), Type::Integer.with_position((0, 0)))], - Type::Integer.with_position((0, 0)), + vec![(Identifier::new("x"), Type::Integer.with_position((13, 16)))], + Type::Integer.with_position((19, 23)), Block::new(vec![Statement::Expression(Expression::Identifier( Identifier::new("x") )) - .with_position((0, 0))]) - .with_position((0, 0)) + .with_position((25, 26))]) + .with_position((9, 28)) ))) ); }