From f271f9d9d8e7a3342033d3880177866f943af516 Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Sun, 3 May 2026 15:44:58 -0500 Subject: [PATCH] add for-range loops to El (for i in 0..n) Adds `for i in start..end` (exclusive) and `for i in start..=end` (inclusive) range loop syntax. Existing `for item in list` iteration is preserved; the parser branches on DotDot/DotDotEq presence after the start expression. Lexer adds DotDot and DotDotEq tokens with longer-match-first priority. Codegen emits a C `for` loop with the loop variable scoped to the statement; inclusive uses `<=`, exclusive `<`. --- el-compiler/src/codegen.el | 22 ++++++++++++++++++++++ el-compiler/src/lexer.el | 20 ++++++++++++++++++-- el-compiler/src/parser.el | 30 ++++++++++++++++++++++++++++-- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/el-compiler/src/codegen.el b/el-compiler/src/codegen.el index 7a94911..5eed473 100644 --- a/el-compiler/src/codegen.el +++ b/el-compiler/src/codegen.el @@ -1141,6 +1141,28 @@ fn cg_stmt(stmt: Map, indent: String, declared: [String]) -> [Strin return declared } + if kind == "ForRange" { + let var_name: String = stmt["var"] + let start_expr = stmt["start"] + let end_expr = stmt["end"] + let inclusive: Bool = stmt["inclusive"] + let body = stmt["body"] + let start_c: String = cg_expr(start_expr) + let end_c: String = cg_expr(end_expr) + // Loop variable introduced as a C local scoped to the for statement. + // Body gets its own declared clone so let-bindings don't leak out. + let body_decl = native_list_clone(declared) + let body_decl = native_list_append(body_decl, var_name) + if inclusive { + emit_line(indent + "for (el_val_t " + var_name + " = " + start_c + "; " + var_name + " <= " + end_c + "; " + var_name + "++) {") + } else { + emit_line(indent + "for (el_val_t " + var_name + " = " + start_c + "; " + var_name + " < " + end_c + "; " + var_name + "++) {") + } + cg_stmts(body, indent + " ", body_decl) + emit_line(indent + "}") + return declared + } + if kind == "FnDef" { return declared } if kind == "TypeDef" { return declared } if kind == "EnumDef" { return declared } diff --git a/el-compiler/src/lexer.el b/el-compiler/src/lexer.el index cef3b33..1ecd95e 100644 --- a/el-compiler/src/lexer.el +++ b/el-compiler/src/lexer.el @@ -698,8 +698,24 @@ fn lex(source: String) -> [Map] { let i = i + 1 } else { if ch == "." { - let tokens = native_list_append(tokens, make_tok("Dot", ".")) - let i = i + 1 + // Check for ..= (inclusive range) before .. (exclusive range) before single . + let peek2_i = i + 2 + let peek2_ch = "" + if peek2_i < total { + let peek2_ch: String = native_list_get(chars, peek2_i) + } + if peek_ch == "." { + if peek2_ch == "=" { + let tokens = native_list_append(tokens, make_tok("DotDotEq", "..=")) + let i = i + 3 + } else { + let tokens = native_list_append(tokens, make_tok("DotDot", "..")) + let i = i + 2 + } + } else { + let tokens = native_list_append(tokens, make_tok("Dot", ".")) + let i = i + 1 + } } else { if ch == ";" { let tokens = native_list_append(tokens, make_tok("Semicolon", ";")) diff --git a/el-compiler/src/parser.el b/el-compiler/src/parser.el index 7742935..0e5375a 100644 --- a/el-compiler/src/parser.el +++ b/el-compiler/src/parser.el @@ -870,7 +870,7 @@ fn parse_stmt(tokens: [Map], pos: Int) -> Map { return make_result({ "stmt": "Continue" }, pos + 1) } - // for loop + // for loop (range or list iteration) if k == "For" { let p = pos + 1 let item_name = tok_value(tokens, p) @@ -880,8 +880,34 @@ fn parse_stmt(tokens: [Map], pos: Int) -> Map { state_set("__no_block_expr", "1") let r = parse_expr(tokens, p) state_set("__no_block_expr", prev_no_block) - let list_expr = r["node"] + let start_expr = r["node"] let p = r["pos"] + // Check for range operator: .. (exclusive) or ..= (inclusive) + let range_k = tok_kind(tokens, p) + if range_k == "DotDot" { + // exclusive range: for i in start..end + let p = p + 1 + let r2 = parse_expr(tokens, p) + let end_expr = r2["node"] + let p = r2["pos"] + let r3 = parse_block(tokens, p) + let body = r3["stmts"] + let p = r3["pos"] + return make_result({ "stmt": "ForRange", "var": item_name, "start": start_expr, "end": end_expr, "inclusive": false, "body": body }, p) + } + if range_k == "DotDotEq" { + // inclusive range: for i in start..=end + let p = p + 1 + let r2 = parse_expr(tokens, p) + let end_expr = r2["node"] + let p = r2["pos"] + let r3 = parse_block(tokens, p) + let body = r3["stmts"] + let p = r3["pos"] + return make_result({ "stmt": "ForRange", "var": item_name, "start": start_expr, "end": end_expr, "inclusive": true, "body": body }, p) + } + // No range operator: regular for-in (list iteration) + let list_expr = start_expr let r2 = parse_block(tokens, p) let body = r2["stmts"] let p = r2["pos"]