while loops
This commit is contained in:
+9
-7
@@ -12,11 +12,12 @@
|
|||||||
|
|
||||||
### types and expressions
|
### types and expressions
|
||||||
|
|
||||||
- exact-width `i8` through `i64`, `u8` through `u64`, `f32`, `f64`, `isize`, `usize`, `void`, and inferred integer-constrained `int`
|
- exact-width `i8` through `i64`, `u8` through `u64`, `f32`, `f64`, `isize`, `usize`, `bool`, `void`, and inferred integer-constrained `int`
|
||||||
- target-dependent atomic C primitives from `c_char` through `c_longdouble`
|
- target-dependent atomic C primitives from `c_char` through `c_longdouble`
|
||||||
- C primitives remain semantically distinct from exact-width Brolang primitives until target lowering
|
- C primitives remain semantically distinct from exact-width Brolang primitives until target lowering
|
||||||
- contextual integer literals and constant folding of addition and negation trees
|
- contextual integer and character literals and constant folding of addition and negation trees
|
||||||
- strict numeric conversions, checked integer addition, and unary negation
|
- strict numeric conversions, checked integer addition, and unary negation
|
||||||
|
- boolean literals, comparisons, unary `!`, and short-circuiting `and` / `or`
|
||||||
- arrays `[N]T`, sentinel arrays `[N;S]T`, single-item pointers `@T`, many-item pointers `*T`, sentinel many-item pointers `[*;S]T`, pointer offsets, slices, and explicit slicing
|
- arrays `[N]T`, sentinel arrays `[N;S]T`, single-item pointers `@T`, many-item pointers `*T`, sentinel many-item pointers `[*;S]T`, pointer offsets, slices, and explicit slicing
|
||||||
- immutable UTF-8 string literals typed as pointers to static sentinel arrays: `@[N;0]u8`
|
- immutable UTF-8 string literals typed as pointers to static sentinel arrays: `@[N;0]u8`
|
||||||
- pointer-to-array `.len`, indexing, and slicing without explicit dereference
|
- pointer-to-array `.len`, indexing, and slicing without explicit dereference
|
||||||
@@ -24,10 +25,12 @@
|
|||||||
- information-preserving and information-forgetting pointer-to-array decay and sentinel slice/pointer weakening; array values never implicitly decay
|
- information-preserving and information-forgetting pointer-to-array decay and sentinel slice/pointer weakening; array values never implicitly decay
|
||||||
- narrow immutable zero-terminated byte pointer conversion to `*c_char` and `[*;0]c_char`, without general `u8`/`c_char` interchange
|
- narrow immutable zero-terminated byte pointer conversion to `*c_char` and `[*;0]c_char`, without general `u8`/`c_char` interchange
|
||||||
- optionals with trapping postfix `?`, `orelse`, and nullable pointer representation
|
- optionals with trapping postfix `?`, `orelse`, and nullable pointer representation
|
||||||
|
- conditional optional unwrapping with immutable then-block bindings: `if value |binding| { ... }`
|
||||||
- source-order native structs, defined or opaque `c_struct`, and keyed record literals
|
- source-order native structs, defined or opaque `c_struct`, and keyed record literals
|
||||||
- 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
|
||||||
|
- boolean `if` statements and `while` loops with optional post-iteration assignment/expression clauses
|
||||||
|
|
||||||
### functions and packages
|
### functions and packages
|
||||||
|
|
||||||
@@ -40,6 +43,9 @@
|
|||||||
- file-local relative imports, aliases, and qualified member access
|
- file-local relative imports, aliases, and qualified member access
|
||||||
- relative `.h` imports as synthetic package namespaces
|
- relative `.h` imports as synthetic package namespaces
|
||||||
- transitive external C function prototypes, typedef chains, C scalars, fixed arrays, complete plain records/unions, and pointers to opaque C records
|
- transitive external C function prototypes, typedef chains, C scalars, fixed arrays, complete plain records/unions, and pointers to opaque C records
|
||||||
|
- imported external C object variables, including writable globals and immutable arrays
|
||||||
|
- object-like scalar and plain record/union C macro constants
|
||||||
|
- supported static inline C functions through generated external wrappers
|
||||||
- bodyless manual and imported C variadic declarations with target-aware default argument promotions
|
- bodyless manual and imported C variadic declarations with target-aware default argument promotions
|
||||||
- passing concrete `c_func` declarations/definitions as C callback values and calling non-null C function pointers with postfix call syntax
|
- passing concrete `c_func` declarations/definitions as C callback values and calling non-null C function pointers with postfix call syntax
|
||||||
- reference-time diagnostics for unsupported imported C declarations
|
- reference-time diagnostics for unsupported imported C declarations
|
||||||
@@ -59,13 +65,9 @@
|
|||||||
### foreign functions and linking
|
### foreign functions and linking
|
||||||
|
|
||||||
- exporting brolang functions to c
|
- exporting brolang functions to c
|
||||||
|
- additional target-specific C ABI lowering
|
||||||
|
|
||||||
### scalar and compound types
|
### scalar and compound types
|
||||||
|
|
||||||
- tuples and native variadic functions
|
- tuples and native variadic functions
|
||||||
- C enums and non-plain C record layouts
|
- C enums and non-plain C record layouts
|
||||||
|
|
||||||
### advanced c imports
|
|
||||||
|
|
||||||
- C enums, external variables, macros, and static inline functions
|
|
||||||
- additional target-specific C ABI lowering
|
|
||||||
|
|||||||
@@ -95,9 +95,12 @@
|
|||||||
- new `Optional_Is_Some` / `Optional_Value` IR opcodes (the `Unwrap` presence-test + extract, minus the trap)
|
- new `Optional_Is_Some` / `Optional_Value` IR opcodes (the `Unwrap` presence-test + extract, minus the trap)
|
||||||
- conditional unwrapping with guard clause: `if val |v : v >= 10| { ... } else { ... }` - unwrap `val` into `v` if it is not `none`
|
- conditional unwrapping with guard clause: `if val |v : v >= 10| { ... } else { ... }` - unwrap `val` into `v` if it is not `none`
|
||||||
- multi-unwrap (see section below)
|
- multi-unwrap (see section below)
|
||||||
- while loops (operates on boolean conditions). examples:
|
- while loops (implemented; operates on boolean conditions). examples:
|
||||||
- `while condition { ... }` - iterate while the condition is true
|
- `while condition { ... }` - iterate while the condition is true
|
||||||
- `while condition : i += 1 { ... }` - iterate while the condition is true and execute `i += 1` (continue expression) after each iteration
|
- `while condition : i = i + 1 { ... }` - execute the update after each completed iteration
|
||||||
|
- 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
|
||||||
|
- compound assignment (`+=`) remains deferred
|
||||||
- ranges (see section below)
|
- ranges (see section below)
|
||||||
- for loops (operates on iterable sequences). examples:
|
- for loops (operates on iterable sequences). 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`)
|
||||||
@@ -109,8 +112,11 @@
|
|||||||
- `for 0..(len) |i| { ... }` or equivalently `for 0..=(len - 1) |i| { ... }` - calculating range bounds, expressions must be parenthesized
|
- `for 0..(len) |i| { ... }` or equivalently `for 0..=(len - 1) |i| { ... }` - calculating range bounds, expressions must be parenthesized
|
||||||
- for all conditionals/guards, parentheses are optional but allowed for visual clarity
|
- for all conditionals/guards, parentheses are optional but allowed for visual clarity
|
||||||
|
|
||||||
6. compound assignment
|
6. compound assignment: `+=`, `-=`, `*=`, `/=`
|
||||||
|
|
||||||
|
7. enums (native and c interop) (see below)
|
||||||
|
|
||||||
|
8. distinct types (see below)
|
||||||
|
|
||||||
## A word on multi-unwrap
|
## A word on multi-unwrap
|
||||||
|
|
||||||
@@ -175,3 +181,59 @@ Ranges represent a sequence of values, commonly used in for loops, and is itself
|
|||||||
```
|
```
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
# A word on distinct types
|
||||||
|
|
||||||
|
Distinct types are considered distinct from their backing type. They do not implicitly coerce to their backing type.
|
||||||
|
|
||||||
|
```
|
||||||
|
# distinct type
|
||||||
|
UserID :: distinct u32
|
||||||
|
|
||||||
|
# instantiate distinct type
|
||||||
|
my_id UserId :: UserID(42) # value must be of to backing type
|
||||||
|
```
|
||||||
|
|
||||||
|
# A word on enums
|
||||||
|
|
||||||
|
```
|
||||||
|
# standard enums
|
||||||
|
Animal :: enum {
|
||||||
|
dog
|
||||||
|
cat
|
||||||
|
bird
|
||||||
|
lizard
|
||||||
|
}
|
||||||
|
|
||||||
|
# enums with backing type
|
||||||
|
Nat :: enum(u8) { # in this case, a maximum of 256 values are possible
|
||||||
|
one # default: implicitly starts from value 0
|
||||||
|
two
|
||||||
|
three
|
||||||
|
four
|
||||||
|
five
|
||||||
|
}
|
||||||
|
|
||||||
|
# enums with backing type with explicit associated values
|
||||||
|
# note: must not be jumbled (i.e. `first_val = 1` must come before `other_val = 2`), but is allowed to be discontiguous (i.e. `one = 1` can be followed by `three = 3` without `two = 2` in between)
|
||||||
|
Nat :: enum(u8) {
|
||||||
|
one = 1
|
||||||
|
two = 2
|
||||||
|
three = 3
|
||||||
|
# no four
|
||||||
|
five = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
# enums with backing type with semi-implicit associated values
|
||||||
|
Nat :: enum(u8) {
|
||||||
|
one = 1 # starts from value 1
|
||||||
|
two # implicitly gets value 2
|
||||||
|
three # etc...
|
||||||
|
four
|
||||||
|
five
|
||||||
|
}
|
||||||
|
|
||||||
|
# using enums
|
||||||
|
dog_tag1 Animal :: Animal.dog
|
||||||
|
dog_tag2 Animal :: .dog # type inferred
|
||||||
|
```
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ Stmt_Kind :: enum u8 {
|
|||||||
Return,
|
Return,
|
||||||
Expression,
|
Expression,
|
||||||
If,
|
If,
|
||||||
|
While,
|
||||||
}
|
}
|
||||||
|
|
||||||
Stmt :: struct {
|
Stmt :: struct {
|
||||||
@@ -133,8 +134,11 @@ Stmt :: struct {
|
|||||||
// `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
|
||||||
// `else_body` as the else-block. An `else if` chain is represented as an
|
// `else_body` as the else-block. An `else if` chain is represented as an
|
||||||
// `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,
|
||||||
|
// and `update` as the optional post-iteration statement.
|
||||||
body: []Stmt_Id,
|
body: []Stmt_Id,
|
||||||
else_body: []Stmt_Id,
|
else_body: []Stmt_Id,
|
||||||
|
update: Stmt_Id,
|
||||||
diagnostic: source.Diagnostic_Id,
|
diagnostic: source.Diagnostic_Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -631,6 +631,13 @@ mark_block_imports_used :: proc(checker: ^Checker, statements: []ast.Stmt_Id, fi
|
|||||||
mark_expr_imports_used(checker, statement.expr, file)
|
mark_expr_imports_used(checker, statement.expr, file)
|
||||||
mark_block_imports_used(checker, statement.body, file)
|
mark_block_imports_used(checker, statement.body, file)
|
||||||
mark_block_imports_used(checker, statement.else_body, file)
|
mark_block_imports_used(checker, statement.else_body, file)
|
||||||
|
case .While:
|
||||||
|
mark_expr_imports_used(checker, statement.expr, file)
|
||||||
|
mark_block_imports_used(checker, statement.body, file)
|
||||||
|
if statement.update != ast.INVALID_STMT {
|
||||||
|
update := [1]ast.Stmt_Id{statement.update}
|
||||||
|
mark_block_imports_used(checker, update[:], file)
|
||||||
|
}
|
||||||
case .Invalid:
|
case .Invalid:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1424,6 +1431,13 @@ infer_statements :: proc(
|
|||||||
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
|
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
|
||||||
infer_statements(checker, statement.else_body, locals, pkg, file, demanded, result)
|
infer_statements(checker, statement.else_body, locals, pkg, file, demanded, result)
|
||||||
}
|
}
|
||||||
|
case .While:
|
||||||
|
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
||||||
|
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
|
||||||
|
if statement.update != ast.INVALID_STMT {
|
||||||
|
update := [1]ast.Stmt_Id{statement.update}
|
||||||
|
infer_statements(checker, update[:], locals, pkg, file, demanded, result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resize(locals, scope_start)
|
resize(locals, scope_start)
|
||||||
@@ -2460,7 +2474,13 @@ build_expr :: proc(
|
|||||||
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
|
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
|
||||||
case .Add:
|
case .Add:
|
||||||
stack[frame_index].stage = 1
|
stack[frame_index].stage = 1
|
||||||
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
|
// Preserve assignment/return context for literal operands, e.g.
|
||||||
|
// assigning `i + 1` back into a `u32` local.
|
||||||
|
left_expected := types.INVALID
|
||||||
|
if types.is_concrete_scalar(frame.expected) && !types.is_bool(frame.expected) {
|
||||||
|
left_expected = frame.expected
|
||||||
|
}
|
||||||
|
append(&stack, Build_Expr_Frame{expr=expr.left, expected=left_expected, template=ast.INVALID_FUNCTION})
|
||||||
case .Call:
|
case .Call:
|
||||||
if expr.left != ast.INVALID_EXPR {
|
if expr.left != ast.INVALID_EXPR {
|
||||||
stack[frame_index].stage = 6
|
stack[frame_index].stage = 6
|
||||||
@@ -2600,6 +2620,12 @@ build_expr :: proc(
|
|||||||
right_expected := types.INVALID
|
right_expected := types.INVALID
|
||||||
if types.is_many_pointer(checker.module.exprs[last].type, &checker.module.types) {
|
if types.is_many_pointer(checker.module.exprs[last].type, &checker.module.types) {
|
||||||
right_expected = types.USIZE
|
right_expected = types.USIZE
|
||||||
|
} else if eval_constant(checker, expr.right).kind == .Value {
|
||||||
|
// A constant RHS adopts the concrete LHS type before numeric
|
||||||
|
// compatibility is checked.
|
||||||
|
right_expected = checker.module.exprs[last].type
|
||||||
|
} else if types.is_concrete_scalar(frame.expected) && !types.is_bool(frame.expected) {
|
||||||
|
right_expected = frame.expected
|
||||||
}
|
}
|
||||||
append(&stack, Build_Expr_Frame{expr=expr.right, expected=right_expected, template=ast.INVALID_FUNCTION})
|
append(&stack, Build_Expr_Frame{expr=expr.right, expected=right_expected, template=ast.INVALID_FUNCTION})
|
||||||
continue
|
continue
|
||||||
@@ -3067,6 +3093,39 @@ 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 .While:
|
||||||
|
condition := build_expr(
|
||||||
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
|
types.BOOL, ctx.pkg, ctx.file,
|
||||||
|
)
|
||||||
|
if checker.module.exprs[condition].kind != .Invalid &&
|
||||||
|
!types.is_bool(checker.module.exprs[condition].type) {
|
||||||
|
id := source.add(checker.diagnostics, statement.span, "'while' condition must be a bool")
|
||||||
|
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
|
||||||
|
ctx.problematic^ = true
|
||||||
|
}
|
||||||
|
loop_body := build_block(ctx, statement.body)
|
||||||
|
update := hir.INVALID_STMT
|
||||||
|
if statement.update != ast.INVALID_STMT {
|
||||||
|
update_ast := [1]ast.Stmt_Id{statement.update}
|
||||||
|
update_body := build_block(ctx, update_ast[:])
|
||||||
|
if len(update_body) > 0 {
|
||||||
|
update = update_body[0]
|
||||||
|
}
|
||||||
|
delete(update_body, checker.allocator)
|
||||||
|
}
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind=.While,
|
||||||
|
span=statement.span,
|
||||||
|
expr=condition,
|
||||||
|
then_body=loop_body,
|
||||||
|
update=update,
|
||||||
|
local=hir.INVALID_LOCAL,
|
||||||
|
target=hir.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
|
||||||
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{
|
||||||
@@ -3082,8 +3141,9 @@ build_block :: proc(ctx: ^Build_Ctx, statements: []ast.Stmt_Id) -> []hir.Stmt_Id
|
|||||||
|
|
||||||
// Reports whether every control-flow path through `stmts` terminates (returns or traps),
|
// Reports whether every control-flow path through `stmts` terminates (returns or traps),
|
||||||
// so the end of the block is unreachable. A `.Return` or `.Trap` terminates outright; an
|
// so the end of the block is unreachable. A `.Return` or `.Trap` terminates outright; an
|
||||||
// `.If` terminates only when it has an `else` and both arms terminate. Recursion into the
|
// `.If` terminates only when it has an `else` and both arms terminate. A literal
|
||||||
// `then_body`/`else_body` slices handles nested ifs and `else if` chains.
|
// `while true` cannot fall through because the language has no `break` statement.
|
||||||
|
// Recursion into the branch slices handles nested ifs and `else if` chains.
|
||||||
all_paths_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
all_paths_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
||||||
for id in stmts {
|
for id in stmts {
|
||||||
statement := module.statements[id]
|
statement := module.statements[id]
|
||||||
@@ -3096,6 +3156,13 @@ all_paths_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
|||||||
all_paths_return(module, statement.else_body) {
|
all_paths_return(module, statement.else_body) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
case .While:
|
||||||
|
if statement.expr != hir.INVALID_EXPR && int(statement.expr) < len(module.exprs) {
|
||||||
|
condition := module.exprs[statement.expr]
|
||||||
|
if condition.kind == .Bool && condition.integer != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ Stmt_Kind :: enum u8 {
|
|||||||
Sink,
|
Sink,
|
||||||
Trap,
|
Trap,
|
||||||
If,
|
If,
|
||||||
|
While,
|
||||||
}
|
}
|
||||||
|
|
||||||
Stmt :: struct {
|
Stmt :: struct {
|
||||||
@@ -151,8 +152,11 @@ Stmt :: struct {
|
|||||||
expr: Expr_Id,
|
expr: Expr_Id,
|
||||||
// `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
|
||||||
|
// body, and `update` as the optional post-iteration statement.
|
||||||
then_body: []Stmt_Id,
|
then_body: []Stmt_Id,
|
||||||
else_body: []Stmt_Id,
|
else_body: []Stmt_Id,
|
||||||
|
update: Stmt_Id,
|
||||||
diagnostic: source.Diagnostic_Id,
|
diagnostic: source.Diagnostic_Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
|||||||
case "and": return .Keyword_And
|
case "and": return .Keyword_And
|
||||||
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 "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
|
||||||
@@ -109,8 +110,7 @@ lex :: proc(
|
|||||||
cursor += 1
|
cursor += 1
|
||||||
append_token(&stream, source_file, .Colon_Colon, start, cursor)
|
append_token(&stream, source_file, .Colon_Colon, start, cursor)
|
||||||
} else {
|
} else {
|
||||||
id := source.add(diagnostics, source.Span{file=source_file.id, start=source.Offset(start), end=source.Offset(cursor)}, "expected a second ':'")
|
append_token(&stream, source_file, .Colon, start, cursor)
|
||||||
append_token(&stream, source_file, .Invalid, start, cursor, diagnostic=id)
|
|
||||||
}
|
}
|
||||||
case '=':
|
case '=':
|
||||||
start := cursor
|
start := cursor
|
||||||
|
|||||||
+18
-1
@@ -497,6 +497,20 @@ float_predicate :: proc(predicate: ir.Compare_Predicate) -> string {
|
|||||||
return "oeq"
|
return "oeq"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emit_entry_allocas :: proc(emitter: ^Emitter, instructions: []ir.Instruction) {
|
||||||
|
for instruction, instruction_index in instructions {
|
||||||
|
if instruction.op == .Alloca &&
|
||||||
|
types.is_runtime_value(instruction.type, &emitter.module.types) {
|
||||||
|
fmt.sbprintf(
|
||||||
|
&emitter.builder,
|
||||||
|
" %%v%d = alloca %s\n",
|
||||||
|
instruction_index,
|
||||||
|
llvm_type(instruction.type, &emitter.module.types),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
emit_instruction_stream :: proc(
|
emit_instruction_stream :: proc(
|
||||||
emitter: ^Emitter,
|
emitter: ^Emitter,
|
||||||
instructions: []ir.Instruction,
|
instructions: []ir.Instruction,
|
||||||
@@ -706,7 +720,9 @@ emit_instruction_stream :: proc(
|
|||||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid allocation type")
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid allocation type")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fmt.sbprintf(&emitter.builder, " %%v%d = alloca %s\n", instruction_index, llvm_type(instruction.type, &emitter.module.types))
|
if global_initializer {
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = alloca %s\n", instruction_index, llvm_type(instruction.type, &emitter.module.types))
|
||||||
|
}
|
||||||
case .Index_Address:
|
case .Index_Address:
|
||||||
if !valid_instruction(instructions, instruction.a) ||
|
if !valid_instruction(instructions, instruction.a) ||
|
||||||
!valid_value(instructions, instruction.b, types.USIZE, &emitter.module.types) {
|
!valid_value(instructions, instruction.b, types.USIZE, &emitter.module.types) {
|
||||||
@@ -1726,6 +1742,7 @@ emit_functions :: proc(emitter: ^Emitter) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
strings.write_string(&emitter.builder, ") {\nentry:\n")
|
strings.write_string(&emitter.builder, ") {\nentry:\n")
|
||||||
|
emit_entry_allocas(emitter, function.instructions)
|
||||||
if function.calling_convention == .C {
|
if function.calling_convention == .C {
|
||||||
for param_type, index in function.param_types {
|
for param_type, index in function.param_types {
|
||||||
if !types.is_record(param_type, &emitter.module.types) {
|
if !types.is_record(param_type, &emitter.module.types) {
|
||||||
|
|||||||
@@ -708,6 +708,60 @@ 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 .While:
|
||||||
|
condition_lbl := fresh_label(state)
|
||||||
|
body_lbl := fresh_label(state)
|
||||||
|
exit_lbl := fresh_label(state)
|
||||||
|
update_lbl := condition_lbl
|
||||||
|
if statement.update != hir.INVALID_STMT {
|
||||||
|
update_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,
|
||||||
|
})
|
||||||
|
condition := lower_expr(state, statement.expr)
|
||||||
|
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)
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
if statement.update != hir.INVALID_STMT {
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
update := [1]hir.Stmt_Id{statement.update}
|
||||||
|
lower_statements(state, update[:])
|
||||||
|
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,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -928,6 +928,9 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
if current(parser).kind == .Keyword_If {
|
if current(parser).kind == .Keyword_If {
|
||||||
return parse_if(parser)
|
return parse_if(parser)
|
||||||
}
|
}
|
||||||
|
if current(parser).kind == .Keyword_While {
|
||||||
|
return parse_while(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
|
||||||
@@ -1123,6 +1126,96 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parse_while_update :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||||
|
parenthesized := false
|
||||||
|
if _, ok := allow(parser, .Left_Paren); ok {
|
||||||
|
parenthesized = true
|
||||||
|
parser.delimiter_depth += 1
|
||||||
|
skip_newlines(parser)
|
||||||
|
}
|
||||||
|
|
||||||
|
update := ast.INVALID_STMT
|
||||||
|
if current(parser).kind == .Left_Brace ||
|
||||||
|
current(parser).kind == .Right_Paren ||
|
||||||
|
current(parser).kind == .Eof {
|
||||||
|
diagnostic := source.add(
|
||||||
|
parser.diagnostics,
|
||||||
|
current(parser).span,
|
||||||
|
"expected a while update statement after ':'",
|
||||||
|
)
|
||||||
|
update = ast.stmt_id(len(parser.module.statements))
|
||||||
|
append(&parser.module.statements, ast.Stmt{
|
||||||
|
kind=.Invalid,
|
||||||
|
span=current(parser).span,
|
||||||
|
expr=ast.INVALID_EXPR,
|
||||||
|
update=ast.INVALID_STMT,
|
||||||
|
diagnostic=diagnostic,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
saved := parser.no_struct_literal
|
||||||
|
parser.no_struct_literal = true
|
||||||
|
update = parse_statement(parser)
|
||||||
|
parser.no_struct_literal = saved
|
||||||
|
statement := &parser.module.statements[update]
|
||||||
|
switch statement.kind {
|
||||||
|
case .Assignment, .Expression:
|
||||||
|
case .Invalid, .Declaration, .Return, .If, .While:
|
||||||
|
diagnostic := source.add(
|
||||||
|
parser.diagnostics,
|
||||||
|
statement.span,
|
||||||
|
"while update must be an assignment, sink, or expression statement",
|
||||||
|
)
|
||||||
|
statement.kind = .Invalid
|
||||||
|
statement.diagnostic = diagnostic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if parenthesized {
|
||||||
|
skip_newlines(parser)
|
||||||
|
if _, ok := allow(parser, .Right_Paren); !ok {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected ')' after while update")
|
||||||
|
for current(parser).kind != .Right_Paren &&
|
||||||
|
current(parser).kind != .Left_Brace &&
|
||||||
|
current(parser).kind != .Newline &&
|
||||||
|
current(parser).kind != .Eof {
|
||||||
|
advance(parser)
|
||||||
|
}
|
||||||
|
_, _ = allow(parser, .Right_Paren)
|
||||||
|
}
|
||||||
|
parser.delimiter_depth -= 1
|
||||||
|
}
|
||||||
|
return update
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_while :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||||
|
start := advance(parser) // consume 'while'
|
||||||
|
skip_newlines(parser)
|
||||||
|
saved := parser.no_struct_literal
|
||||||
|
parser.no_struct_literal = true
|
||||||
|
condition := parse_expression(parser)
|
||||||
|
parser.no_struct_literal = saved
|
||||||
|
skip_newlines(parser)
|
||||||
|
|
||||||
|
update := ast.INVALID_STMT
|
||||||
|
if _, ok := allow(parser, .Colon); ok {
|
||||||
|
skip_newlines(parser)
|
||||||
|
update = parse_while_update(parser)
|
||||||
|
skip_newlines(parser)
|
||||||
|
}
|
||||||
|
body := parse_block(parser)
|
||||||
|
|
||||||
|
id := ast.stmt_id(len(parser.module.statements))
|
||||||
|
append(&parser.module.statements, ast.Stmt{
|
||||||
|
kind=.While,
|
||||||
|
span=span_from(start.span, previous(parser).span),
|
||||||
|
expr=condition,
|
||||||
|
body=body,
|
||||||
|
update=update,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
||||||
advance(parser)
|
advance(parser)
|
||||||
if _, ok := allow(parser, .Left_Paren); !ok {
|
if _, ok := allow(parser, .Left_Paren); !ok {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ Kind :: enum u8 {
|
|||||||
String,
|
String,
|
||||||
Character,
|
Character,
|
||||||
Underscore,
|
Underscore,
|
||||||
|
Colon,
|
||||||
Colon_Colon,
|
Colon_Colon,
|
||||||
Equal,
|
Equal,
|
||||||
Equal_Equal,
|
Equal_Equal,
|
||||||
@@ -53,6 +54,7 @@ Kind :: enum u8 {
|
|||||||
Keyword_And,
|
Keyword_And,
|
||||||
Keyword_Or,
|
Keyword_Or,
|
||||||
Keyword_If,
|
Keyword_If,
|
||||||
|
Keyword_While,
|
||||||
Keyword_Else,
|
Keyword_Else,
|
||||||
Keyword_True,
|
Keyword_True,
|
||||||
Keyword_False,
|
Keyword_False,
|
||||||
|
|||||||
@@ -2293,6 +2293,18 @@ count_substring_occurrences :: proc(text, needle: string) -> int {
|
|||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
find_substring_offset :: proc(text, needle: string) -> int {
|
||||||
|
if len(needle) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
for index := 0; index + len(needle) <= len(text); index += 1 {
|
||||||
|
if text[index:index + len(needle)] == needle {
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
find_cimport_variable :: proc(result: ^cimport.Result, name: string) -> (^cimport.Variable, bool) {
|
find_cimport_variable :: proc(result: ^cimport.Result, name: string) -> (^cimport.Variable, bool) {
|
||||||
for &variable in result.variables {
|
for &variable in result.variables {
|
||||||
if variable.name == name {
|
if variable.name == name {
|
||||||
@@ -3905,3 +3917,166 @@ if_unwrap_binding_is_immutable :: proc(t: ^testing.T) {
|
|||||||
}
|
}
|
||||||
testing.expect(t, found)
|
testing.expect(t, found)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
while_loops_compile_and_run :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-while-loop"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/programs/while_loop", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 42)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
while_loop_diagnostics_cover_condition_update_and_scope :: proc(t: ^testing.T) {
|
||||||
|
text := `bad_condition :: func() void {
|
||||||
|
while 1 {}
|
||||||
|
}
|
||||||
|
bad_unresolved :: func() void {
|
||||||
|
while false : missing = 1 {}
|
||||||
|
}
|
||||||
|
bad_immutable :: func() void {
|
||||||
|
i :: 0
|
||||||
|
while false : i = i + 1 {}
|
||||||
|
}
|
||||||
|
bad_body_scope :: func() void {
|
||||||
|
running :: false
|
||||||
|
while running : i = 1 {
|
||||||
|
i u32 = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bad_declaration_update :: func() void {
|
||||||
|
while false : i u32 = 0 {}
|
||||||
|
}
|
||||||
|
bad_missing_update :: func() void {
|
||||||
|
while false : {}
|
||||||
|
}
|
||||||
|
main :: func() void {
|
||||||
|
bad_condition()
|
||||||
|
bad_unresolved()
|
||||||
|
bad_immutable()
|
||||||
|
bad_body_scope()
|
||||||
|
bad_declaration_update()
|
||||||
|
bad_missing_update()
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
|
||||||
|
non_bool := false
|
||||||
|
unresolved_missing := false
|
||||||
|
unresolved_body_local := false
|
||||||
|
immutable := false
|
||||||
|
disallowed := false
|
||||||
|
missing := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
non_bool = non_bool || strings.contains(diagnostic.message, "'while' condition must be a bool")
|
||||||
|
unresolved_missing = unresolved_missing || strings.contains(diagnostic.message, "cannot assign unresolved local 'missing'")
|
||||||
|
unresolved_body_local = unresolved_body_local || strings.contains(diagnostic.message, "cannot assign unresolved local 'i'")
|
||||||
|
immutable = immutable || strings.contains(diagnostic.message, "cannot assign immutable local 'i'")
|
||||||
|
disallowed = disallowed || strings.contains(diagnostic.message, "while update must be an assignment, sink, or expression statement")
|
||||||
|
missing = missing || strings.contains(diagnostic.message, "expected a while update statement after ':'")
|
||||||
|
}
|
||||||
|
testing.expect(t, non_bool)
|
||||||
|
testing.expect(t, unresolved_missing)
|
||||||
|
testing.expect(t, unresolved_body_local)
|
||||||
|
testing.expect(t, immutable)
|
||||||
|
testing.expect(t, disallowed)
|
||||||
|
testing.expect(t, missing)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
while_true_and_potential_fallthrough_have_distinct_return_analysis :: proc(t: ^testing.T) {
|
||||||
|
text := `forever :: func() i32 {
|
||||||
|
while true {}
|
||||||
|
}
|
||||||
|
maybe :: func(run bool) i32 {
|
||||||
|
while run {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main :: func() void {
|
||||||
|
if false {
|
||||||
|
_ = forever()
|
||||||
|
}
|
||||||
|
_ = maybe(false)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
|
||||||
|
missing_return_count := 0
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
if strings.contains(diagnostic.message, "does not return a value") {
|
||||||
|
missing_return_count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
testing.expect_value(t, missing_return_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
while_loop_allocas_are_emitted_in_the_entry_block :: proc(t: ^testing.T) {
|
||||||
|
text := `main :: func() i32 {
|
||||||
|
i u32 = 0
|
||||||
|
while i < 2 and true : i = i + 1 {
|
||||||
|
value u32 = i
|
||||||
|
_ = value
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
first_loop_label := find_substring_offset(llvm_text, "bro_block_")
|
||||||
|
testing.expect(t, first_loop_label >= 0)
|
||||||
|
alloca_count := 0
|
||||||
|
for function in ir_module.functions {
|
||||||
|
if !function.is_main {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for instruction, instruction_index in function.instructions {
|
||||||
|
if instruction.op != .Alloca {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
alloca_count += 1
|
||||||
|
needle := fmt.tprintf(" %%v%d = alloca ", instruction_index)
|
||||||
|
offset := find_substring_offset(llvm_text, needle)
|
||||||
|
testing.expect(t, offset >= 0 && offset < first_loop_label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
testing.expect(t, alloca_count >= 3)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# Milestone 5: boolean while loops with optional post-iteration updates.
|
||||||
|
|
||||||
|
return_before_update :: func() i32 {
|
||||||
|
i i32 = 0
|
||||||
|
while true : i = i + 1 {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
total i32 = 0
|
||||||
|
|
||||||
|
# ordinary condition and update
|
||||||
|
i u32 = 0
|
||||||
|
while i < 5 : i = i + 1 {
|
||||||
|
total = total + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
# equivalent parenthesized header
|
||||||
|
j u32 = 0
|
||||||
|
while (j < 4) : (j = j + 1) {
|
||||||
|
total = total + 3
|
||||||
|
}
|
||||||
|
|
||||||
|
# nested loops and body-local storage
|
||||||
|
outer u32 = 0
|
||||||
|
while outer < 2 : outer = outer + 1 {
|
||||||
|
inner u32 = 0
|
||||||
|
while inner < 3 : inner = inner + 1 {
|
||||||
|
total = total + 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# The body-local k shadows only inside the body. The update still targets
|
||||||
|
# the mutable k declared before the loop.
|
||||||
|
k u32 = 0
|
||||||
|
while k < 4 : k = k + 1 {
|
||||||
|
k u32 = 100
|
||||||
|
if k == 100 {
|
||||||
|
total = total + 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# zero iterations
|
||||||
|
while false {
|
||||||
|
total = total + 100
|
||||||
|
}
|
||||||
|
|
||||||
|
# A return exits before the update clause.
|
||||||
|
total = total + return_before_update()
|
||||||
|
return total
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user