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 `<`.
This commit is contained in:
Will Anderson
2026-05-03 15:44:58 -05:00
parent 49a8a1c24b
commit f271f9d9d8
3 changed files with 68 additions and 4 deletions
+22
View File
@@ -1141,6 +1141,28 @@ fn cg_stmt(stmt: Map<String, Any>, 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 }
+18 -2
View File
@@ -698,8 +698,24 @@ fn lex(source: String) -> [Map<String, Any>] {
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", ";"))
+28 -2
View File
@@ -870,7 +870,7 @@ fn parse_stmt(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
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<String, Any>], pos: Int) -> Map<String, Any> {
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"]