Add more value mutability
This commit is contained in:
parent
8666f1cd9b
commit
e7b5390a55
@ -54,7 +54,7 @@ pub enum Statement {
|
||||
operator: Node<AssignmentOperator>,
|
||||
value: Box<Node<Statement>>,
|
||||
},
|
||||
MutAssignment {
|
||||
AssignmentMut {
|
||||
identifier: Node<Identifier>,
|
||||
value: Box<Node<Statement>>,
|
||||
},
|
||||
@ -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 {
|
||||
|
@ -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)?;
|
||||
}
|
||||
|
@ -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()
|
||||
})
|
||||
|
@ -95,6 +95,16 @@ impl Value {
|
||||
Value::Mutable(Arc::new(RwLock::new(ValueData::Boolean(boolean))))
|
||||
}
|
||||
|
||||
pub fn string_mut<T: ToString>(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<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(Value::Immutable(left), Value::Immutable(right)) => {
|
||||
|
@ -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";
|
||||
|
Loading…
Reference in New Issue
Block a user