feat: 添加for-in循环支持,更新解析器、解释器和相关测试用例

This commit is contained in:
0264408
2026-06-16 19:10:26 +08:00
parent 75c1d230ad
commit 8b20580402
6 changed files with 215 additions and 3 deletions
+118
View File
@@ -102,6 +102,42 @@ impl Interpreter {
}
Ok(Signal::None)
}
Stmt::ForIn { var_name, iterable, body } => {
let iter_val = self.evaluate(iterable)?;
let items: Vec<Value> = match &iter_val {
Value::Array(arr) => arr.borrow().clone(),
Value::Object(obj) => obj.borrow().keys().map(|k| Value::String(k.clone())).collect(),
Value::String(s) => s.chars().map(|c| Value::String(c.to_string())).collect(),
_ => {
return Err(RuntimeError::RuntimeError {
message: "for-in requires an array, object, or string".into(),
token: None,
});
}
};
let mut signal = Signal::None;
for item in items {
// Create a new scope for each iteration so the loop variable
// is isolated and break/continue clean up correctly.
let previous = Rc::clone(&self.env);
self.env = Rc::new(RefCell::new(Env::new(Some(previous))));
self.env.borrow_mut().define(var_name.clone(), item, true);
signal = self.execute(*body.clone())?;
// Restore parent scope before checking signal
let parent = self.env.borrow().parent.as_ref().unwrap().clone();
self.env = parent;
match signal {
Signal::Break => { signal = Signal::None; break; }
Signal::Continue => { signal = Signal::None; continue; }
sig @ Signal::Return(_) => return Ok(sig),
Signal::None => {}
}
}
Ok(signal)
}
Stmt::Function { name, params, body } => {
let func = Value::Function(Rc::new(Function {
params,
@@ -1612,5 +1648,87 @@ mod tests {
});
assert!(result.is_err(), "Expected error for pop on non-array");
}
// =========================================================================
// For-In loops
// =========================================================================
#[test]
fn eval_forin_array_elements() {
// Sum elements via for-in
let interp = run("let sum = 0; let arr = [1, 2, 3]; for (x in arr) { sum = sum + x; }");
assert_num(&get_var(&interp, "sum"), 6.0);
}
#[test]
fn eval_forin_empty_array() {
let interp = run("let count = 0; let arr = []; for (x in arr) { count = count + 1; }");
assert_num(&get_var(&interp, "count"), 0.0);
}
#[test]
fn eval_forin_object_keys() {
let interp = run("let keys = \"\"; let obj = {a: 1, b: 2}; for (k in obj) { keys = keys + k; }");
match get_var(&interp, "keys") {
Value::String(s) => {
// Object iteration order is not guaranteed, so check length
assert_eq!(s.len(), 2);
assert!(s.contains('a'));
assert!(s.contains('b'));
}
v => panic!("Expected String, got {:?}", v),
}
}
#[test]
fn eval_forin_string_chars() {
let interp = run("let result = \"\"; for (c in \"ab\") { result = result + c; }");
match get_var(&interp, "result") {
Value::String(s) => assert_eq!(s, "ab"),
v => panic!("Expected String, got {:?}", v),
}
}
#[test]
fn eval_forin_break() {
let interp = run("let sum = 0; for (x in [1, 2, 3, 4]) { if (x == 3) { break; } sum = sum + x; }");
assert_num(&get_var(&interp, "sum"), 3.0); // 1 + 2
}
#[test]
fn eval_forin_continue() {
let interp = run("let sum = 0; for (x in [1, 2, 3]) { if (x == 2) { continue; } sum = sum + x; }");
assert_num(&get_var(&interp, "sum"), 4.0); // 1 + 3
}
#[test]
fn eval_forin_loop_var_doesnt_leak() {
// Loop variable should not be accessible outside the loop
let interp = run("for (x in [1]) { let _ = x; }");
// x should not exist in the outer scope
match get_var(&interp, "x") {
Value::Nil => {} // Expected: x is not defined
v => panic!("Expected Nil (undefined), got {:?}", v),
}
}
#[test]
fn eval_forin_with_object_array() {
// Iterate over an array of objects
let interp = run("
let arr = [{v: 10}, {v: 20}];
let total = 0;
for (obj in arr) { total = total + obj.v; }
");
assert_num(&get_var(&interp, "total"), 30.0);
}
#[test]
fn error_forin_non_iterable() {
let result = std::panic::catch_unwind(|| {
run("for (x in 42) { let _ = x; }"); // Can't iterate a number
});
assert!(result.is_err(), "Expected error for for-in on non-iterable");
}
}