sentinel pointers and c strings

This commit is contained in:
2026-06-14 19:21:49 +02:00
parent 2d3d0bd266
commit 3b7c3fcbd0
16 changed files with 512 additions and 55 deletions
+201 -1
View File
@@ -174,6 +174,58 @@ main :: func() void {}
testing.expect_value(t, len(module.functions[0].params), 2)
}
@(test)
parser_accepts_sentinel_many_item_pointer_types :: proc(t: ^testing.T) {
text := `zero :: func(value [*;0]u8) void {}
newline :: func(value [*;'\n']mut u8) void {}
nullable :: func(value ?[*;0]u8) void {}
main :: func() void {}
`
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)
zero, zero_ok := types.node(&module.type_store, module.functions[0].params[0].type)
newline, newline_ok := types.node(&module.type_store, module.functions[1].params[0].type)
nullable, nullable_ok := types.node(&module.type_store, module.functions[2].params[0].type)
nullable_child, nullable_child_ok := types.node(&module.type_store, nullable.child)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, zero_ok && zero.kind == .Pointer && zero.many && zero.has_sentinel && zero.sentinel == 0)
testing.expect(t, newline_ok && newline.kind == .Pointer && newline.many && newline.mutable &&
newline.has_sentinel && newline.sentinel == '\n')
testing.expect(t, nullable_ok && nullable.kind == .Optional)
testing.expect(t, nullable_child_ok && nullable_child.kind == .Pointer &&
nullable_child.many && nullable_child.has_sentinel)
}
@(test)
parser_diagnoses_malformed_sentinel_pointer_types :: proc(t: ^testing.T) {
text := `bad :: func(value [*0]u8) void {}
main :: func() void {}
`
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 := false
for diagnostic in diagnostics.items {
found = found || strings.contains(diagnostic.message, "expected ';' after '*' in sentinel pointer type")
}
testing.expect(t, found)
}
@(test)
parser_accepts_single_statement_one_line_functions :: proc(t: ^testing.T) {
text := `give :: func() i8 { return 7 }
@@ -742,7 +794,7 @@ main :: func() void {
maybe ?i32 = 5
_ = c
_ = values[2]
_ = values.ptr + 1
_ = (&values).ptr + 1
_ = values.len
_ = values[0..2]
_ = "hello".ptr
@@ -784,6 +836,134 @@ main :: func() void {
testing.expect(t, strings.contains(llvm_text, "orelse_some"))
}
@(test)
string_literals_preserve_static_length_and_sentinel_through_pointer_views :: proc(t: ^testing.T) {
text := `take_sentinel_pointer :: func(value [*;0]u8) void {}
take_mut_sentinel_pointer :: func(value [*;0]mut u8) void {}
take_pointer :: func(value *u8) void {}
take_sentinel_slice :: func(value [;0]u8) void {}
take_mut_sentinel_slice :: func(value [;0]mut u8) void {}
take_slice :: func(value []u8) void {}
take_c_string :: c_func(value *c_char) c_int
take_c_sentinel :: c_func(value [*;0]c_char) c_int
main :: func() void {
text :: "hello"
values [2;0]mut u8 = [1, 2]
pointer :: &values
_ = text.len
_ = text.ptr
_ = text[0]
_ = text[1..]
_ = pointer.len
_ = pointer.ptr
_ = pointer[0]
_ = pointer[1..]
offset [*;0]u8 :: text.ptr + 1
suffix [*;0]u8 :: text[1..].ptr
middle []u8 :: text[1..3]
_ = offset
_ = suffix
_ = middle
take_sentinel_pointer(text)
take_pointer(text)
take_sentinel_slice(text)
take_slice(text)
take_mut_sentinel_pointer(pointer)
take_mut_sentinel_slice(pointer)
take_sentinel_pointer(pointer)
take_sentinel_slice(pointer)
_ = take_c_string(text)
_ = take_c_sentinel(text)
_ = take_c_string(text.ptr)
_ = take_c_sentinel(text.ptr)
}
`
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)
string_type := types.INVALID
decays := 0
for expr in hir_module.exprs {
if expr.kind == .String {
string_type = expr.type
}
if expr.kind == .Decay_Array_Pointer {
decays += 1
}
}
pointer, array, string_ok := types.array_pointer(string_type, &hir_module.types)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, string_ok && !pointer.mutable && array.child == types.U8 &&
array.count == 5 && array.has_sentinel && array.sentinel == 0)
testing.expect(t, decays >= 6)
testing.expect(t, strings.contains(llvm_text, "@bro.str.0 = private unnamed_addr constant [6 x i8] c\"hello\\00\""))
testing.expect(t, strings.contains(llvm_text, "declare i32 @take_c_string(ptr)"))
testing.expect(t, strings.contains(llvm_text, "declare i32 @take_c_sentinel(ptr)"))
}
@(test)
array_pointer_and_c_string_coercion_restrictions_are_diagnosed :: proc(t: ^testing.T) {
text := `take_c_string :: c_func(value *c_char) c_int
take_mut_c_string :: c_func(value *mut c_char) c_int
take_pointer :: func(value *u8) void {}
take_mut_pointer :: func(value *mut u8) void {}
take_slice :: func(value []u8) void {}
bad_sentinel :: func(value [*;256]u8) void {}
main :: func() void {
values [1;0]mut u8 = [1]
_ = values.ptr
take_pointer(values)
take_slice(values)
ordinary *u8 :: "hello"
nonzero [1;'\n']mut u8 = [1]
take_pointer("hello"[1..])
_ = take_c_string(ordinary)
_ = take_c_string((&nonzero).ptr)
_ = take_c_string(1)
take_mut_pointer("hello")
_ = take_mut_c_string("hello")
}
`
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)
conversion_errors := 0
array_ptr_error := false
sentinel_error := false
for diagnostic in diagnostics.items {
conversion_errors += 1 if strings.contains(diagnostic.message, "cannot implicitly convert") else 0
array_ptr_error = array_ptr_error ||
strings.contains(diagnostic.message, "arrays do not expose '.ptr'")
sentinel_error = sentinel_error ||
strings.contains(diagnostic.message, "sentinel value does not fit array, slice, or pointer")
}
testing.expect_value(t, conversion_errors, 8)
testing.expect(t, array_ptr_error)
testing.expect(t, sentinel_error)
}
@(test)
c_variadic_calls_promote_extras_and_emit_variadic_llvm :: proc(t: ^testing.T) {
text := `variadic :: c_func(tag c_int, ...) c_int
@@ -1471,6 +1651,26 @@ valid_program_compiles_and_runs :: proc(t: ^testing.T) {
testing.expect_value(t, state.exit_code, 0)
}
@(test)
c_printf_accepts_a_string_literal :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-printf"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/interop/printf", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
sentinel_pointer_views_compile_and_run :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-sentinel-pointer"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/sentinel_pointer", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 303)
}
@(test)
foreign_function_links_from_c_source :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-foreign-source"