for loops

This commit is contained in:
2026-06-22 20:11:18 +02:00
parent 380b5943b3
commit 27f42dd253
15 changed files with 1369 additions and 22 deletions
+102 -1
View File
@@ -20,6 +20,7 @@ Parser :: struct {
file: ast.File_Id,
cursor: int,
delimiter_depth: int,
range_disabled: int,
// Suppresses `Name { ... }` struct-literal parsing at delimiter depth 0 so a
// control-flow condition like `if foo { ... }` does not swallow the block as a
// struct literal. Nested `(`/`[`/call-arg contexts (delimiter_depth > 0) still
@@ -630,6 +631,9 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
if _, ok := allow(parser, .Right_Paren); !ok {
source.add(parser.diagnostics, current(parser).span, "expected ')'")
}
if expr != ast.INVALID_EXPR && int(expr) < len(parser.module.exprs) {
parser.module.exprs[expr].parenthesized = true
}
return expr
case .Invalid:
advance(parser)
@@ -649,6 +653,8 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
infix_binding_power :: proc(kind: token.Kind) -> (left, right: int, ok: bool) {
#partial switch kind {
case .Range, .Range_Inclusive:
return 0, 1, true
case .Keyword_Orelse:
return 2, 3, true
case .Keyword_Or:
@@ -665,6 +671,7 @@ infix_binding_power :: proc(kind: token.Kind) -> (left, right: int, ok: bool) {
infix_expr_kind :: proc(kind: token.Kind) -> ast.Expr_Kind {
#partial switch kind {
case .Range, .Range_Inclusive: return .Range
case .Keyword_Orelse: return .Orelse
case .Keyword_Or: return .Or
case .Keyword_And: return .And
@@ -678,6 +685,18 @@ infix_expr_kind :: proc(kind: token.Kind) -> ast.Expr_Kind {
}
}
is_simple_range_bound :: proc(expr: ast.Expr) -> bool {
if expr.parenthesized {
return true
}
#partial switch expr.kind {
case .Integer, .Float, .String, .Bool, .Name:
return true
case:
return false
}
}
prefix_binding_power :: proc(kind: token.Kind) -> (right: int, ok: bool) {
#partial switch kind {
case .Minus, .Ampersand, .Bang:
@@ -762,7 +781,9 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
if _, ok := allow(parser, .Range); ok {
slicing = true
} else {
parser.range_disabled += 1
start_expr = parse_expression_bp(parser, 0, nesting+1)
parser.range_disabled -= 1
skip_newlines(parser)
if _, ok := allow(parser, .Range); ok {
slicing = true
@@ -770,7 +791,9 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
}
skip_newlines(parser)
if slicing && current(parser).kind != .Right_Bracket {
parser.range_disabled += 1
end_expr = parse_expression_bp(parser, 0, nesting+1)
parser.range_disabled -= 1
skip_newlines(parser)
}
end_token, ok := allow(parser, .Right_Bracket)
@@ -822,6 +845,10 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
continue
}
left_power, right_power, ok := infix_binding_power(current(parser).kind)
if ok && (current(parser).kind == .Range || current(parser).kind == .Range_Inclusive) &&
parser.range_disabled > 0 {
ok = false
}
if !ok || left_power < minimum_binding_power {
break
}
@@ -830,9 +857,26 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
right := parse_expression_bp(parser, right_power, nesting+1)
left_expr := parser.module.exprs[left]
right_expr := parser.module.exprs[right]
if operator.kind == .Range || operator.kind == .Range_Inclusive {
if !is_simple_range_bound(left_expr) {
source.add(
parser.diagnostics,
left_expr.span,
"range bounds with operators must be parenthesized",
)
}
if !is_simple_range_bound(right_expr) {
source.add(
parser.diagnostics,
right_expr.span,
"range bounds with operators must be parenthesized",
)
}
}
left = add_expr(parser, ast.Expr{
kind=infix_expr_kind(operator.kind),
span=span_from(left_expr.span, right_expr.span),
integer=1 if operator.kind == .Range_Inclusive else 0,
left=left,
right=right,
diagnostic=source.INVALID_DIAGNOSTIC,
@@ -931,6 +975,9 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
if current(parser).kind == .Keyword_While {
return parse_while(parser)
}
if current(parser).kind == .Keyword_For {
return parse_for(parser)
}
if current(parser).kind == .Identifier || current(parser).kind == .Underscore {
start_cursor := parser.cursor
@@ -1159,7 +1206,7 @@ parse_while_update :: proc(parser: ^Parser) -> ast.Stmt_Id {
statement := &parser.module.statements[update]
switch statement.kind {
case .Assignment, .Expression:
case .Invalid, .Declaration, .Return, .If, .While:
case .Invalid, .Declaration, .Return, .If, .While, .For:
diagnostic := source.add(
parser.diagnostics,
statement.span,
@@ -1187,6 +1234,60 @@ parse_while_update :: proc(parser: ^Parser) -> ast.Stmt_Id {
return update
}
parse_for :: proc(parser: ^Parser) -> ast.Stmt_Id {
start := advance(parser) // consume 'for'
skip_newlines(parser)
saved := parser.no_struct_literal
parser.no_struct_literal = true
iterable := parse_expression(parser)
parser.no_struct_literal = saved
skip_newlines(parser)
pointer_capture := false
item_name := symbol.INVALID
index_name := symbol.INVALID
if _, ok := allow(parser, .Pipe); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '|' before for-loop captures")
} else {
if _, ok := allow(parser, .At); ok {
pointer_capture = true
}
item, item_ok := allow(parser, .Identifier)
if item_ok {
item_name = item.symbol
} else {
source.add(parser.diagnostics, current(parser).span, "expected a for-loop item capture")
}
if _, ok := allow(parser, .Comma); ok {
index, index_ok := allow(parser, .Identifier)
if index_ok {
index_name = index.symbol
} else {
source.add(parser.diagnostics, current(parser).span, "expected an index capture after ','")
}
}
if _, ok := allow(parser, .Pipe); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '|' to close for-loop captures")
}
}
skip_newlines(parser)
body := parse_block(parser)
id := ast.stmt_id(len(parser.module.statements))
append(&parser.module.statements, ast.Stmt{
kind=.For,
span=span_from(start.span, previous(parser).span),
name=item_name,
index_name=index_name,
pointer_capture=pointer_capture,
expr=iterable,
body=body,
update=ast.INVALID_STMT,
diagnostic=source.INVALID_DIAGNOSTIC,
})
return id
}
parse_while :: proc(parser: ^Parser) -> ast.Stmt_Id {
start := advance(parser) // consume 'while'
skip_newlines(parser)