while loops
This commit is contained in:
@@ -2293,6 +2293,18 @@ count_substring_occurrences :: proc(text, needle: string) -> int {
|
||||
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) {
|
||||
for &variable in result.variables {
|
||||
if variable.name == name {
|
||||
@@ -3905,3 +3917,166 @@ if_unwrap_binding_is_immutable :: proc(t: ^testing.T) {
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user