while loops
This commit is contained in:
@@ -120,6 +120,7 @@ Stmt_Kind :: enum u8 {
|
||||
Return,
|
||||
Expression,
|
||||
If,
|
||||
While,
|
||||
}
|
||||
|
||||
Stmt :: struct {
|
||||
@@ -133,8 +134,11 @@ Stmt :: struct {
|
||||
// `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` 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,
|
||||
else_body: []Stmt_Id,
|
||||
update: Stmt_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_block_imports_used(checker, statement.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:
|
||||
}
|
||||
}
|
||||
@@ -1424,6 +1431,13 @@ infer_statements :: proc(
|
||||
infer_statements(checker, statement.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)
|
||||
@@ -2460,7 +2474,13 @@ build_expr :: proc(
|
||||
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
|
||||
case .Add:
|
||||
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:
|
||||
if expr.left != ast.INVALID_EXPR {
|
||||
stack[frame_index].stage = 6
|
||||
@@ -2600,6 +2620,12 @@ build_expr :: proc(
|
||||
right_expected := types.INVALID
|
||||
if types.is_many_pointer(checker.module.exprs[last].type, &checker.module.types) {
|
||||
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})
|
||||
continue
|
||||
@@ -3067,6 +3093,39 @@ build_block :: proc(ctx: ^Build_Ctx, statements: []ast.Stmt_Id) -> []hir.Stmt_Id
|
||||
diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
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:
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
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),
|
||||
// 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
|
||||
// `then_body`/`else_body` slices handles nested ifs and `else if` chains.
|
||||
// `.If` terminates only when it has an `else` and both arms terminate. A literal
|
||||
// `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 {
|
||||
for id in stmts {
|
||||
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) {
|
||||
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
|
||||
|
||||
@@ -141,6 +141,7 @@ Stmt_Kind :: enum u8 {
|
||||
Sink,
|
||||
Trap,
|
||||
If,
|
||||
While,
|
||||
}
|
||||
|
||||
Stmt :: struct {
|
||||
@@ -151,8 +152,11 @@ Stmt :: struct {
|
||||
expr: Expr_Id,
|
||||
// `If` statements use `expr` as the condition and `then_body`/`else_body` as
|
||||
// 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,
|
||||
else_body: []Stmt_Id,
|
||||
update: Stmt_Id,
|
||||
diagnostic: source.Diagnostic_Id,
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
||||
case "and": return .Keyword_And
|
||||
case "or": return .Keyword_Or
|
||||
case "if": return .Keyword_If
|
||||
case "while": return .Keyword_While
|
||||
case "else": return .Keyword_Else
|
||||
case "true": return .Keyword_True
|
||||
case "false": return .Keyword_False
|
||||
@@ -109,8 +110,7 @@ lex :: proc(
|
||||
cursor += 1
|
||||
append_token(&stream, source_file, .Colon_Colon, start, cursor)
|
||||
} 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, .Invalid, start, cursor, diagnostic=id)
|
||||
append_token(&stream, source_file, .Colon, start, cursor)
|
||||
}
|
||||
case '=':
|
||||
start := cursor
|
||||
|
||||
+18
-1
@@ -497,6 +497,20 @@ float_predicate :: proc(predicate: ir.Compare_Predicate) -> string {
|
||||
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(
|
||||
emitter: ^Emitter,
|
||||
instructions: []ir.Instruction,
|
||||
@@ -706,7 +720,9 @@ emit_instruction_stream :: proc(
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid allocation type")
|
||||
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:
|
||||
if !valid_instruction(instructions, instruction.a) ||
|
||||
!valid_value(instructions, instruction.b, types.USIZE, &emitter.module.types) {
|
||||
@@ -1726,6 +1742,7 @@ emit_functions :: proc(emitter: ^Emitter) {
|
||||
continue
|
||||
}
|
||||
strings.write_string(&emitter.builder, ") {\nentry:\n")
|
||||
emit_entry_allocas(emitter, function.instructions)
|
||||
if function.calling_convention == .C {
|
||||
for param_type, index in function.param_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,
|
||||
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 {
|
||||
return parse_if(parser)
|
||||
}
|
||||
if current(parser).kind == .Keyword_While {
|
||||
return parse_while(parser)
|
||||
}
|
||||
|
||||
if current(parser).kind == .Identifier || current(parser).kind == .Underscore {
|
||||
start_cursor := parser.cursor
|
||||
@@ -1123,6 +1126,96 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_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) {
|
||||
advance(parser)
|
||||
if _, ok := allow(parser, .Left_Paren); !ok {
|
||||
|
||||
@@ -13,6 +13,7 @@ Kind :: enum u8 {
|
||||
String,
|
||||
Character,
|
||||
Underscore,
|
||||
Colon,
|
||||
Colon_Colon,
|
||||
Equal,
|
||||
Equal_Equal,
|
||||
@@ -53,6 +54,7 @@ Kind :: enum u8 {
|
||||
Keyword_And,
|
||||
Keyword_Or,
|
||||
Keyword_If,
|
||||
Keyword_While,
|
||||
Keyword_Else,
|
||||
Keyword_True,
|
||||
Keyword_False,
|
||||
|
||||
Reference in New Issue
Block a user