add break and continue statements to El

This commit is contained in:
Will Anderson
2026-05-03 15:43:20 -05:00
parent 3cc9b1cc3d
commit 252ad04c96
3 changed files with 60 additions and 35 deletions
+31 -19
View File
@@ -1,4 +1,4 @@
// parser.el el self-hosting recursive descent parser
// parser.el - el self-hosting recursive descent parser
//
// Consumes the token list produced by lexer.el and builds a list of AST
// statement maps. Each statement and expression is a Map<String, Any>.
@@ -11,7 +11,7 @@
//
// Entry point: fn parse(tokens: [Map<String, Any>]) -> [Map<String, Any>]
// Token access helpers
// -- Token access helpers ------------------------------------------------------
fn tok_at(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
native_list_get(tokens, pos)
@@ -36,13 +36,13 @@ fn expect(tokens: [Map<String, Any>], pos: Int, kind: String) -> Int {
pos + 1
}
// Result helpers
// -- Result helpers ------------------------------------------------------------
fn make_result(node: Map<String, Any>, pos: Int) -> Map<String, Any> {
{ "node": node, "pos": pos }
}
// Type annotation parser
// -- Type annotation parser ----------------------------------------------------
// Skips over a type annotation, returning the new position.
// Types can be: Ident, [Type], Map<K,V>, Type?, Type<Type,...>
@@ -100,8 +100,8 @@ fn skip_type(tokens: [Map<String, Any>], pos: Int) -> Int {
pos + 1
}
// Parameter list
// Parses (name: Type, name: Type, ...) returns { "params": [...], "pos": ... }
// -- Parameter list ------------------------------------------------------------
// Parses (name: Type, name: Type, ...) - returns { "params": [...], "pos": ... }
fn parse_params(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let p = expect(tokens, pos, "LParen")
@@ -140,7 +140,7 @@ fn parse_params(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
{ "params": params, "pos": p }
}
// Expression parsing
// -- Expression parsing --------------------------------------------------------
fn parse_primary(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let k = tok_kind(tokens, pos)
@@ -212,14 +212,14 @@ fn parse_primary(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
//
// Suppression: when parse_if / parse_while / parse_for / parse_match
// are parsing a head expression, they set __no_block_expr=1 so a stray
// `{` here doesn't get gobbled as a Map literal it belongs to the
// `{` here doesn't get gobbled as a Map literal - it belongs to the
// following block. Without this, `if a || b { ... }` could mis-parse
// (the `||` recursion lands at `{` and tries to read the if-body as a
// Map, then loops forever when keys don't match `Str: expr`).
if k == "LBrace" {
let no_block: String = state_get("__no_block_expr")
if str_eq(no_block, "1") {
// Fall through to fallback caller will see `{` and treat it
// Fall through to fallback - caller will see `{` and treat it
// as the start of the block they're expecting.
return make_result({ "expr": "Nil" }, pos)
}
@@ -300,7 +300,7 @@ fn parse_primary(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
// token kinds for the deploy/retry DSLs, but they're also valid as
// parameter names and local variables. When one of these appears in
// expression position (where only an Ident makes sense), treat it as
// an Ident carrying the original text otherwise references to a
// an Ident carrying the original text - otherwise references to a
// parameter named `target` compile to EL_NULL.
if k == "Target" { return make_result({ "expr": "Ident", "name": v }, pos + 1) }
if k == "To" { return make_result({ "expr": "Ident", "name": v }, pos + 1) }
@@ -472,9 +472,9 @@ fn parse_block(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
{ "stmts": stmts, "pos": p }
}
// Postfix expressions (calls, field access, index)
// -- Postfix expressions (calls, field access, index) -------------------------
// is_duration_unit recognise the postfix unit suffix on a numeric literal.
// is_duration_unit - recognise the postfix unit suffix on a numeric literal.
// Used by parse_postfix to detect `30.seconds`-shape time literals before
// falling back to the generic `obj.field` field-access lowering. Singular
// and plural forms map to the same nanosecond multiplier; codegen does the
@@ -578,7 +578,7 @@ fn parse_postfix(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
make_result(node, p)
}
// Binary expression precedence climbing
// -- Binary expression precedence climbing ------------------------------------
fn op_precedence(kind: String) -> Int {
if kind == "Or" { return 1 }
@@ -593,6 +593,7 @@ fn op_precedence(kind: String) -> Int {
if kind == "Minus" { return 5 }
if kind == "Star" { return 6 }
if kind == "Slash" { return 6 }
if kind == "Percent" { return 6 }
0
}
@@ -609,6 +610,7 @@ fn is_binop(kind: String) -> Bool {
if kind == "Minus" { return true }
if kind == "Star" { return true }
if kind == "Slash" { return true }
if kind == "Percent" { return true }
false
}
@@ -641,7 +643,7 @@ fn parse_expr(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
parse_binop(tokens, pos, 1)
}
// Statement parsing
// -- Statement parsing ---------------------------------------------------------
fn parse_stmt(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let k = tok_kind(tokens, pos)
@@ -653,7 +655,7 @@ fn parse_stmt(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let p = p + 1
let ltype = ""
let k2 = tok_kind(tokens, p)
// optional type annotation: name: Type capture the leading
// optional type annotation: name: Type - capture the leading
// identifier so codegen can dispatch arithmetic vs concat on
// `+` between two typed Idents.
if k2 == "Colon" {
@@ -687,7 +689,7 @@ fn parse_stmt(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
return make_result({ "stmt": "Return", "value": val }, p)
}
// extern fn declaration (no body forward declaration for separate compilation)
// extern fn declaration (no body - forward declaration for separate compilation)
if k == "Extern" {
let p = pos + 1
let k2: String = tok_kind(tokens, p)
@@ -858,6 +860,16 @@ fn parse_stmt(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
return make_result({ "stmt": "While", "cond": cond, "body": body }, p)
}
// break statement
if k == "Break" {
return make_result({ "stmt": "Break" }, pos + 1)
}
// continue statement
if k == "Continue" {
return make_result({ "stmt": "Continue" }, pos + 1)
}
// for loop
if k == "For" {
let p = pos + 1
@@ -876,7 +888,7 @@ fn parse_stmt(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
return make_result({ "stmt": "For", "item": item_name, "list": list_expr, "body": body }, p)
}
// @decorator capture decorator name and attach to following stmt
// @decorator - capture decorator name and attach to following stmt
if k == "At" {
let p = pos + 1
let dec_name = tok_value(tokens, p)
@@ -1039,7 +1051,7 @@ fn parse_stmt(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
make_result({ "stmt": "Expr", "value": val }, p)
}
// Top-level parse
// -- Top-level parse ------------------------------------------------------------
fn parse(tokens: [Map<String, Any>]) -> [Map<String, Any>] {
let total: Int = native_list_len(tokens)
@@ -1058,7 +1070,7 @@ fn parse(tokens: [Map<String, Any>]) -> [Map<String, Any>] {
let stmt = r["node"]
let new_pos: Int = r["pos"]
let stmts = native_list_append(stmts, stmt)
// Guard against infinite loops if pos didn't advance, force it
// Guard against infinite loops - if pos didn't advance, force it
if new_pos <= pos {
let pos = pos + 1
} else {