labeled block statements
This commit is contained in:
@@ -391,9 +391,16 @@
|
|||||||
loop, a labeled one searches by label). The checker tracks a `loop_labels` stack and a
|
loop, a labeled one searches by label). The checker tracks a `loop_labels` stack and a
|
||||||
`Yield_Target.defer_floor`; a `yield :L v` desugars to `slot = v; flush defers to L's body;
|
`Yield_Target.defer_floor`; a `yield :L v` desugars to `slot = v; flush defers to L's body;
|
||||||
break :L`, reusing the milestone-18 break lowering — **no new IR opcode, no emitter change**
|
break :L`, reusing the milestone-18 break lowering — **no new IR opcode, no emitter change**
|
||||||
- deferred (`// ponytail:`): a labeled bare block as a *plain statement* with `break :blk`;
|
- a labeled bare block as a *plain statement* is also exitable with `break :blk` (a HIR
|
||||||
`none`-before-concrete typing in an untyped block whose first concrete yield references a
|
`.Block` break target; not a loop, so unlabeled `break`/`continue` and `continue :blk` skip
|
||||||
block local (annotate instead)
|
it). The checker tracks a parallel `loop_is_loop` stack so labeled `break` reaches a loop or
|
||||||
|
block while `continue` and unlabeled `break`/`continue` reach only the innermost loop
|
||||||
|
- untyped block `none`-before-concrete typing now builds the block's leading (yield-free)
|
||||||
|
statements first (a throwaway probe), so a first concrete `yield :blk` that references a
|
||||||
|
block local still resolves the result to `?T`
|
||||||
|
- deferred (`// ponytail:`): the same `none`-before-concrete typing in an untyped block (or
|
||||||
|
loop) whose concrete yield references a local declared *past* the first yield (annotate);
|
||||||
|
same-label loop/block shadowing resolves innermost-wins
|
||||||
|
|
||||||
21. unions and tagged unions
|
21. unions and tagged unions
|
||||||
|
|
||||||
|
|||||||
+105
-13
@@ -95,9 +95,12 @@ Build_Ctx :: struct {
|
|||||||
// only see loops opened within the defer (those past `loop_floor`).
|
// only see loops opened within the defer (those past `loop_floor`).
|
||||||
defers: ^[dynamic][]hir.Stmt_Id,
|
defers: ^[dynamic][]hir.Stmt_Id,
|
||||||
loop_defer_starts: ^[dynamic]int,
|
loop_defer_starts: ^[dynamic]int,
|
||||||
// Parallel to `loop_defer_starts`: the label of each enclosing loop (INVALID when
|
// Parallel to `loop_defer_starts`: the label of each enclosing break target (INVALID
|
||||||
// unlabeled), so a `break :L` / `continue :L` can target an outer labeled loop.
|
// when unlabeled), so a `break :L` / `continue :L` can target an outer one. A labeled
|
||||||
|
// block statement is a break target too; `loop_is_loop` distinguishes loops (which
|
||||||
|
// `continue` and unlabeled `break`/`continue` target) from value/labeled blocks.
|
||||||
loop_labels: ^[dynamic]symbol.Id,
|
loop_labels: ^[dynamic]symbol.Id,
|
||||||
|
loop_is_loop: ^[dynamic]bool,
|
||||||
defer_depth: int,
|
defer_depth: int,
|
||||||
loop_floor: int,
|
loop_floor: int,
|
||||||
}
|
}
|
||||||
@@ -4603,7 +4606,9 @@ build_block :: proc(
|
|||||||
}
|
}
|
||||||
append(ctx.loop_defer_starts, len(ctx.defers^))
|
append(ctx.loop_defer_starts, len(ctx.defers^))
|
||||||
append(ctx.loop_labels, statement.label)
|
append(ctx.loop_labels, statement.label)
|
||||||
|
append(ctx.loop_is_loop, true)
|
||||||
loop_body := build_block(ctx, statement.body)
|
loop_body := build_block(ctx, statement.body)
|
||||||
|
pop(ctx.loop_is_loop)
|
||||||
pop(ctx.loop_labels)
|
pop(ctx.loop_labels)
|
||||||
pop(ctx.loop_defer_starts)
|
pop(ctx.loop_defer_starts)
|
||||||
update := hir.INVALID_STMT
|
update := hir.INVALID_STMT
|
||||||
@@ -4700,7 +4705,9 @@ build_block :: proc(
|
|||||||
}
|
}
|
||||||
append(ctx.loop_defer_starts, len(ctx.defers^))
|
append(ctx.loop_defer_starts, len(ctx.defers^))
|
||||||
append(ctx.loop_labels, statement.label)
|
append(ctx.loop_labels, statement.label)
|
||||||
|
append(ctx.loop_is_loop, true)
|
||||||
loop_body := build_block(ctx, statement.body, capture_start)
|
loop_body := build_block(ctx, statement.body, capture_start)
|
||||||
|
pop(ctx.loop_is_loop)
|
||||||
pop(ctx.loop_labels)
|
pop(ctx.loop_labels)
|
||||||
pop(ctx.loop_defer_starts)
|
pop(ctx.loop_defer_starts)
|
||||||
resize(ctx.locals, capture_start)
|
resize(ctx.locals, capture_start)
|
||||||
@@ -4740,14 +4747,22 @@ build_block :: proc(
|
|||||||
// hides the loops opened outside the defer.
|
// hides the loops opened outside the defer.
|
||||||
target_index := -1
|
target_index := -1
|
||||||
if symbol.is_valid(statement.label) {
|
if symbol.is_valid(statement.label) {
|
||||||
|
// `break :L` targets a labeled loop or block; `continue :L` only a loop.
|
||||||
for i := len(ctx.loop_labels^) - 1; i >= ctx.loop_floor; i -= 1 {
|
for i := len(ctx.loop_labels^) - 1; i >= ctx.loop_floor; i -= 1 {
|
||||||
if ctx.loop_labels^[i] == statement.label {
|
if ctx.loop_labels^[i] == statement.label &&
|
||||||
|
(ctx.loop_is_loop^[i] || statement.kind == .Break) {
|
||||||
|
target_index = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unlabeled `break`/`continue` targets the innermost loop, skipping blocks.
|
||||||
|
for i := len(ctx.loop_defer_starts^) - 1; i >= ctx.loop_floor; i -= 1 {
|
||||||
|
if ctx.loop_is_loop^[i] {
|
||||||
target_index = i
|
target_index = i
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if len(ctx.loop_defer_starts^) > ctx.loop_floor {
|
|
||||||
target_index = len(ctx.loop_defer_starts^) - 1
|
|
||||||
}
|
}
|
||||||
if target_index < 0 {
|
if target_index < 0 {
|
||||||
keyword := "break" if statement.kind == .Break else "continue"
|
keyword := "break" if statement.kind == .Break else "continue"
|
||||||
@@ -4776,6 +4791,25 @@ build_block :: proc(
|
|||||||
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
case .Block:
|
case .Block:
|
||||||
|
if symbol.is_valid(statement.label) {
|
||||||
|
// A labeled block statement (`blk: { … break :blk … }`): a break target
|
||||||
|
// with an exit-label boundary, built as a HIR `.Block`. Not a loop, so
|
||||||
|
// unlabeled `break`/`continue` and `continue :blk` skip it.
|
||||||
|
append(ctx.loop_defer_starts, len(ctx.defers^))
|
||||||
|
append(ctx.loop_labels, statement.label)
|
||||||
|
append(ctx.loop_is_loop, false)
|
||||||
|
built := build_block(ctx, statement.body)
|
||||||
|
pop(ctx.loop_is_loop)
|
||||||
|
pop(ctx.loop_labels)
|
||||||
|
pop(ctx.loop_defer_starts)
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Block, span = statement.span, label = statement.label,
|
||||||
|
then_body = built, local = hir.INVALID_LOCAL, target = hir.INVALID_EXPR,
|
||||||
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
// A bare `{ ... }` scope: build it (its own locals/defers are scoped by
|
// A bare `{ ... }` scope: build it (its own locals/defers are scoped by
|
||||||
// the recursive call) and splice its statements in.
|
// the recursive call) and splice its statements in.
|
||||||
block := build_block(ctx, statement.body)
|
block := build_block(ctx, statement.body)
|
||||||
@@ -5364,6 +5398,62 @@ value_loop_element_type :: proc(ctx: ^Build_Ctx, loop_stmt: ast.Stmt) -> types.T
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stmt_contains_yield reports whether a statement contains a `yield` anywhere within it
|
||||||
|
// (recursing through if/block/loop bodies). Used to stop the leading probe build before any
|
||||||
|
// statement that yields (the block's yield target is not pushed during the probe).
|
||||||
|
stmt_contains_yield :: proc(checker: ^Checker, id: ast.Stmt_Id) -> bool {
|
||||||
|
s := checker.ast_module.statements[id]
|
||||||
|
#partial switch s.kind {
|
||||||
|
case .Yield:
|
||||||
|
return true
|
||||||
|
case .If, .For, .While, .Block:
|
||||||
|
for sub in s.body {
|
||||||
|
if stmt_contains_yield(checker, sub) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for sub in s.else_body {
|
||||||
|
if stmt_contains_yield(checker, sub) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// block_element_type pre-types an untyped value block's element from its first concrete
|
||||||
|
// `yield :blk`, building the block's leading (yield-free) statements first so the probe can
|
||||||
|
// reference block locals declared before the first yield. The leading build is a throwaway
|
||||||
|
// (its scope is restored). INVALID when there is no concrete yield, or the concrete yield
|
||||||
|
// references a local only in scope past the first yield (annotate the binding instead).
|
||||||
|
block_element_type :: proc(ctx: ^Build_Ctx, block_stmts: []ast.Stmt_Id) -> types.Type {
|
||||||
|
checker := ctx.checker
|
||||||
|
concrete := first_concrete_yield_expr(checker, block_stmts)
|
||||||
|
if concrete == ast.INVALID_EXPR {
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
lead_end := len(block_stmts)
|
||||||
|
for id, i in block_stmts {
|
||||||
|
if stmt_contains_yield(checker, id) {
|
||||||
|
lead_end = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scope_start := len(ctx.locals^)
|
||||||
|
defer_start := len(ctx.defers^)
|
||||||
|
lead := build_block(ctx, block_stmts[:lead_end], close = false)
|
||||||
|
delete(lead, checker.allocator)
|
||||||
|
probe := build_expr(checker, concrete, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
||||||
|
result := checker.module.exprs[probe].type if checker.module.exprs[probe].kind != .Invalid else types.INVALID
|
||||||
|
// Discard the throwaway leading build's scope (its hir stmts/locals are dead but stable).
|
||||||
|
for i := defer_start; i < len(ctx.defers^); i += 1 {
|
||||||
|
delete(ctx.defers^[i], checker.allocator)
|
||||||
|
}
|
||||||
|
resize(ctx.defers, defer_start)
|
||||||
|
resize(ctx.locals, scope_start)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// build_value_labeled_block turns `x :: blk: { …; yield :blk v }` into a result slot each
|
// build_value_labeled_block turns `x :: blk: { …; yield :blk v }` into a result slot each
|
||||||
// `yield :blk` assigns (via the build_block `.Yield` desugar → `slot = v; break :blk`), then
|
// `yield :blk` assigns (via the build_block `.Yield` desugar → `slot = v; break :blk`), then
|
||||||
// reads it after the block. Every path must yield (or otherwise exit); HIR holds a `.Block`
|
// reads it after the block. Every path must yield (or otherwise exit); HIR holds a `.Block`
|
||||||
@@ -5388,14 +5478,12 @@ build_value_labeled_block :: proc(
|
|||||||
result_optional = types.is_optional(slot_type, &checker.module.types)
|
result_optional = types.is_optional(slot_type, &checker.module.types)
|
||||||
} else if result_optional {
|
} else if result_optional {
|
||||||
// Untyped block that also yields `none`: pre-type the element from the first
|
// Untyped block that also yields `none`: pre-type the element from the first
|
||||||
// concrete yield (a block has no captures, so a capture-free probe suffices).
|
// concrete yield (regardless of source order) so a `none` yielded first still
|
||||||
elem := first_concrete_yield_expr(checker, block_stmts)
|
// resolves the result to `?T`.
|
||||||
if elem != ast.INVALID_EXPR {
|
elem := block_element_type(ctx, block_stmts)
|
||||||
probe := build_expr(checker, elem, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
if is_runtime_type(checker, elem) {
|
||||||
if checker.module.exprs[probe].kind != .Invalid {
|
slot_type = types.optional(&checker.module.types, elem)
|
||||||
slot_type = types.optional(&checker.module.types, checker.module.exprs[probe].type)
|
slot = new_value_slot(ctx, slot_type)
|
||||||
slot = new_value_slot(ctx, slot_type)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
append(ctx.yield_targets, Yield_Target{
|
append(ctx.yield_targets, Yield_Target{
|
||||||
@@ -5722,6 +5810,8 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
loop_defer_starts.allocator = checker.allocator
|
loop_defer_starts.allocator = checker.allocator
|
||||||
loop_labels: [dynamic]symbol.Id
|
loop_labels: [dynamic]symbol.Id
|
||||||
loop_labels.allocator = checker.allocator
|
loop_labels.allocator = checker.allocator
|
||||||
|
loop_is_loop: [dynamic]bool
|
||||||
|
loop_is_loop.allocator = checker.allocator
|
||||||
yield_targets: [dynamic]Yield_Target
|
yield_targets: [dynamic]Yield_Target
|
||||||
yield_targets.allocator = checker.allocator
|
yield_targets.allocator = checker.allocator
|
||||||
ctx := Build_Ctx{
|
ctx := Build_Ctx{
|
||||||
@@ -5738,6 +5828,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
defers = &defers,
|
defers = &defers,
|
||||||
loop_defer_starts = &loop_defer_starts,
|
loop_defer_starts = &loop_defer_starts,
|
||||||
loop_labels = &loop_labels,
|
loop_labels = &loop_labels,
|
||||||
|
loop_is_loop = &loop_is_loop,
|
||||||
yield_targets = &yield_targets,
|
yield_targets = &yield_targets,
|
||||||
}
|
}
|
||||||
block := build_block(&ctx, function.body)
|
block := build_block(&ctx, function.body)
|
||||||
@@ -5789,6 +5880,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
delete(defers)
|
delete(defers)
|
||||||
delete(loop_defer_starts)
|
delete(loop_defer_starts)
|
||||||
delete(loop_labels)
|
delete(loop_labels)
|
||||||
|
delete(loop_is_loop)
|
||||||
delete(yield_targets)
|
delete(yield_targets)
|
||||||
delete(locals)
|
delete(locals)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1058,14 +1058,16 @@ parse_loop_control :: proc(parser: ^Parser, kind: ast.Stmt_Kind) -> ast.Stmt_Id
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A bare `{ ... }` introduces a nested scope. Locals declared inside are not
|
// A bare `{ ... }` introduces a nested scope. Locals declared inside are not
|
||||||
// visible after it, and any `defer`s inside it run at the closing brace.
|
// visible after it, and any `defer`s inside it run at the closing brace. A labeled
|
||||||
parse_block_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
// `blk: { ... }` can be exited early with `break :blk`.
|
||||||
|
parse_block_statement :: proc(parser: ^Parser, label := symbol.INVALID) -> ast.Stmt_Id {
|
||||||
start := current(parser).span // the '{'
|
start := current(parser).span // the '{'
|
||||||
body := parse_block(parser)
|
body := parse_block(parser)
|
||||||
id := ast.stmt_id(len(parser.module.statements))
|
id := ast.stmt_id(len(parser.module.statements))
|
||||||
append(&parser.module.statements, ast.Stmt{
|
append(&parser.module.statements, ast.Stmt{
|
||||||
kind=.Block,
|
kind=.Block,
|
||||||
span=start,
|
span=start,
|
||||||
|
label=label,
|
||||||
body=body,
|
body=body,
|
||||||
expr=ast.INVALID_EXPR,
|
expr=ast.INVALID_EXPR,
|
||||||
update=ast.INVALID_STMT,
|
update=ast.INVALID_STMT,
|
||||||
@@ -1159,6 +1161,12 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
if current(parser).kind == .Left_Brace {
|
if current(parser).kind == .Left_Brace {
|
||||||
return parse_block_statement(parser)
|
return parse_block_statement(parser)
|
||||||
}
|
}
|
||||||
|
// `blk: { ... }` is a labeled block statement (exitable via `break :blk`). At
|
||||||
|
// statement start, `Identifier ':'` (a single colon, not `::`) opens one.
|
||||||
|
if current(parser).kind == .Identifier && peek(parser).kind == .Colon {
|
||||||
|
label := parse_optional_loop_label(parser)
|
||||||
|
return parse_block_statement(parser, label)
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
+12
-3
@@ -2133,8 +2133,9 @@ yield_compiles_and_runs :: proc(t: ^testing.T) {
|
|||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
// Value blocks, value if-statements (incl. `return` branches and unwrap-`if`),
|
// Value blocks, value if-statements (incl. `return` branches and unwrap-`if`),
|
||||||
// `orelse`, value loops, labeled value blocks (`blk: { … yield :blk v }`, incl.
|
// `orelse`, value loops, labeled value blocks (`blk: { … yield :blk v }`, incl.
|
||||||
// defer-capture and `{T,none}` → optional), and outer-loop control (`yield :outer v`
|
// defer-capture, `{T,none}` → optional, and `none` before a concrete yield that uses a
|
||||||
// and `break :outer` from an inner loop) together produce 42.
|
// block local), outer-loop control (`yield :outer v` / `break :outer`), and labeled
|
||||||
|
// block statements exited via `break :blk` together produce 42.
|
||||||
testing.expect_value(t, state.exit_code, 42)
|
testing.expect_value(t, state.exit_code, 42)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2182,7 +2183,8 @@ yield_control_flow_is_diagnosed :: proc(t: ^testing.T) {
|
|||||||
// Value if/loop/block misuse: an `if` value without an `else`; a branch that neither
|
// Value if/loop/block misuse: an `if` value without an `else`; a branch that neither
|
||||||
// yields nor exits on every path (20.6); a value loop whose body lacks a trailing
|
// yields nor exits on every path (20.6); a value loop whose body lacks a trailing
|
||||||
// fall-through `yield`; a `yield :label` with no matching value loop; a labeled value
|
// fall-through `yield`; a `yield :label` with no matching value loop; a labeled value
|
||||||
// block that does not yield on every path, and a `break :label` naming no loop (20.7).
|
// block that does not yield on every path; a `break :label` naming no loop; and a
|
||||||
|
// `continue :label` targeting a block (not a loop).
|
||||||
text := `main :: func() i32 {
|
text := `main :: func() i32 {
|
||||||
noelse :: if (true) {
|
noelse :: if (true) {
|
||||||
yield 1
|
yield 1
|
||||||
@@ -2205,6 +2207,9 @@ yield_control_flow_is_diagnosed :: proc(t: ^testing.T) {
|
|||||||
for 0..10 |m| {
|
for 0..10 |m| {
|
||||||
break :nope
|
break :nope
|
||||||
}
|
}
|
||||||
|
scope: {
|
||||||
|
continue :scope
|
||||||
|
}
|
||||||
return noelse + badbranch + noloopyield + noblockyield
|
return noelse + badbranch + noloopyield + noblockyield
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@@ -2226,6 +2231,7 @@ yield_control_flow_is_diagnosed :: proc(t: ^testing.T) {
|
|||||||
stray_label := false
|
stray_label := false
|
||||||
block_no_yield := false
|
block_no_yield := false
|
||||||
bad_break_label := false
|
bad_break_label := false
|
||||||
|
continue_block := false
|
||||||
for diagnostic in diagnostics.items {
|
for diagnostic in diagnostics.items {
|
||||||
no_else = no_else || strings.contains(diagnostic.message, "an 'if' used as a value must have an 'else'")
|
no_else = no_else || strings.contains(diagnostic.message, "an 'if' used as a value must have an 'else'")
|
||||||
branch_no_yield = branch_no_yield || strings.contains(diagnostic.message, "a value branch must end with 'yield' or exit on every path")
|
branch_no_yield = branch_no_yield || strings.contains(diagnostic.message, "a value branch must end with 'yield' or exit on every path")
|
||||||
@@ -2233,6 +2239,8 @@ yield_control_flow_is_diagnosed :: proc(t: ^testing.T) {
|
|||||||
stray_label = stray_label || strings.contains(diagnostic.message, "no enclosing value loop or block is labeled 'stray'")
|
stray_label = stray_label || strings.contains(diagnostic.message, "no enclosing value loop or block is labeled 'stray'")
|
||||||
block_no_yield = block_no_yield || strings.contains(diagnostic.message, "a labeled value block must 'yield' on every path")
|
block_no_yield = block_no_yield || strings.contains(diagnostic.message, "a labeled value block must 'yield' on every path")
|
||||||
bad_break_label = bad_break_label || strings.contains(diagnostic.message, "no enclosing loop is labeled 'nope'")
|
bad_break_label = bad_break_label || strings.contains(diagnostic.message, "no enclosing loop is labeled 'nope'")
|
||||||
|
// `continue :scope` targets a labeled block, which is not a loop.
|
||||||
|
continue_block = continue_block || strings.contains(diagnostic.message, "no enclosing loop is labeled 'scope'")
|
||||||
}
|
}
|
||||||
testing.expect(t, no_else)
|
testing.expect(t, no_else)
|
||||||
testing.expect(t, branch_no_yield)
|
testing.expect(t, branch_no_yield)
|
||||||
@@ -2240,6 +2248,7 @@ yield_control_flow_is_diagnosed :: proc(t: ^testing.T) {
|
|||||||
testing.expect(t, stray_label)
|
testing.expect(t, stray_label)
|
||||||
testing.expect(t, block_no_yield)
|
testing.expect(t, block_no_yield)
|
||||||
testing.expect(t, bad_break_label)
|
testing.expect(t, bad_break_label)
|
||||||
|
testing.expect(t, continue_block)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
|
|||||||
@@ -240,6 +240,45 @@ break_outer :: func() i32 {
|
|||||||
return count # 5
|
return count # 5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# A labeled block *statement* (not a value source): `break :blk` exits it early.
|
||||||
|
stmt_block :: func(early i32) i32 {
|
||||||
|
x i32 = 0
|
||||||
|
blk: {
|
||||||
|
x = 1
|
||||||
|
if (early == 1) break :blk
|
||||||
|
x = 2
|
||||||
|
}
|
||||||
|
return x # early == 1 -> 1, else 2
|
||||||
|
}
|
||||||
|
|
||||||
|
# `break :search` escapes a nested loop and the block in one jump; the block's
|
||||||
|
# defer still runs on the way out.
|
||||||
|
stmt_block_escape :: func() i32 {
|
||||||
|
hits i32 = 0
|
||||||
|
search: {
|
||||||
|
defer hits += 1000
|
||||||
|
for 0..10 |i| {
|
||||||
|
hits += 1
|
||||||
|
if (i == 3) break :search
|
||||||
|
}
|
||||||
|
hits += 100 # skipped by break :search
|
||||||
|
}
|
||||||
|
return hits # 4 + 1000 (defer) = 1004
|
||||||
|
}
|
||||||
|
|
||||||
|
# Item B: a `none` yielded before a concrete `yield :blk` that references a block local.
|
||||||
|
lblock_local :: func() i32 {
|
||||||
|
r :: blk: {
|
||||||
|
val :: 9
|
||||||
|
if (false) yield :blk none
|
||||||
|
yield :blk val
|
||||||
|
}
|
||||||
|
if r |v| {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
main :: func() i32 {
|
main :: func() i32 {
|
||||||
if (basic() != 42) return 101
|
if (basic() != 42) return 101
|
||||||
if (typed() != 100) return 102
|
if (typed() != 100) return 102
|
||||||
@@ -276,5 +315,10 @@ main :: func() i32 {
|
|||||||
if (yield_outer(99) != -1) return 129
|
if (yield_outer(99) != -1) return 129
|
||||||
if (break_outer() != 5) return 130
|
if (break_outer() != 5) return 130
|
||||||
|
|
||||||
|
if (stmt_block(1) != 1) return 131
|
||||||
|
if (stmt_block(0) != 2) return 132
|
||||||
|
if (stmt_block_escape() != 1004) return 133
|
||||||
|
if (lblock_local() != 9) return 134
|
||||||
|
|
||||||
return 42
|
return 42
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user