Skip to content

Commit 4e7a515

Browse files
committed
feat: Implement for-in loop with coroutine-based iteration protocol and object prototype methods.
1 parent 908f3db commit 4e7a515

18 files changed

+960
-287
lines changed

Cargo.lock

Lines changed: 139 additions & 146 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo_codes/test_loops.ch

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
let io = import "stdlib/io"
2+
3+
# 1. Test condition-based loop (Go-style while)
4+
let i = 0
5+
for i < 5 {
6+
io.println("for i < 5: " + i)
7+
i = i + 1
8+
}
9+
10+
# 2. Test infinite loop with break
11+
let k = 0
12+
for {
13+
if k >= 3 {
14+
break
15+
}
16+
io.println("infinite for k: " + k)
17+
k = k + 1
18+
}
19+
20+
io.println("--- Array.iter() (Values only) ---")
21+
let arr = [10, 20, 30]
22+
for x in arr {
23+
io.println("arr x: " + x)
24+
}
25+
26+
io.println("--- Array.entries() (Key-Value pairs) ---")
27+
for entry in arr:entries() {
28+
io.println("index: " + entry.key + ", value: " + entry.value)
29+
}
30+
31+
io.println("--- Object.iter() (Values only) ---")
32+
let obj = ${ a: 1, b: 2 }
33+
for v in obj {
34+
io.println("obj v: " + v)
35+
}
36+
37+
io.println("--- Object.entries() (Key-Value pairs) ---")
38+
for entry in obj:entries() {
39+
io.println("entry key: " + entry.key + ", value: " + entry.value)
40+
}
41+
42+
io.println("--- Object.keys() ---")
43+
let keys = obj:keys()
44+
for k in keys {
45+
io.println("key: " + k)
46+
}
47+
48+
io.println("--- String.iter() ---")
49+
let s = "ABC"
50+
for char in s {
51+
io.println("char: " + char)
52+
}

src/chen.pest

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ CATCH = @{ "catch" ~ !(ASCII_ALPHANUMERIC | "_") }
2222
FINALLY = @{ "finally" ~ !(ASCII_ALPHANUMERIC | "_") }
2323
THROW = @{ "throw" ~ !(ASCII_ALPHANUMERIC | "_") }
2424
IMPORT = @{ "import" ~ !(ASCII_ALPHANUMERIC | "_") }
25+
IN = @{ "in" ~ !(ASCII_ALPHANUMERIC | "_") }
2526
// NULL removed
2627

2728
// 标识符 (不能是关键字)
2829
identifier = @{ !keyword ~ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }
29-
keyword = { LET | FOR | DEF | RETURN | IF | ELSE | TRUE | FALSE | BREAK | CONTINUE | TRY | CATCH | FINALLY | THROW | IMPORT }
30+
keyword = { LET | FOR | DEF | RETURN | IF | ELSE | TRUE | FALSE | BREAK | CONTINUE | TRY | CATCH | FINALLY | THROW | IMPORT | IN }
3031

3132
// 字面量
3233
integer = @{ "-"? ~ ASCII_DIGIT+ }
@@ -103,7 +104,6 @@ statement = {
103104
continue_stmt |
104105
try_catch |
105106
throw_stmt |
106-
throw_stmt |
107107
assignment |
108108
expression
109109
}
@@ -112,7 +112,8 @@ declaration = { LET ~ identifier ~ assign ~ NEWLINE* ~ expression }
112112
assignment_target = { identifier ~ postfix* }
113113
assignment = { assignment_target ~ assign ~ NEWLINE* ~ expression }
114114

115-
for_loop = { FOR ~ NEWLINE* ~ expression ~ block }
115+
for_loop = { FOR ~ NEWLINE* (for_in_header | expression)? ~ block }
116+
for_in_header = { identifier ~ IN ~ expression }
116117
function_def = { DEF ~ identifier? ~ "(" ~ NEWLINE* ~ parameters? ~ NEWLINE* ~ ")" ~ block }
117118
return_stmt = { RETURN ~ NEWLINE* ~ expression }
118119
break_stmt = { BREAK }

