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
+7 -3
View File
@@ -17,11 +17,15 @@
- C primitives remain semantically distinct from exact-width Brolang primitives until target lowering
- contextual integer literals and constant folding of addition and negation trees
- strict numeric conversions, checked integer addition, and unary negation
- arrays `[N]T`, sentinel arrays `[N;S]T`, indexing, pointers, pointer offsets, slices, and explicit slicing
- immutable UTF-8 sentinel-slice strings and Unicode code-point character literals
- 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`
- pointer-to-array `.len`, indexing, and slicing without explicit dereference
- arrays expose `.len` but not `.ptr`; slices and pointers-to-arrays expose `.ptr` and preserve sentinel information when available
- 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
- optionals with trapping postfix `?`, `orelse`, and nullable pointer representation
- source-order native structs, keyed literals, and defined or opaque pointer-only `c_struct`
- postfix pointer dereference, explicit `.ptr`/`.len`, general writable locations, function calls, assignments, and returns
- postfix pointer dereference, general writable locations, function calls, assignments, and returns
### functions and packages
+14 -2
View File
@@ -51,6 +51,17 @@ Bodyless manual and imported C functions may be variadic:
log_values :: c_func(tag c_int, ...) c_int
```
Zero-terminated byte strings can be passed directly to immutable C character
pointers without making `u8` and `c_char` generally interchangeable:
```bro
printf :: c_func(format *c_char, ...) c_int
main :: func() void {
_ = printf("answer: %d\n", 42)
}
```
Arguments after `...` accept concrete scalars, pointers, and nullable pointers.
Narrow integers are promoted to the target C `int` or `unsigned int`, and
`f32`/`c_float` are promoted to `c_double`. Arrays, slices, structs, and other
@@ -87,8 +98,9 @@ Current prototype features:
- Immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks
- Exact-width signed/unsigned integers, `f32`, `f64`, `isize`, `usize`, and loose integer-constrained `int`
- Target-dependent atomic `c_*` primitive types, `c_func`, and pointer-only `c_struct`
- Arrays, sentinel arrays, pointers, slices, sentinel slices, strings, character literals, optionals, and native structs
- Explicit `.ptr`/`.len`, slicing, postfix pointer dereference and optional unwrap, and keyed struct literals
- Arrays, sentinel arrays, single-item pointers, many-item pointers, sentinel many-item pointers, slices, sentinel slices, strings, character literals, optionals, and native structs
- String literals as immutable pointers to static zero-terminated byte arrays
- Pointer-preserving `.ptr`/`.len`, pointer-to-array indexing and slicing, postfix pointer dereference and optional unwrap, and keyed struct literals
- Contextual integer constants and compile-time folding of addition and unary negation trees
- Directory packages with merged declarations and file-local relative imports
- Relative C header imports as synthetic package namespaces
+11 -1
View File
@@ -20,7 +20,7 @@
- `[]T`: pointer and length
- `[;S]T`: pointer and length with a sentinel invariant
- ordinary slices do not guarantee null termination
- string literals as immutable sentinel slices backed by static arrays
- string literals as immutable sentinel slices backed by static arrays (superseded by milestone 3.5)
- character literals
- optionals with trapping unwrap and fallback operations
- native structs with compiler-controlled layout
@@ -44,6 +44,16 @@
- emit LLVM c-variadic declarations and calls
- keep native brolang variadics and tuple design separate
3.5. sentinel pointers and c strings (implemented)
- add sentinel many-item pointers: `[*;S]T`
- represent string literals as immutable pointers to statically stored sentinel arrays: `@[N;0]u8`
- arrays expose `.len` but no `.ptr`; slices and pointers-to-arrays expose sentinel-preserving `.ptr`
- allow pointer-to-array `.len`, indexing, slicing, pointer decay, and slice construction without explicit dereference
- preserve or forget sentinel information through compatible pointer and slice coercions without copying arrays
- allow zero-terminated immutable byte pointer views to convert to immutable `*c_char` and `[*;0]c_char`
- keep `u8` and `c_char` distinct to preserve target-dependent scalar c semantics
- reject general `u8`/`c_char` interchange, slice-to-pointer coercion, and conversion to mutable c character pointers
4. advanced c interop
- by-value records and unions
- function pointers and callbacks
+76 -26
View File
@@ -221,6 +221,19 @@ is_runtime_type :: proc(checker: ^Checker, value: types.Type) -> bool {
return types.is_runtime_value(value, &checker.module.types)
}
string_literal_type :: proc(checker: ^Checker, string_id: u64) -> types.Type {
length: u64
if string_id < u64(len(checker.ast_module.strings)) {
length = u64(len(checker.ast_module.strings[string_id]))
}
array := types.array(&checker.module.types, types.U8, length, false, true, 0)
return types.pointer(&checker.module.types, array, false, false)
}
container_pointer_type :: proc(store: ^types.Store, item: types.Node) -> types.Type {
return types.pointer(store, item.child, item.mutable, true, item.has_sentinel, item.sentinel)
}
resolve_inferred_array :: proc(checker: ^Checker, value: types.Type, expr_id: ast.Expr_Id) -> types.Type {
item, ok := types.node(&checker.module.types, value)
if !ok || item.kind != .Array || !item.inferred_count ||
@@ -665,7 +678,7 @@ validate_type_nodes :: proc(checker: ^Checker) {
source.addf(
checker.diagnostics,
source.Span{},
"sentinel value does not fit array or slice element type %s",
"sentinel value does not fit array, slice, or pointer element type %s",
types.name(item.child),
)
}
@@ -809,7 +822,7 @@ infer_compound_expr :: proc(
store := &checker.module.types
#partial switch expr.kind {
case .String:
return types.slice(store, types.U8, false, true, 0)
return string_literal_type(checker, expr.integer)
case .Array:
element := types.INVALID
for arg in expr.args {
@@ -835,11 +848,12 @@ infer_compound_expr :: proc(
case .Index:
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded)
return types.child_type(value, store)
item, ok := types.container(value, store)
return item.child if ok else types.INVALID
case .Slice:
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
item, ok := types.node(store, value)
if !ok || (item.kind != .Array && item.kind != .Slice) {
item, ok := types.container(value, store)
if !ok || item.kind == .Pointer {
return types.INVALID
}
for bound in expr.args {
@@ -851,14 +865,15 @@ infer_compound_expr :: proc(
return types.slice(store, item.child, item.mutable, preserve, item.sentinel)
case .Field:
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
item, has_item := types.node(store, value)
field_name := symbol_text(checker, expr.name)
item, has_item := types.container(value, store)
if has_item && (item.kind == .Array || item.kind == .Slice) {
if field_name == "len" {
return types.USIZE
}
if field_name == "ptr" {
return types.pointer(store, item.child, item.mutable, true)
if field_name == "ptr" &&
(item.kind == .Slice || types.is_pointer(value, store)) {
return container_pointer_type(store, item)
}
}
if types.is_pointer(value, store) {
@@ -950,13 +965,14 @@ infer_expr :: proc(
last = find_infer_local(locals, expr.name)
} else {
base_type := find_infer_local(locals, expr.qualifier)
item, has_item := types.node(&checker.module.types, base_type)
item, has_item := types.container(base_type, &checker.module.types)
field_name := symbol_text(checker, expr.name)
if has_item && (item.kind == .Array || item.kind == .Slice) {
if field_name == "len" {
last = types.USIZE
} else if field_name == "ptr" {
last = types.pointer(&checker.module.types, item.child, item.mutable, true)
} else if field_name == "ptr" &&
(item.kind == .Slice || types.is_pointer(base_type, &checker.module.types)) {
last = container_pointer_type(&checker.module.types, item)
}
}
if types.is_pointer(base_type, &checker.module.types) {
@@ -1296,11 +1312,35 @@ coerce_expr :: proc(
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if types.can_weaken_slice(actual, expected, &checker.module.types) {
return add_hir_expr(checker, hir.Expr{
kind=.Weaken_Slice,
span=span,
type=expected,
left=expr_id,
target=hir.INVALID_REF,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if types.can_decay_array_pointer(actual, expected, &checker.module.types) {
return add_hir_expr(checker, hir.Expr{
kind=.Decay_Array_Pointer,
span=span,
type=expected,
left=expr_id,
target=hir.INVALID_REF,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if types.is_optional(expected, &checker.module.types) {
child := types.child_type(expected, &checker.module.types)
if types.equal(actual, child) ||
types.can_widen(actual, child) ||
types.can_weaken_pointer(actual, child, &checker.module.types) {
types.can_weaken_pointer(actual, child, &checker.module.types) ||
types.can_weaken_slice(actual, child, &checker.module.types) ||
types.can_decay_array_pointer(actual, child, &checker.module.types) {
value := coerce_expr(checker, expr_id, child, span)
return add_hir_expr(checker, hir.Expr{
kind=.Optional_Some,
@@ -1475,7 +1515,8 @@ hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: [
return types.is_mutable(pointer_type, &checker.module.types)
case .Index:
container_type := checker.module.exprs[expr.left].type
return types.is_mutable(container_type, &checker.module.types)
item, ok := types.container(container_type, &checker.module.types)
return ok && item.mutable
case .Field:
base_type := checker.module.exprs[expr.left].type
if types.is_pointer(base_type, &checker.module.types) {
@@ -1539,7 +1580,7 @@ build_compound_expr :: proc(
store := &checker.module.types
#partial switch expr.kind {
case .String:
string_type := types.slice(store, types.U8, false, true, 0)
string_type := string_literal_type(checker, expr.integer)
return add_hir_expr(checker, hir.Expr{
kind=.String, span=expr.span, type=string_type, integer=i64(expr.integer),
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
@@ -1639,9 +1680,9 @@ build_compound_expr :: proc(
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
index := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.USIZE, pkg, file)
container_type := checker.module.exprs[container].type
item, ok := types.node(store, container_type)
if !ok || (item.kind != .Array && item.kind != .Slice && !(item.kind == .Pointer && item.many)) {
id := source.add(checker.diagnostics, expr.span, "indexing requires an array, slice, or many-item pointer")
item, ok := types.container(container_type, store)
if !ok {
id := source.add(checker.diagnostics, expr.span, "indexing requires an array, slice, pointer-to-array, or many-item pointer")
return invalid_hir_expr(checker, expr.span, id)
}
return add_hir_expr(checker, hir.Expr{
@@ -1651,9 +1692,9 @@ build_compound_expr :: proc(
case .Slice:
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
container_type := checker.module.exprs[container].type
item, ok := types.node(store, container_type)
if !ok || (item.kind != .Array && item.kind != .Slice) {
id := source.add(checker.diagnostics, expr.span, "slicing requires an array or slice")
item, ok := types.container(container_type, store)
if !ok || item.kind == .Pointer {
id := source.add(checker.diagnostics, expr.span, "slicing requires an array, slice, or pointer-to-array")
return invalid_hir_expr(checker, expr.span, id)
}
bounds := make([]hir.Expr_Id, 2, checker.allocator)
@@ -1673,8 +1714,8 @@ build_compound_expr :: proc(
case .Field:
base := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
base_type := checker.module.exprs[base].type
item, has_item := types.node(store, base_type)
field_name := symbol_text(checker, expr.name)
item, has_item := types.container(base_type, store)
if has_item && (item.kind == .Array || item.kind == .Slice) {
if field_name == "len" {
return add_hir_expr(checker, hir.Expr{
@@ -1682,13 +1723,18 @@ build_compound_expr :: proc(
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if field_name == "ptr" {
if field_name == "ptr" &&
(item.kind == .Slice || types.is_pointer(base_type, store)) {
return add_hir_expr(checker, hir.Expr{
kind=.Slice_Ptr, span=expr.span,
type=types.pointer(store, item.child, item.mutable, true), left=base,
type=container_pointer_type(store, item), left=base,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if field_name == "ptr" && item.kind == .Array {
id := source.add(checker.diagnostics, expr.span, "arrays do not expose '.ptr'; take their address first")
return invalid_hir_expr(checker, expr.span, id)
}
}
if types.is_pointer(base_type, store) {
base_type = types.child_type(base_type, store)
@@ -1848,7 +1894,7 @@ build_expr :: proc(
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
base_type := local.type
item, has_item := types.node(&checker.module.types, base_type)
item, has_item := types.container(base_type, &checker.module.types)
field_name := symbol_text(checker, expr.name)
if has_item && (item.kind == .Array || item.kind == .Slice) {
if field_name == "len" {
@@ -1856,12 +1902,16 @@ build_expr :: proc(
kind=.Length, span=expr.span, type=types.USIZE, left=base,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
} else if field_name == "ptr" {
} else if field_name == "ptr" &&
(item.kind == .Slice || types.is_pointer(base_type, &checker.module.types)) {
last = add_hir_expr(checker, hir.Expr{
kind=.Slice_Ptr, span=expr.span,
type=types.pointer(&checker.module.types, item.child, item.mutable, true), left=base,
type=container_pointer_type(&checker.module.types, item), left=base,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
} else if field_name == "ptr" && item.kind == .Array {
id := source.add(checker.diagnostics, expr.span, "arrays do not expose '.ptr'; take their address first")
last = invalid_hir_expr(checker, expr.span, id)
}
}
if types.is_pointer(base_type, &checker.module.types) {
+2
View File
@@ -95,6 +95,8 @@ Expr_Kind :: enum u8 {
Widen,
C_Vararg_Promote,
Weaken_Pointer,
Weaken_Slice,
Decay_Array_Pointer,
Negate,
Add,
Pointer_Add,
+2
View File
@@ -89,6 +89,8 @@ Opcode :: enum u8 {
Widen,
C_Vararg_Promote,
Weaken_Pointer,
Weaken_Slice,
Decay_Array_Pointer,
Neg_Checked,
Add_Checked,
Pointer_Add,
+54 -14
View File
@@ -112,7 +112,8 @@ valid_value :: proc(
switch instructions[value_id].op {
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
.Load_Global, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse,
.Widen, .C_Vararg_Promote, .Weaken_Pointer, .Neg_Checked, .Add_Checked, .Pointer_Add, .Call:
.Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
.Neg_Checked, .Add_Checked, .Pointer_Add, .Call:
return true
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
.Store, .Trap, .Return, .Return_Void:
@@ -306,21 +307,17 @@ emit_instruction_stream :: proc(
case .Param, .Const:
case .String:
string_id := int(instruction.integer)
_, array, pointer_ok := types.array_pointer(instruction.type, &emitter.module.types)
if string_id < 0 || string_id >= len(emitter.module.strings) ||
!types.is_slice(instruction.type, &emitter.module.types) {
!pointer_ok || array.child != types.U8 || !array.has_sentinel ||
array.sentinel != 0 || array.count != u64(len(emitter.module.strings[string_id])) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid string literal")
continue
}
type_name := llvm_type(instruction.type, &emitter.module.types)
fmt.sbprintf(
&emitter.builder,
" %%string_ptr%d = insertvalue %s poison, ptr @bro.str.%d, 0\n",
instruction_index, type_name, string_id,
)
fmt.sbprintf(
&emitter.builder,
" %%v%d = insertvalue %s %%string_ptr%d, i64 %d, 1\n",
instruction_index, type_name, instruction_index, len(emitter.module.strings[string_id]),
" %%v%d = getelementptr %s, ptr @bro.str.%d, i64 0\n",
instruction_index, llvm_type(types.child_type(instruction.type, &emitter.module.types), &emitter.module.types), string_id,
)
case .Aggregate:
item, ok := types.node(&emitter.module.types, instruction.type)
@@ -465,7 +462,7 @@ emit_instruction_stream :: proc(
continue
}
container := instructions[instruction.a]
container_node, container_ok := types.node(&emitter.module.types, container.type)
container_node, container_ok := types.container(container.type, &emitter.module.types)
if !container_ok {
emit_recovery_value(emitter, instruction_index, instruction, "invalid index container")
continue
@@ -504,7 +501,13 @@ emit_instruction_stream :: proc(
fmt.sbprintf(&emitter.builder, " unreachable\nindex_continue%d:\n", instruction_index)
}
if container_node.kind == .Array {
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %s, i64 0, i64 ", instruction_index, llvm_type(container.type, &emitter.module.types), pointer_name)
array_type := container.type
_, array, array_pointer_ok := types.array_pointer(container.type, &emitter.module.types)
if array_pointer_ok {
array_type = types.child_type(container.type, &emitter.module.types)
container_node = array
}
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %s, i64 0, i64 ", instruction_index, llvm_type(array_type, &emitter.module.types), pointer_name)
} else {
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %s, i64 ", instruction_index, llvm_type(instruction.type, &emitter.module.types), pointer_name)
}
@@ -558,7 +561,7 @@ emit_instruction_stream :: proc(
continue
}
container := instructions[instruction.a]
item, ok := types.node(&emitter.module.types, container.type)
item, ok := types.container(container.type, &emitter.module.types)
if !ok || (item.kind != .Array && item.kind != .Slice) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container")
continue
@@ -566,7 +569,11 @@ emit_instruction_stream :: proc(
pointer_name := fmt.tprintf("%%v%d", instruction.a)
length_name := fmt.tprintf("%d", item.count)
if item.kind == .Array {
fmt.sbprintf(&emitter.builder, " %%slice_ptr%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a)
array_type := container.type
if types.is_pointer(container.type, &emitter.module.types) {
array_type = types.child_type(container.type, &emitter.module.types)
}
fmt.sbprintf(&emitter.builder, " %%slice_ptr%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(array_type, &emitter.module.types), instruction.a)
pointer_name = fmt.tprintf("%%slice_ptr%d", instruction_index)
} else if item.kind == .Slice {
fmt.sbprintf(&emitter.builder, " %%slice_ptr%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a)
@@ -621,12 +628,21 @@ emit_instruction_stream :: proc(
continue
}
container_type := instructions[instruction.a].type
_, _, array_pointer_ok := types.array_pointer(container_type, &emitter.module.types)
if types.is_array(container_type, &emitter.module.types) {
fmt.sbprintf(
&emitter.builder,
" %%v%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n",
instruction_index, llvm_type(container_type, &emitter.module.types), instruction.a,
)
} else if array_pointer_ok {
fmt.sbprintf(
&emitter.builder,
" %%v%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n",
instruction_index,
llvm_type(types.child_type(container_type, &emitter.module.types), &emitter.module.types),
instruction.a,
)
} else if types.is_slice(container_type, &emitter.module.types) {
fmt.sbprintf(
&emitter.builder,
@@ -741,6 +757,30 @@ emit_instruction_stream :: proc(
continue
}
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr %%v%d, ptr null\n", instruction_index, instruction.a)
case .Weaken_Slice:
if !valid_instruction(instructions, instruction.a) ||
!types.can_weaken_slice(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice weakening operand")
continue
}
type_name := llvm_type(instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, %s %%v%d, %s zeroinitializer\n", instruction_index, type_name, instruction.a, type_name)
case .Decay_Array_Pointer:
if !valid_instruction(instructions, instruction.a) ||
!types.can_decay_array_pointer(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid array pointer decay operand")
continue
}
array_type := types.child_type(instructions[instruction.a].type, &emitter.module.types)
array, _ := types.node(&emitter.module.types, array_type)
if types.is_pointer(instruction.type, &emitter.module.types) {
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(array_type, &emitter.module.types), instruction.a)
} else {
type_name := llvm_type(instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, " %%decay_ptr%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(array_type, &emitter.module.types), instruction.a)
fmt.sbprintf(&emitter.builder, " %%decay_slice%d = insertvalue %s poison, ptr %%decay_ptr%d, 0\n", instruction_index, type_name, instruction_index)
fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s %%decay_slice%d, i64 %d, 1\n", instruction_index, type_name, instruction_index, array.count)
}
case .Neg_Checked:
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid negation operand")
+18 -3
View File
@@ -227,6 +227,14 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
_, array, array_ok := types.array_pointer(container_type, &state.hir_module.types)
if array_ok {
return append_instruction(state, ir.Instruction{
op=.Const, span=expr.span, type=types.USIZE, integer=i64(array.count),
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
value := lower_nested_expr(state, expr.left)
return append_instruction(state, ir.Instruction{
op=.Length, span=expr.span, type=types.USIZE,
@@ -324,7 +332,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
})
}
_ = pop(&stack)
case .Widen, .C_Vararg_Promote, .Weaken_Pointer:
case .Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer:
stack[frame_index].stage = 1
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Negate:
@@ -357,9 +365,16 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
continue
}
if frame.stage == 1 {
op := ir.Opcode.Widen
#partial switch expr.kind {
case .Weaken_Pointer: op = .Weaken_Pointer
case .Weaken_Slice: op = .Weaken_Slice
case .Decay_Array_Pointer: op = .Decay_Array_Pointer
case .C_Vararg_Promote: op = .C_Vararg_Promote
case: op = .Widen
}
last = append_instruction(state, ir.Instruction{
op=.Weaken_Pointer if expr.kind == .Weaken_Pointer else
(.C_Vararg_Promote if expr.kind == .C_Vararg_Promote else .Widen),
op=op,
span=expr.span, type=expr.type, target=ir.INVALID_REF,
a=last, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
+11
View File
@@ -166,6 +166,17 @@ parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
node := types.Node{}
if _, ok := allow(parser, .Right_Bracket); ok {
node.kind = .Slice
} else if _, ok := allow(parser, .Star); ok {
node.kind = .Pointer
node.many = true
node.has_sentinel = true
if _, ok = allow(parser, .Semicolon); !ok {
source.add(parser.diagnostics, current(parser).span, "expected ';' after '*' in sentinel pointer type")
}
node.sentinel, _ = parse_type_constant(parser)
if _, ok = allow(parser, .Right_Bracket); !ok {
source.add(parser.diagnostics, current(parser).span, "expected ']' after sentinel pointer type")
}
} else if _, ok := allow(parser, .Semicolon); ok {
node.kind = .Slice
node.has_sentinel = true
+93 -5
View File
@@ -488,13 +488,56 @@ is_many_pointer :: proc(value: Type, store: ^Store) -> bool {
return ok && item.kind == .Pointer && item.many
}
array_pointer :: proc(value: Type, store: ^Store) -> (pointer_item, array_item: Node, ok: bool) {
pointer_ok: bool
pointer_item, pointer_ok = node(store, value)
if !pointer_ok || pointer_item.kind != .Pointer || pointer_item.many {
return {}, {}, false
}
array_ok: bool
array_item, array_ok = node(store, pointer_item.child)
if !array_ok || array_item.kind != .Array {
return {}, {}, false
}
return pointer_item, array_item, true
}
container :: proc(value: Type, store: ^Store) -> (Node, bool) {
item, ok := node(store, value)
if !ok {
return {}, false
}
if item.kind == .Array || item.kind == .Slice || (item.kind == .Pointer && item.many) {
return item, true
}
pointer_node, array_node, array_ok := array_pointer(value, store)
if !array_ok {
return {}, false
}
array_node.mutable = pointer_node.mutable && array_node.mutable
return array_node, true
}
is_c_struct :: proc(value: Type, store: ^Store) -> bool {
item, ok := node(store, value)
return ok && item.kind == .Struct && item.c_layout
}
pointer :: proc(store: ^Store, child: Type, mutable, many: bool) -> Type {
return intern(store, Node{kind=.Pointer, child=child, mutable=mutable, many=many})
pointer :: proc(
store: ^Store,
child: Type,
mutable, many: bool,
has_sentinel := false,
sentinel: u64 = 0,
) -> Type {
return intern(store, Node{
kind=.Pointer,
child=child,
mutable=mutable,
many=many,
has_sentinel=has_sentinel,
sentinel=sentinel,
})
}
slice :: proc(store: ^Store, child: Type, mutable: bool, has_sentinel := false, sentinel: u64 = 0) -> Type {
@@ -540,12 +583,57 @@ with_array_count :: proc(store: ^Store, value: Type, count: u64) -> Type {
}
can_weaken_pointer :: proc(from, to: Type, store: ^Store) -> bool {
from_node, from_ok := node(store, from)
to_node, to_ok := node(store, to)
if !from_ok || !to_ok || from_node.kind != .Pointer || to_node.kind != .Pointer ||
from_node.many != to_node.many || (to_node.mutable && !from_node.mutable) {
return false
}
if to_node.has_sentinel &&
(!from_node.has_sentinel || from_node.sentinel != to_node.sentinel) {
return false
}
same_child := from_node.child == to_node.child
c_string := from_node.many && from_node.child == U8 && to_node.child == C_CHAR &&
from_node.has_sentinel && from_node.sentinel == 0 && !to_node.mutable
return same_child || c_string
}
can_weaken_slice :: proc(from, to: Type, store: ^Store) -> bool {
from_node, from_ok := node(store, from)
to_node, to_ok := node(store, to)
return from_ok && to_ok &&
from_node.kind == .Pointer && to_node.kind == .Pointer &&
from_node.child == to_node.child && from_node.many == to_node.many &&
from_node.mutable && !to_node.mutable
from_node.kind == .Slice && to_node.kind == .Slice &&
from_node.child == to_node.child &&
(!to_node.mutable || from_node.mutable) &&
(!to_node.has_sentinel ||
(from_node.has_sentinel && from_node.sentinel == to_node.sentinel))
}
can_decay_array_pointer :: proc(from, to: Type, store: ^Store) -> bool {
from_pointer, array, from_ok := array_pointer(from, store)
to_node, to_ok := node(store, to)
if !from_ok || !to_ok {
return false
}
mutable := from_pointer.mutable && array.mutable
if to_node.mutable && !mutable {
return false
}
if to_node.has_sentinel &&
(!array.has_sentinel || array.sentinel != to_node.sentinel) {
return false
}
if to_node.kind == .Slice {
return array.child == to_node.child
}
if to_node.kind != .Pointer || !to_node.many {
return false
}
same_child := array.child == to_node.child
c_string := array.child == U8 && to_node.child == C_CHAR &&
array.has_sentinel && array.sentinel == 0 && !to_node.mutable
return same_child || c_string
}
is_opaque_struct :: proc(value: Type, store: ^Store) -> bool {
+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"
+1
View File
@@ -9,6 +9,7 @@ main :: func() void {
_ = native.imported_scalar(3, 4)
_ = native.child_value(7)
_ = native.imported_read(native.imported_handle()?)
_ = native.imported_string("hello")
_ = native.configured_value(9)
signed i8 :: -2
unsigned u16 :: 3
+1
View File
@@ -21,6 +21,7 @@ int imported_add(imported_int_alias left, int right);
unsigned long imported_scalar(unsigned char value, unsigned long extra);
Imported_Handle *imported_handle(void);
int imported_read(const Imported_Handle *handle);
int imported_string(const char *value);
Imported_Value imported_by_value(Imported_Value value);
int imported_volatile(volatile int *value);
_Bool imported_bool(_Bool value);
+8
View File
@@ -1,6 +1,7 @@
#include "include/native.h"
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
struct Imported_Handle {
int value;
@@ -28,6 +29,13 @@ int imported_read(const Imported_Handle *value) {
return value->value;
}
int imported_string(const char *value) {
if (strcmp(value, "hello") != 0) {
abort();
}
return 0;
}
int imported_variadic(int marker, ...) {
va_list args;
va_start(args, marker);
+5
View File
@@ -0,0 +1,5 @@
printf :: c_func(format *c_char, ...) c_int
main :: func() void {
_ = printf("answer: %d\n", 42)
}
@@ -0,0 +1,8 @@
main :: func() i32 {
values [2;0]i32 :: [101, 101]
array_pointer :: &values
suffix [;0]i32 :: array_pointer[1..]
ordinary []i32 :: suffix
pointer [*;0]i32 :: suffix.ptr
return array_pointer[0] + pointer[0] + ordinary[0]
}