diff --git a/src/interpreter/interpreter.rs b/src/interpreter/interpreter.rs index e50193e..06748c2 100644 --- a/src/interpreter/interpreter.rs +++ b/src/interpreter/interpreter.rs @@ -294,14 +294,20 @@ impl Interpreter { let l = self.evaluate(*left)?; let r = self.evaluate(*right)?; match op { - crate::ast::expr::BinaryOp::Add => match (l, r) { - (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a+b)), - (Value::String(a), Value::String(b)) => Ok(Value::String(a+&b)), - _ => Err(RuntimeError::RuntimeError { - message: "Invalid '+' operands".to_string(), - token: None, - }), - }, + crate::ast::expr::BinaryOp::Add => { + // If either operand is a string, coerce both to string and concatenate. + if matches!(l, Value::String(_)) || matches!(r, Value::String(_)) { + Ok(Value::String(format!("{}{}", l, r))) + } else { + match (l, r) { + (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a+b)), + _ => Err(RuntimeError::RuntimeError { + message: "Invalid '+' operands".to_string(), + token: None, + }), + } + } + } crate::ast::expr::BinaryOp::Sub => match (l,r) { (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a-b)), _ => Err(RuntimeError::RuntimeError { @@ -468,9 +474,13 @@ impl Interpreter { 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)) + if let Value::String(s) = &left { + Ok(Value::String(format!("{}{}", s, right))) + } else { + 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)?; @@ -667,6 +677,55 @@ mod tests { } } + #[test] + fn eval_string_plus_number() { + match eval_expr("\"a\" + 42") { + Value::String(s) => assert_eq!(s, "a42"), + v => panic!("Expected String, got {:?}", v), + } + } + + #[test] + fn eval_number_plus_string() { + match eval_expr("42 + \"a\"") { + Value::String(s) => assert_eq!(s, "42a"), + v => panic!("Expected String, got {:?}", v), + } + } + + #[test] + fn eval_string_plus_bool() { + match eval_expr("\"t\" + true") { + Value::String(s) => assert_eq!(s, "ttrue"), + v => panic!("Expected String, got {:?}", v), + } + } + + #[test] + fn eval_bool_plus_string() { + match eval_expr("false + \"x\"") { + Value::String(s) => assert_eq!(s, "falsex"), + v => panic!("Expected String, got {:?}", v), + } + } + + #[test] + fn eval_string_plus_nil() { + match eval_expr("\"x\" + nil") { + Value::String(s) => assert_eq!(s, "xnil"), + v => panic!("Expected String, got {:?}", v), + } + } + + #[test] + fn eval_plusequal_string_concat() { + let interp = run("let s = \"hello\"; s += \" world\"; s += 42;"); + match get_var(&interp, "s") { + Value::String(s) => assert_eq!(s, "hello world42"), + v => panic!("Expected String, got {:?}", v), + } + } + // ========================================================================= // Comparison // ========================================================================= @@ -1324,11 +1383,11 @@ mod tests { } #[test] - fn error_type_mismatch_add_number_and_string() { - let result = std::panic::catch_unwind(|| { - eval_expr("1 + \"hello\""); - }); - assert!(result.is_err(), "Expected error for adding number + string"); + fn eval_number_plus_string_concatenates() { + match eval_expr("1 + \"hello\"") { + Value::String(s) => assert_eq!(s, "1hello"), + v => panic!("Expected String, got {:?}", v), + } } #[test]