for loops
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"))
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user