src/compiler.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ impl<'a> Compiler<'a> {
380380
self.emit(Instruction::Pop, loc);
381381
}
382382
Statement::Loop(e) => self.compile_loop(e),
383+
Statement::ForIn(e) => self.compile_for_in(e),
383384
Statement::Assign(e) => self.compile_assign(e),
384385
Statement::Break(loc) => {
385386
let end_label = self
@@ -858,6 +859,130 @@ impl<'a> Compiler<'a> {
858859
);
859860
}
860861

862+
fn compile_for_in(&mut self, for_in: ForInLoop) {
863+
let loc = for_in.loc;
864+
let unique_id = self.unique_id();
865+
let loop_start = format!("for_in_start_{}", unique_id);
866+
let loop_end = format!("for_in_end_{}", unique_id);
867+
let iter_var = format!("@iter_{}", unique_id);
868+
869+
self.begin_scope();
870+
871+
// 1. Compile iterable, call :iter() on it, and store it in a hidden local variable
872+
self.compile_expression(for_in.iterable);
873+
self.emit(Instruction::GetMethod("iter".to_string()), loc); // [iterable, iter_fn, iterable]
874+
self.emit(Instruction::CallStack(1), loc); // [coroutine]
875+
let iter_loc = self.define_variable(iter_var);
876+
match iter_loc {
877+
VarLocation::Local(offset) => {
878+
self.emit(Instruction::MovePlusFP(offset as usize), loc);
879+
}
880+
_ => unreachable!("Hidden iterator variable must be local"),
881+
}
882+
883+
// loop_start label
884+
self.program.syms.insert(
885+
loop_start.clone(),
886+
Symbol {
887+
location: self.program.instructions.len() as i32,
888+
narguments: 0,
889+
nlocals: 0,
890+
upvalues: Vec::new(),
891+
},
892+
);
893+
894+
// 2. Check status: coroutine.status(iterable)
895+
self.emit(Instruction::Load("coroutine".to_string()), loc); // [coroutine]
896+
self.emit(Instruction::GetField("status".to_string()), loc); // [status_fn]
897+
match iter_loc {
898+
VarLocation::Local(offset) => {
899+
self.emit(Instruction::DupPlusFP(offset), loc); // [status_fn, iterable]
900+
}
901+
_ => unreachable!(),
902+
}
903+
self.emit(Instruction::CallStack(1), loc); // [status_val]
904+
self.emit(Instruction::Push(crate::value::Value::string("dead".to_string())), loc);
905+
self.emit(Instruction::Equal, loc);
906+
self.emit(Instruction::JumpIfTrue(loop_end.clone()), loc);
907+
908+
self.emit(Instruction::Load("coroutine".to_string()), loc); // [resume_fn, iterable, coroutine]
909+
self.emit(Instruction::GetField("resume".to_string()), loc); // [resume_fn, iterable, resume_fn]
910+
match iter_loc {
911+
VarLocation::Local(offset) => {
912+
self.emit(Instruction::DupPlusFP(offset), loc); // [resume_fn, iterable, resume_fn, iterable]
913+
}
914+
_ => unreachable!(),
915+
}
916+
self.emit(Instruction::CallStack(1), loc); // [resume_fn, iterable, yielded_val]
917+
918+
// 3.5 Check status again after resume - if it just died, the returned value is the final result, not an iteration item.
919+
self.emit(Instruction::Load("coroutine".to_string()), loc); // [resume_fn, iterable, yielded_val, coroutine]
920+
self.emit(Instruction::GetField("status".to_string()), loc); // [resume_fn, iterable, yielded_val, status_fn]
921+
match iter_loc {
922+
VarLocation::Local(offset) => {
923+
self.emit(Instruction::DupPlusFP(offset), loc); // [..., status_fn, iterable]
924+
}
925+
_ => unreachable!(),
926+
}
927+
self.emit(Instruction::CallStack(1), loc); // [resume_fn, iterable, yielded_val, status_val]
928+
self.emit(Instruction::Push(crate::value::Value::string("dead".to_string())), loc);
929+
self.emit(Instruction::Equal, loc);
930+
let continue_label = format!("for_in_continue_{}", unique_id);
931+
self.emit(Instruction::JumpIfFalse(continue_label.clone()), loc);
932+
self.emit(Instruction::Pop, loc); // Pop yielded_val since coroutine is dead
933+
self.emit(Instruction::Jump(loop_end.clone()), loc);
934+
935+
self.program.syms.insert(
936+
continue_label.clone(),
937+
Symbol {
938+
location: self.program.instructions.len() as i32,
939+
narguments: 0,
940+
nlocals: 0,
941+
upvalues: Vec::new(),
942+
},
943+
);
944+
945+
// 4. Define loop variable and assign yielded value
946+
self.begin_scope();
947+
let var_loc = self.define_variable(for_in.var);
948+
match var_loc {
949+
VarLocation::Local(offset) => {
950+
self.emit(Instruction::MovePlusFP(offset as usize), loc);
951+
}
952+
VarLocation::Global(name) => {
953+
self.emit(Instruction::Store(name), loc);
954+
}
955+
VarLocation::Upvalue(_) => unreachable!(),
956+
}
957+
958+
self.current_state().loop_stack.push(LoopLabels {
959+
start: loop_start.clone(),
960+
end: loop_end.clone(),
961+
});
962+
963+
// 5. Body
964+
for stmt in for_in.body {
965+
self.compile_statement(stmt);
966+
}
967+
968+
self.end_scope(loc, false);
969+
self.current_state().loop_stack.pop();
970+
971+
self.emit(Instruction::Jump(loop_start.clone()), loc);
972+
973+
// loop_end label
974+
self.program.syms.insert(
975+
loop_end.clone(),
976+
Symbol {
977+
location: self.program.instructions.len() as i32,
978+
narguments: 0,
979+
nlocals: 0,
980+
upvalues: Vec::new(),
981+
},
982+
);
983+
self.end_scope(loc, false);
984+
}
985+
861986
fn compile_try_catch(&mut self, tc: TryCatch) {
862987
let loc = tc.loc;
863988
let unique_id = self.unique_id();

src/expression.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ pub enum Statement {
105105
Return(Return),
106106
Local(Local),
107107
Assign(Assign),
108+
/// For-In 循环: for var in iterable { body }
109+
ForIn(ForInLoop),
108110
/// 设置属性: obj.field = value
109111
SetField {
110112
object: Expression,
@@ -160,6 +162,18 @@ pub struct Loop {
160162
pub loc: Location,
161163
}
162164

165+
/// For-In 循环语句
166+
#[derive(Debug, PartialEq, Clone)]
167+
pub struct ForInLoop {
168+
/// 循环变量名
169+
pub var: String,
170+
/// 可迭代对象表达式 (例如一个协程或数组)
171+
pub iterable: Expression,
172+
/// 循环体
173+
pub body: Vec<Statement>,
174+
pub loc: Location,
175+
}
176+
163177
/// Try-Catch-Finally 异常处理
164178
#[derive(Debug, PartialEq, Clone)]
165179
pub struct TryCatch {

src/parser/handwritten.rs

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
use thiserror::Error;
88

99
use crate::expression::{
10-
Assign, Ast, BinaryOperation, Expression, FunctionCall, FunctionDeclaration, If, Literal, Local, Loop, MethodCall,
11-
Return, Statement, TryCatch, Unary,
10+
Assign, Ast, BinaryOperation, Expression, ForInLoop, FunctionCall, FunctionDeclaration, If, Literal, Local, Loop,
11+
MethodCall, Return, Statement, TryCatch, Unary,
1212
};
1313
use crate::tokenizer::Keyword;
1414
use crate::tokenizer::Location;
@@ -284,18 +284,66 @@ impl Parser {
284284

285285
fn parse_for(&mut self) -> Result<Statement, ParseError> {
286286
let start_loc = self.peek_location();
287-
let condition = self.parse_expression_logic()?;
288287

289-
self.skip_newlines();
290-
self.consume(&Token::LBig, "Expected '{' after for condition")?;
291-
let body = self.parse_block()?;
292-
self.consume(&Token::RBig, "Expected '}' after for block")?;
288+
// 1. Check for infinite loop: for { ... }
289+
if self.check(&Token::LBig) {
290+
self.advance(); // consume '{'
291+
let body = self.parse_block()?;
292+
self.consume(&Token::RBig, "Expected '}' after for block")?;
293+
return Ok(Statement::Loop(Loop {
294+
test: Expression::Literal(Literal::Value(Value::Bool(true)), start_loc),
295+
body,
296+
loc: start_loc,
297+
}));
298+
}
293299

294-
Ok(Statement::Loop(Loop {
295-
test: condition,
296-
body,
297-
loc: start_loc,
298-
}))
300+
// 2. Check for for-in or while-style:
301+
// We look ahead to see if it's an Identifier followed by IN
302+
let is_for_in = if let Some(Token::Identifier(_)) = self.peek() {
303+
self.tokens
304+
.get(self.current + 1)
305+
.map(|(t, _)| t == &Token::Keyword(Keyword::IN))
306+
.unwrap_or(false)
307+
} else {
308+
false
309+
};
310+
311+
if is_for_in {
312+
let var_name = if let Some(Token::Identifier(name)) = self.advance() {
313+
name.clone()
314+
} else {
315+
unreachable!()
316+
};
317+
318+
self.consume(&Token::Keyword(Keyword::IN), "Expected 'in' after variable name")?;
319+
let iterable = self.parse_expression_logic()?;
320+
321+
self.skip_newlines();
322+
self.consume(&Token::LBig, "Expected '{' after for-in iterable")?;
323+
let body = self.parse_block()?;
324+
self.consume(&Token::RBig, "Expected '}' after for-in block")?;
325+
326+
Ok(Statement::ForIn(ForInLoop {
327+
var: var_name,
328+
iterable,
329+
body,
330+
loc: start_loc,
331+
}))
332+
} else {
333+
// While style: for condition { ... }
334+
let condition = self.parse_expression_logic()?;
335+
336+
self.skip_newlines();
337+
self.consume(&Token::LBig, "Expected '{' after for condition")?;
338+
let body = self.parse_block()?;
339+
self.consume(&Token::RBig, "Expected '}' after for block")?;
340+
341+
Ok(Statement::Loop(Loop {
342+
test: condition,
343+
body,
344+
loc: start_loc,
345+
}))
346+
}
299347
}
300348

301349
fn parse_function_definition(&mut self) -> Result<FunctionDeclaration, ParseError> {

src/parser/pest_impl.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,19 +145,39 @@ fn parse_assignment_target(pair: Pair<Rule>) -> Expression {
145145

146146
fn parse_for_loop(pair: Pair<Rule>, loc: Location) -> Statement {
147147
let inner = pair.into_inner();
148-
let mut test = Expression::Literal(Literal::Value(Value::Bool(false)), loc);
148+
let mut test = None;
149+
let mut for_in = None;
149150
let mut body = Vec::new();
150151

151152
for p in inner {
152153
match p.as_rule() {
153-
Rule::expression => test = parse_expression(p),
154+
Rule::expression => test = Some(parse_expression(p)),
155+
Rule::for_in_header => {
156+
let mut header_inner = p.into_inner();
157+
let var = header_inner.next().unwrap().as_str().to_string();
158+
let iterable = parse_expression(header_inner.next().unwrap());
159+
for_in = Some((var, iterable));
160+
}
154161
Rule::block => body = parse_block(p),
155-
Rule::FOR => {}
162+
Rule::FOR | Rule::IN => {}
156163
_ => unreachable!("Unexpected rule in for_loop: {:?}", p.as_rule()),
157164
}
158165
}
159166

160-
Statement::Loop(Loop { test, body, loc })
167+
if let Some((var, iterable)) = for_in {
168+
Statement::ForIn(ForInLoop {
169+
var,
170+
iterable,
171+
body,
172+
loc,
173+
})
174+
} else {
175+
Statement::Loop(Loop {
176+
test: test.unwrap_or(Expression::Literal(Literal::Value(Value::Bool(true)), loc)),
177+
body,
178+
loc,
179+
})
180+
}
161181
}
162182

163183
fn build_function_declaration(pair: Pair<Rule>) -> FunctionDeclaration {

0 commit comments

Comments
 (0)