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
+3
View File
@@ -30,7 +30,10 @@
- complete plain imported C structs and unions as runtime values; incomplete or unsupported-layout records remain pointer-only - complete plain imported C structs and unions as runtime values; incomplete or unsupported-layout records remain pointer-only
- C function pointer types as pointer-sized runtime values, including manual `*c_func(...) T` spelling and nullable imported callback typedefs - C function pointer types as pointer-sized runtime values, including manual `*c_func(...) T` spelling and nullable imported callback typedefs
- postfix pointer dereference, general writable locations, function calls, assignments, and returns - postfix pointer dereference, general writable locations, function calls, assignments, and returns
- first-class exclusive and inclusive integer ranges: `start..end` and `start..=end`
- boolean `if` statements and `while` loops with optional post-iteration assignment/expression clauses - boolean `if` statements and `while` loops with optional post-iteration assignment/expression clauses
- `for` loops over ranges, arrays, slices, and pointers-to-arrays, with copy, pointer, and optional `usize` index captures
- `|@item|` pointer captures inherit pointee mutability from the iterable; arrays require an explicit pointer such as `&items`
### functions and packages ### functions and packages
+7 -5
View File
@@ -101,11 +101,11 @@
- the condition and update may be parenthesized independently for visual clarity - the condition and update may be parenthesized independently for visual clarity
- update targets must already be declared and mutable; loops do not introduce implicit induction variables - update targets must already be declared and mutable; loops do not introduce implicit induction variables
- compound assignment (`+=`) remains deferred - compound assignment (`+=`) remains deferred
- ranges (see section below) - ranges (implemented; see section below)
- for loops (operates on iterable sequences). examples: - for loops (implemented; operates on ranges, arrays, slices, and pointers-to-arrays). examples:
- `for items |item| { ... }` - capture just the `item` value in the array/slice (uses copy semantics, i.e. gets a `T`) - `for items |item| { ... }` - capture just the `item` value in the array/slice (uses copy semantics, i.e. gets a `T`)
- `for items |&item| { ... }` - capture just the `item` value in the array/slice (uses (immutable) reference semantics, i.e. gets a `@T`) - `for (&items) |@item| { ... }` - capture a pointer to each array element; its `@T` / `@mut T` mutability follows the iterable
- `for items |&mut item| { ... }` - capture just the `item` value in the array/slice (uses (mutable) reference semantics, i.e. gets a `@mut T`) - `for items_slice |@item| { ... }` - slices already refer to backing storage and support pointer capture directly
- `for items |item, idx| { ... }` - capture `item` and its index index in the array/slice - `for items |item, idx| { ... }` - capture `item` and its index index in the array/slice
- `for 0..10 |i| { ... }` - iterate over the range `0..10` (exclusive) - `for 0..10 |i| { ... }` - iterate over the range `0..10` (exclusive)
- `for 0..=10 |i| { ... }` - iterate over the range `0..10` (inclusive) - `for 0..=10 |i| { ... }` - iterate over the range `0..10` (inclusive)
@@ -180,7 +180,9 @@ Ranges represent a sequence of values, commonly used in for loops, and is itself
# 0..n + 1 # ERROR: must parenthesize complex expressions # 0..n + 1 # ERROR: must parenthesize complex expressions
``` ```
This rule keeps the grammar simple and forces clarity at the call site — no precedence rules to remember. Also, being a value type, ranges can be assigned to variables and passed around like any other value. This rule keeps the grammar simple and forces clarity at the call site — no precedence rules to remember. Also, being a value type, ranges can be assigned to variables and passed around like any other value. Range bounds are evaluated once, must have compatible concrete integer types, and descending ranges are empty.
For-loop captures are immutable and scoped to the loop body. Sequence index captures are `usize`. Pointer capture uses `|@item|`; arrays must be passed by pointer (for example `&items`), while slices can be used directly. Sentinel elements are not included in iteration.
# A word on distinct types # A word on distinct types
+8
View File
@@ -92,6 +92,7 @@ Expr_Kind :: enum u8 {
Ge, Ge,
And, And,
Or, Or,
Range,
Call, Call,
} }
@@ -104,6 +105,7 @@ Expr :: struct {
left: Expr_Id, left: Expr_Id,
right: Expr_Id, right: Expr_Id,
diagnostic: source.Diagnostic_Id, diagnostic: source.Diagnostic_Id,
parenthesized: bool,
kind: Expr_Kind, kind: Expr_Kind,
} }
@@ -121,14 +123,17 @@ Stmt_Kind :: enum u8 {
Expression, Expression,
If, If,
While, While,
For,
} }
Stmt :: struct { Stmt :: struct {
kind: Stmt_Kind, kind: Stmt_Kind,
span: source.Span, span: source.Span,
name: symbol.Id, name: symbol.Id,
index_name: symbol.Id,
type: Type_Syntax, type: Type_Syntax,
immutable: bool, immutable: bool,
pointer_capture: bool,
target: Expr_Id, target: Expr_Id,
expr: Expr_Id, expr: Expr_Id,
// `If` statements use `expr` as the condition, `body` as the then-block, and // `If` statements use `expr` as the condition, `body` as the then-block, and
@@ -136,6 +141,9 @@ Stmt :: struct {
// `else_body` holding a single nested `If` statement. // `else_body` holding a single nested `If` statement.
// `While` statements use `expr` as the condition, `body` as the loop body, // `While` statements use `expr` as the condition, `body` as the loop body,
// and `update` as the optional post-iteration statement. // and `update` as the optional post-iteration statement.
// `For` statements use `expr` as the iterable, `name` as the item capture,
// `index_name` as the optional index capture, and `pointer_capture` to
// distinguish `|@item|` from copy capture.
body: []Stmt_Id, body: []Stmt_Id,
else_body: []Stmt_Id, else_body: []Stmt_Id,
update: Stmt_Id, update: Stmt_Id,
+208 -6
View File
@@ -611,7 +611,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
} }
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Keyed: case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Keyed:
append(&stack, expr.left) append(&stack, expr.left)
case .Add, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or: case .Add, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
append(&stack, expr.left, expr.right) append(&stack, expr.left, expr.right)
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Name: case .Invalid, .Integer, .Float, .String, .Bool, .None, .Name:
} }
@@ -638,6 +638,9 @@ mark_block_imports_used :: proc(checker: ^Checker, statements: []ast.Stmt_Id, fi
update := [1]ast.Stmt_Id{statement.update} update := [1]ast.Stmt_Id{statement.update}
mark_block_imports_used(checker, update[:], file) mark_block_imports_used(checker, update[:], file)
} }
case .For:
mark_expr_imports_used(checker, statement.expr, file)
mark_block_imports_used(checker, statement.body, file)
case .Invalid: case .Invalid:
} }
} }
@@ -1028,6 +1031,23 @@ infer_compound_expr :: proc(
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) _ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded) _ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded)
return types.BOOL return types.BOOL
case .Range:
left := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
right := infer_nested_expr(checker, expr.right, locals, pkg, file, demanded)
left_const := eval_constant(checker, expr.left)
right_const := eval_constant(checker, expr.right)
child := types.INVALID
if left_const.kind == .Value && right_const.kind != .Value {
child = right
} else if right_const.kind == .Value && left_const.kind != .Value {
child = left
} else {
child = types.widest(left, right)
}
if !types.is_concrete_integer(child) {
return types.INVALID
}
return types.range(store, child)
case .String: case .String:
return string_literal_type(checker, expr.integer) return string_literal_type(checker, expr.integer)
case .Array: case .Array:
@@ -1165,7 +1185,7 @@ infer_expr :: proc(
_ = pop(&stack) _ = pop(&stack)
case .String, .Array, .None, .Address, .Deref, .Index, .Slice, case .String, .Array, .None, .Address, .Deref, .Index, .Slice,
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed, .Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed,
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or: .Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded) last = infer_compound_expr(checker, expr, locals, pkg, file, demanded)
_ = pop(&stack) _ = pop(&stack)
case .Name: case .Name:
@@ -1413,7 +1433,7 @@ infer_statements :: proc(
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded) returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
if !types.is_valid(result^) { if !types.is_valid(result^) {
result^ = returned result^ = returned
} else { } else if !types.equal(result^, returned) {
result^ = types.widest(result^, returned) result^ = types.widest(result^, returned)
} }
} }
@@ -1438,6 +1458,29 @@ infer_statements :: proc(
update := [1]ast.Stmt_Id{statement.update} update := [1]ast.Stmt_Id{statement.update}
infer_statements(checker, update[:], locals, pkg, file, demanded, result) infer_statements(checker, update[:], locals, pkg, file, demanded, result)
} }
case .For:
iterable_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
capture_start := len(locals^)
capture_type := types.INVALID
if types.is_range(iterable_type, &checker.module.types) {
capture_type = types.child_type(iterable_type, &checker.module.types)
} else {
item, ok := sequence_item(iterable_type, &checker.module.types)
if ok {
capture_type = item.child
if statement.pointer_capture {
capture_type = types.pointer(&checker.module.types, item.child, item.mutable, false)
}
}
}
if symbol.is_valid(statement.name) {
append(locals, Infer_Local{name=statement.name, type=capture_type})
}
if symbol.is_valid(statement.index_name) {
append(locals, Infer_Local{name=statement.index_name, type=types.USIZE})
}
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
resize(locals, capture_start)
} }
} }
resize(locals, scope_start) resize(locals, scope_start)
@@ -1899,6 +1942,19 @@ hir_is_location :: proc(checker: ^Checker, expr_id: hir.Expr_Id) -> bool {
return false return false
} }
sequence_item :: proc(value: types.Type, store: ^types.Store) -> (types.Node, bool) {
item, ok := types.node(store, value)
if ok && (item.kind == .Array || item.kind == .Slice) {
return item, true
}
pointer, array, pointer_ok := types.array_pointer(value, store)
if pointer_ok {
array.mutable = pointer.mutable && array.mutable
return array, true
}
return {}, false
}
find_struct_field :: proc(checker: ^Checker, struct_type: types.Type, name: symbol.Id) -> (int, types.Field, bool) { find_struct_field :: proc(checker: ^Checker, struct_type: types.Type, name: symbol.Id) -> (int, types.Field, bool) {
for field, index in types.fields_for(&checker.module.types, struct_type) { for field, index in types.fields_for(&checker.module.types, struct_type) {
if field.name == u32(name) { if field.name == u32(name) {
@@ -2192,6 +2248,46 @@ build_compound_expr :: proc(
kind=.Orelse, span=expr.span, type=child, left=optional, right=fallback, kind=.Orelse, span=expr.span, type=child, left=optional, right=fallback,
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC, target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
case .Range:
expected_child := types.INVALID
if types.is_range(expected, store) {
expected_child = types.child_type(expected, store)
}
left_const := eval_constant(checker, expr.left)
right_const := eval_constant(checker, expr.right)
left, right: hir.Expr_Id
if types.is_valid(expected_child) {
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, expected_child, pkg, file)
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, expected_child, pkg, file)
} else if right_const.kind == .Value && left_const.kind != .Value {
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, checker.module.exprs[left].type, pkg, file)
} else if left_const.kind == .Value && right_const.kind != .Value {
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, checker.module.exprs[right].type, pkg, file)
} else {
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
}
child := expected_child
if !types.is_valid(child) {
child = types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
}
if !types.is_concrete_integer(child) {
id := source.add(checker.diagnostics, expr.span, "range bounds must be compatible concrete integers")
return invalid_hir_expr(checker, expr.span, id)
}
left = coerce_expr(checker, left, child, checker.module.exprs[left].span)
right = coerce_expr(checker, right, child, checker.module.exprs[right].span)
args := make([]hir.Expr_Id, 2, checker.allocator)
args[0] = left
args[1] = right
return add_hir_expr(checker, hir.Expr{
kind=.Range, span=expr.span, type=types.range(store, child),
integer=i64(expr.integer), args=args,
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Bool: case .Bool:
return add_hir_expr(checker, hir.Expr{ return add_hir_expr(checker, hir.Expr{
kind=.Bool, span=expr.span, type=types.BOOL, integer=i64(expr.integer), kind=.Bool, span=expr.span, type=types.BOOL, integer=i64(expr.integer),
@@ -2391,7 +2487,7 @@ build_expr :: proc(
switch expr.kind { switch expr.kind {
case .String, .Array, .None, .Address, .Deref, .Index, .Slice, case .String, .Array, .None, .Address, .Deref, .Index, .Slice,
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed, .Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed,
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or: .Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
last = build_compound_expr( last = build_compound_expr(
checker, expr, locals, global_reads, calls, frame.expected, pkg, file, checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
) )
@@ -2849,11 +2945,16 @@ make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator) return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator)
} }
build_block :: proc(ctx: ^Build_Ctx, statements: []ast.Stmt_Id) -> []hir.Stmt_Id { build_block :: proc(
ctx: ^Build_Ctx,
statements: []ast.Stmt_Id,
duplicate_scope_start := -1,
) -> []hir.Stmt_Id {
checker := ctx.checker checker := ctx.checker
body: [dynamic]hir.Stmt_Id body: [dynamic]hir.Stmt_Id
body.allocator = checker.allocator body.allocator = checker.allocator
scope_start := len(ctx.locals^) scope_start := len(ctx.locals^)
duplicate_start := scope_start if duplicate_scope_start < 0 else duplicate_scope_start
for statement_id in statements { for statement_id in statements {
statement := checker.ast_module.statements[statement_id] statement := checker.ast_module.statements[statement_id]
switch statement.kind { switch statement.kind {
@@ -2876,7 +2977,7 @@ build_block :: proc(ctx: ^Build_Ctx, statements: []ast.Stmt_Id) -> []hir.Stmt_Id
value = invalid_hir_expr(checker, statement.span, id) value = invalid_hir_expr(checker, statement.span, id)
value_type = types.INVALID value_type = types.INVALID
} }
if _, found := find_build_local(ctx.locals^[scope_start:], statement.name); found { if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); found {
id := source.addf( id := source.addf(
checker.diagnostics, statement.span, checker.diagnostics, statement.span,
"duplicate local '%s'", symbol_text(checker, statement.name), "duplicate local '%s'", symbol_text(checker, statement.name),
@@ -3126,6 +3227,107 @@ build_block :: proc(ctx: ^Build_Ctx, statements: []ast.Stmt_Id) -> []hir.Stmt_Id
diagnostic=source.INVALID_DIAGNOSTIC, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
case .For:
iterable := build_expr(
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
types.INVALID, ctx.pkg, ctx.file,
)
iterable_type := checker.module.exprs[iterable].type
capture_type := types.I64
iterator_type := types.INVALID
valid_loop := checker.module.exprs[iterable].kind != .Invalid
diagnostic := source.INVALID_DIAGNOSTIC
is_range := types.is_range(iterable_type, &checker.module.types)
if is_range {
capture_type = types.child_type(iterable_type, &checker.module.types)
if statement.pointer_capture {
diagnostic = source.add(checker.diagnostics, statement.span, "range loops do not support pointer captures")
valid_loop = false
}
if symbol.is_valid(statement.index_name) {
diagnostic = source.add(checker.diagnostics, statement.span, "range loops do not support index captures")
valid_loop = false
}
} else {
item, ok := sequence_item(iterable_type, &checker.module.types)
if !ok {
diagnostic = source.add(
checker.diagnostics,
statement.span,
"for-loop iterable must be a range, array, slice, or pointer-to-array",
)
valid_loop = false
} else {
iterator_type = types.pointer(
&checker.module.types,
item.child,
item.mutable,
true,
item.has_sentinel,
item.sentinel,
)
capture_type = item.child
if statement.pointer_capture {
_, _, array_pointer_ok := types.array_pointer(iterable_type, &checker.module.types)
if !types.is_slice(iterable_type, &checker.module.types) && !array_pointer_ok {
diagnostic = source.add(
checker.diagnostics,
statement.span,
"pointer capture over an array requires a pointer-to-array such as '&items'",
)
valid_loop = false
}
capture_type = types.pointer(&checker.module.types, item.child, item.mutable, false)
}
}
}
capture_start := len(ctx.locals^)
item_local := hir.local_id(len(ctx.hir_locals^))
append(ctx.hir_locals, hir.Local{name=statement.name, type=capture_type, mutable=false})
append(ctx.locals, Build_Local{name=statement.name, type=capture_type, mutable=false, id=item_local})
index_local := hir.INVALID_LOCAL
if symbol.is_valid(statement.index_name) {
if statement.index_name == statement.name {
diagnostic = source.add(checker.diagnostics, statement.span, "for-loop captures must have distinct names")
valid_loop = false
} else {
index_local = hir.local_id(len(ctx.hir_locals^))
append(ctx.hir_locals, hir.Local{name=statement.index_name, type=types.USIZE, mutable=false})
append(ctx.locals, Build_Local{name=statement.index_name, type=types.USIZE, mutable=false, id=index_local})
}
}
loop_body := build_block(ctx, statement.body, capture_start)
resize(ctx.locals, capture_start)
append(&body, hir.stmt_id(len(checker.module.statements)))
if valid_loop {
append(&checker.module.statements, hir.Stmt{
kind=.For,
span=statement.span,
local=item_local,
index_local=index_local,
expr=iterable,
iterator_type=iterator_type,
pointer_capture=statement.pointer_capture,
then_body=loop_body,
update=hir.INVALID_STMT,
target=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
} else {
delete(loop_body, checker.allocator)
append(&checker.module.statements, hir.Stmt{
kind=.Trap,
span=statement.span,
local=hir.INVALID_LOCAL,
index_local=hir.INVALID_LOCAL,
expr=hir.INVALID_EXPR,
target=hir.INVALID_EXPR,
diagnostic=diagnostic,
})
ctx.problematic^ = true
}
case .Invalid: case .Invalid:
append(&body, hir.stmt_id(len(checker.module.statements))) append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{ append(&checker.module.statements, hir.Stmt{
+8
View File
@@ -111,6 +111,7 @@ Expr_Kind :: enum u8 {
Ge, Ge,
And, And,
Or, Or,
Range,
Call, Call,
} }
@@ -142,18 +143,25 @@ Stmt_Kind :: enum u8 {
Trap, Trap,
If, If,
While, While,
For,
} }
Stmt :: struct { Stmt :: struct {
kind: Stmt_Kind, kind: Stmt_Kind,
span: source.Span, span: source.Span,
local: Local_Id, local: Local_Id,
index_local: Local_Id,
target: Expr_Id, target: Expr_Id,
expr: Expr_Id, expr: Expr_Id,
iterator_type: types.Type,
pointer_capture: bool,
// `If` statements use `expr` as the condition and `then_body`/`else_body` as // `If` statements use `expr` as the condition and `then_body`/`else_body` as
// the branch statement lists. // the branch statement lists.
// `While` statements use `expr` as the condition, `then_body` as the loop // `While` statements use `expr` as the condition, `then_body` as the loop
// body, and `update` as the optional post-iteration statement. // body, and `update` as the optional post-iteration statement.
// `For` statements use `expr` as the iterable, `local` as the item capture,
// `index_local` as the optional sequence index, and `iterator_type` as the
// normalized many-item pointer type for sequence iteration.
then_body: []Stmt_Id, then_body: []Stmt_Id,
else_body: []Stmt_Id, else_body: []Stmt_Id,
update: Stmt_Id, update: Stmt_Id,
+2
View File
@@ -84,6 +84,8 @@ Opcode :: enum u8 {
Slice, Slice,
Length, Length,
Slice_Ptr, Slice_Ptr,
Extract,
Select,
Unwrap, Unwrap,
Optional_Is_Some, Optional_Is_Some,
Optional_Value, Optional_Value,
+4
View File
@@ -27,6 +27,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
case "or": return .Keyword_Or case "or": return .Keyword_Or
case "if": return .Keyword_If case "if": return .Keyword_If
case "while": return .Keyword_While case "while": return .Keyword_While
case "for": return .Keyword_For
case "else": return .Keyword_Else case "else": return .Keyword_Else
case "true": return .Keyword_True case "true": return .Keyword_True
case "false": return .Keyword_False case "false": return .Keyword_False
@@ -162,6 +163,9 @@ lex :: proc(
if cursor < len(bytes) && bytes[cursor] == '.' { if cursor < len(bytes) && bytes[cursor] == '.' {
cursor += 1 cursor += 1
append_token(&stream, source_file, .Ellipsis, start, cursor) append_token(&stream, source_file, .Ellipsis, start, cursor)
} else if cursor < len(bytes) && bytes[cursor] == '=' {
cursor += 1
append_token(&stream, source_file, .Range_Inclusive, start, cursor)
} else { } else {
append_token(&stream, source_file, .Range, start, cursor) append_token(&stream, source_file, .Range, start, cursor)
} }
+59 -5
View File
@@ -146,6 +146,10 @@ llvm_type :: proc(value: types.Type, store: ^types.Store = nil) -> string {
return "ptr" return "ptr"
case .Slice: case .Slice:
return "{ ptr, i64 }" return "{ ptr, i64 }"
case .Range:
item, _ := types.node(store, value)
child := llvm_type(item.child, store)
return fmt.tprintf("{{ %s, %s, i1 }}", child, child)
case .Array: case .Array:
item, _ := types.node(store, value) item, _ := types.node(store, value)
return fmt.tprintf("[%d x %s]", types.physical_count(value, store), llvm_type(item.child, store)) return fmt.tprintf("[%d x %s]", types.physical_count(value, store), llvm_type(item.child, store))
@@ -232,7 +236,8 @@ valid_value :: proc(
} }
switch instructions[value_id].op { switch instructions[value_id].op {
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some, case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
.Load_Global, .Function_Address, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Load_Global, .Function_Address, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr,
.Extract, .Select, .Unwrap,
.Optional_Is_Some, .Optional_Value, .Orelse, .Optional_Is_Some, .Optional_Value, .Orelse,
.Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer, .Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
.Neg_Checked, .Add_Checked, .Pointer_Add, .Not, .Compare, .Call: .Neg_Checked, .Add_Checked, .Pointer_Add, .Not, .Compare, .Call:
@@ -551,6 +556,8 @@ emit_instruction_stream :: proc(
expected_count = int(item.field_count) expected_count = int(item.field_count)
} else if ok && item.kind == .Union { } else if ok && item.kind == .Union {
expected_count = 1 expected_count = 1
} else if ok && item.kind == .Range {
expected_count = 3
} else { } else {
emit_recovery_value(emitter, instruction_index, instruction, "invalid aggregate type") emit_recovery_value(emitter, instruction_index, instruction, "invalid aggregate type")
continue continue
@@ -585,6 +592,8 @@ emit_instruction_stream :: proc(
element_type := item.child element_type := item.child
if item.kind == .Struct { if item.kind == .Struct {
element_type = types.fields_for(&emitter.module.types, instruction.type)[arg_index].type element_type = types.fields_for(&emitter.module.types, instruction.type)[arg_index].type
} else if item.kind == .Range && arg_index == 2 {
element_type = types.BOOL
} }
final := arg_index == total-1 final := arg_index == total-1
if final { if final {
@@ -924,6 +933,46 @@ emit_instruction_stream :: proc(
} else { } else {
emit_recovery_value(emitter, instruction_index, instruction, "invalid container pointer") emit_recovery_value(emitter, instruction_index, instruction, "invalid container pointer")
} }
case .Extract:
if !valid_instruction(instructions, instruction.a) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid aggregate extraction")
continue
}
aggregate_type := instructions[instruction.a].type
item, ok := types.node(&emitter.module.types, aggregate_type)
field_index := int(instruction.integer)
expected_type := types.INVALID
if ok && item.kind == .Range && field_index >= 0 && field_index < 3 {
expected_type = types.BOOL if field_index == 2 else item.child
}
if !types.equal(expected_type, instruction.type) ||
!valid_value(instructions, instruction.a, aggregate_type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid aggregate extraction")
continue
}
fmt.sbprintf(
&emitter.builder,
" %%v%d = extractvalue %s %%v%d, %d\n",
instruction_index,
llvm_type(aggregate_type, &emitter.module.types),
instruction.a,
field_index,
)
case .Select:
if len(instruction.args) != 2 ||
!valid_value(instructions, instruction.a, types.BOOL, &emitter.module.types) ||
!valid_value(instructions, instruction.args[0], instruction.type, &emitter.module.types) ||
!valid_value(instructions, instruction.args[1], instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid selection")
continue
}
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 ", instruction_index)
write_operand(&emitter.builder, instructions, instruction.a, types.BOOL, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", %s ", llvm_type(instruction.type, &emitter.module.types))
write_operand(&emitter.builder, instructions, instruction.args[0], instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", %s ", llvm_type(instruction.type, &emitter.module.types))
write_operand(&emitter.builder, instructions, instruction.args[1], instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
case .Unwrap: case .Unwrap:
optional_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID optional_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID
item, ok := types.node(&emitter.module.types, optional_type) item, ok := types.node(&emitter.module.types, optional_type)
@@ -1152,14 +1201,19 @@ emit_instruction_stream :: proc(
emit_trap_call(emitter, message) emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index) fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index)
case .Pointer_Add: case .Pointer_Add:
item, ok := types.node(&emitter.module.types, instruction.type) result_item, result_ok := types.node(&emitter.module.types, instruction.type)
if !ok || item.kind != .Pointer || !item.many || base_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID
!valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) || base_item, base_ok := types.node(&emitter.module.types, base_type)
if !result_ok || result_item.kind != .Pointer ||
!base_ok || base_item.kind != .Pointer || !base_item.many ||
result_item.child != base_item.child ||
result_item.mutable != base_item.mutable ||
!valid_value(instructions, instruction.a, base_type, &emitter.module.types) ||
!valid_value(instructions, instruction.b, types.USIZE, &emitter.module.types) { !valid_value(instructions, instruction.b, types.USIZE, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid pointer offset") emit_recovery_value(emitter, instruction_index, instruction, "invalid pointer offset")
continue continue
} }
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %%v%d, i64 ", instruction_index, llvm_type(item.child, &emitter.module.types), instruction.a) fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %%v%d, i64 ", instruction_index, llvm_type(result_item.child, &emitter.module.types), instruction.a)
write_operand(&emitter.builder, instructions, instruction.b, types.USIZE, &emitter.module.types) write_operand(&emitter.builder, instructions, instruction.b, types.USIZE, &emitter.module.types)
strings.write_string(&emitter.builder, "\n") strings.write_string(&emitter.builder, "\n")
case .Call: case .Call:
+357 -1
View File
@@ -150,6 +150,18 @@ lower_location :: proc(state: ^State, expr_id: hir.Expr_Id, for_write := false)
return ir.INVALID_INSTRUCTION return ir.INVALID_INSTRUCTION
} }
hir_expr_is_location :: proc(state: ^State, expr_id: hir.Expr_Id) -> bool {
if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(state.hir_module.exprs) {
return false
}
#partial switch state.hir_module.exprs[expr_id].kind {
case .Local, .Global, .Deref, .Index, .Field:
return true
case:
return false
}
}
lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id { lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
expr := state.hir_module.exprs[expr_id] expr := state.hir_module.exprs[expr_id]
#partial switch expr.kind { #partial switch expr.kind {
@@ -169,6 +181,30 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
case .Range:
args := make([]ir.Instruction_Id, 3, state.allocator)
args[0] = lower_nested_expr(state, expr.args[0])
args[1] = lower_nested_expr(state, expr.args[1])
args[2] = append_instruction(state, ir.Instruction{
op=.Const,
span=expr.span,
type=types.BOOL,
integer=expr.integer,
target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
return append_instruction(state, ir.Instruction{
op=.Aggregate,
span=expr.span,
type=expr.type,
args=args,
target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
case .None: case .None:
return append_instruction(state, ir.Instruction{ return append_instruction(state, ir.Instruction{
op=.None, span=expr.span, type=expr.type, op=.None, span=expr.span, type=expr.type,
@@ -381,7 +417,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
_ = pop(&stack) _ = pop(&stack)
case .String, .Array, .Struct, .None, .Optional_Some, .Address, .Deref, case .String, .Array, .Struct, .Range, .None, .Optional_Some, .Address, .Deref,
.Index, .Slice, .Field, .Length, .Slice_Ptr, .Unwrap, .Orelse, .Index, .Slice, .Field, .Length, .Slice_Ptr, .Unwrap, .Orelse,
.Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or: .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
last = lower_compound_expr(state, frame.expr) last = lower_compound_expr(state, frame.expr)
@@ -762,6 +798,326 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
case .For:
iterable_type := hir_module.exprs[statement.expr].type
if types.is_range(iterable_type, &hir_module.types) {
child := types.child_type(iterable_type, &hir_module.types)
range_value := lower_expr(state, statement.expr)
start := append_instruction(state, ir.Instruction{
op=.Extract, span=statement.span, type=child, integer=0,
target=ir.INVALID_REF, a=range_value, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
end := append_instruction(state, ir.Instruction{
op=.Extract, span=statement.span, type=child, integer=1,
target=ir.INVALID_REF, a=range_value, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
inclusive := append_instruction(state, ir.Instruction{
op=.Extract, span=statement.span, type=types.BOOL, integer=2,
target=ir.INVALID_REF, a=range_value, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
current_slot := append_instruction(state, ir.Instruction{
op=.Alloca, span=statement.span, type=child,
target=ir.local_ref(ir.Local_Id(statement.local)),
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
state.local_slots[statement.local] = current_slot
append_instruction(state, ir.Instruction{
op=.Store, span=statement.span, type=child,
target=ir.INVALID_REF, a=current_slot, b=start,
diagnostic=source.INVALID_DIAGNOSTIC,
})
condition_lbl := fresh_label(state)
body_lbl := fresh_label(state)
update_lbl := fresh_label(state)
exit_lbl := fresh_label(state)
append_instruction(state, ir.Instruction{
op=.Br, span=statement.span, type=types.VOID, integer=condition_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Label, span=statement.span, type=types.VOID, integer=condition_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
current := append_instruction(state, ir.Instruction{
op=.Load, span=statement.span, type=child,
target=ir.INVALID_REF, a=current_slot, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
less := append_instruction(state, ir.Instruction{
op=.Compare, span=statement.span, type=types.BOOL,
integer=i64(ir.Compare_Predicate.Lt),
target=ir.INVALID_REF, a=current, b=end,
diagnostic=source.INVALID_DIAGNOSTIC,
})
equal := append_instruction(state, ir.Instruction{
op=.Compare, span=statement.span, type=types.BOOL,
integer=i64(ir.Compare_Predicate.Eq),
target=ir.INVALID_REF, a=current, b=end,
diagnostic=source.INVALID_DIAGNOSTIC,
})
false_value := append_instruction(state, ir.Instruction{
op=.Const, span=statement.span, type=types.BOOL, integer=0,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
true_value := append_instruction(state, ir.Instruction{
op=.Const, span=statement.span, type=types.BOOL, integer=1,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
inclusive_args := make([]ir.Instruction_Id, 2, state.allocator)
inclusive_args[0] = equal
inclusive_args[1] = false_value
inclusive_equal := append_instruction(state, ir.Instruction{
op=.Select, span=statement.span, type=types.BOOL,
target=ir.INVALID_REF, a=inclusive, b=ir.INVALID_INSTRUCTION,
args=inclusive_args, diagnostic=source.INVALID_DIAGNOSTIC,
})
condition_args := make([]ir.Instruction_Id, 2, state.allocator)
condition_args[0] = true_value
condition_args[1] = inclusive_equal
condition := append_instruction(state, ir.Instruction{
op=.Select, span=statement.span, type=types.BOOL,
target=ir.INVALID_REF, a=less, b=ir.INVALID_INSTRUCTION,
args=condition_args, diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Cond_Br, span=statement.span, type=types.VOID,
a=condition, integer=body_lbl, target=ir.Ref(u32(exit_lbl)),
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Label, span=statement.span, type=types.VOID, integer=body_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
lower_statements(state, statement.then_body)
after_body := append_instruction(state, ir.Instruction{
op=.Load, span=statement.span, type=child,
target=ir.INVALID_REF, a=current_slot, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
at_end := append_instruction(state, ir.Instruction{
op=.Compare, span=statement.span, type=types.BOOL,
integer=i64(ir.Compare_Predicate.Eq),
target=ir.INVALID_REF, a=after_body, b=end,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Cond_Br, span=statement.span, type=types.VOID,
a=at_end, integer=exit_lbl, target=ir.Ref(u32(update_lbl)),
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Label, span=statement.span, type=types.VOID, integer=update_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
one := append_instruction(state, ir.Instruction{
op=.Const, span=statement.span, type=child, integer=1,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
next := append_instruction(state, ir.Instruction{
op=.Add_Checked, span=statement.span, type=child,
target=ir.INVALID_REF, a=after_body, b=one,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Store, span=statement.span, type=child,
target=ir.INVALID_REF, a=current_slot, b=next,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Br, span=statement.span, type=types.VOID, integer=condition_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Label, span=statement.span, type=types.VOID, integer=exit_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
continue
}
item, item_ok := types.container(iterable_type, &hir_module.types)
if !item_ok {
append_instruction(state, ir.Instruction{
op=.Trap, span=statement.span, type=types.VOID,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=statement.diagnostic,
})
continue
}
iterable_value := ir.INVALID_INSTRUCTION
if types.is_array(iterable_type, &hir_module.types) {
if hir_expr_is_location(state, statement.expr) {
iterable_value = lower_location(state, statement.expr)
} else {
value := lower_expr(state, statement.expr)
iterable_value = append_instruction(state, ir.Instruction{
op=.Alloca, span=statement.span, type=iterable_type,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Store, span=statement.span, type=iterable_type,
target=ir.INVALID_REF, a=iterable_value, b=value,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
} else {
iterable_value = lower_expr(state, statement.expr)
}
base := append_instruction(state, ir.Instruction{
op=.Slice_Ptr, span=statement.span, type=statement.iterator_type,
target=ir.INVALID_REF, a=iterable_value, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
length := ir.INVALID_INSTRUCTION
if item.kind == .Array {
length = append_instruction(state, ir.Instruction{
op=.Const, span=statement.span, type=types.USIZE, integer=i64(item.count),
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
} else {
length = append_instruction(state, ir.Instruction{
op=.Length, span=statement.span, type=types.USIZE,
target=ir.INVALID_REF, a=iterable_value, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
counter_target := ir.INVALID_REF
if statement.index_local != hir.INVALID_LOCAL {
counter_target = ir.local_ref(ir.Local_Id(statement.index_local))
}
counter_slot := append_instruction(state, ir.Instruction{
op=.Alloca, span=statement.span, type=types.USIZE,
target=counter_target, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
if statement.index_local != hir.INVALID_LOCAL {
state.local_slots[statement.index_local] = counter_slot
}
zero := append_instruction(state, ir.Instruction{
op=.Const, span=statement.span, type=types.USIZE, integer=0,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Store, span=statement.span, type=types.USIZE,
target=ir.INVALID_REF, a=counter_slot, b=zero,
diagnostic=source.INVALID_DIAGNOSTIC,
})
capture_type := state.func_locals[statement.local].type
capture_slot := append_instruction(state, ir.Instruction{
op=.Alloca, span=statement.span, type=capture_type,
target=ir.local_ref(ir.Local_Id(statement.local)),
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
state.local_slots[statement.local] = capture_slot
condition_lbl := fresh_label(state)
body_lbl := fresh_label(state)
update_lbl := fresh_label(state)
exit_lbl := fresh_label(state)
append_instruction(state, ir.Instruction{
op=.Br, span=statement.span, type=types.VOID, integer=condition_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Label, span=statement.span, type=types.VOID, integer=condition_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
index := append_instruction(state, ir.Instruction{
op=.Load, span=statement.span, type=types.USIZE,
target=ir.INVALID_REF, a=counter_slot, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
condition := append_instruction(state, ir.Instruction{
op=.Compare, span=statement.span, type=types.BOOL,
integer=i64(ir.Compare_Predicate.Lt),
target=ir.INVALID_REF, a=index, b=length,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Cond_Br, span=statement.span, type=types.VOID,
a=condition, integer=body_lbl, target=ir.Ref(u32(exit_lbl)),
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Label, span=statement.span, type=types.VOID, integer=body_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
pointer_type := statement.iterator_type
if statement.pointer_capture {
pointer_type = capture_type
}
element_pointer := append_instruction(state, ir.Instruction{
op=.Pointer_Add, span=statement.span, type=pointer_type,
target=ir.INVALID_REF, a=base, b=index,
diagnostic=source.INVALID_DIAGNOSTIC,
})
captured := element_pointer
if !statement.pointer_capture {
captured = append_instruction(state, ir.Instruction{
op=.Load, span=statement.span, type=item.child,
target=ir.INVALID_REF, a=element_pointer, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
append_instruction(state, ir.Instruction{
op=.Store, span=statement.span, type=capture_type,
target=ir.INVALID_REF, a=capture_slot, b=captured,
diagnostic=source.INVALID_DIAGNOSTIC,
})
lower_statements(state, statement.then_body)
append_instruction(state, ir.Instruction{
op=.Br, span=statement.span, type=types.VOID, integer=update_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Label, span=statement.span, type=types.VOID, integer=update_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
one := append_instruction(state, ir.Instruction{
op=.Const, span=statement.span, type=types.USIZE, integer=1,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
next := append_instruction(state, ir.Instruction{
op=.Add_Checked, span=statement.span, type=types.USIZE,
target=ir.INVALID_REF, a=index, b=one,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Store, span=statement.span, type=types.USIZE,
target=ir.INVALID_REF, a=counter_slot, b=next,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Br, span=statement.span, type=types.VOID, integer=condition_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Label, span=statement.span, type=types.VOID, integer=exit_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
} }
} }
} }
+102 -1
View File
@@ -20,6 +20,7 @@ Parser :: struct {
file: ast.File_Id, file: ast.File_Id,
cursor: int, cursor: int,
delimiter_depth: int, delimiter_depth: int,
range_disabled: int,
// Suppresses `Name { ... }` struct-literal parsing at delimiter depth 0 so a // Suppresses `Name { ... }` struct-literal parsing at delimiter depth 0 so a
// control-flow condition like `if foo { ... }` does not swallow the block as a // control-flow condition like `if foo { ... }` does not swallow the block as a
// struct literal. Nested `(`/`[`/call-arg contexts (delimiter_depth > 0) still // 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 { if _, ok := allow(parser, .Right_Paren); !ok {
source.add(parser.diagnostics, current(parser).span, "expected ')'") 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 return expr
case .Invalid: case .Invalid:
advance(parser) 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) { infix_binding_power :: proc(kind: token.Kind) -> (left, right: int, ok: bool) {
#partial switch kind { #partial switch kind {
case .Range, .Range_Inclusive:
return 0, 1, true
case .Keyword_Orelse: case .Keyword_Orelse:
return 2, 3, true return 2, 3, true
case .Keyword_Or: 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 { infix_expr_kind :: proc(kind: token.Kind) -> ast.Expr_Kind {
#partial switch kind { #partial switch kind {
case .Range, .Range_Inclusive: return .Range
case .Keyword_Orelse: return .Orelse case .Keyword_Orelse: return .Orelse
case .Keyword_Or: return .Or case .Keyword_Or: return .Or
case .Keyword_And: return .And 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) { prefix_binding_power :: proc(kind: token.Kind) -> (right: int, ok: bool) {
#partial switch kind { #partial switch kind {
case .Minus, .Ampersand, .Bang: 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 { if _, ok := allow(parser, .Range); ok {
slicing = true slicing = true
} else { } else {
parser.range_disabled += 1
start_expr = parse_expression_bp(parser, 0, nesting+1) start_expr = parse_expression_bp(parser, 0, nesting+1)
parser.range_disabled -= 1
skip_newlines(parser) skip_newlines(parser)
if _, ok := allow(parser, .Range); ok { if _, ok := allow(parser, .Range); ok {
slicing = true slicing = true
@@ -770,7 +791,9 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
} }
skip_newlines(parser) skip_newlines(parser)
if slicing && current(parser).kind != .Right_Bracket { if slicing && current(parser).kind != .Right_Bracket {
parser.range_disabled += 1
end_expr = parse_expression_bp(parser, 0, nesting+1) end_expr = parse_expression_bp(parser, 0, nesting+1)
parser.range_disabled -= 1
skip_newlines(parser) skip_newlines(parser)
} }
end_token, ok := allow(parser, .Right_Bracket) end_token, ok := allow(parser, .Right_Bracket)
@@ -822,6 +845,10 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
continue continue
} }
left_power, right_power, ok := infix_binding_power(current(parser).kind) 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 { if !ok || left_power < minimum_binding_power {
break 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) right := parse_expression_bp(parser, right_power, nesting+1)
left_expr := parser.module.exprs[left] left_expr := parser.module.exprs[left]
right_expr := parser.module.exprs[right] 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{ left = add_expr(parser, ast.Expr{
kind=infix_expr_kind(operator.kind), kind=infix_expr_kind(operator.kind),
span=span_from(left_expr.span, right_expr.span), span=span_from(left_expr.span, right_expr.span),
integer=1 if operator.kind == .Range_Inclusive else 0,
left=left, left=left,
right=right, right=right,
diagnostic=source.INVALID_DIAGNOSTIC, diagnostic=source.INVALID_DIAGNOSTIC,
@@ -931,6 +975,9 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
if current(parser).kind == .Keyword_While { if current(parser).kind == .Keyword_While {
return parse_while(parser) return parse_while(parser)
} }
if current(parser).kind == .Keyword_For {
return parse_for(parser)
}
if current(parser).kind == .Identifier || current(parser).kind == .Underscore { if current(parser).kind == .Identifier || current(parser).kind == .Underscore {
start_cursor := parser.cursor start_cursor := parser.cursor
@@ -1159,7 +1206,7 @@ parse_while_update :: proc(parser: ^Parser) -> ast.Stmt_Id {
statement := &parser.module.statements[update] statement := &parser.module.statements[update]
switch statement.kind { switch statement.kind {
case .Assignment, .Expression: case .Assignment, .Expression:
case .Invalid, .Declaration, .Return, .If, .While: case .Invalid, .Declaration, .Return, .If, .While, .For:
diagnostic := source.add( diagnostic := source.add(
parser.diagnostics, parser.diagnostics,
statement.span, statement.span,
@@ -1187,6 +1234,60 @@ parse_while_update :: proc(parser: ^Parser) -> ast.Stmt_Id {
return update 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 { parse_while :: proc(parser: ^Parser) -> ast.Stmt_Id {
start := advance(parser) // consume 'while' start := advance(parser) // consume 'while'
skip_newlines(parser) skip_newlines(parser)
+2
View File
@@ -27,6 +27,7 @@ Kind :: enum u8 {
Minus, Minus,
Dot, Dot,
Range, Range,
Range_Inclusive,
Ellipsis, Ellipsis,
At, At,
Star, Star,
@@ -55,6 +56,7 @@ Kind :: enum u8 {
Keyword_Or, Keyword_Or,
Keyword_If, Keyword_If,
Keyword_While, Keyword_While,
Keyword_For,
Keyword_Else, Keyword_Else,
Keyword_True, Keyword_True,
Keyword_False, Keyword_False,
+18 -4
View File
@@ -59,6 +59,7 @@ Kind :: enum u8 {
Array, Array,
Pointer, Pointer,
Slice, Slice,
Range,
Optional, Optional,
Function, Function,
Named, Named,
@@ -392,7 +393,7 @@ is_concrete_scalar :: proc(value: Type) -> bool {
is_concrete :: proc(value: Type, store: ^Store = nil) -> bool { is_concrete :: proc(value: Type, store: ^Store = nil) -> bool {
value_kind := kind(value, store) value_kind := kind(value, store)
if value_kind == .Scalar || value_kind == .Array || value_kind == .Pointer || if value_kind == .Scalar || value_kind == .Array || value_kind == .Pointer ||
value_kind == .Slice || value_kind == .Optional { value_kind == .Slice || value_kind == .Range || value_kind == .Optional {
return true return true
} }
if value_kind == .Struct || value_kind == .Union { if value_kind == .Struct || value_kind == .Union {
@@ -414,6 +415,10 @@ is_slice :: proc(value: Type, store: ^Store) -> bool {
return kind(value, store) == .Slice return kind(value, store) == .Slice
} }
is_range :: proc(value: Type, store: ^Store) -> bool {
return kind(value, store) == .Range
}
is_optional :: proc(value: Type, store: ^Store) -> bool { is_optional :: proc(value: Type, store: ^Store) -> bool {
return kind(value, store) == .Optional return kind(value, store) == .Optional
} }
@@ -486,7 +491,7 @@ is_runtime_value :: proc(value: Type, store: ^Store) -> bool {
if value_kind == .Scalar || value_kind == .Pointer { if value_kind == .Scalar || value_kind == .Pointer {
return true return true
} }
if value_kind == .Slice || value_kind == .Array || value_kind == .Optional { if value_kind == .Slice || value_kind == .Array || value_kind == .Range || value_kind == .Optional {
return !contains_c_struct_by_value(value, store) return !contains_c_struct_by_value(value, store)
} }
if value_kind == .Struct || value_kind == .Union { if value_kind == .Struct || value_kind == .Union {
@@ -521,7 +526,7 @@ contains_c_struct_by_value :: proc(value: Type, store: ^Store, depth := 0) -> bo
if item.kind == .Union { if item.kind == .Union {
return item.opaque || (item.c_layout && item.field_count == 0) return item.opaque || (item.c_layout && item.field_count == 0)
} }
if item.kind == .Array || item.kind == .Slice || item.kind == .Optional { if item.kind == .Array || item.kind == .Slice || item.kind == .Range || item.kind == .Optional {
return contains_c_struct_by_value(item.child, store, depth+1) return contains_c_struct_by_value(item.child, store, depth+1)
} }
return false return false
@@ -665,6 +670,10 @@ slice :: proc(store: ^Store, child: Type, mutable: bool, has_sentinel := false,
}) })
} }
range :: proc(store: ^Store, child: Type) -> Type {
return intern(store, Node{kind=.Range, child=child})
}
array :: proc( array :: proc(
store: ^Store, store: ^Store,
child: Type, child: Type,
@@ -805,6 +814,11 @@ size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
return u64(target.pointer_bits(selected)/8) return u64(target.pointer_bits(selected)/8)
case .Slice: case .Slice:
return u64(target.pointer_bits(selected)/8*2) return u64(target.pointer_bits(selected)/8*2)
case .Range:
child_size := size(child_type(value, store), store, selected)
child_align := u64(alignment_of(child_type(value, store), store, selected))
raw_size := child_size*2+1
return (raw_size+child_align-1)/child_align*child_align
case .Array: case .Array:
item, _ := node(store, value) item, _ := node(store, value)
return physical_count(value, store)*size(item.child, store, selected) return physical_count(value, store)*size(item.child, store, selected)
@@ -855,7 +869,7 @@ alignment_of :: proc(value: Type, store: ^Store, selected := target.DEFAULT) ->
return alignment(value, selected) return alignment(value, selected)
case .Pointer, .Slice: case .Pointer, .Slice:
return target.pointer_bits(selected)/8 return target.pointer_bits(selected)/8
case .Array, .Optional: case .Array, .Range, .Optional:
return alignment_of(child_type(value, store), store, selected) return alignment_of(child_type(value, store), store, selected)
case .Function: case .Function:
return 1 return 1
+469
View File
@@ -4080,3 +4080,472 @@ while_loop_allocas_are_emitted_in_the_entry_block :: proc(t: ^testing.T) {
} }
testing.expect(t, alloca_count >= 3) testing.expect(t, alloca_count >= 3)
} }
@(test)
for_loop_tokens_and_parser_capture_range_shape :: proc(t: ^testing.T) {
text := `main :: func() void {
for 0..4 |value| {
_ = value
}
items [1]mut i32 = [1]
for (&items) |@item, index| {
_ = item
_ = index
}
for 0..=1 |inclusive| {
_ = inclusive
}
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
for_count := 0
range_count := 0
inclusive_count := 0
for tok in stream.items {
#partial switch tok.kind {
case .Keyword_For: for_count += 1
case .Range: range_count += 1
case .Range_Inclusive: inclusive_count += 1
case:
}
}
testing.expect_value(t, for_count, 3)
testing.expect_value(t, range_count, 1)
testing.expect_value(t, inclusive_count, 1)
module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&module)
testing.expect_value(t, len(diagnostics.items), 0)
function := module.functions[0]
first := module.statements[function.body[0]]
second := module.statements[function.body[2]]
third := module.statements[function.body[3]]
testing.expect_value(t, first.kind, ast.Stmt_Kind.For)
testing.expect(t, !first.pointer_capture)
testing.expect_value(t, module.exprs[first.expr].kind, ast.Expr_Kind.Range)
testing.expect_value(t, module.exprs[first.expr].integer, u64(0))
testing.expect_value(t, second.kind, ast.Stmt_Kind.For)
testing.expect(t, second.pointer_capture)
testing.expect(t, symbol.is_valid(second.index_name))
testing.expect_value(t, third.kind, ast.Stmt_Kind.For)
testing.expect_value(t, module.exprs[third.expr].integer, u64(1))
}
@(test)
range_bound_parenthesization_is_enforced :: proc(t: ^testing.T) {
text := `main :: func() void {
limit :: 3
for 0..limit + 1 |bad| {
_ = bad
}
for 0..(limit + 1) |good| {
_ = good
}
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&module)
found := 0
for diagnostic in diagnostics.items {
found += 1 if strings.contains(diagnostic.message, "range bounds with operators must be parenthesized") else 0
}
testing.expect_value(t, found, 1)
}
@(test)
parser_diagnoses_malformed_for_captures :: proc(t: ^testing.T) {
cases := [4]struct {
text: string,
needle: string,
}{
{`main :: func() void {
for [1] item {}
}
`, "expected '|' before for-loop captures"},
{`main :: func() void {
for [1] |@| {}
}
`, "expected a for-loop item capture"},
{`main :: func() void {
for [1] |item,| {}
}
`, "expected an index capture after ','"},
{`main :: func() void {
for [1] |item {}
}
`, "expected '|' to close for-loop captures"},
}
for test_case in cases {
source_file := source.Source{path="test.bro", text=test_case.text}
diagnostics := source.init_diagnostics(&source_file)
symbols := symbol.init_table()
stream := lexer.lex(&source_file, &diagnostics, &symbols)
module := parser.parse(&stream, &source_file, &diagnostics)
found := false
for diagnostic in diagnostics.items {
found = found || strings.contains(diagnostic.message, test_case.needle)
}
testing.expect(t, found)
ast.destroy_module(&module)
delete(stream.items)
symbol.destroy_table(&symbols)
source.destroy_diagnostics(&diagnostics)
}
}
@(test)
for_loops_compile_and_run :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-for-loop"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/for_loop", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 42)
}
@(test)
range_loop_edges_compile_and_run :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-for-loop-edges"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/for_loop_edges", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 42)
}
@(test)
for_loop_diagnostics_cover_iterables_captures_and_scope :: proc(t: ^testing.T) {
text := `bad_iterable :: func() void {
for 1 |item| {
_ = item
}
}
bad_array_pointer_capture :: func() void {
for [1] |@item| {
_ = item
}
}
bad_range_pointer_capture :: func() void {
for 0..1 |@item| {
_ = item
}
}
bad_range_index_capture :: func() void {
for 0..1 |item, index| {
_ = item
_ = index
}
}
bad_duplicate_capture :: func() void {
for [1] |item, item| {
_ = item
}
}
bad_capture_redeclaration :: func() void {
for [1] |item| {
item i32 = 2
_ = item
}
}
bad_capture_assignment :: func() void {
for [1] |item| {
item = 2
}
}
bad_immutable_pointer_capture :: func() void {
items :: [1]
for (&items) |@item| {
item^ = 2
}
}
bad_scope :: func() void {
for [1] |item| {
_ = item
}
_ = item
}
bad_integer_bounds :: func() void {
start i32 = 0
end u32 = 1
for start..end |item| {
_ = item
}
}
bad_float_bounds :: func() void {
for 0.0..1.0 |item| {
_ = item
}
}
main :: func() void {
bad_iterable()
bad_array_pointer_capture()
bad_range_pointer_capture()
bad_range_index_capture()
bad_duplicate_capture()
bad_capture_redeclaration()
bad_capture_assignment()
bad_immutable_pointer_capture()
bad_scope()
bad_integer_bounds()
bad_float_bounds()
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
unsupported := false
array_pointer := false
range_pointer := false
range_index := false
duplicate_capture := false
redeclaration := false
immutable := false
immutable_pointer := false
scope := false
integer_bounds := 0
for diagnostic in diagnostics.items {
unsupported = unsupported || strings.contains(diagnostic.message, "for-loop iterable must be a range")
array_pointer = array_pointer || strings.contains(diagnostic.message, "pointer capture over an array requires")
range_pointer = range_pointer || strings.contains(diagnostic.message, "range loops do not support pointer captures")
range_index = range_index || strings.contains(diagnostic.message, "range loops do not support index captures")
duplicate_capture = duplicate_capture || strings.contains(diagnostic.message, "for-loop captures must have distinct names")
redeclaration = redeclaration || strings.contains(diagnostic.message, "duplicate local 'item'")
immutable = immutable || strings.contains(diagnostic.message, "cannot assign immutable local 'item'")
immutable_pointer = immutable_pointer || strings.contains(diagnostic.message, "assignment target is not writable")
scope = scope || strings.contains(diagnostic.message, "unresolved global 'item'")
integer_bounds += 1 if strings.contains(diagnostic.message, "range bounds must be compatible concrete integers") else 0
}
testing.expect(t, unsupported)
testing.expect(t, array_pointer)
testing.expect(t, range_pointer)
testing.expect(t, range_index)
testing.expect(t, duplicate_capture)
testing.expect(t, redeclaration)
testing.expect(t, immutable)
testing.expect(t, immutable_pointer)
testing.expect(t, scope)
testing.expect_value(t, integer_bounds, 2)
}
@(test)
for_pointer_capture_respects_pointer_and_array_mutability :: proc(t: ^testing.T) {
text := `readonly :: func() void {
values [1]mut i32 = [1]
items @[1]mut i32 = &values
items[0] = 7
for items |@item| {
item^ = 7
}
}
writable :: func() void {
values [1]mut i32 = [1]
items @mut [1]mut i32 = &values
items[0] = 7
for items |@item| {
item^ = 7
}
}
main :: func() void {
readonly()
writable()
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
readonly_errors := 0
for diagnostic in diagnostics.items {
readonly_errors += 1 if strings.contains(diagnostic.message, "assignment target is not writable") else 0
}
testing.expect_value(t, readonly_errors, 2)
}
@(test)
pointer_field_passthrough_respects_pointee_mutability :: proc(t: ^testing.T) {
text := `Point :: struct {
x i32
}
readonly :: func(point @Point) i32 {
return point.x
}
bad_write :: func(point @Point) void {
point.x = 7
}
writable :: func(point @mut Point) void {
point.x += 1
}
main :: func() i32 {
point Point = Point { x = 41 }
writable(&point)
bad_write(&point)
return readonly(&point)
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
readonly_errors := 0
for diagnostic in diagnostics.items {
readonly_errors += 1 if strings.contains(diagnostic.message, "assignment target is not writable") else 0
}
testing.expect_value(t, readonly_errors, 1)
}
@(test)
equal_range_returns_infer_a_usable_result_type :: proc(t: ^testing.T) {
text := `choose :: func(first bool) int {
if first {
return 0..1
}
return 2..3
}
main :: func() i32 {
total i32 = 0
for choose(false) |value| {
total = total + value
}
return total
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
ir_module := lower.lower(&hir_module)
defer ir.destroy_module(&ir_module)
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
defer delete(llvm_text)
testing.expect_value(t, len(diagnostics.items), 0)
found := false
for function in hir_module.functions {
if symbol.resolve(&symbols, function.name) == "choose" {
found = true
testing.expect(t, types.is_range(function.result, &hir_module.types))
testing.expect_value(t, types.child_type(function.result, &hir_module.types), types.I8)
}
}
testing.expect(t, found)
testing.expect(t, strings.contains(llvm_text, "extractvalue"))
}
@(test)
for_loop_lowering_evaluates_once_and_avoids_index_bounds_checks :: proc(t: ^testing.T) {
text := `make_range :: func() int {
return 0..2
}
make_array :: func() int {
return [1, 2]
}
main :: func() i32 {
total i32 = 0
for make_range() |value| {
total = total + value
}
for make_array() |value| {
total = total + value
}
return total
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
ir_module := lower.lower(&hir_module)
defer ir.destroy_module(&ir_module)
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
defer delete(llvm_text)
testing.expect_value(t, len(diagnostics.items), 0)
call_count := 0
extract_count := 0
select_count := 0
pointer_add_count := 0
index_address_count := 0
first_loop_label := find_substring_offset(llvm_text, "bro_block_")
testing.expect(t, first_loop_label >= 0)
for function in ir_module.functions {
if !function.is_main {
continue
}
for instruction, instruction_index in function.instructions {
#partial switch instruction.op {
case .Call: call_count += 1
case .Extract: extract_count += 1
case .Select: select_count += 1
case .Pointer_Add: pointer_add_count += 1
case .Index_Address: index_address_count += 1
case .Alloca:
needle := fmt.tprintf(" %%v%d = alloca ", instruction_index)
offset := find_substring_offset(llvm_text, needle)
testing.expect(t, offset >= 0 && offset < first_loop_label)
case:
}
}
}
testing.expect_value(t, call_count, 2)
testing.expect_value(t, extract_count, 3)
testing.expect_value(t, select_count, 2)
testing.expect(t, pointer_add_count >= 1)
testing.expect_value(t, index_address_count, 0)
testing.expect(t, !strings.contains(llvm_text, "index_ok"))
}
+52
View File
@@ -0,0 +1,52 @@
# Milestone 5: ranges and sequence for loops.
pass :: func(value int) int {
return value
}
main :: func() i32 {
total i32 = 0
items [3]mut i32 = [1, 2, 3]
for items |item, index| {
total = total + item
_ = index
}
for (&items) |@item| {
item^ = item^ + 1
}
view []mut i32 = (&items)[..]
for view |@item, index| {
item^ = item^ + 1
_ = index
}
for view |item| {
total = total + item
}
for [4, 5] |item| {
total = total + item
}
for "ab" |byte| {
_ = byte
total = total + 1
}
for 0..4 |value| {
total = total + value
}
for 1..=3 |value| {
total = total + value
}
once :: 0..1
for pass(once) |value| {
_ = value
total = total + 1
}
return total
}
+70
View File
@@ -0,0 +1,70 @@
make_range :: func(calls @mut i32, end usize) int {
calls^ = calls^ + 1
return 0..end
}
global_range :: 0..1
main :: func() i32 {
total i32 = 0
first u8 = 254
last u8 = 255
for first..=last |value| {
_ = value
total = total + 1
}
signed_start i8 = -2
signed_end i8 = 1
for signed_start..signed_end |value| {
_ = value
total = total + 1
}
limit usize = 3
for 0..(limit + 1) |value| {
_ = value
total = total + 1
}
for 0..2 |outer| {
_ = outer
for 0..3 |inner| {
_ = inner
total = total + 1
}
}
for 5..2 |value| {
_ = value
total = total + 100
}
empty [0]i32 = []
for empty |value| {
_ = value
total = total + 100
}
pointed i32 = 3
pointers [1]@mut i32 = [&pointed]
for pointers |pointer| {
total = total + pointer^
}
calls i32 = 0
for make_range(&calls, 2) |value| {
_ = value
total = total + 1
}
for global_range |value| {
_ = value
total = total + 1
}
if calls == 1 {
total = total + 21
}
return total
}