diff --git a/dust-lang/src/abstract_tree.rs b/dust-lang/src/abstract_tree.rs index 4fb063a..7139175 100644 --- a/dust-lang/src/abstract_tree.rs +++ b/dust-lang/src/abstract_tree.rs @@ -54,7 +54,7 @@ pub enum Statement { operator: Node, value: Box>, }, - MutAssignment { + AssignmentMut { identifier: Node, value: Box>, }, @@ -135,6 +135,7 @@ pub enum Statement { // Hard-coded value Constant(Value), + ConstantMut(Value), // A statement that always returns None. Created with a semicolon, it causes the preceding // statement to return None. This is analagous to the semicolon in Rust. @@ -146,6 +147,7 @@ impl Statement { match self { Statement::AsyncBlock(_) => None, Statement::Assignment { .. } => None, + Statement::AssignmentMut { .. } => None, Statement::Block(statements) => statements.last().unwrap().inner.expected_type(context), Statement::BinaryOperation { left, @@ -192,6 +194,7 @@ impl Statement { }, Statement::BuiltInFunctionCall { function, .. } => function.expected_return_type(), Statement::Constant(value) => Some(value.r#type()), + Statement::ConstantMut(value) => Some(value.r#type()), Statement::FieldsStructInstantiation { name, .. } => context.get_type(&name.inner), Statement::Invokation { invokee: function, .. @@ -218,7 +221,6 @@ impl Statement { Some(Type::Map(types)) } - Statement::MutAssignment { .. } => None, Statement::Nil(_) => None, Statement::UnaryOperation { operator, operand } => match operator.inner { UnaryOperator::Negate => Some(operand.inner.expected_type(context)?), @@ -254,6 +256,9 @@ impl Display for Statement { } => { write!(f, "{identifier} {operator} {value}") } + Statement::AssignmentMut { identifier, value } => { + write!(f, "mut {identifier} = {value}") + } Statement::AsyncBlock(statements) => { write!(f, "async {{ ")?; @@ -340,6 +345,7 @@ impl Display for Statement { write!(f, ")") } Statement::Constant(value) => write!(f, "{value}"), + Statement::ConstantMut(value) => write!(f, "{value}"), Statement::FieldsStructInstantiation { name, fields } => { write!(f, "{name} {{ ")?; @@ -452,9 +458,6 @@ impl Display for Statement { write!(f, "}}") } - Statement::MutAssignment { identifier, value } => { - write!(f, "mut {identifier} = {value}") - } Statement::Nil(node) => write!(f, "{node};"), Statement::UnaryOperation { operator, operand } => { let operator = match operator.inner { diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index 54bbb15..4a4b2d6 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -91,6 +91,20 @@ impl<'a> Analyzer<'a> { }); } } + Statement::AssignmentMut { identifier, value } => { + self.analyze_statement(value)?; + + let value_type = value.inner.expected_type(self.context); + + if let Some(r#type) = value_type { + self.context + .set_type(identifier.inner.clone(), r#type, identifier.position); + } else { + return Err(AnalyzerError::ExpectedValue { + actual: value.as_ref().clone(), + }); + } + } Statement::AsyncBlock(statements) => { for statement in statements { self.analyze_statement(statement)?; @@ -294,6 +308,7 @@ impl<'a> Analyzer<'a> { } } Statement::Constant(_) => {} + Statement::ConstantMut(_) => {} Statement::FieldsStructInstantiation { name, fields: field_arguments, @@ -479,20 +494,6 @@ impl<'a> Analyzer<'a> { self.analyze_statement(value_node)?; } } - Statement::MutAssignment { identifier, value } => { - self.analyze_statement(value)?; - - let value_type = value.inner.expected_type(self.context); - - if let Some(r#type) = value_type { - self.context - .set_type(identifier.inner.clone(), r#type, identifier.position); - } else { - return Err(AnalyzerError::ExpectedValue { - actual: value.as_ref().clone(), - }); - } - } Statement::Nil(node) => { self.analyze_statement(node)?; } diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index 9a39451..cba0319 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -284,10 +284,17 @@ impl<'src> Parser<'src> { .parse() .map_err(|error| ParseError::BooleanError { error, position })?; - Ok(Node::new( - Statement::Constant(Value::boolean(boolean)), - position, - )) + if let ParserMode::Mutable = self.mode { + Ok(Node::new( + Statement::ConstantMut(Value::boolean_mut(boolean)), + position, + )) + } else { + Ok(Node::new( + Statement::Constant(Value::boolean(boolean)), + position, + )) + } } (Token::Float(text), position) => { self.next_token()?; @@ -479,10 +486,17 @@ impl<'src> Parser<'src> { (Token::String(string), position) => { self.next_token()?; - Ok(Node::new( - Statement::Constant(Value::string(string)), - position, - )) + if let ParserMode::Mutable = self.mode { + Ok(Node::new( + Statement::ConstantMut(Value::string_mut(string)), + position, + )) + } else { + Ok(Node::new( + Statement::Constant(Value::string(string)), + position, + )) + } } (Token::LeftCurlyBrace, left_position) => { self.next_token()?; @@ -708,11 +722,11 @@ impl<'src> Parser<'src> { }); } - let value = Box::new(self.parse_statement(0)?); + let value = Box::new(self.parse_statement_in_mode(ParserMode::Mutable, 0)?); let value_end = value.position.1; Ok(Node::new( - Statement::MutAssignment { identifier, value }, + Statement::AssignmentMut { identifier, value }, (left_position.0, value_end), )) } @@ -1142,6 +1156,7 @@ impl<'src> Parser<'src> { #[derive(Debug, PartialEq, Clone, Copy)] enum ParserMode { Condition, + Mutable, None, } @@ -1258,20 +1273,20 @@ mod tests { #[test] fn mutable_variable() { - let input = "mut x = 42"; + let input = "mut x = false"; assert_eq!( parse(input), Ok(AbstractSyntaxTree { nodes: [Node::new( - Statement::MutAssignment { + Statement::AssignmentMut { identifier: Node::new(Identifier::new("x"), (4, 5)), value: Box::new(Node::new( - Statement::Constant(Value::integer(42)), - (8, 10) + Statement::ConstantMut(Value::boolean_mut(false)), + (8, 13) )), }, - (0, 10) + (0, 13) )] .into() }) diff --git a/dust-lang/src/value.rs b/dust-lang/src/value.rs index 9920a42..cafea0e 100644 --- a/dust-lang/src/value.rs +++ b/dust-lang/src/value.rs @@ -95,6 +95,16 @@ impl Value { Value::Mutable(Arc::new(RwLock::new(ValueData::Boolean(boolean)))) } + pub fn string_mut(to_string: T) -> Self { + Value::Mutable(Arc::new(RwLock::new(ValueData::String( + to_string.to_string(), + )))) + } + + pub fn is_mutable(&self) -> bool { + matches!(self, Value::Mutable(_)) + } + pub fn to_mut(self) -> Self { match self { Value::Immutable(inner) => { @@ -104,7 +114,7 @@ impl Value { } } - pub fn mutate(&self, other: &Value) -> Result<(), VmError> { + pub fn mutate(&self, other: &Value) { let other_data = match other { Value::Immutable(inner) => inner.as_ref().clone(), Value::Mutable(inner_locked) => inner_locked.read().unwrap().clone(), @@ -113,8 +123,6 @@ impl Value { match self { Value::Mutable(locked) => { *locked.write().unwrap() = other_data; - - Ok(()) } Value::Immutable(_) => todo!(), } @@ -254,7 +262,7 @@ impl Value { } } (Value::Immutable(left), Value::Mutable(right)) => { - match (&*right.read().unwrap(), left.as_ref()) { + match (left.as_ref(), &*right.read().unwrap()) { (ValueData::Float(left), ValueData::Float(right)) => { return Ok(Value::float(left + right)); } @@ -286,6 +294,50 @@ impl Value { Err(ValueError::CannotAdd(self.clone(), other.clone())) } + pub fn add_mut(&self, other: &Value) -> Result<(), ValueError> { + match (self, other) { + (Value::Mutable(left), Value::Mutable(right)) => { + match (&mut *left.write().unwrap(), &*right.read().unwrap()) { + (ValueData::Float(left), ValueData::Float(right)) => { + *left += right; + return Ok(()); + } + (ValueData::Integer(left), ValueData::Integer(right)) => { + *left = left.saturating_add(*right); + return Ok(()); + } + (ValueData::String(left), ValueData::String(right)) => { + left.push_str(right); + return Ok(()); + } + _ => {} + } + } + (Value::Mutable(left), Value::Immutable(right)) => { + match (&mut *left.write().unwrap(), right.as_ref()) { + (ValueData::Float(left), ValueData::Float(right)) => { + *left += right; + return Ok(()); + } + (ValueData::Integer(left), ValueData::Integer(right)) => { + *left = left.saturating_add(*right); + return Ok(()); + } + (ValueData::String(left), ValueData::String(right)) => { + left.push_str(right); + return Ok(()); + } + _ => {} + } + } + (Value::Immutable(_), _) => { + return Err(ValueError::CannotMutate(self.clone())); + } + } + + Err(ValueError::CannotAdd(self.clone(), other.clone())) + } + pub fn subtract(&self, other: &Value) -> Result { match (self, other) { (Value::Immutable(left), Value::Immutable(right)) => { diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index ed3732e..40f7428 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -137,14 +137,24 @@ impl Vm { position: value_position, }); }; - let new_value = left_value.add(&right_value).map_err(|value_error| { - VmError::ValueError { - error: value_error, - position: (identifier.position.0, value_position.1), - } - })?; - self.context.set_value(identifier.inner, new_value); + if left_value.is_mutable() { + left_value.add_mut(&right_value).map_err(|value_error| { + VmError::ValueError { + error: value_error, + position: (identifier.position.0, value_position.1), + } + })?; + } else { + let new_value = left_value.add(&right_value).map_err(|value_error| { + VmError::ValueError { + error: value_error, + position: (identifier.position.0, value_position.1), + } + })?; + + self.context.set_value(identifier.inner, new_value); + } Ok(None) } @@ -649,7 +659,7 @@ impl Vm { Ok(Some(Value::map(values))) } - Statement::MutAssignment { identifier, value } => { + Statement::AssignmentMut { identifier, value } => { let position = value.position; let value = if let Some(value) = self.run_statement(*value)? { value.to_mut() @@ -661,6 +671,7 @@ impl Vm { Ok(None) } + Statement::ConstantMut(value) => Ok(Some(value)), Statement::Nil(node) => { let _return = self.run_statement(*node)?; @@ -881,6 +892,20 @@ mod tests { use super::*; + #[test] + fn mutate_variable() { + let input = " + mut x = '' + + x += 'foo' + x += 'bar' + + x + "; + + assert_eq!(run(input), Ok(Some(Value::string_mut("foobar")))); + } + #[test] fn async_block() { let input = "x = 1; async { x += 1; x -= 1; } x";