sentinel pointers and c strings
This commit is contained in:
+7
-3
@@ -17,11 +17,15 @@
|
|||||||
- 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 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
|
||||||
- arrays `[N]T`, sentinel arrays `[N;S]T`, indexing, pointers, 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 sentinel-slice strings and Unicode code-point character literals
|
- 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
|
- optionals with trapping postfix `?`, `orelse`, and nullable pointer representation
|
||||||
- source-order native structs, keyed literals, and defined or opaque pointer-only `c_struct`
|
- 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
|
### functions and packages
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,17 @@ Bodyless manual and imported C functions may be variadic:
|
|||||||
log_values :: c_func(tag c_int, ...) c_int
|
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.
|
Arguments after `...` accept concrete scalars, pointers, and nullable pointers.
|
||||||
Narrow integers are promoted to the target C `int` or `unsigned int`, and
|
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
|
`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
|
- Immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks
|
||||||
- Exact-width signed/unsigned integers, `f32`, `f64`, `isize`, `usize`, and loose integer-constrained `int`
|
- 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`
|
- 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
|
- Arrays, sentinel arrays, single-item pointers, many-item pointers, sentinel many-item 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
|
- 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
|
- Contextual integer constants and compile-time folding of addition and unary negation trees
|
||||||
- Directory packages with merged declarations and file-local relative imports
|
- Directory packages with merged declarations and file-local relative imports
|
||||||
- Relative C header imports as synthetic package namespaces
|
- Relative C header imports as synthetic package namespaces
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
- `[]T`: pointer and length
|
- `[]T`: pointer and length
|
||||||
- `[;S]T`: pointer and length with a sentinel invariant
|
- `[;S]T`: pointer and length with a sentinel invariant
|
||||||
- ordinary slices do not guarantee null termination
|
- 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
|
- character literals
|
||||||
- optionals with trapping unwrap and fallback operations
|
- optionals with trapping unwrap and fallback operations
|
||||||
- native structs with compiler-controlled layout
|
- native structs with compiler-controlled layout
|
||||||
@@ -44,6 +44,16 @@
|
|||||||
- emit LLVM c-variadic declarations and calls
|
- emit LLVM c-variadic declarations and calls
|
||||||
- keep native brolang variadics and tuple design separate
|
- 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
|
4. advanced c interop
|
||||||
- by-value records and unions
|
- by-value records and unions
|
||||||
- function pointers and callbacks
|
- function pointers and callbacks
|
||||||
|
|||||||
@@ -221,6 +221,19 @@ is_runtime_type :: proc(checker: ^Checker, value: types.Type) -> bool {
|
|||||||
return types.is_runtime_value(value, &checker.module.types)
|
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 {
|
resolve_inferred_array :: proc(checker: ^Checker, value: types.Type, expr_id: ast.Expr_Id) -> types.Type {
|
||||||
item, ok := types.node(&checker.module.types, value)
|
item, ok := types.node(&checker.module.types, value)
|
||||||
if !ok || item.kind != .Array || !item.inferred_count ||
|
if !ok || item.kind != .Array || !item.inferred_count ||
|
||||||
@@ -665,7 +678,7 @@ validate_type_nodes :: proc(checker: ^Checker) {
|
|||||||
source.addf(
|
source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
source.Span{},
|
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),
|
types.name(item.child),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -809,7 +822,7 @@ infer_compound_expr :: proc(
|
|||||||
store := &checker.module.types
|
store := &checker.module.types
|
||||||
#partial switch expr.kind {
|
#partial switch expr.kind {
|
||||||
case .String:
|
case .String:
|
||||||
return types.slice(store, types.U8, false, true, 0)
|
return string_literal_type(checker, expr.integer)
|
||||||
case .Array:
|
case .Array:
|
||||||
element := types.INVALID
|
element := types.INVALID
|
||||||
for arg in expr.args {
|
for arg in expr.args {
|
||||||
@@ -835,11 +848,12 @@ infer_compound_expr :: proc(
|
|||||||
case .Index:
|
case .Index:
|
||||||
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
|
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
|
||||||
_ = infer_nested_expr(checker, expr.right, 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:
|
case .Slice:
|
||||||
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
|
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
|
||||||
item, ok := types.node(store, value)
|
item, ok := types.container(value, store)
|
||||||
if !ok || (item.kind != .Array && item.kind != .Slice) {
|
if !ok || item.kind == .Pointer {
|
||||||
return types.INVALID
|
return types.INVALID
|
||||||
}
|
}
|
||||||
for bound in expr.args {
|
for bound in expr.args {
|
||||||
@@ -851,14 +865,15 @@ infer_compound_expr :: proc(
|
|||||||
return types.slice(store, item.child, item.mutable, preserve, item.sentinel)
|
return types.slice(store, item.child, item.mutable, preserve, item.sentinel)
|
||||||
case .Field:
|
case .Field:
|
||||||
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
|
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)
|
field_name := symbol_text(checker, expr.name)
|
||||||
|
item, has_item := types.container(value, store)
|
||||||
if has_item && (item.kind == .Array || item.kind == .Slice) {
|
if has_item && (item.kind == .Array || item.kind == .Slice) {
|
||||||
if field_name == "len" {
|
if field_name == "len" {
|
||||||
return types.USIZE
|
return types.USIZE
|
||||||
}
|
}
|
||||||
if field_name == "ptr" {
|
if field_name == "ptr" &&
|
||||||
return types.pointer(store, item.child, item.mutable, true)
|
(item.kind == .Slice || types.is_pointer(value, store)) {
|
||||||
|
return container_pointer_type(store, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if types.is_pointer(value, store) {
|
if types.is_pointer(value, store) {
|
||||||
@@ -950,13 +965,14 @@ infer_expr :: proc(
|
|||||||
last = find_infer_local(locals, expr.name)
|
last = find_infer_local(locals, expr.name)
|
||||||
} else {
|
} else {
|
||||||
base_type := find_infer_local(locals, expr.qualifier)
|
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)
|
field_name := symbol_text(checker, expr.name)
|
||||||
if has_item && (item.kind == .Array || item.kind == .Slice) {
|
if has_item && (item.kind == .Array || item.kind == .Slice) {
|
||||||
if field_name == "len" {
|
if field_name == "len" {
|
||||||
last = types.USIZE
|
last = types.USIZE
|
||||||
} else if field_name == "ptr" {
|
} else if field_name == "ptr" &&
|
||||||
last = types.pointer(&checker.module.types, item.child, item.mutable, true)
|
(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) {
|
if types.is_pointer(base_type, &checker.module.types) {
|
||||||
@@ -1296,11 +1312,35 @@ coerce_expr :: proc(
|
|||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
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) {
|
if types.is_optional(expected, &checker.module.types) {
|
||||||
child := types.child_type(expected, &checker.module.types)
|
child := types.child_type(expected, &checker.module.types)
|
||||||
if types.equal(actual, child) ||
|
if types.equal(actual, child) ||
|
||||||
types.can_widen(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)
|
value := coerce_expr(checker, expr_id, child, span)
|
||||||
return add_hir_expr(checker, hir.Expr{
|
return add_hir_expr(checker, hir.Expr{
|
||||||
kind=.Optional_Some,
|
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)
|
return types.is_mutable(pointer_type, &checker.module.types)
|
||||||
case .Index:
|
case .Index:
|
||||||
container_type := checker.module.exprs[expr.left].type
|
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:
|
case .Field:
|
||||||
base_type := checker.module.exprs[expr.left].type
|
base_type := checker.module.exprs[expr.left].type
|
||||||
if types.is_pointer(base_type, &checker.module.types) {
|
if types.is_pointer(base_type, &checker.module.types) {
|
||||||
@@ -1539,7 +1580,7 @@ build_compound_expr :: proc(
|
|||||||
store := &checker.module.types
|
store := &checker.module.types
|
||||||
#partial switch expr.kind {
|
#partial switch expr.kind {
|
||||||
case .String:
|
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{
|
return add_hir_expr(checker, hir.Expr{
|
||||||
kind=.String, span=expr.span, type=string_type, integer=i64(expr.integer),
|
kind=.String, span=expr.span, type=string_type, integer=i64(expr.integer),
|
||||||
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
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)
|
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)
|
index := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.USIZE, pkg, file)
|
||||||
container_type := checker.module.exprs[container].type
|
container_type := checker.module.exprs[container].type
|
||||||
item, ok := types.node(store, container_type)
|
item, ok := types.container(container_type, store)
|
||||||
if !ok || (item.kind != .Array && item.kind != .Slice && !(item.kind == .Pointer && item.many)) {
|
if !ok {
|
||||||
id := source.add(checker.diagnostics, expr.span, "indexing requires an array, slice, or many-item pointer")
|
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 invalid_hir_expr(checker, expr.span, id)
|
||||||
}
|
}
|
||||||
return add_hir_expr(checker, hir.Expr{
|
return add_hir_expr(checker, hir.Expr{
|
||||||
@@ -1651,9 +1692,9 @@ build_compound_expr :: proc(
|
|||||||
case .Slice:
|
case .Slice:
|
||||||
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
container_type := checker.module.exprs[container].type
|
container_type := checker.module.exprs[container].type
|
||||||
item, ok := types.node(store, container_type)
|
item, ok := types.container(container_type, store)
|
||||||
if !ok || (item.kind != .Array && item.kind != .Slice) {
|
if !ok || item.kind == .Pointer {
|
||||||
id := source.add(checker.diagnostics, expr.span, "slicing requires an array or slice")
|
id := source.add(checker.diagnostics, expr.span, "slicing requires an array, slice, or pointer-to-array")
|
||||||
return invalid_hir_expr(checker, expr.span, id)
|
return invalid_hir_expr(checker, expr.span, id)
|
||||||
}
|
}
|
||||||
bounds := make([]hir.Expr_Id, 2, checker.allocator)
|
bounds := make([]hir.Expr_Id, 2, checker.allocator)
|
||||||
@@ -1673,8 +1714,8 @@ build_compound_expr :: proc(
|
|||||||
case .Field:
|
case .Field:
|
||||||
base := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
base := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
base_type := checker.module.exprs[base].type
|
base_type := checker.module.exprs[base].type
|
||||||
item, has_item := types.node(store, base_type)
|
|
||||||
field_name := symbol_text(checker, expr.name)
|
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 has_item && (item.kind == .Array || item.kind == .Slice) {
|
||||||
if field_name == "len" {
|
if field_name == "len" {
|
||||||
return add_hir_expr(checker, hir.Expr{
|
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,
|
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{
|
return add_hir_expr(checker, hir.Expr{
|
||||||
kind=.Slice_Ptr, span=expr.span,
|
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,
|
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) {
|
if types.is_pointer(base_type, store) {
|
||||||
base_type = types.child_type(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,
|
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
base_type := local.type
|
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)
|
field_name := symbol_text(checker, expr.name)
|
||||||
if has_item && (item.kind == .Array || item.kind == .Slice) {
|
if has_item && (item.kind == .Array || item.kind == .Slice) {
|
||||||
if field_name == "len" {
|
if field_name == "len" {
|
||||||
@@ -1856,12 +1902,16 @@ build_expr :: proc(
|
|||||||
kind=.Length, span=expr.span, type=types.USIZE, left=base,
|
kind=.Length, span=expr.span, type=types.USIZE, left=base,
|
||||||
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
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{
|
last = add_hir_expr(checker, hir.Expr{
|
||||||
kind=.Slice_Ptr, span=expr.span,
|
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,
|
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) {
|
if types.is_pointer(base_type, &checker.module.types) {
|
||||||
|
|||||||
@@ -95,6 +95,8 @@ Expr_Kind :: enum u8 {
|
|||||||
Widen,
|
Widen,
|
||||||
C_Vararg_Promote,
|
C_Vararg_Promote,
|
||||||
Weaken_Pointer,
|
Weaken_Pointer,
|
||||||
|
Weaken_Slice,
|
||||||
|
Decay_Array_Pointer,
|
||||||
Negate,
|
Negate,
|
||||||
Add,
|
Add,
|
||||||
Pointer_Add,
|
Pointer_Add,
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ Opcode :: enum u8 {
|
|||||||
Widen,
|
Widen,
|
||||||
C_Vararg_Promote,
|
C_Vararg_Promote,
|
||||||
Weaken_Pointer,
|
Weaken_Pointer,
|
||||||
|
Weaken_Slice,
|
||||||
|
Decay_Array_Pointer,
|
||||||
Neg_Checked,
|
Neg_Checked,
|
||||||
Add_Checked,
|
Add_Checked,
|
||||||
Pointer_Add,
|
Pointer_Add,
|
||||||
|
|||||||
+54
-14
@@ -112,7 +112,8 @@ valid_value :: proc(
|
|||||||
switch instructions[value_id].op {
|
switch instructions[value_id].op {
|
||||||
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
|
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
|
||||||
.Load_Global, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse,
|
.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
|
return true
|
||||||
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
|
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
|
||||||
.Store, .Trap, .Return, .Return_Void:
|
.Store, .Trap, .Return, .Return_Void:
|
||||||
@@ -306,21 +307,17 @@ emit_instruction_stream :: proc(
|
|||||||
case .Param, .Const:
|
case .Param, .Const:
|
||||||
case .String:
|
case .String:
|
||||||
string_id := int(instruction.integer)
|
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) ||
|
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")
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid string literal")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
|
||||||
fmt.sbprintf(
|
fmt.sbprintf(
|
||||||
&emitter.builder,
|
&emitter.builder,
|
||||||
" %%string_ptr%d = insertvalue %s poison, ptr @bro.str.%d, 0\n",
|
" %%v%d = getelementptr %s, ptr @bro.str.%d, i64 0\n",
|
||||||
instruction_index, type_name, string_id,
|
instruction_index, llvm_type(types.child_type(instruction.type, &emitter.module.types), &emitter.module.types), 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]),
|
|
||||||
)
|
)
|
||||||
case .Aggregate:
|
case .Aggregate:
|
||||||
item, ok := types.node(&emitter.module.types, instruction.type)
|
item, ok := types.node(&emitter.module.types, instruction.type)
|
||||||
@@ -465,7 +462,7 @@ emit_instruction_stream :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
container := instructions[instruction.a]
|
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 {
|
if !container_ok {
|
||||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid index container")
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid index container")
|
||||||
continue
|
continue
|
||||||
@@ -504,7 +501,13 @@ emit_instruction_stream :: proc(
|
|||||||
fmt.sbprintf(&emitter.builder, " unreachable\nindex_continue%d:\n", instruction_index)
|
fmt.sbprintf(&emitter.builder, " unreachable\nindex_continue%d:\n", instruction_index)
|
||||||
}
|
}
|
||||||
if container_node.kind == .Array {
|
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 {
|
} else {
|
||||||
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %s, i64 ", instruction_index, llvm_type(instruction.type, &emitter.module.types), pointer_name)
|
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
|
continue
|
||||||
}
|
}
|
||||||
container := instructions[instruction.a]
|
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) {
|
if !ok || (item.kind != .Array && item.kind != .Slice) {
|
||||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container")
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container")
|
||||||
continue
|
continue
|
||||||
@@ -566,7 +569,11 @@ emit_instruction_stream :: proc(
|
|||||||
pointer_name := fmt.tprintf("%%v%d", instruction.a)
|
pointer_name := fmt.tprintf("%%v%d", instruction.a)
|
||||||
length_name := fmt.tprintf("%d", item.count)
|
length_name := fmt.tprintf("%d", item.count)
|
||||||
if item.kind == .Array {
|
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)
|
pointer_name = fmt.tprintf("%%slice_ptr%d", instruction_index)
|
||||||
} else if item.kind == .Slice {
|
} 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)
|
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
|
continue
|
||||||
}
|
}
|
||||||
container_type := instructions[instruction.a].type
|
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) {
|
if types.is_array(container_type, &emitter.module.types) {
|
||||||
fmt.sbprintf(
|
fmt.sbprintf(
|
||||||
&emitter.builder,
|
&emitter.builder,
|
||||||
" %%v%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n",
|
" %%v%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n",
|
||||||
instruction_index, llvm_type(container_type, &emitter.module.types), instruction.a,
|
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) {
|
} else if types.is_slice(container_type, &emitter.module.types) {
|
||||||
fmt.sbprintf(
|
fmt.sbprintf(
|
||||||
&emitter.builder,
|
&emitter.builder,
|
||||||
@@ -741,6 +757,30 @@ emit_instruction_stream :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr %%v%d, ptr null\n", instruction_index, instruction.a)
|
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:
|
case .Neg_Checked:
|
||||||
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) {
|
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) {
|
||||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid negation operand")
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid negation operand")
|
||||||
|
|||||||
@@ -227,6 +227,14 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
|||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
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)
|
value := lower_nested_expr(state, expr.left)
|
||||||
return append_instruction(state, ir.Instruction{
|
return append_instruction(state, ir.Instruction{
|
||||||
op=.Length, span=expr.span, type=types.USIZE,
|
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)
|
_ = 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
|
stack[frame_index].stage = 1
|
||||||
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
||||||
case .Negate:
|
case .Negate:
|
||||||
@@ -357,9 +365,16 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if frame.stage == 1 {
|
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{
|
last = append_instruction(state, ir.Instruction{
|
||||||
op=.Weaken_Pointer if expr.kind == .Weaken_Pointer else
|
op=op,
|
||||||
(.C_Vararg_Promote if expr.kind == .C_Vararg_Promote else .Widen),
|
|
||||||
span=expr.span, type=expr.type, target=ir.INVALID_REF,
|
span=expr.span, type=expr.type, target=ir.INVALID_REF,
|
||||||
a=last, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
a=last, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -166,6 +166,17 @@ parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
|||||||
node := types.Node{}
|
node := types.Node{}
|
||||||
if _, ok := allow(parser, .Right_Bracket); ok {
|
if _, ok := allow(parser, .Right_Bracket); ok {
|
||||||
node.kind = .Slice
|
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 {
|
} else if _, ok := allow(parser, .Semicolon); ok {
|
||||||
node.kind = .Slice
|
node.kind = .Slice
|
||||||
node.has_sentinel = true
|
node.has_sentinel = true
|
||||||
|
|||||||
@@ -488,13 +488,56 @@ is_many_pointer :: proc(value: Type, store: ^Store) -> bool {
|
|||||||
return ok && item.kind == .Pointer && item.many
|
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 {
|
is_c_struct :: proc(value: Type, store: ^Store) -> bool {
|
||||||
item, ok := node(store, value)
|
item, ok := node(store, value)
|
||||||
return ok && item.kind == .Struct && item.c_layout
|
return ok && item.kind == .Struct && item.c_layout
|
||||||
}
|
}
|
||||||
|
|
||||||
pointer :: proc(store: ^Store, child: Type, mutable, many: bool) -> Type {
|
pointer :: proc(
|
||||||
return intern(store, Node{kind=.Pointer, child=child, mutable=mutable, many=many})
|
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 {
|
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 {
|
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)
|
from_node, from_ok := node(store, from)
|
||||||
to_node, to_ok := node(store, to)
|
to_node, to_ok := node(store, to)
|
||||||
return from_ok && to_ok &&
|
return from_ok && to_ok &&
|
||||||
from_node.kind == .Pointer && to_node.kind == .Pointer &&
|
from_node.kind == .Slice && to_node.kind == .Slice &&
|
||||||
from_node.child == to_node.child && from_node.many == to_node.many &&
|
from_node.child == to_node.child &&
|
||||||
from_node.mutable && !to_node.mutable
|
(!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 {
|
is_opaque_struct :: proc(value: Type, store: ^Store) -> bool {
|
||||||
|
|||||||
+201
-1
@@ -174,6 +174,58 @@ main :: func() void {}
|
|||||||
testing.expect_value(t, len(module.functions[0].params), 2)
|
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)
|
@(test)
|
||||||
parser_accepts_single_statement_one_line_functions :: proc(t: ^testing.T) {
|
parser_accepts_single_statement_one_line_functions :: proc(t: ^testing.T) {
|
||||||
text := `give :: func() i8 { return 7 }
|
text := `give :: func() i8 { return 7 }
|
||||||
@@ -742,7 +794,7 @@ main :: func() void {
|
|||||||
maybe ?i32 = 5
|
maybe ?i32 = 5
|
||||||
_ = c
|
_ = c
|
||||||
_ = values[2]
|
_ = values[2]
|
||||||
_ = values.ptr + 1
|
_ = (&values).ptr + 1
|
||||||
_ = values.len
|
_ = values.len
|
||||||
_ = values[0..2]
|
_ = values[0..2]
|
||||||
_ = "hello".ptr
|
_ = "hello".ptr
|
||||||
@@ -784,6 +836,134 @@ main :: func() void {
|
|||||||
testing.expect(t, strings.contains(llvm_text, "orelse_some"))
|
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)
|
@(test)
|
||||||
c_variadic_calls_promote_extras_and_emit_variadic_llvm :: proc(t: ^testing.T) {
|
c_variadic_calls_promote_extras_and_emit_variadic_llvm :: proc(t: ^testing.T) {
|
||||||
text := `variadic :: c_func(tag c_int, ...) c_int
|
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)
|
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)
|
@(test)
|
||||||
foreign_function_links_from_c_source :: proc(t: ^testing.T) {
|
foreign_function_links_from_c_source :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-foreign-source"
|
output := "/tmp/brolang-test-foreign-source"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ main :: func() void {
|
|||||||
_ = native.imported_scalar(3, 4)
|
_ = native.imported_scalar(3, 4)
|
||||||
_ = native.child_value(7)
|
_ = native.child_value(7)
|
||||||
_ = native.imported_read(native.imported_handle()?)
|
_ = native.imported_read(native.imported_handle()?)
|
||||||
|
_ = native.imported_string("hello")
|
||||||
_ = native.configured_value(9)
|
_ = native.configured_value(9)
|
||||||
signed i8 :: -2
|
signed i8 :: -2
|
||||||
unsigned u16 :: 3
|
unsigned u16 :: 3
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ int imported_add(imported_int_alias left, int right);
|
|||||||
unsigned long imported_scalar(unsigned char value, unsigned long extra);
|
unsigned long imported_scalar(unsigned char value, unsigned long extra);
|
||||||
Imported_Handle *imported_handle(void);
|
Imported_Handle *imported_handle(void);
|
||||||
int imported_read(const Imported_Handle *handle);
|
int imported_read(const Imported_Handle *handle);
|
||||||
|
int imported_string(const char *value);
|
||||||
Imported_Value imported_by_value(Imported_Value value);
|
Imported_Value imported_by_value(Imported_Value value);
|
||||||
int imported_volatile(volatile int *value);
|
int imported_volatile(volatile int *value);
|
||||||
_Bool imported_bool(_Bool value);
|
_Bool imported_bool(_Bool value);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "include/native.h"
|
#include "include/native.h"
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
struct Imported_Handle {
|
struct Imported_Handle {
|
||||||
int value;
|
int value;
|
||||||
@@ -28,6 +29,13 @@ int imported_read(const Imported_Handle *value) {
|
|||||||
return value->value;
|
return value->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int imported_string(const char *value) {
|
||||||
|
if (strcmp(value, "hello") != 0) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int imported_variadic(int marker, ...) {
|
int imported_variadic(int marker, ...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, marker);
|
va_start(args, marker);
|
||||||
|
|||||||
@@ -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]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user