diff --git a/examples/assign_op.ast b/examples/assign_op.ast new file mode 100644 index 0000000..5e7d2fd --- /dev/null +++ b/examples/assign_op.ast @@ -0,0 +1,15 @@ +let a = 1 +a += 2 +print("a=", a) + +let b = 2 +b -= 1 +print("b=", b) + +let c = 5 +c *= 5 +print("c=", c) + +let d = 10 +c /= 5 +print("d=", d) \ No newline at end of file diff --git a/src/ast/expr.rs b/src/ast/expr.rs index f20ba39..87772d5 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -11,6 +11,7 @@ pub enum Expr { /// 赋值表达式:a = b Assign { name: String, + op: AssignOp, value: Box, }, @@ -24,6 +25,7 @@ pub enum Expr { Set { object: Box, name: String, + op: AssignOp, value: Box, }, @@ -47,6 +49,7 @@ pub enum Expr { IndexSet { array: Box, index: Box, + op: AssignOp, value: Box, }, @@ -118,3 +121,12 @@ pub enum LogicalOp { And, Or, } + +#[derive(Debug, Clone, Copy)] +pub enum AssignOp { + Equal, // = + PlusEqual, // += + MinusEqual, // -= + StarEqual, // *= + SlashEqual, // /= +} diff --git a/src/interpreter/interpreter.rs b/src/interpreter/interpreter.rs index eff1c7f..5bd707a 100644 --- a/src/interpreter/interpreter.rs +++ b/src/interpreter/interpreter.rs @@ -141,8 +141,15 @@ impl Interpreter { }), } } - Expr::Assign { name, value } => { - let val = self.evaluate(*value)?; + Expr::Assign { name, op, value } => { + let rhs = self.evaluate(*value)?; + let val = match op { + AssignOp::Equal => rhs, + _ => { + let current = self.env.borrow().get(&name).ok_or_else(|| RuntimeError::RuntimeError { message: format!("Undefined variable '{}'", name), token: None })?; + self.apply_assign_op(current.clone(), rhs, op)? + } + }; if !self.env.borrow_mut().assign(&name, val.clone()) { return Err(RuntimeError::RuntimeError { message: format!("Undefined variable '{}'", name), @@ -169,11 +176,19 @@ impl Interpreter { }), } } - Expr::Set { object, name, value } => { + Expr::Set { object, name, op, value } => { let obj = self.evaluate(*object)?; + let rhs = self.evaluate(*value)?; match obj { Value::Object(map) => { - let val = self.evaluate(*value)?; + let val = match op { + AssignOp::Equal => rhs, + _ => { + let map_borrow = map.borrow(); + let current = map_borrow.get(&name).ok_or_else(|| RuntimeError::RuntimeError { message: format!("Property '{}' does not exist", name), token: None })?; + self.apply_assign_op(current.clone(), rhs, op)? + } + }; map.borrow_mut().insert(name, val.clone()); Ok(val) } @@ -208,10 +223,10 @@ impl Interpreter { }), } } - Expr::IndexSet { array, index, value } => { + Expr::IndexSet { array, index, op, value } => { let arr = self.evaluate(*array)?; let idx = self.evaluate(*index)?; - let val = self.evaluate(*value)?; + let rhs = self.evaluate(*value)?; let i = self.as_array_index(&idx)?; match arr { Value::Array(vec) => { @@ -222,6 +237,13 @@ impl Interpreter { token: None, }); } + let val = match op { + AssignOp::Equal => rhs, + _ => { + let current = vec[i].clone(); + self.apply_assign_op(current, rhs, op)? + } + }; vec[i] = val.clone(); Ok(val) } @@ -398,6 +420,32 @@ impl Interpreter { } } + fn apply_assign_op(&self, left: Value, right: Value, op: AssignOp) -> Result { + match op { + AssignOp::Equal => Ok(right), + AssignOp::PlusEqual => { + let left_num = self.as_number(&left)?; + let right_num = self.as_number(&right)?; + Ok(Value::Number(left_num + right_num)) + } + AssignOp::MinusEqual => { + let left_num = self.as_number(&left)?; + let right_num = self.as_number(&right)?; + Ok(Value::Number(left_num - right_num)) + } + AssignOp::StarEqual => { + let left_num = self.as_number(&left)?; + let right_num = self.as_number(&right)?; + Ok(Value::Number(left_num * right_num)) + } + AssignOp::SlashEqual => { + let left_num = self.as_number(&left)?; + let right_num = self.as_number(&right)?; + Ok(Value::Number(left_num / right_num)) + } + } + } + fn as_number(&self, val: &Value) -> Result { if let Value::Number(n) = val { Ok(*n) diff --git a/src/lexer/lexer.rs b/src/lexer/lexer.rs index 05c8746..508a0a3 100644 --- a/src/lexer/lexer.rs +++ b/src/lexer/lexer.rs @@ -56,9 +56,27 @@ impl Lexer { ',' => TokenKind::Comma, ';' => TokenKind::Semicolon, - '+' => TokenKind::Plus, - '-' => TokenKind::Minus, - '*' => TokenKind::Star, + '+' => { + if self.match_char('=') { + TokenKind::PlusEqual + } else { + TokenKind::Plus + } + } + '-' => { + if self.match_char('=') { + TokenKind::MinusEqual + } else { + TokenKind::Minus + } + } + '*' => { + if self.match_char('=') { + TokenKind::StarEqual + } else { + TokenKind::Star + } + } '.' => TokenKind::Dot, ':' => TokenKind::Colon, '[' => TokenKind::LeftBracket, @@ -71,6 +89,8 @@ impl Lexer { self.advance(); } return None; + } else if self.match_char('=') { + TokenKind::SlashEqual } else { TokenKind::Slash } diff --git a/src/lexer/token.rs b/src/lexer/token.rs index 4690664..4d7f75d 100644 --- a/src/lexer/token.rs +++ b/src/lexer/token.rs @@ -16,6 +16,7 @@ pub enum TokenKind { Less, LessEqual, EqualEqual, BangEqual, AndAnd, OrOr, + PlusEqual, MinusEqual, StarEqual, SlashEqual, // 字面量 Identifier(String), diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 301d1b2..a7c1b62 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -202,12 +202,26 @@ impl Parser { fn assignment(&mut self) -> Expr { let expr = self.logical_or(); - if self.match_kind(&[TokenKind::Equal]) { + let op = if self.match_kind(&[TokenKind::Equal]) { + Some(AssignOp::Equal) + } else if self.match_kind(&[TokenKind::PlusEqual]) { + Some(AssignOp::PlusEqual) + } else if self.match_kind(&[TokenKind::MinusEqual]) { + Some(AssignOp::MinusEqual) + } else if self.match_kind(&[TokenKind::StarEqual]) { + Some(AssignOp::StarEqual) + } else if self.match_kind(&[TokenKind::SlashEqual]) { + Some(AssignOp::SlashEqual) + } else { + None + }; + + if let Some(op) = op { let value = self.assignment(); return match expr { - Expr::Variable(name) => Expr::Assign { name, value: Box::new(value) }, - Expr::Get { object, name } => Expr::Set { object, name, value: Box::new(value) }, - Expr::IndexGet { array, index } => Expr::IndexSet { array, index, value: Box::new(value) }, + Expr::Variable(name) => Expr::Assign { name, op, value: Box::new(value) }, + Expr::Get { object, name } => Expr::Set { object, name, op, value: Box::new(value) }, + Expr::IndexGet { array, index } => Expr::IndexSet { array, index, op, value: Box::new(value) }, _ => panic!("Invalid assignment target."), }; }