Compare commits
4 Commits
7f7de7a8d2
...
8ebbf72388
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ebbf72388 | |||
| eddca5c3bf | |||
| 8ab5b71a0c | |||
| 7cb4ace0fa |
+27
-4
@@ -34,7 +34,7 @@ roadmap and milestone history.
|
|||||||
- compile-time `minval!(T)` and `maxval!(T)` bounds for concrete native, C, and scalar-backed distinct integer types; the result retains `T`
|
- compile-time `minval!(T)` and `maxval!(T)` bounds for concrete native, C, and scalar-backed distinct integer types; the result retains `T`
|
||||||
- arrays `[N]T`, inferred-count arrays `[_]T`, sentinel arrays `[N;S]T`, compile-time expression array counts, slices `[]T` / `[;S]T`, single-item pointers `@T`, many-item pointers `*T`, and sentinel many-item pointers `[*;S]T`
|
- arrays `[N]T`, inferred-count arrays `[_]T`, sentinel arrays `[N;S]T`, compile-time expression array counts, slices `[]T` / `[;S]T`, single-item pointers `@T`, many-item pointers `*T`, and sentinel many-item pointers `[*;S]T`
|
||||||
- pointer mutability via `mut`, optional pointers as nullable pointers, pointer arithmetic for many-item pointers, postfix dereference `^`, and trapping optional unwrap `?`
|
- pointer mutability via `mut`, optional pointers as nullable pointers, pointer arithmetic for many-item pointers, postfix dereference `^`, and trapping optional unwrap `?`
|
||||||
- pointer-to-array `.len`, indexing, slicing, `.ptr` on slices and pointers-to-arrays, implicit address-taking for array-variable slices, and pointer/slice sentinel weakening
|
- pointer-to-array `.len`, indexing, slicing, `.ptr` on slices and pointers-to-arrays, implicit address-taking for array-variable slices, pointer/slice sentinel weakening, and checked explicit sentinel slicing `base[start..end;S]`
|
||||||
- `ptrcast!(ptr)` infers its complete pointer result from context; `ptrcast!(P, ptr)` supplies `P` explicitly; both change the child type, then permit only ordinary implicit pointer conversions to the requested target
|
- `ptrcast!(ptr)` infers its complete pointer result from context; `ptrcast!(P, ptr)` supplies `P` explicitly; both change the child type, then permit only ordinary implicit pointer conversions to the requested target
|
||||||
- unsafe `constcast!(value)` for restoring mutability to pointers, optional pointers, and slices without changing their child type or shape
|
- unsafe `constcast!(value)` for restoring mutability to pointers, optional pointers, and slices without changing their child type or shape
|
||||||
- UTF-8 string literals as immutable pointers to static zero-terminated byte arrays, supporting `\\`, `\"`, `\n`, `\r`, `\t`, `\0`, and `\xNN` escapes, plus raw backtick multiline strings
|
- UTF-8 string literals as immutable pointers to static zero-terminated byte arrays, supporting `\\`, `\"`, `\n`, `\r`, `\t`, `\0`, and `\xNN` escapes, plus raw backtick multiline strings
|
||||||
@@ -44,10 +44,32 @@ roadmap and milestone history.
|
|||||||
- compiler-reordered native structs with fields laid out by decreasing alignment (declaration order breaks ties and remains the reflection/diagnostic order), opaque nominal records with `Name :: opaque`, complete source-order `c_struct { ... }`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)`
|
- compiler-reordered native structs with fields laid out by decreasing alignment (declaration order breaks ties and remains the reflection/diagnostic order), opaque nominal records with `Name :: opaque`, complete source-order `c_struct { ... }`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)`
|
||||||
- named native struct fields may declare defaults with `field T = expression`; keyed literals use defaults for omitted fields and explicit initializers override them
|
- named native struct fields may declare defaults with `field T = expression`; keyed literals use defaults for omitted fields and explicit initializers override them
|
||||||
- void-payload tagged-union variants, anonymous struct payloads, contextual `.variant`, `.variant{payload}`, and `.variant{field = value}` construction
|
- void-payload tagged-union variants, anonymous struct payloads, contextual `.variant`, `.variant{payload}`, and `.variant{field = value}` construction
|
||||||
- native sum composition with `A | B` for unbacked enums and tagged unions, optionally grouped as `(A | B)`, using program-global `u16` variant ids
|
- native sum composition with `A | B` for unbacked enums and tagged unions, optionally grouped as `(A | B)`, using program-global `u16` variant ids; parenthesized types allow newlines after `(`, before `)`, and on either side of `|`, including in aliases and fallible signatures
|
||||||
- fallible channel types `T ! E`, where `E` is a native enum, native struct, tagged union, or supported sum composition; `void ! E` functions complete successfully on fallthrough, and void-success `catch` handlers may fall through without `yield`
|
- fallible channel types `T ! E`, where `E` is a native enum, native struct, tagged union, or supported sum composition; `void ! E` functions complete successfully on fallthrough, and void-success `catch` handlers may fall through without `yield`
|
||||||
- bodyful local functions and root `main` may write `T!` to infer a specialization-local error channel from propagated `try` expressions and concretely typed error returns; inference composes only existing named error types, never synthesizes variants, and requires at least one inferred error
|
- bodyful local functions and root `main` may write `T!` to infer a specialization-local error channel from propagated `try` expressions and concretely typed error returns; inference composes only existing named error types, never synthesizes variants, and requires at least one inferred error
|
||||||
|
|
||||||
|
#### checked sentinel slicing
|
||||||
|
|
||||||
|
`base[start..end;S]` constructs a sentinel slice `[;S]T` without copying elements.
|
||||||
|
The sentinel `S` must be a compile-time integer representable by the source element type `T`;
|
||||||
|
the result retains the source's element mutability. Bounds use `usize`, as in ordinary slicing.
|
||||||
|
`start` defaults to zero; `end` may be omitted for arrays, pointers-to-arrays, and slices, where
|
||||||
|
it defaults to the logical length. Many-item pointers, including sentinel pointers, always
|
||||||
|
require an explicit end: `pointer[..count;0]`.
|
||||||
|
|
||||||
|
The result contains elements `[start, end)` and excludes the sentinel at `end`.
|
||||||
|
Before constructing it, runtime checks require ordered in-range bounds, a readable endpoint,
|
||||||
|
and `base[end] == S`; failure traps. For a bounded source without a sentinel, `end` must be
|
||||||
|
strictly less than its length. A sentinel source permits `end == len`, reading its stored
|
||||||
|
sentinel. A many-item pointer has no length to check: the caller must provide valid memory
|
||||||
|
through the endpoint, and the endpoint value is still checked. Comptime evaluation enforces
|
||||||
|
the same endpoint and sentinel requirements and diagnoses failures.
|
||||||
|
|
||||||
|
Ordinary `base[start..end]` slicing keeps its existing bounds rules and preserves a source
|
||||||
|
sentinel only when the end bound is omitted. Explicit sentinel slicing can assert a different
|
||||||
|
sentinel only when the actual endpoint matches it; it never adds a terminator or restores
|
||||||
|
mutability.
|
||||||
|
|
||||||
#### distinct types
|
#### distinct types
|
||||||
|
|
||||||
`Name :: distinct T` creates a nominal identity and reuses `T`'s runtime representation. When
|
`Name :: distinct T` creates a nominal identity and reuses `T`'s runtime representation. When
|
||||||
@@ -266,7 +288,8 @@ exactly once. Bare functions named `memcopy` or `memset` remain ordinary user fu
|
|||||||
- `std/arraylist` generic `ArrayList(T)` with direct `items` slice access, explicit capacity, allocator ownership, fallible reserve/append, clear, and deinit
|
- `std/arraylist` generic `ArrayList(T)` with direct `items` slice access, explicit capacity, allocator ownership, fallible reserve/append, clear, and deinit
|
||||||
- `std/meta` reflection records plus `EnumFieldStruct(E, Field, default ?Field)`, implemented with `struct_type!`; it produces a record with one field per native enum member in declaration order, where outer `null` means no field default
|
- `std/meta` reflection records plus `EnumFieldStruct(E, Field, default ?Field)`, implemented with `struct_type!`; it produces a record with one field per native enum member in declaration order, where outer `null` means no field default
|
||||||
- `std/io` explicit `Io` capabilities, provider-bound `Reader`/`Writer` handles, existing-file open/close operations, allocation-free `write_all`, and comptime-expanded writer-first `print`; formatting supports natural `{}`, byte `{s}`, decimal `{d}`, integer `{b}` / `{o}` / `{x}` / `{X}`, byte-character `{c}`, scientific float `{e}`, recursively scalar-backed distinct values, and `{{` / `}}`, with malformed formats and incompatible tuple fields rejected at comptime
|
- `std/io` explicit `Io` capabilities, provider-bound `Reader`/`Writer` handles, existing-file open/close operations, allocation-free `write_all`, and comptime-expanded writer-first `print`; formatting supports natural `{}`, byte `{s}`, decimal `{d}`, integer `{b}` / `{o}` / `{x}` / `{X}`, byte-character `{c}`, scientific float `{e}`, recursively scalar-backed distinct values, and `{{` / `}}`, with malformed formats and incompatible tuple fields rejected at comptime
|
||||||
- entry points are either `main func() ...` or `main func(init process.Init) ...`; their success channel is `void`, `i32`, or `int` and may have an error channel; an unhandled entry error exits with status 1. `std/process.Init` carries startup capabilities, currently only `io`, while the system provider remains hidden inside `std/io`
|
- entry points are either `main func() ...` or `main func(init process.Init) ...`; their success channel is `void`, `i32`, or `int` and may have an error channel; an unhandled entry error exits with status 1. `std/process.Init` contains exactly `io io.Io` and `args Args`, while the system provider remains hidden inside `std/io`
|
||||||
|
- `std/process.Args` contains exactly `values [][;0]u8`; use `args.values.len` and direct indexing. Native startup scans each argument once into heap-owned slice descriptors while borrowing, not copying, the strings; index 0 is the executable name when present, and empty arguments remain empty slices. The descriptors and their borrowed views are valid until `main` returns; the native wrapper frees the descriptors on both success and error
|
||||||
- `std/debug.print` is an allocation-free, failure-ignoring stderr escape hatch independent of `process.Init`
|
- `std/debug.print` is an allocation-free, failure-ignoring stderr escape hatch independent of `process.Init`
|
||||||
- `std/testing` supplies fallible `expect`, expected-first `expect_equal`, and exact compile-time `expect_type`; direct calls through
|
- `std/testing` supplies fallible `expect`, expected-first `expect_equal`, and exact compile-time `expect_type`; direct calls through
|
||||||
an alias of exactly `@std/testing` receive compiler-injected source locations
|
an alias of exactly `@std/testing` receive compiler-injected source locations
|
||||||
@@ -277,7 +300,7 @@ exactly once. Bare functions named `memcopy` or `memset` remain ordinary user fu
|
|||||||
- lazy semantic checking of demanded function specializations
|
- lazy semantic checking of demanded function specializations
|
||||||
- static, eager runtime, mutable runtime, and deferred problematic globals with cycle diagnostics
|
- static, eager runtime, mutable runtime, and deferred problematic globals with cycle diagnostics
|
||||||
- demand-driven LLVM declarations for referenced foreign functions
|
- demand-driven LLVM declarations for referenced foreign functions
|
||||||
- root `main` may be parameterless or accept canonical `@std/process Init`; the generated C entry point obtains the hidden system I/O provider and constructs the init value
|
- root `main` may be parameterless or accept canonical `@std/process Init`; the generated C entry point accepts native `argc`/`argv`, obtains the hidden system I/O provider, and constructs the init value using computed record layout. Both `Init` and `Args` must have their canonical auto-layout record shapes. Descriptor allocation is size-checked, zero-count safe, and exits with status 2 on overflow or allocation failure; parameterless `main` does not prepare arguments
|
||||||
- `brolang test [root]` reuses `build.bro`, discovers only explicit test-import edges, skips the
|
- `brolang test [root]` reuses `build.bro`, discovers only explicit test-import edges, skips the
|
||||||
application entry point, and runs tests sequentially while continuing after assertion failures
|
application entry point, and runs tests sequentially while continuing after assertion failures
|
||||||
- replaceable dynamically loaded libclang C-import backend
|
- replaceable dynamically loaded libclang C-import backend
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ odin build . -out:build/brolang
|
|||||||
./build/prototype
|
./build/prototype
|
||||||
```
|
```
|
||||||
|
|
||||||
Programs may receive the system I/O capability explicitly. Standard-stream
|
Programs may receive system I/O and process arguments explicitly through
|
||||||
helpers bind the provider, handle, and callback; `main func() ...` remains valid.
|
`process.Init`; `main func() ...` remains valid. Standard-stream helpers bind the
|
||||||
|
provider, handle, and callback.
|
||||||
|
|
||||||
```bro
|
```bro
|
||||||
io :: import "@std/io"
|
io :: import "@std/io"
|
||||||
@@ -23,6 +24,16 @@ main func(init process.Init) void {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`Init` contains `io io.Io` and `args process.Args`. `Args.values` has type
|
||||||
|
`[][;0]u8`: use `init.args.values.len` for the count and
|
||||||
|
`init.args.values[index]` for a borrowed, zero-terminated argument slice.
|
||||||
|
Index 0 is the executable name when present, and empty arguments are empty
|
||||||
|
slices. Startup scans each native argument once and allocates only the slice
|
||||||
|
descriptors; argument strings are never copied. The native entry wrapper owns
|
||||||
|
the descriptors until `main` returns, freeing them on both success and error.
|
||||||
|
An empty native argument list is supported; allocation overflow or failure exits
|
||||||
|
with status 2. Parameterless `main` does not prepare unused arguments.
|
||||||
|
|
||||||
Bodyless `c_func` declarations bind exact external symbols and require concrete
|
Bodyless `c_func` declarations bind exact external symbols and require concrete
|
||||||
types. C primitives use atomic target-dependent names and remain semantically
|
types. C primitives use atomic target-dependent names and remain semantically
|
||||||
distinct from exact-width Brolang primitives:
|
distinct from exact-width Brolang primitives:
|
||||||
|
|||||||
@@ -400,6 +400,9 @@ block_reads_name :: proc(checker: ^Checker, statements: []ast.Stmt_Id, name: sym
|
|||||||
case .Call, .Array, .Struct_Literal, .Slice:
|
case .Call, .Array, .Struct_Literal, .Slice:
|
||||||
append(&expr_stack, ..expr.args)
|
append(&expr_stack, ..expr.args)
|
||||||
append(&expr_stack, expr.left)
|
append(&expr_stack, expr.left)
|
||||||
|
if expr.kind == .Slice {
|
||||||
|
append(&expr_stack, expr.right)
|
||||||
|
}
|
||||||
case .Negate, .Not, .Bit_Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast:
|
case .Negate, .Not, .Bit_Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast:
|
||||||
append(&expr_stack, expr.left)
|
append(&expr_stack, expr.left)
|
||||||
case .Comptime:
|
case .Comptime:
|
||||||
@@ -743,12 +746,18 @@ comptime_slice_bytes :: proc(
|
|||||||
pkg: ast.Package_Id,
|
pkg: ast.Package_Id,
|
||||||
file: ast.File_Id,
|
file: ast.File_Id,
|
||||||
) -> (string, bool) {
|
) -> (string, bool) {
|
||||||
|
if expr.right != ast.INVALID_EXPR {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
state := ct_state_make(checker, pkg, file, diagnose=false)
|
state := ct_state_make(checker, pkg, file, diagnose=false)
|
||||||
defer ct_state_destroy(&state)
|
defer ct_state_destroy(&state)
|
||||||
value, flow, ok := ct_eval_slice_expr(&state, expr, 0)
|
value, flow, ok := ct_eval_slice_expr(&state, expr, 0)
|
||||||
if !ok || flow.kind != .Normal {
|
if !ok || flow.kind != .Normal {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
if item, item_ok := types.container(state.values[value].type, &checker.module.types); item_ok && item.has_sentinel {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
return ct_value_bytes(&state, value)
|
return ct_value_bytes(&state, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1747,16 +1756,47 @@ configure_entry_point :: proc(checker: ^Checker) {
|
|||||||
if io_package == ast.INVALID_PACKAGE {
|
if io_package == ast.INVALID_PACKAGE {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
init_item, init_ok := types.node(&checker.module.types, parameter_type)
|
args_name := symbol.intern(checker.symbols, "Args")
|
||||||
init_fields := types.fields_for(&checker.module.types, parameter_type)
|
args_type := types.resolve_alias(
|
||||||
io_field_name := symbol.intern(checker.symbols, "io")
|
types.find_named(&checker.module.types, u32(process_package), u32(args_name)),
|
||||||
if !init_ok || init_item.kind != .Struct || init_item.tuple || init_item.c_layout ||
|
&checker.module.types,
|
||||||
len(init_fields) != 1 || init_fields[0].name != u32(io_field_name) ||
|
)
|
||||||
!types.equal(types.resolve_alias(init_fields[0].type, &checker.module.types), io_type) {
|
args_item, args_ok := types.node(&checker.module.types, args_type)
|
||||||
|
args_fields := types.fields_for(&checker.module.types, args_type)
|
||||||
|
values_name := symbol.intern(checker.symbols, "values")
|
||||||
|
argument_slice := types.slice(&checker.module.types, types.U8, false, true, 0)
|
||||||
|
argument_values := types.slice(&checker.module.types, argument_slice, false)
|
||||||
|
if !args_ok || args_item.kind != .Struct || args_item.tuple || args_item.c_layout ||
|
||||||
|
len(args_fields) != 1 || args_fields[0].name != u32(values_name) ||
|
||||||
|
!types.equal(types.resolve_alias(args_fields[0].type, &checker.module.types), argument_values) {
|
||||||
checker.template_diagnostics[main_template] = source.add(
|
checker.template_diagnostics[main_template] = source.add(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
main.span,
|
main.span,
|
||||||
"@std/process Init must be an auto-layout record containing exactly 'io io.Io'",
|
"@std/process Args must be an auto-layout record containing exactly 'values [][;0]u8'",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
init_item, init_ok := types.node(&checker.module.types, parameter_type)
|
||||||
|
init_fields := types.fields_for(&checker.module.types, parameter_type)
|
||||||
|
io_field_name := symbol.intern(checker.symbols, "io")
|
||||||
|
args_field_name := symbol.intern(checker.symbols, "args")
|
||||||
|
io_field_ok, args_field_ok := false, false
|
||||||
|
for field in init_fields {
|
||||||
|
field_type := types.resolve_alias(field.type, &checker.module.types)
|
||||||
|
if field.name == u32(io_field_name) && types.equal(field_type, io_type) {
|
||||||
|
io_field_ok = true
|
||||||
|
}
|
||||||
|
if field.name == u32(args_field_name) && types.equal(field_type, args_type) {
|
||||||
|
args_field_ok = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !init_ok || init_item.kind != .Struct || init_item.tuple || init_item.c_layout ||
|
||||||
|
len(init_fields) != 2 || !io_field_ok || !args_field_ok {
|
||||||
|
checker.template_diagnostics[main_template] = source.add(
|
||||||
|
checker.diagnostics,
|
||||||
|
main.span,
|
||||||
|
"@std/process Init must be an auto-layout record containing exactly 'io io.Io' and 'args Args'",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2753,6 +2793,15 @@ match_inferred_type_pattern :: proc(
|
|||||||
values, bound, span, diagnose, depth+1,
|
values, bound, span, diagnose, depth+1,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if actual_ok && actual_item.kind == .Slice {
|
||||||
|
if pattern_item.has_sentinel && (!actual_item.has_sentinel || pattern_item.sentinel != actual_item.sentinel) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return match_inferred_type_pattern(
|
||||||
|
checker, function, prefix, pattern_item.child, actual_item.child,
|
||||||
|
values, bound, span, diagnose, depth+1,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !actual_ok || pattern_item.kind != actual_item.kind {
|
if !actual_ok || pattern_item.kind != actual_item.kind {
|
||||||
resolved := type_from_syntax(checker, pattern, function.pkg, function.file)
|
resolved := type_from_syntax(checker, pattern, function.pkg, function.file)
|
||||||
@@ -3786,6 +3835,9 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
|
|||||||
if expr.left != ast.INVALID_EXPR {
|
if expr.left != ast.INVALID_EXPR {
|
||||||
append(&stack, expr.left)
|
append(&stack, expr.left)
|
||||||
}
|
}
|
||||||
|
if expr.kind == .Slice && expr.right != ast.INVALID_EXPR {
|
||||||
|
append(&stack, expr.right)
|
||||||
|
}
|
||||||
case .Negate, .Not, .Bit_Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast:
|
case .Negate, .Not, .Bit_Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast:
|
||||||
append(&stack, expr.left)
|
append(&stack, expr.left)
|
||||||
case .Comptime:
|
case .Comptime:
|
||||||
@@ -5168,6 +5220,15 @@ infer_compound_expr :: proc(
|
|||||||
_ = record_demand(checker, bound, types.USIZE, locals, local_types, pkg, file)
|
_ = record_demand(checker, bound, types.USIZE, locals, local_types, pkg, file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if expr.right != ast.INVALID_EXPR {
|
||||||
|
state := ct_state_make(checker, pkg, file, diagnose=false)
|
||||||
|
defer ct_state_destroy(&state)
|
||||||
|
sentinel, sentinel_ok := checked_slice_sentinel(&state, expr.right, item.child)
|
||||||
|
if !sentinel_ok {
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
return types.slice(store, item.child, item.mutable, true, u64(sentinel))
|
||||||
|
}
|
||||||
preserve := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
|
preserve := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
|
||||||
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:
|
||||||
@@ -8773,6 +8834,22 @@ build_compound_expr :: proc(
|
|||||||
id := source.add(checker.diagnostics, expr.span, "many-item pointer slicing requires an explicit end bound")
|
id := source.add(checker.diagnostics, expr.span, "many-item pointer slicing requires an explicit end bound")
|
||||||
return invalid_hir_expr(checker, expr.span, id)
|
return invalid_hir_expr(checker, expr.span, id)
|
||||||
}
|
}
|
||||||
|
sentinel_expr := hir.INVALID_EXPR
|
||||||
|
sentinel := item.sentinel
|
||||||
|
if expr.right != ast.INVALID_EXPR {
|
||||||
|
state := ct_state_make(checker, pkg, file)
|
||||||
|
defer ct_state_destroy(&state)
|
||||||
|
constant, sentinel_ok := checked_slice_sentinel(&state, expr.right, item.child)
|
||||||
|
if !sentinel_ok {
|
||||||
|
return invalid_hir_expr(checker, expr.span, state.diagnostic)
|
||||||
|
}
|
||||||
|
sentinel = u64(constant)
|
||||||
|
sentinel_expr = add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Integer, span=checker.ast_module.exprs[expr.right].span, type=item.child, integer=i64(sentinel),
|
||||||
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
bounds := make([]hir.Expr_Id, 2, checker.allocator)
|
bounds := make([]hir.Expr_Id, 2, checker.allocator)
|
||||||
bounds[0] = hir.INVALID_EXPR
|
bounds[0] = hir.INVALID_EXPR
|
||||||
bounds[1] = hir.INVALID_EXPR
|
bounds[1] = hir.INVALID_EXPR
|
||||||
@@ -8791,11 +8868,11 @@ build_compound_expr :: proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
preserve_sentinel := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
|
preserve_sentinel := expr.right != ast.INVALID_EXPR || item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
|
||||||
result_type := types.slice(store, item.child, item.mutable, preserve_sentinel, item.sentinel)
|
result_type := types.slice(store, item.child, item.mutable, preserve_sentinel, sentinel)
|
||||||
return add_hir_expr(checker, hir.Expr{
|
return add_hir_expr(checker, hir.Expr{
|
||||||
kind=.Slice, span=expr.span, type=result_type, args=bounds, left=container,
|
kind=.Slice, span=expr.span, type=result_type, args=bounds, left=container,
|
||||||
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
target=hir.INVALID_REF, right=sentinel_expr, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
case .Field:
|
case .Field:
|
||||||
if enum_type, enum_ok := enum_type_from_field_expr(checker, expr, pkg, file, true); enum_ok {
|
if enum_type, enum_ok := enum_type_from_field_expr(checker, expr, pkg, file, true); enum_ok {
|
||||||
|
|||||||
+110
-10
@@ -792,6 +792,17 @@ ct_place_get :: proc(state: ^Ct_State, place_id: Ct_Place_Id) -> (Ct_Value_Id, b
|
|||||||
#partial switch elem.kind {
|
#partial switch elem.kind {
|
||||||
case .Index:
|
case .Index:
|
||||||
index := int(elem.index)
|
index := int(elem.index)
|
||||||
|
if value.kind == .Array {
|
||||||
|
item, ok := types.node(&state.checker.module.types, value.type)
|
||||||
|
if ok && item.has_sentinel && u64(index) == item.count {
|
||||||
|
integer := i128(item.sentinel)
|
||||||
|
if types.is_signed(types.resolve_alias(item.child, &state.checker.module.types), state.checker.target) {
|
||||||
|
integer = i128(i64(item.sentinel))
|
||||||
|
}
|
||||||
|
current = ct_add_value(state, Ct_Value{kind=.Integer, type=item.child, integer=integer})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
if value.kind != .Array || index < 0 || index >= len(children) {
|
if value.kind != .Array || index < 0 || index >= len(children) {
|
||||||
return INVALID_CT_VALUE, false
|
return INVALID_CT_VALUE, false
|
||||||
}
|
}
|
||||||
@@ -963,7 +974,7 @@ ct_value_can_materialize :: proc(state: ^Ct_State, id: Ct_Value_Id, depth := 0)
|
|||||||
store := &state.checker.module.types
|
store := &state.checker.module.types
|
||||||
slice, slice_ok := types.node(store, value.type)
|
slice, slice_ok := types.node(store, value.type)
|
||||||
place_id := Ct_Place_Id(value.index)
|
place_id := Ct_Place_Id(value.index)
|
||||||
if !slice_ok || slice.kind != .Slice || slice.mutable || value.start != 0 ||
|
if !slice_ok || slice.kind != .Slice || slice.mutable ||
|
||||||
place_id == INVALID_CT_PLACE || int(place_id) >= len(state.places) {
|
place_id == INVALID_CT_PLACE || int(place_id) >= len(state.places) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -977,7 +988,7 @@ ct_value_can_materialize :: proc(state: ^Ct_State, id: Ct_Value_Id, depth := 0)
|
|||||||
}
|
}
|
||||||
root := state.values[root_id]
|
root := state.values[root_id]
|
||||||
array, array_ok := types.node(store, root.type)
|
array, array_ok := types.node(store, root.type)
|
||||||
return array_ok && array.kind == .Array && u64(value.count) == array.count &&
|
return array_ok && array.kind == .Array && u64(value.start)+u64(value.count) <= array.count &&
|
||||||
types.equal(slice.child, array.child) && ct_value_can_materialize(state, root_id, depth+1)
|
types.equal(slice.child, array.child) && ct_value_can_materialize(state, root_id, depth+1)
|
||||||
case .Range, .Array, .Struct, .Optional_Some, .Fallible:
|
case .Range, .Array, .Struct, .Optional_Some, .Fallible:
|
||||||
for child in ct_child_slice(state, value) {
|
for child in ct_child_slice(state, value) {
|
||||||
@@ -1065,7 +1076,7 @@ ct_materialize_array_slice :: proc(
|
|||||||
store := &state.checker.module.types
|
store := &state.checker.module.types
|
||||||
slice, slice_ok := types.node(store, value.type)
|
slice, slice_ok := types.node(store, value.type)
|
||||||
place_id := Ct_Place_Id(value.index)
|
place_id := Ct_Place_Id(value.index)
|
||||||
if !slice_ok || slice.kind != .Slice || slice.mutable || value.start != 0 ||
|
if !slice_ok || slice.kind != .Slice || slice.mutable ||
|
||||||
place_id == INVALID_CT_PLACE || int(place_id) >= len(state.places) {
|
place_id == INVALID_CT_PLACE || int(place_id) >= len(state.places) {
|
||||||
return hir.INVALID_EXPR, false
|
return hir.INVALID_EXPR, false
|
||||||
}
|
}
|
||||||
@@ -1079,7 +1090,7 @@ ct_materialize_array_slice :: proc(
|
|||||||
}
|
}
|
||||||
root := state.values[root_id]
|
root := state.values[root_id]
|
||||||
array, array_ok := types.node(store, root.type)
|
array, array_ok := types.node(store, root.type)
|
||||||
if !array_ok || array.kind != .Array || u64(value.count) != array.count ||
|
if !array_ok || array.kind != .Array || u64(value.start)+u64(value.count) > array.count ||
|
||||||
!types.equal(slice.child, array.child) {
|
!types.equal(slice.child, array.child) {
|
||||||
return hir.INVALID_EXPR, false
|
return hir.INVALID_EXPR, false
|
||||||
}
|
}
|
||||||
@@ -1093,7 +1104,28 @@ ct_materialize_array_slice :: proc(
|
|||||||
if !ok {
|
if !ok {
|
||||||
return hir.INVALID_EXPR, false
|
return hir.INVALID_EXPR, false
|
||||||
}
|
}
|
||||||
return coerce_expr(state.checker, address, value.type, span), true
|
checker := state.checker
|
||||||
|
bounds := make([]hir.Expr_Id, 2, checker.allocator)
|
||||||
|
endpoints := [2]u32{value.start, value.start+value.count}
|
||||||
|
for integer, index in endpoints {
|
||||||
|
bounds[index] = add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Integer, span=span, type=types.USIZE, integer=i64(integer),
|
||||||
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
sentinel_expr := hir.INVALID_EXPR
|
||||||
|
if slice.has_sentinel {
|
||||||
|
sentinel_expr = add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Integer, span=span, type=slice.child, integer=i64(slice.sentinel),
|
||||||
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Slice, span=span, type=value.type, args=bounds, left=address, right=sentinel_expr,
|
||||||
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
}), true
|
||||||
}
|
}
|
||||||
|
|
||||||
ct_materialize_value :: proc(
|
ct_materialize_value :: proc(
|
||||||
@@ -1223,7 +1255,7 @@ ct_materialize_value :: proc(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
if state.diagnostic == source.INVALID_DIAGNOSTIC {
|
if state.diagnostic == source.INVALID_DIAGNOSTIC {
|
||||||
state.diagnostic = source.add(checker.diagnostics, span, "only immutable full-array comptime slices can materialize as runtime memory")
|
state.diagnostic = source.add(checker.diagnostics, span, "only immutable array-backed comptime slices can materialize as runtime memory")
|
||||||
}
|
}
|
||||||
return invalid_hir_expr(checker, span, state.diagnostic, value.type)
|
return invalid_hir_expr(checker, span, state.diagnostic, value.type)
|
||||||
case .Function:
|
case .Function:
|
||||||
@@ -1948,6 +1980,22 @@ ct_eval_struct_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Ty
|
|||||||
return ct_add_value(state, Ct_Value{kind=.Struct, type=struct_type, start=start, count=u32(len(values)), active=active_field}), ct_flow(.Normal), true
|
return ct_add_value(state, Ct_Value{kind=.Struct, type=struct_type, start=start, count=u32(len(values)), active=active_field}), ct_flow(.Normal), true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checked_slice_sentinel :: proc(state: ^Ct_State, id: ast.Expr_Id, child: types.Type) -> (i128, bool) {
|
||||||
|
checker := state.checker
|
||||||
|
span := checker.ast_module.exprs[id].span
|
||||||
|
previous_error := state.error
|
||||||
|
constant := eval_integer_constant_in_state(state, id, allow_bool=false)
|
||||||
|
state.error = previous_error
|
||||||
|
if constant.kind != .Value {
|
||||||
|
return 0, ct_fail(state, .Not_Comptime, span, "slice sentinel must be a compile-time integer")
|
||||||
|
}
|
||||||
|
representation := types.resolve_alias(child, &checker.module.types)
|
||||||
|
if !types.is_concrete_integer(representation) || !fits_integer_type(constant.value, representation, checker.target) {
|
||||||
|
return 0, ct_failf(state, .Not_Comptime, span, "slice sentinel value does not fit element type %s", type_label(checker, child))
|
||||||
|
}
|
||||||
|
return constant.value, true
|
||||||
|
}
|
||||||
|
|
||||||
ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
|
ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
|
||||||
checker := state.checker
|
checker := state.checker
|
||||||
store := &checker.module.types
|
store := &checker.module.types
|
||||||
@@ -1958,6 +2006,7 @@ ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_
|
|||||||
container_place := INVALID_CT_PLACE
|
container_place := INVALID_CT_PLACE
|
||||||
container_start := 0
|
container_start := 0
|
||||||
container_len := 0
|
container_len := 0
|
||||||
|
bounded := true
|
||||||
item: types.Node
|
item: types.Node
|
||||||
item_ok := false
|
item_ok := false
|
||||||
if base_value != INVALID_CT_VALUE && int(base_value) < len(state.values) {
|
if base_value != INVALID_CT_VALUE && int(base_value) < len(state.values) {
|
||||||
@@ -1967,6 +2016,25 @@ ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_
|
|||||||
container_start = int(base.start)
|
container_start = int(base.start)
|
||||||
container_len = int(base.count)
|
container_len = int(base.count)
|
||||||
item, item_ok = types.node(store, base.type)
|
item, item_ok = types.node(store, base.type)
|
||||||
|
} else if base.kind == .Array && checker.ast_module.exprs[expr.left].kind == .Array && expr.right != ast.INVALID_EXPR {
|
||||||
|
item, item_ok = types.node(store, base.type)
|
||||||
|
container_place = ct_add_place(state, ct_add_cell(state, base_value, false), base.type, false)
|
||||||
|
container_len = int(base.count)
|
||||||
|
item.mutable = false
|
||||||
|
} else if base.kind == .String && expr.right != ast.INVALID_EXPR && base.index < u64(len(checker.ast_module.strings)) {
|
||||||
|
text := checker.ast_module.strings[base.index]
|
||||||
|
array_type := types.array(store, types.U8, u64(len(text)), false, true, 0)
|
||||||
|
children := make([]Ct_Value_Id, len(text), checker.allocator)
|
||||||
|
defer delete(children, checker.allocator)
|
||||||
|
for byte, index in transmute([]u8)text {
|
||||||
|
children[index] = ct_add_value(state, Ct_Value{kind=.Integer, type=types.U8, integer=i128(byte)})
|
||||||
|
}
|
||||||
|
child_start := u32(len(state.children))
|
||||||
|
append(&state.children, ..children)
|
||||||
|
array_value := ct_add_value(state, Ct_Value{kind=.Array, type=array_type, start=child_start, count=u32(len(text))})
|
||||||
|
container_place = ct_add_place(state, ct_add_cell(state, array_value, false), array_type, false)
|
||||||
|
container_len = len(text)
|
||||||
|
item, item_ok = types.container(base.type, store)
|
||||||
} else if base.kind == .Pointer {
|
} else if base.kind == .Pointer {
|
||||||
pointer_item, pointer_ok := types.node(store, base.type)
|
pointer_item, pointer_ok := types.node(store, base.type)
|
||||||
array_item: types.Node
|
array_item: types.Node
|
||||||
@@ -1980,6 +2048,15 @@ ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_
|
|||||||
item = array_item
|
item = array_item
|
||||||
item.mutable = pointer_item.mutable && array_item.mutable
|
item.mutable = pointer_item.mutable && array_item.mutable
|
||||||
item_ok = true
|
item_ok = true
|
||||||
|
} else if pointer_ok && pointer_item.many {
|
||||||
|
if expr.args[1] == ast.INVALID_EXPR {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "many-item pointer slicing requires an explicit end bound")
|
||||||
|
}
|
||||||
|
container_place = Ct_Place_Id(base.index)
|
||||||
|
container_start = int(base.active)
|
||||||
|
item = pointer_item
|
||||||
|
item_ok = true
|
||||||
|
bounded = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2031,11 +2108,31 @@ ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_
|
|||||||
end = int(integer)
|
end = int(integer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if start < 0 || end < start || end > container_len {
|
if start < 0 || end < start || bounded && end > container_len || container_start < 0 {
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime slice bounds out of range")
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime slice bounds out of range")
|
||||||
}
|
}
|
||||||
preserve_sentinel := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
|
sentinel := item.sentinel
|
||||||
result_type := types.slice(store, item.child, item.mutable, preserve_sentinel, item.sentinel)
|
if expr.right != ast.INVALID_EXPR {
|
||||||
|
constant, sentinel_ok := checked_slice_sentinel(state, expr.right, item.child)
|
||||||
|
if !sentinel_ok {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||||
|
}
|
||||||
|
if bounded && (end == container_len && !item.has_sentinel) {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime sentinel slice endpoint out of range")
|
||||||
|
}
|
||||||
|
endpoint := ct_extend_place(state, container_place, Ct_Path_Elem{kind=.Index, index=u32(container_start+end)}, item.child, false)
|
||||||
|
value, value_ok := ct_place_get(state, endpoint)
|
||||||
|
if !value_ok {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime sentinel slice endpoint out of range")
|
||||||
|
}
|
||||||
|
actual, actual_ok := ct_integer_value(state, value)
|
||||||
|
if !actual_ok || actual != constant {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime slice sentinel mismatch")
|
||||||
|
}
|
||||||
|
sentinel = u64(constant)
|
||||||
|
}
|
||||||
|
preserve_sentinel := expr.right != ast.INVALID_EXPR || item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
|
||||||
|
result_type := types.slice(store, item.child, item.mutable, preserve_sentinel, sentinel)
|
||||||
return ct_add_value(state, Ct_Value{
|
return ct_add_value(state, Ct_Value{
|
||||||
kind=.Slice, type=result_type, index=u64(container_place),
|
kind=.Slice, type=result_type, index=u64(container_place),
|
||||||
start=u32(container_start+start), count=u32(end-start),
|
start=u32(container_start+start), count=u32(end-start),
|
||||||
@@ -5973,7 +6070,7 @@ eval_integer_constant_in_context :: proc(
|
|||||||
return eval_integer_constant_in_state(&state, expr_id, depth)
|
return eval_integer_constant_in_state(&state, expr_id, depth)
|
||||||
}
|
}
|
||||||
|
|
||||||
eval_integer_constant_in_state :: proc(state: ^Ct_State, expr_id: ast.Expr_Id, depth := 0) -> Constant {
|
eval_integer_constant_in_state :: proc(state: ^Ct_State, expr_id: ast.Expr_Id, depth := 0, allow_bool := true) -> Constant {
|
||||||
if depth > 64 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(state.checker.ast_module.exprs) {
|
if depth > 64 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(state.checker.ast_module.exprs) {
|
||||||
return Constant{kind=.Not_Constant}
|
return Constant{kind=.Not_Constant}
|
||||||
}
|
}
|
||||||
@@ -5994,6 +6091,9 @@ eval_integer_constant_in_state :: proc(state: ^Ct_State, expr_id: ast.Expr_Id, d
|
|||||||
}
|
}
|
||||||
return Constant{kind=.Not_Constant}
|
return Constant{kind=.Not_Constant}
|
||||||
}
|
}
|
||||||
|
if !allow_bool && state.values[value].kind != .Integer {
|
||||||
|
return Constant{kind=.Not_Constant}
|
||||||
|
}
|
||||||
integer, integer_ok := ct_integer_value(state, value)
|
integer, integer_ok := ct_integer_value(state, value)
|
||||||
if !integer_ok {
|
if !integer_ok {
|
||||||
return Constant{kind=.Not_Constant}
|
return Constant{kind=.Not_Constant}
|
||||||
|
|||||||
@@ -86,6 +86,9 @@ Opcode :: enum u8 {
|
|||||||
Store,
|
Store,
|
||||||
Fill,
|
Fill,
|
||||||
Slice,
|
Slice,
|
||||||
|
// Private native-entry operations: own descriptors, borrow argument strings.
|
||||||
|
Prepare_Args,
|
||||||
|
Free_Args,
|
||||||
Length,
|
Length,
|
||||||
Slice_Ptr,
|
Slice_Ptr,
|
||||||
Extract,
|
Extract,
|
||||||
|
|||||||
+97
-3
@@ -20,6 +20,7 @@ Emitter :: struct {
|
|||||||
builder: strings.Builder,
|
builder: strings.Builder,
|
||||||
messages: [dynamic]Trap_Message,
|
messages: [dynamic]Trap_Message,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
|
prepares_args: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
C_Record_ABI_Kind :: enum u8 {
|
C_Record_ABI_Kind :: enum u8 {
|
||||||
@@ -284,7 +285,7 @@ valid_value :: proc(
|
|||||||
}
|
}
|
||||||
switch instructions[value_id].op {
|
switch instructions[value_id].op {
|
||||||
case .Param, .Const, .Poison, .String, .Aggregate, .Null, .Optional_Some,
|
case .Param, .Const, .Poison, .String, .Aggregate, .Null, .Optional_Some,
|
||||||
.Load_Global, .Function_Address, .Address_Of, .Load, .Union_Tag, .Slice, .Length, .Slice_Ptr,
|
.Load_Global, .Function_Address, .Address_Of, .Load, .Union_Tag, .Slice, .Prepare_Args, .Length, .Slice_Ptr,
|
||||||
.Fallible_Error, .Extract, .Select, .Unwrap,
|
.Fallible_Error, .Extract, .Select, .Unwrap,
|
||||||
.Optional_Is_Some, .Optional_Value, .Orelse,
|
.Optional_Is_Some, .Optional_Value, .Orelse,
|
||||||
.Widen, .Sum_Widen, .Sum_Project, .C_Coerce, .C_Vararg_Promote, .Retype, .Scalar_Cast, .Pointer_Cast, .Const_Cast, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
|
.Widen, .Sum_Widen, .Sum_Project, .C_Coerce, .C_Vararg_Promote, .Retype, .Scalar_Cast, .Pointer_Cast, .Const_Cast, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
|
||||||
@@ -295,7 +296,7 @@ valid_value :: proc(
|
|||||||
.Shift_Left, .Shift_Right, .Shift_Left_Saturating, .Compare, .Call:
|
.Shift_Left, .Shift_Right, .Shift_Left_Saturating, .Compare, .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, .Fill, .Mem_Copy, .Mem_Set, .Trap, .Label, .Br, .Cond_Br, .Return, .Return_Void:
|
.Store, .Fill, .Free_Args, .Mem_Copy, .Mem_Set, .Trap, .Label, .Br, .Cond_Br, .Return, .Return_Void:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -1545,6 +1546,16 @@ emit_instruction_stream :: proc(
|
|||||||
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
||||||
fmt.sbprintf(&emitter.builder, ", ptr %%memset_element%d\n", instruction_index)
|
fmt.sbprintf(&emitter.builder, ", ptr %%memset_element%d\n", instruction_index)
|
||||||
fmt.sbprintf(&emitter.builder, " %%memset_next%d = add i64 %%memset_index%d, 1\n store i64 %%memset_next%d, ptr %%memset_index_slot%d\n br label %%memset_loop%d\nmemset_done%d:\n", instruction_index, instruction_index, instruction_index, instruction_index, instruction_index, instruction_index)
|
fmt.sbprintf(&emitter.builder, " %%memset_next%d = add i64 %%memset_index%d, 1\n store i64 %%memset_next%d, ptr %%memset_index_slot%d\n br label %%memset_loop%d\nmemset_done%d:\n", instruction_index, instruction_index, instruction_index, instruction_index, instruction_index, instruction_index)
|
||||||
|
case .Prepare_Args:
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = call %s @bro.prepare_args(i32 ", instruction_index, llvm_type(instruction.type, &emitter.module.types))
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a, types.C_INT, &emitter.module.types)
|
||||||
|
strings.write_string(&emitter.builder, ", ptr ")
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.b, instructions[instruction.b].type, &emitter.module.types)
|
||||||
|
strings.write_string(&emitter.builder, ")\n")
|
||||||
|
case .Free_Args:
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%args_data%d = extractvalue %s ", instruction_index, llvm_type(instructions[instruction.a].type, &emitter.module.types))
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a, instructions[instruction.a].type, &emitter.module.types)
|
||||||
|
fmt.sbprintf(&emitter.builder, ", 0\n call void @free(ptr %%args_data%d)\n", instruction_index)
|
||||||
case .Slice:
|
case .Slice:
|
||||||
if !valid_instruction(instructions, instruction.a) {
|
if !valid_instruction(instructions, instruction.a) {
|
||||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container")
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container")
|
||||||
@@ -1556,6 +1567,11 @@ emit_instruction_stream :: proc(
|
|||||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container")
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
check_sentinel := instruction.b != ir.INVALID_INSTRUCTION
|
||||||
|
if check_sentinel && !valid_value(instructions, instruction.b, item.child, &emitter.module.types) {
|
||||||
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice sentinel")
|
||||||
|
continue
|
||||||
|
}
|
||||||
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 {
|
||||||
@@ -1597,13 +1613,25 @@ emit_instruction_stream :: proc(
|
|||||||
if item.kind == .Pointer {
|
if item.kind == .Pointer {
|
||||||
fmt.sbprintf(&emitter.builder, " %%slice_ok%d = or i1 false, %%slice_order%d\n", instruction_index, instruction_index)
|
fmt.sbprintf(&emitter.builder, " %%slice_ok%d = or i1 false, %%slice_order%d\n", instruction_index, instruction_index)
|
||||||
} else {
|
} else {
|
||||||
fmt.sbprintf(&emitter.builder, " %%slice_end_ok%d = icmp ule i64 %s, %s\n", instruction_index, end_name, length_name)
|
comparison := "ult" if check_sentinel && !item.has_sentinel else "ule"
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%slice_end_ok%d = icmp %s i64 %s, %s\n", instruction_index, comparison, end_name, length_name)
|
||||||
fmt.sbprintf(&emitter.builder, " %%slice_ok%d = and i1 %%slice_order%d, %%slice_end_ok%d\n", instruction_index, instruction_index, instruction_index)
|
fmt.sbprintf(&emitter.builder, " %%slice_ok%d = and i1 %%slice_order%d, %%slice_end_ok%d\n", instruction_index, instruction_index, instruction_index)
|
||||||
}
|
}
|
||||||
fmt.sbprintf(&emitter.builder, " br i1 %%slice_ok%d, label %%slice_continue%d, label %%slice_trap%d\nslice_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
|
fmt.sbprintf(&emitter.builder, " br i1 %%slice_ok%d, label %%slice_continue%d, label %%slice_trap%d\nslice_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
|
||||||
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "slice bounds out of range")
|
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "slice bounds out of range")
|
||||||
emit_trap_call(emitter, message)
|
emit_trap_call(emitter, message)
|
||||||
fmt.sbprintf(&emitter.builder, " unreachable\nslice_continue%d:\n", instruction_index)
|
fmt.sbprintf(&emitter.builder, " unreachable\nslice_continue%d:\n", instruction_index)
|
||||||
|
if check_sentinel {
|
||||||
|
element_type := llvm_type(item.child, &emitter.module.types)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%slice_sentinel_ptr%d = getelementptr %s, ptr %s, i64 %s\n", instruction_index, element_type, pointer_name, end_name)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%slice_sentinel_value%d = load %s, ptr %%slice_sentinel_ptr%d\n", instruction_index, element_type, instruction_index)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%slice_sentinel_ok%d = icmp eq %s %%slice_sentinel_value%d, ", instruction_index, element_type, instruction_index)
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.b, item.child, &emitter.module.types)
|
||||||
|
fmt.sbprintf(&emitter.builder, "\n br i1 %%slice_sentinel_ok%d, label %%slice_sentinel_continue%d, label %%slice_sentinel_trap%d\nslice_sentinel_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
|
||||||
|
sentinel_message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "slice sentinel mismatch")
|
||||||
|
emit_trap_call(emitter, sentinel_message)
|
||||||
|
fmt.sbprintf(&emitter.builder, " unreachable\nslice_sentinel_continue%d:\n", instruction_index)
|
||||||
|
}
|
||||||
fmt.sbprintf(&emitter.builder, " %%slice_start%d = getelementptr %s, ptr %s, i64 %s\n", instruction_index, llvm_type(item.child, &emitter.module.types), pointer_name, start_name)
|
fmt.sbprintf(&emitter.builder, " %%slice_start%d = getelementptr %s, ptr %s, i64 %s\n", instruction_index, llvm_type(item.child, &emitter.module.types), pointer_name, start_name)
|
||||||
fmt.sbprintf(&emitter.builder, " %%slice_result%d = insertvalue %s poison, ptr %%slice_start%d, 0\n", instruction_index, llvm_type(instruction.type, &emitter.module.types), instruction_index)
|
fmt.sbprintf(&emitter.builder, " %%slice_result%d = insertvalue %s poison, ptr %%slice_start%d, 0\n", instruction_index, llvm_type(instruction.type, &emitter.module.types), instruction_index)
|
||||||
fmt.sbprintf(&emitter.builder, " %%slice_result_len%d = sub i64 %s, %s\n", instruction_index, end_name, start_name)
|
fmt.sbprintf(&emitter.builder, " %%slice_result_len%d = sub i64 %s, %s\n", instruction_index, end_name, start_name)
|
||||||
@@ -2729,6 +2757,11 @@ emit_functions :: proc(emitter: ^Emitter) {
|
|||||||
if function.implementation == .Declaration && function.link_name == "write" {
|
if function.implementation == .Declaration && function.link_name == "write" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if emitter.prepares_args && function.implementation == .Declaration &&
|
||||||
|
(function.link_name == "malloc" || function.link_name == "free" ||
|
||||||
|
function.link_name == "strlen" || function.link_name == "exit") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if function.implementation == .Declaration {
|
if function.implementation == .Declaration {
|
||||||
duplicate := false
|
duplicate := false
|
||||||
for previous in emitter.module.functions[:function_index] {
|
for previous in emitter.module.functions[:function_index] {
|
||||||
@@ -2863,6 +2896,59 @@ emit_declarations :: proc(emitter: ^Emitter) {
|
|||||||
&emitter.builder,
|
&emitter.builder,
|
||||||
"\ndefine internal void @bro.trap(ptr %message, i64 %length) noreturn {\nentry:\n %written = call i64 @write(i32 2, ptr %message, i64 %length)\n call void @llvm.trap()\n unreachable\n}\n\n",
|
"\ndefine internal void @bro.trap(ptr %message, i64 %length) noreturn {\nentry:\n %written = call i64 @write(i32 2, ptr %message, i64 %length)\n call void @llvm.trap()\n unreachable\n}\n\n",
|
||||||
)
|
)
|
||||||
|
if emitter.prepares_args {
|
||||||
|
emit_argument_runtime(emitter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only native entry wrappers own these descriptors; argument bytes stay borrowed.
|
||||||
|
emit_argument_runtime :: proc(emitter: ^Emitter) {
|
||||||
|
strings.write_string(&emitter.builder, `declare ptr @malloc(i64)
|
||||||
|
declare void @free(ptr)
|
||||||
|
declare i64 @strlen(ptr)
|
||||||
|
declare void @exit(i32) noreturn
|
||||||
|
|
||||||
|
define internal { ptr, i64 } @bro.prepare_args(i32 %argc, ptr %argv) {
|
||||||
|
entry:
|
||||||
|
%negative = icmp slt i32 %argc, 0
|
||||||
|
br i1 %negative, label %failure, label %check_empty
|
||||||
|
check_empty:
|
||||||
|
%count = zext i32 %argc to i64
|
||||||
|
%empty = icmp eq i64 %count, 0
|
||||||
|
br i1 %empty, label %empty_result, label %check_size
|
||||||
|
check_size:
|
||||||
|
%allocation = call { i64, i1 } @llvm.umul.with.overflow.i64(i64 %count, i64 16)
|
||||||
|
%size = extractvalue { i64, i1 } %allocation, 0
|
||||||
|
%overflow = extractvalue { i64, i1 } %allocation, 1
|
||||||
|
br i1 %overflow, label %failure, label %allocate
|
||||||
|
allocate:
|
||||||
|
%data = call ptr @malloc(i64 %size)
|
||||||
|
%failed = icmp eq ptr %data, null
|
||||||
|
br i1 %failed, label %failure, label %loop
|
||||||
|
loop:
|
||||||
|
%index = phi i64 [ 0, %allocate ], [ %next, %loop ]
|
||||||
|
%native = getelementptr ptr, ptr %argv, i64 %index
|
||||||
|
%string = load ptr, ptr %native
|
||||||
|
%length = call i64 @strlen(ptr %string)
|
||||||
|
%descriptor = getelementptr { ptr, i64 }, ptr %data, i64 %index
|
||||||
|
%with_pointer = insertvalue { ptr, i64 } poison, ptr %string, 0
|
||||||
|
%argument = insertvalue { ptr, i64 } %with_pointer, i64 %length, 1
|
||||||
|
store { ptr, i64 } %argument, ptr %descriptor
|
||||||
|
%next = add i64 %index, 1
|
||||||
|
%more = icmp ult i64 %next, %count
|
||||||
|
br i1 %more, label %loop, label %done
|
||||||
|
done:
|
||||||
|
%result_pointer = insertvalue { ptr, i64 } poison, ptr %data, 0
|
||||||
|
%result = insertvalue { ptr, i64 } %result_pointer, i64 %count, 1
|
||||||
|
ret { ptr, i64 } %result
|
||||||
|
empty_result:
|
||||||
|
ret { ptr, i64 } zeroinitializer
|
||||||
|
failure:
|
||||||
|
call void @exit(i32 2)
|
||||||
|
unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
emit :: proc(
|
emit :: proc(
|
||||||
@@ -2878,6 +2964,14 @@ emit :: proc(
|
|||||||
builder=strings.builder_make(allocator),
|
builder=strings.builder_make(allocator),
|
||||||
allocator=allocator,
|
allocator=allocator,
|
||||||
}
|
}
|
||||||
|
for function in module.functions {
|
||||||
|
for instruction in function.instructions {
|
||||||
|
if instruction.op == .Prepare_Args {
|
||||||
|
emitter.prepares_args = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
emitter.messages.allocator = allocator
|
emitter.messages.allocator = allocator
|
||||||
defer {
|
defer {
|
||||||
for message in emitter.messages {
|
for message in emitter.messages {
|
||||||
|
|||||||
@@ -306,9 +306,13 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
|||||||
args[index] = lower_nested_expr(state, arg)
|
args[index] = lower_nested_expr(state, arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
checked_sentinel := ir.INVALID_INSTRUCTION
|
||||||
|
if expr.right != hir.INVALID_EXPR {
|
||||||
|
checked_sentinel = lower_nested_expr(state, expr.right)
|
||||||
|
}
|
||||||
return append_instruction(state, ir.Instruction{
|
return append_instruction(state, ir.Instruction{
|
||||||
op=.Slice, span=expr.span, type=expr.type, args=args,
|
op=.Slice, span=expr.span, type=expr.type, args=args,
|
||||||
target=ir.INVALID_REF, a=container, b=ir.INVALID_INSTRUCTION,
|
target=ir.INVALID_REF, a=container, b=checked_sentinel,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
case .Unwrap:
|
case .Unwrap:
|
||||||
@@ -1778,8 +1782,48 @@ append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, alloca
|
|||||||
|
|
||||||
instructions: [dynamic]ir.Instruction
|
instructions: [dynamic]ir.Instruction
|
||||||
instructions.allocator = allocator
|
instructions.allocator = allocator
|
||||||
|
param_types: []types.Type
|
||||||
args: []ir.Instruction_Id
|
args: []ir.Instruction_Id
|
||||||
|
prepared_args := ir.INVALID_INSTRUCTION
|
||||||
if param_index >= 0 {
|
if param_index >= 0 {
|
||||||
|
init_type := main_function.locals[param_index].type
|
||||||
|
init_fields := types.fields_for(&module.types, init_type)
|
||||||
|
io_index := 0
|
||||||
|
if !types.equal(
|
||||||
|
types.resolve_alias(init_fields[0].type, &module.types),
|
||||||
|
types.resolve_alias(hir_module.functions[provider_index].result, &module.types),
|
||||||
|
) {
|
||||||
|
io_index = 1
|
||||||
|
}
|
||||||
|
args_index := 1 - io_index
|
||||||
|
args_type := types.resolve_alias(init_fields[args_index].type, &module.types)
|
||||||
|
values_type := types.resolve_alias(types.fields_for(&module.types, args_type)[0].type, &module.types)
|
||||||
|
argument_pointer := types.pointer(&module.types, types.U8, false, true, true, 0)
|
||||||
|
argv_type := types.pointer(&module.types, argument_pointer, false, true)
|
||||||
|
param_types = make([]types.Type, 2, allocator)
|
||||||
|
param_types[0] = types.C_INT
|
||||||
|
param_types[1] = argv_type
|
||||||
|
for param_type, index in param_types {
|
||||||
|
append(&instructions, ir.Instruction{
|
||||||
|
op=.Param, type=param_type, target=ir.local_ref(ir.Local_Id(index)),
|
||||||
|
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
prepared_args = ir.instruction_id(len(instructions))
|
||||||
|
append(&instructions, ir.Instruction{
|
||||||
|
op=.Prepare_Args, type=values_type, target=ir.INVALID_REF,
|
||||||
|
a=ir.Instruction_Id(0), b=ir.Instruction_Id(1),
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
values_args := make([]ir.Instruction_Id, 1, allocator)
|
||||||
|
values_args[0] = prepared_args
|
||||||
|
process_args := ir.instruction_id(len(instructions))
|
||||||
|
append(&instructions, ir.Instruction{
|
||||||
|
op=.Aggregate, type=args_type, args=values_args, target=ir.INVALID_REF,
|
||||||
|
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
provider_call := ir.instruction_id(len(instructions))
|
provider_call := ir.instruction_id(len(instructions))
|
||||||
append(&instructions, ir.Instruction{
|
append(&instructions, ir.Instruction{
|
||||||
op=.Call,
|
op=.Call,
|
||||||
@@ -1789,12 +1833,13 @@ append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, alloca
|
|||||||
b=ir.INVALID_INSTRUCTION,
|
b=ir.INVALID_INSTRUCTION,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
init_args := make([]ir.Instruction_Id, 1, allocator)
|
init_args := make([]ir.Instruction_Id, len(init_fields), allocator)
|
||||||
init_args[0] = provider_call
|
init_args[io_index] = provider_call
|
||||||
|
init_args[args_index] = process_args
|
||||||
init_value := ir.instruction_id(len(instructions))
|
init_value := ir.instruction_id(len(instructions))
|
||||||
append(&instructions, ir.Instruction{
|
append(&instructions, ir.Instruction{
|
||||||
op=.Aggregate,
|
op=.Aggregate,
|
||||||
type=main_function.locals[param_index].type,
|
type=init_type,
|
||||||
args=init_args,
|
args=init_args,
|
||||||
target=ir.INVALID_REF,
|
target=ir.INVALID_REF,
|
||||||
a=ir.INVALID_INSTRUCTION,
|
a=ir.INVALID_INSTRUCTION,
|
||||||
@@ -1814,6 +1859,13 @@ append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, alloca
|
|||||||
b=ir.INVALID_INSTRUCTION,
|
b=ir.INVALID_INSTRUCTION,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
|
if prepared_args != ir.INVALID_INSTRUCTION {
|
||||||
|
append(&instructions, ir.Instruction{
|
||||||
|
op=.Free_Args, type=types.VOID, target=ir.INVALID_REF,
|
||||||
|
a=prepared_args, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if types.kind(main_function.result, &hir_module.types) == .Fallible {
|
if types.kind(main_function.result, &hir_module.types) == .Fallible {
|
||||||
success := types.fallible_success(main_function.result, &hir_module.types)
|
success := types.fallible_success(main_function.result, &hir_module.types)
|
||||||
@@ -1919,6 +1971,7 @@ append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, alloca
|
|||||||
implementation=.Definition,
|
implementation=.Definition,
|
||||||
linkage=.External,
|
linkage=.External,
|
||||||
is_main=true,
|
is_main=true,
|
||||||
|
param_types=param_types,
|
||||||
result=types.I32,
|
result=types.I32,
|
||||||
instructions=instructions[:],
|
instructions=instructions[:],
|
||||||
problematic=problematic,
|
problematic=problematic,
|
||||||
|
|||||||
@@ -429,7 +429,8 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
|||||||
return types.BOOL
|
return types.BOOL
|
||||||
case .Left_Paren:
|
case .Left_Paren:
|
||||||
advance(parser)
|
advance(parser)
|
||||||
grouped := parse_type(parser)
|
skip_newlines(parser)
|
||||||
|
grouped := parse_type_pipe_tail(parser, parse_type_atom(parser), true)
|
||||||
if _, ok := allow(parser, .Right_Paren); !ok {
|
if _, ok := allow(parser, .Right_Paren); !ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected ')' after grouped type")
|
source.add(parser.diagnostics, current(parser).span, "expected ')' after grouped type")
|
||||||
}
|
}
|
||||||
@@ -533,10 +534,19 @@ sum_syntax_requires_resolution :: proc(parser: ^Parser, value: types.Type, depth
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_type_pipe_tail :: proc(parser: ^Parser, left: ast.Type_Syntax) -> ast.Type_Syntax {
|
parse_type_pipe_tail :: proc(parser: ^Parser, left: ast.Type_Syntax, multiline := false) -> ast.Type_Syntax {
|
||||||
result := left
|
result := left
|
||||||
for current(parser).kind == .Pipe {
|
for {
|
||||||
|
if multiline {
|
||||||
|
skip_newlines(parser)
|
||||||
|
}
|
||||||
|
if current(parser).kind != .Pipe {
|
||||||
|
break
|
||||||
|
}
|
||||||
operator := advance(parser)
|
operator := advance(parser)
|
||||||
|
if multiline {
|
||||||
|
skip_newlines(parser)
|
||||||
|
}
|
||||||
right := parse_type_atom(parser)
|
right := parse_type_atom(parser)
|
||||||
composed, compose_error := types.compose_sum(&parser.module.type_store, result, right)
|
composed, compose_error := types.compose_sum(&parser.module.type_store, result, right)
|
||||||
if compose_error == .Unsupported {
|
if compose_error == .Unsupported {
|
||||||
@@ -1440,6 +1450,7 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
|||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
start_expr := ast.INVALID_EXPR
|
start_expr := ast.INVALID_EXPR
|
||||||
end_expr := ast.INVALID_EXPR
|
end_expr := ast.INVALID_EXPR
|
||||||
|
sentinel_expr := ast.INVALID_EXPR
|
||||||
slicing := false
|
slicing := false
|
||||||
if _, ok := allow(parser, .Range); ok {
|
if _, ok := allow(parser, .Range); ok {
|
||||||
slicing = true
|
slicing = true
|
||||||
@@ -1453,12 +1464,19 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
if slicing && current(parser).kind != .Right_Bracket {
|
if slicing && current(parser).kind != .Right_Bracket && current(parser).kind != .Semicolon {
|
||||||
parser.range_disabled += 1
|
parser.range_disabled += 1
|
||||||
end_expr = parse_expression_bp(parser, 0, nesting+1)
|
end_expr = parse_expression_bp(parser, 0, nesting+1)
|
||||||
parser.range_disabled -= 1
|
parser.range_disabled -= 1
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
}
|
}
|
||||||
|
if slicing {
|
||||||
|
if _, ok := allow(parser, .Semicolon); ok {
|
||||||
|
skip_newlines(parser)
|
||||||
|
sentinel_expr = parse_expression_bp(parser, 0, nesting+1)
|
||||||
|
skip_newlines(parser)
|
||||||
|
}
|
||||||
|
}
|
||||||
end_token, ok := allow(parser, .Right_Bracket)
|
end_token, ok := allow(parser, .Right_Bracket)
|
||||||
if !ok {
|
if !ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected ']' after index or slice")
|
source.add(parser.diagnostics, current(parser).span, "expected ']' after index or slice")
|
||||||
@@ -1475,7 +1493,7 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
|||||||
span=span_from(left_expr.span, end_token.span),
|
span=span_from(left_expr.span, end_token.span),
|
||||||
args=args,
|
args=args,
|
||||||
left=left,
|
left=left,
|
||||||
right=ast.INVALID_EXPR,
|
right=sentinel_expr,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+501
-27
@@ -2252,6 +2252,256 @@ main func() i32 {
|
|||||||
testing.expect(t, strings.contains(llvm_text, "getelementptr [4 x i32], ptr"))
|
testing.expect(t, strings.contains(llvm_text, "getelementptr [4 x i32], ptr"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
explicit_sentinel_slices_execute_without_copying :: proc(t: ^testing.T) {
|
||||||
|
directory := "/tmp/brolang-test-explicit-sentinel-slices"
|
||||||
|
main_path := "/tmp/brolang-test-explicit-sentinel-slices/main.bro"
|
||||||
|
output := "/tmp/brolang-test-explicit-sentinel-slices-output"
|
||||||
|
text := `pointer func(value *mut u8, count usize) [;0]mut u8 { return value[..count;0] }
|
||||||
|
bounded func(value []u8, count usize) [;0]u8 { return value[..count;0] }
|
||||||
|
array func(value @mut [3]mut u8, start usize, count usize) [;0]mut u8 { return value[start..count;0] }
|
||||||
|
terminated func(value [;0]u8) [;0]u8 { return value[..;0] }
|
||||||
|
nonzero func(value [*;9]u8, count usize) [;9]u8 { return value[1..count;9] }
|
||||||
|
signed func(value *i16, count usize) [;-7]i16 { return value[..count;-7] }
|
||||||
|
argument func(value [*;0]u8, present bool) ?[;0]u8 {
|
||||||
|
if !present { return null }
|
||||||
|
length usize := 0
|
||||||
|
while value[length] != 0 { length += 1 }
|
||||||
|
return value[..length;0]
|
||||||
|
}
|
||||||
|
static_text func() [;0]u8 { return "hello"[..;0] }
|
||||||
|
static_partial func() [;7]u16 {
|
||||||
|
values [4]u16 := [11, 22, 7, 44]
|
||||||
|
return values[1..2;7]
|
||||||
|
}
|
||||||
|
comptime_text [;0]u8 :: $static_text()
|
||||||
|
comptime_partial [;7]u16 :: $static_partial()
|
||||||
|
equal func($T type, left, right []T) bool {
|
||||||
|
if left.len != right.len { return false }
|
||||||
|
for 0..left.len |index| {
|
||||||
|
if left[index] != right[index] { return false }
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
main func() i32 {
|
||||||
|
values [3]mut u8 := [11, 22, 0]
|
||||||
|
view :: pointer((&values).ptr, 2)
|
||||||
|
if view.len != 2 { return 1 }
|
||||||
|
view[0] = 33
|
||||||
|
if values[0] != 33 { return 2 }
|
||||||
|
all :: values[..]
|
||||||
|
if all.len != 3 { return 3 }
|
||||||
|
part :: bounded(all, 2)
|
||||||
|
if part.len != 2 or part[1] != 22 { return 4 }
|
||||||
|
suffix :: array(&values, 1, 2)
|
||||||
|
suffix[0] = 44
|
||||||
|
if suffix.len != 1 or values[1] != 44 { return 5 }
|
||||||
|
empty :: pointer((&values).ptr + 2, 0)
|
||||||
|
if empty.len != 0 { return 6 }
|
||||||
|
zeros [2;0]mut u8 := [1, 2]
|
||||||
|
full :: terminated(zeros[..])
|
||||||
|
if full.len != 2 or full[1] != 2 { return 7 }
|
||||||
|
nines [2;9]u8 := [3, 4]
|
||||||
|
nine :: nonzero((&nines).ptr, 2)
|
||||||
|
if nine.len != 1 or nine[0] != 4 { return 8 }
|
||||||
|
wide [2]i16 := [123, -7]
|
||||||
|
negative :: signed((&wide).ptr, 1)
|
||||||
|
if negative.len != 1 or negative[0] != 123 { return 9 }
|
||||||
|
direct :: zeros[..;0]
|
||||||
|
if direct.len != 2 { return 10 }
|
||||||
|
if argument((&zeros).ptr, true) |arg| {
|
||||||
|
zeros[0] = 55
|
||||||
|
if arg.len != 2 or arg[0] != 55 { return 11 }
|
||||||
|
} else { return 12 }
|
||||||
|
if argument((&zeros).ptr, false) |_| { return 13 }
|
||||||
|
if comptime_text.len != 5 or comptime_text[0] != 'h' or comptime_text[4] != 'o' { return 14 }
|
||||||
|
if comptime_partial.len != 1 or comptime_partial[0] != 22 { return 15 }
|
||||||
|
if !equal(comptime_text, "hello") or equal(comptime_text, "hellx") { return 16 }
|
||||||
|
if !equal(direct, "7\x02") { return 17 }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||||
|
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
explicit_sentinel_slice_runtime_guards_trap :: proc(t: ^testing.T) {
|
||||||
|
cases := [?]struct {
|
||||||
|
text, message: string,
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
`check func(value *u8, count usize) void { _ = value[..count;0] }
|
||||||
|
main func() void {
|
||||||
|
values [2]u8 := [1, 2]
|
||||||
|
check((&values).ptr, 1)
|
||||||
|
}
|
||||||
|
`, "slice sentinel mismatch",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`check func(value @[2]u8, count usize) void { _ = value[..count;0] }
|
||||||
|
main func() void {
|
||||||
|
values [2]u8 := [1, 0]
|
||||||
|
check(&values, 2)
|
||||||
|
}
|
||||||
|
`, "slice bounds out of range",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`check func(value []u8, count usize) void { _ = value[..count;0] }
|
||||||
|
main func() void {
|
||||||
|
values [2;0]u8 := [1, 2]
|
||||||
|
check(values[..2], 2)
|
||||||
|
}
|
||||||
|
`, "slice bounds out of range",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`check func(value [;0]u8, count usize) void { _ = value[..count;0] }
|
||||||
|
main func() void {
|
||||||
|
values [2;0]u8 := [1, 2]
|
||||||
|
check(values[..], 3)
|
||||||
|
}
|
||||||
|
`, "slice bounds out of range",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`check func(value *u8, start usize, count usize) void { _ = value[start..count;0] }
|
||||||
|
main func() void {
|
||||||
|
values [2]u8 := [1, 0]
|
||||||
|
check((&values).ptr, 2, 1)
|
||||||
|
}
|
||||||
|
`, "slice bounds out of range",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`check func(value [;0]u8, count usize) void { _ = value[..count;9] }
|
||||||
|
main func() void {
|
||||||
|
values [2;0]u8 := [1, 2]
|
||||||
|
check(values[..], 2)
|
||||||
|
}
|
||||||
|
`, "slice sentinel mismatch",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for test_case, index in cases {
|
||||||
|
directory := fmt.tprintf("/tmp/brolang-test-sentinel-slice-trap-%d", index)
|
||||||
|
main_path := fmt.tprintf("%s/main.bro", directory)
|
||||||
|
output := fmt.tprintf("%s-output", directory)
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)test_case.text))
|
||||||
|
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
|
||||||
|
state, stdout, stderr, err := os2.process_exec(
|
||||||
|
os2.Process_Desc{command=[]string{output}}, context.allocator,
|
||||||
|
)
|
||||||
|
defer delete(stdout)
|
||||||
|
defer delete(stderr)
|
||||||
|
testing.expect(t, err == nil)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
testing.expect(t, strings.contains(string(stderr), test_case.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
explicit_sentinel_slice_semantic_errors_are_diagnosed :: proc(t: ^testing.T) {
|
||||||
|
cases := [?]struct {
|
||||||
|
text, message: string,
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
`bad func(value *u8, count usize, mark u8) void { _ = value[..count;mark] }
|
||||||
|
main func() void {
|
||||||
|
values [2]u8 := [1, 0]
|
||||||
|
bad((&values).ptr, 1, 0)
|
||||||
|
}
|
||||||
|
`, "slice sentinel must be a compile-time integer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`bad func(value *u8, count usize) void { _ = value[..count;true] }
|
||||||
|
main func() void {
|
||||||
|
values [2]u8 := [1, 0]
|
||||||
|
bad((&values).ptr, 1)
|
||||||
|
}
|
||||||
|
`, "slice sentinel must be a compile-time integer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`bad func(value *u8, count usize) void { _ = value[..count;256] }
|
||||||
|
main func() void {
|
||||||
|
values [2]u8 := [1, 0]
|
||||||
|
bad((&values).ptr, 1)
|
||||||
|
}
|
||||||
|
`, "slice sentinel value does not fit element type",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`bad func(value *u8, count usize) void { _ = value[..count;-1] }
|
||||||
|
main func() void {
|
||||||
|
values [2]u8 := [1, 0]
|
||||||
|
bad((&values).ptr, 1)
|
||||||
|
}
|
||||||
|
`, "slice sentinel value does not fit element type",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`bad func(value [*;0]u8) void { _ = value[..;0] }
|
||||||
|
main func() void { bad("text") }
|
||||||
|
`, "many-item pointer slicing requires an explicit end bound",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`bad func(value *u8, count usize) [;0]mut u8 { return value[..count;0] }
|
||||||
|
main func() void {
|
||||||
|
values [2]u8 := [1, 0]
|
||||||
|
_ = bad((&values).ptr, 1)
|
||||||
|
}
|
||||||
|
`, "cannot implicitly convert",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`bad func(count usize) usize {
|
||||||
|
values [2]u8 := [1, 0]
|
||||||
|
return values[..count;0].len
|
||||||
|
}
|
||||||
|
result :: $bad(2)
|
||||||
|
main func() void { _ = result }
|
||||||
|
`, "comptime sentinel slice endpoint out of range",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`bad func(count usize) usize {
|
||||||
|
values [2]u8 := [1, 2]
|
||||||
|
return values[..count;0].len
|
||||||
|
}
|
||||||
|
result :: $bad(1)
|
||||||
|
main func() void { _ = result }
|
||||||
|
`, "comptime slice sentinel mismatch",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`bad func(value []u8) usize { return value[..;0].len }
|
||||||
|
result :: $bad("text")
|
||||||
|
main func() void { _ = result }
|
||||||
|
`, "comptime sentinel slice endpoint out of range",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for test_case in cases {
|
||||||
|
source_file := source.Source{path="invalid_sentinel_slice.bro", text=test_case.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)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found = found || strings.contains(diagnostic.message, test_case.message)
|
||||||
|
}
|
||||||
|
testing.expect(t, found, test_case.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
slicing_an_array_rvalue_materializes_a_temporary :: proc(t: ^testing.T) {
|
slicing_an_array_rvalue_materializes_a_temporary :: proc(t: ^testing.T) {
|
||||||
// The checker accepts slicing a non-location array (here, a by-value array
|
// The checker accepts slicing a non-location array (here, a by-value array
|
||||||
@@ -3284,10 +3534,28 @@ main func() int ! Error { return try value() }
|
|||||||
source=`process :: import "@std/process"
|
source=`process :: import "@std/process"
|
||||||
Error :: enum { failed }
|
Error :: enum { failed }
|
||||||
fail func() void ! Error { return .failed }
|
fail func() void ! Error { return .failed }
|
||||||
main func(_ process.Init) void ! Error { try fail() }
|
main func(init process.Init) void ! Error {
|
||||||
|
if init.args.values.len != 3 { return }
|
||||||
|
if init.args.values[1].len != 0 or init.args.values[2].len != 9 { return }
|
||||||
|
if init.args.values[2][0] != 't' or init.args.values[2][8] != 's' { return }
|
||||||
|
try fail()
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
exit_code=1,
|
exit_code=1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name="process-success",
|
||||||
|
source=`process :: import "@std/process"
|
||||||
|
Error :: enum { failed }
|
||||||
|
main func(init process.Init) i32 ! Error {
|
||||||
|
if init.args.values.len != 3 { return .failed }
|
||||||
|
if init.args.values[1].len != 0 or init.args.values[2].len != 9 { return .failed }
|
||||||
|
if init.args.values[2][0] != 't' or init.args.values[2][8] != 's' { return .failed }
|
||||||
|
return 7
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
exit_code=7,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name="inferred-main-try",
|
name="inferred-main-try",
|
||||||
source=`Error :: enum { failed }
|
source=`Error :: enum { failed }
|
||||||
@@ -3328,7 +3596,12 @@ main func() void! { try dispatch(2) }
|
|||||||
source=`process :: import "@std/process"
|
source=`process :: import "@std/process"
|
||||||
Error :: enum { failed }
|
Error :: enum { failed }
|
||||||
fail func() void ! Error { return .failed }
|
fail func() void ! Error { return .failed }
|
||||||
main func(_ process.Init) void! { try fail() }
|
main func(init process.Init) void! {
|
||||||
|
if init.args.values.len != 3 { return }
|
||||||
|
if init.args.values[1].len != 0 or init.args.values[2].len != 9 { return }
|
||||||
|
if init.args.values[2][0] != 't' or init.args.values[2][8] != 's' { return }
|
||||||
|
try fail()
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
exit_code=1,
|
exit_code=1,
|
||||||
},
|
},
|
||||||
@@ -3353,7 +3626,7 @@ main func(_ process.Init) void! { try fail() }
|
|||||||
)
|
)
|
||||||
testing.expect_value(t, status, 0)
|
testing.expect_value(t, status, 0)
|
||||||
state, stdout, stderr, _ := os2.process_exec(
|
state, stdout, stderr, _ := os2.process_exec(
|
||||||
os2.Process_Desc{command=[]string{output}},
|
os2.Process_Desc{command=[]string{output, "", "two words"}},
|
||||||
context.allocator,
|
context.allocator,
|
||||||
)
|
)
|
||||||
defer delete(stdout)
|
defer delete(stdout)
|
||||||
@@ -3468,7 +3741,6 @@ milestone_33_injects_explicit_io_provider_and_runs_std_io :: proc(t: ^testing.T)
|
|||||||
testing.expect_value(t, len(diagnostics.items), 0)
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
testing.expect(t, hir_module.injected_main != hir.INVALID_FUNCTION)
|
testing.expect(t, hir_module.injected_main != hir.INVALID_FUNCTION)
|
||||||
testing.expect(t, hir_module.io_provider != hir.INVALID_FUNCTION)
|
testing.expect(t, hir_module.io_provider != hir.INVALID_FUNCTION)
|
||||||
testing.expect_value(t, count_substring_occurrences(llvm_text, "define i32 @main()"), 1)
|
|
||||||
testing.expect_value(t, count_substring_occurrences(llvm_text, "define internal fastcc i32 @bro__p0__main__"), 1)
|
testing.expect_value(t, count_substring_occurrences(llvm_text, "define internal fastcc i32 @bro__p0__main__"), 1)
|
||||||
testing.expect_value(t, count_substring_occurrences(llvm_text, "declare i64 @write("), 1)
|
testing.expect_value(t, count_substring_occurrences(llvm_text, "declare i64 @write("), 1)
|
||||||
|
|
||||||
@@ -3685,7 +3957,6 @@ milestone_37_validates_process_init_and_hidden_system_provider :: proc(t: ^testi
|
|||||||
Case :: struct {
|
Case :: struct {
|
||||||
io_source: string,
|
io_source: string,
|
||||||
process_source: string,
|
process_source: string,
|
||||||
message: string,
|
|
||||||
}
|
}
|
||||||
cases := []Case{
|
cases := []Case{
|
||||||
{
|
{
|
||||||
@@ -3693,18 +3964,54 @@ milestone_37_validates_process_init_and_hidden_system_provider :: proc(t: ^testi
|
|||||||
system func() Io { return Io {value = 0} }
|
system func() Io { return Io {value = 0} }
|
||||||
`,
|
`,
|
||||||
process_source=`io :: import "@std/io"
|
process_source=`io :: import "@std/io"
|
||||||
Init :: struct { io io.Io }
|
Args :: struct { values [][;0]u8 }
|
||||||
|
Init :: struct { io io.Io, args Args }
|
||||||
`,
|
`,
|
||||||
message="does not provide the required '@hide system func() Io'",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
io_source=`Io :: struct { value i32 }
|
io_source=`Io :: struct { value i32 }
|
||||||
@hide system func() Io { return Io {value = 0} }
|
@hide system func() Io { return Io {value = 0} }
|
||||||
`,
|
`,
|
||||||
process_source=`io :: import "@std/io"
|
process_source=`io :: import "@std/io"
|
||||||
Init :: struct { io io.Io, extra i32 }
|
Args :: struct { values [][;0]u8 }
|
||||||
|
Init :: struct { io io.Io, args Args, extra i32 }
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
io_source=`Io :: struct { value i32 }
|
||||||
|
@hide system func() Io { return Io {value = 0} }
|
||||||
|
`,
|
||||||
|
process_source=`io :: import "@std/io"
|
||||||
|
Args :: struct { values [][;0]u8 }
|
||||||
|
Init :: struct { io io.Io }
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
io_source=`Io :: struct { value i32 }
|
||||||
|
@hide system func() Io { return Io {value = 0} }
|
||||||
|
`,
|
||||||
|
process_source=`io :: import "@std/io"
|
||||||
|
Args :: struct { values [][]u8 }
|
||||||
|
Init :: struct { io io.Io, args Args }
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
io_source=`Io :: struct { value i32 }
|
||||||
|
@hide system func() Io { return Io {value = 0} }
|
||||||
|
`,
|
||||||
|
process_source=`io :: import "@std/io"
|
||||||
|
Args :: struct { values [][;0]u8, extra i32 }
|
||||||
|
Init :: struct { io io.Io, args Args }
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
io_source=`Io :: struct { value i32 }
|
||||||
|
@hide system func() Io { return Io {value = 0} }
|
||||||
|
`,
|
||||||
|
process_source=`io :: import "@std/io"
|
||||||
|
Args :: struct { values [][;0]u8 }
|
||||||
|
Init :: struct { io io.Io, args i32 }
|
||||||
`,
|
`,
|
||||||
message="Init must be an auto-layout record containing exactly 'io io.Io'",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for test_case, index in cases {
|
for test_case, index in cases {
|
||||||
@@ -3735,12 +4042,8 @@ main func(init process.Init) void { _ = init }
|
|||||||
symbols := symbol.init_table()
|
symbols := symbol.init_table()
|
||||||
ast_module, loaded := loader.load(app, &sources, &diagnostics, &symbols, project_root_path=root)
|
ast_module, loaded := loader.load(app, &sources, &diagnostics, &symbols, project_root_path=root)
|
||||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
found := false
|
|
||||||
for diagnostic in diagnostics.items {
|
|
||||||
found = found || strings.contains(diagnostic.message, test_case.message)
|
|
||||||
}
|
|
||||||
testing.expect(t, loaded)
|
testing.expect(t, loaded)
|
||||||
testing.expect(t, found)
|
testing.expect(t, len(diagnostics.items) > 0)
|
||||||
hir.destroy_module(&hir_module)
|
hir.destroy_module(&hir_module)
|
||||||
ast.destroy_module(&ast_module)
|
ast.destroy_module(&ast_module)
|
||||||
symbol.destroy_table(&symbols)
|
symbol.destroy_table(&symbols)
|
||||||
@@ -3749,6 +4052,131 @@ main func(init process.Init) void { _ = init }
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
process_init_supplies_native_argument_slices :: proc(t: ^testing.T) {
|
||||||
|
directory := "/tmp/brolang-test-process-args"
|
||||||
|
main_path := "/tmp/brolang-test-process-args/main.bro"
|
||||||
|
output := "/tmp/brolang-test-process-args/app"
|
||||||
|
text := `process :: import "@std/process"
|
||||||
|
io :: import "@std/io"
|
||||||
|
mem :: import "@std/mem"
|
||||||
|
|
||||||
|
main func(init process.Init) i32 {
|
||||||
|
count :: init.args.values.len
|
||||||
|
if count != 1 and count != 4 and count != 16385 { return 1 }
|
||||||
|
if !mem.eql(init.args.values[0], "/tmp/brolang-test-process-args/app") { return 2 }
|
||||||
|
if count == 4 {
|
||||||
|
if !mem.eql(init.args.values[1], "run") { return 3 }
|
||||||
|
if init.args.values[2].len != 0 { return 4 }
|
||||||
|
if !mem.eql(init.args.values[3], "two words") { return 5 }
|
||||||
|
}
|
||||||
|
if count == 16385 {
|
||||||
|
index := usize(1)
|
||||||
|
while index < count {
|
||||||
|
if init.args.values[index].len != 0 { return 6 }
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
io.write_all(io.stdout(init.io), "args-ok\n") catch |_| { return 7 }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||||
|
testing.expect_value(t, compiler_core.compile_package(
|
||||||
|
directory, output, nil, target.DEFAULT, cimport.Options{}, ".",
|
||||||
|
), 0)
|
||||||
|
large_command := make([]string, 16385)
|
||||||
|
defer delete(large_command)
|
||||||
|
large_command[0] = output
|
||||||
|
for index in 1..<len(large_command) {
|
||||||
|
large_command[index] = ""
|
||||||
|
}
|
||||||
|
commands := [][]string{
|
||||||
|
{output},
|
||||||
|
{output, "run", "", "two words"},
|
||||||
|
large_command,
|
||||||
|
}
|
||||||
|
for command in commands {
|
||||||
|
state, stdout, stderr, error := os2.process_exec(
|
||||||
|
os2.Process_Desc{command=command}, context.allocator,
|
||||||
|
)
|
||||||
|
testing.expect(t, error == nil)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
testing.expect_value(t, string(stdout), "args-ok\n")
|
||||||
|
testing.expect_value(t, len(stderr), 0)
|
||||||
|
delete(stdout)
|
||||||
|
delete(stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
plain_main := "main func() i32 { return 37 }\n"
|
||||||
|
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)plain_main))
|
||||||
|
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
|
||||||
|
for command in commands {
|
||||||
|
state, stdout, stderr, error := os2.process_exec(
|
||||||
|
os2.Process_Desc{command=command}, context.allocator,
|
||||||
|
)
|
||||||
|
testing.expect(t, error == nil)
|
||||||
|
testing.expect_value(t, state.exit_code, 37)
|
||||||
|
testing.expect_value(t, len(stdout), 0)
|
||||||
|
testing.expect_value(t, len(stderr), 0)
|
||||||
|
delete(stdout)
|
||||||
|
delete(stderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
process_init_startup_respects_reordered_fields :: proc(t: ^testing.T) {
|
||||||
|
root := "/tmp/brolang-test-process-reordered"
|
||||||
|
app := "/tmp/brolang-test-process-reordered/app"
|
||||||
|
output := "/tmp/brolang-test-process-reordered/program"
|
||||||
|
io_source := `Io :: struct { value i32 }
|
||||||
|
@hide system func() Io { return Io {value = 37} }
|
||||||
|
`
|
||||||
|
process_source := `io :: import "@std/io"
|
||||||
|
Args :: struct { values [][;0]u8 }
|
||||||
|
Init :: struct { args Args, io io.Io }
|
||||||
|
`
|
||||||
|
main_source := `process :: import "@std/process"
|
||||||
|
main func(init process.Init) i32 {
|
||||||
|
if init.io.value != 37 or init.args.values.len != 3 { return 1 }
|
||||||
|
empty :: init.args.values[1]
|
||||||
|
if empty.len != 0 { return 2 }
|
||||||
|
spaced :: init.args.values[2]
|
||||||
|
if spaced.len != 3 or spaced[0] != 'a' or spaced[1] != ' ' or spaced[2] != 'b' { return 3 }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
_ = os2.remove_all(root)
|
||||||
|
defer _ = os2.remove_all(root)
|
||||||
|
testing.expect(t, os2.make_directory_all(app) == nil)
|
||||||
|
testing.expect(t, os2.make_directory_all(fmt.tprintf("%s/std/io", root)) == nil)
|
||||||
|
testing.expect(t, os2.make_directory_all(fmt.tprintf("%s/std/process", root)) == nil)
|
||||||
|
testing.expect(t, os.write_entire_file(
|
||||||
|
fmt.tprintf("%s/main.bro", app), transmute([]byte)main_source,
|
||||||
|
))
|
||||||
|
testing.expect(t, os.write_entire_file(
|
||||||
|
fmt.tprintf("%s/std/io/io.bro", root), transmute([]byte)io_source,
|
||||||
|
))
|
||||||
|
testing.expect(t, os.write_entire_file(
|
||||||
|
fmt.tprintf("%s/std/process/process.bro", root), transmute([]byte)process_source,
|
||||||
|
))
|
||||||
|
testing.expect_value(t, compiler_core.compile_package(
|
||||||
|
app, output, nil, target.DEFAULT, cimport.Options{}, root,
|
||||||
|
), 0)
|
||||||
|
state, stdout, stderr, error := os2.process_exec(
|
||||||
|
os2.Process_Desc{command=[]string{output, "", "a b"}}, context.allocator,
|
||||||
|
)
|
||||||
|
defer delete(stdout)
|
||||||
|
defer delete(stderr)
|
||||||
|
testing.expect(t, error == nil)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
testing.expect_value(t, len(stdout), 0)
|
||||||
|
testing.expect_value(t, len(stderr), 0)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
milestone_37_tuples_reflection_expand_for_and_debug_print_compile_and_run :: proc(t: ^testing.T) {
|
milestone_37_tuples_reflection_expand_for_and_debug_print_compile_and_run :: proc(t: ^testing.T) {
|
||||||
sources := source.init_store()
|
sources := source.init_store()
|
||||||
@@ -5848,16 +6276,6 @@ main func() void {
|
|||||||
values [2]mut i32 := [1, 2]
|
values [2]mut i32 := [1, 2]
|
||||||
yield &values[0]
|
yield &values[0]
|
||||||
}
|
}
|
||||||
_ = ${
|
|
||||||
values [2]mut i32 := [1, 2]
|
|
||||||
view []mut i32 := values[..]
|
|
||||||
yield view
|
|
||||||
}
|
|
||||||
_ = ${
|
|
||||||
values [2]mut i32 := [1, 2]
|
|
||||||
view []i32 := values[1..]
|
|
||||||
yield view
|
|
||||||
}
|
|
||||||
_ = $spin()
|
_ = $spin()
|
||||||
_ = $missing()
|
_ = $missing()
|
||||||
_ = ${
|
_ = ${
|
||||||
@@ -5880,7 +6298,6 @@ main func() void {
|
|||||||
found_runtime := false
|
found_runtime := false
|
||||||
runtime_only_count := 0
|
runtime_only_count := 0
|
||||||
pointer_errors := 0
|
pointer_errors := 0
|
||||||
slice_errors := 0
|
|
||||||
found_quota := false
|
found_quota := false
|
||||||
found_missing := false
|
found_missing := false
|
||||||
found_yield := false
|
found_yield := false
|
||||||
@@ -5889,7 +6306,6 @@ main func() void {
|
|||||||
found_runtime = found_runtime || strings.contains(message, "unresolved comptime value 'runtime'")
|
found_runtime = found_runtime || strings.contains(message, "unresolved comptime value 'runtime'")
|
||||||
runtime_only_count += 1 if strings.contains(message, "runtime-only") else 0
|
runtime_only_count += 1 if strings.contains(message, "runtime-only") else 0
|
||||||
pointer_errors += 1 if strings.contains(message, "only immutable pointers to whole comptime arrays can materialize as runtime memory") else 0
|
pointer_errors += 1 if strings.contains(message, "only immutable pointers to whole comptime arrays can materialize as runtime memory") else 0
|
||||||
slice_errors += 1 if strings.contains(message, "only immutable full-array comptime slices can materialize as runtime memory") else 0
|
|
||||||
found_quota = found_quota || strings.contains(message, "comptime evaluation exceeded the step quota")
|
found_quota = found_quota || strings.contains(message, "comptime evaluation exceeded the step quota")
|
||||||
found_missing = found_missing || strings.contains(message, "did not return a value")
|
found_missing = found_missing || strings.contains(message, "did not return a value")
|
||||||
found_yield = found_yield || strings.contains(message, "a value block must end with an explicit 'yield'")
|
found_yield = found_yield || strings.contains(message, "a value block must end with an explicit 'yield'")
|
||||||
@@ -5897,7 +6313,6 @@ main func() void {
|
|||||||
testing.expect(t, found_runtime)
|
testing.expect(t, found_runtime)
|
||||||
testing.expect(t, runtime_only_count >= 2)
|
testing.expect(t, runtime_only_count >= 2)
|
||||||
testing.expect(t, pointer_errors >= 4)
|
testing.expect(t, pointer_errors >= 4)
|
||||||
testing.expect(t, slice_errors >= 2)
|
|
||||||
testing.expect(t, found_quota)
|
testing.expect(t, found_quota)
|
||||||
testing.expect(t, found_missing)
|
testing.expect(t, found_missing)
|
||||||
testing.expect(t, found_yield)
|
testing.expect(t, found_yield)
|
||||||
@@ -7680,6 +8095,65 @@ main func() i32 {
|
|||||||
testing.expect(t, strings.contains(llvm_text, "call void @llvm.memcpy.p0.p0.i64"))
|
testing.expect(t, strings.contains(llvm_text, "call void @llvm.memcpy.p0.p0.i64"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
multiline_grouped_sum_types_compile_and_run :: proc(t: ^testing.T) {
|
||||||
|
directory :: "/tmp/brolang-test-multiline-sums"
|
||||||
|
output := "/tmp/brolang-test-multiline-sums-output"
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
testing.expect(t, os.make_directory(directory + "/errors") == nil)
|
||||||
|
errors_text := `AllocError :: enum { alloc }
|
||||||
|
CapacityError :: enum { capacity }
|
||||||
|
SourceError :: union(enum) { source i32 }
|
||||||
|
`
|
||||||
|
testing.expect(t, os.write_entire_file(directory + "/errors/errors.hon", transmute([]byte)errors_text))
|
||||||
|
text := `errors :: import "errors"
|
||||||
|
@hide:file
|
||||||
|
DriverError :: alias (
|
||||||
|
errors.AllocError
|
||||||
|
| (
|
||||||
|
errors.CapacityError |
|
||||||
|
errors.SourceError
|
||||||
|
)
|
||||||
|
)
|
||||||
|
code func(err DriverError) i32 {
|
||||||
|
return match err {
|
||||||
|
.alloc: 1
|
||||||
|
.capacity: 2
|
||||||
|
.source |value|: value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fail func() void ! (
|
||||||
|
errors.AllocError
|
||||||
|
|
|
||||||
|
errors.CapacityError
|
||||||
|
) {
|
||||||
|
return .capacity
|
||||||
|
}
|
||||||
|
main func() i32 {
|
||||||
|
if (code(.alloc) != 1) return 1
|
||||||
|
if (code(.capacity) != 2) return 2
|
||||||
|
if (code(.source{42}) != 42) return 3
|
||||||
|
result := 0
|
||||||
|
fail() catch |err| {
|
||||||
|
result = code(err)
|
||||||
|
}
|
||||||
|
if (result != 2) return 4
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testing.expect(t, os.write_entire_file(directory + "/main.hon", transmute([]byte)text))
|
||||||
|
status := compiler_core.compile_package(directory, output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
if status != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
anonymous_struct_payloads_and_keyed_payload_sugar_compile :: proc(t: ^testing.T) {
|
anonymous_struct_payloads_and_keyed_payload_sugar_compile :: proc(t: ^testing.T) {
|
||||||
text := `PayloadError :: union(enum) {
|
text := `PayloadError :: union(enum) {
|
||||||
|
|||||||
@@ -2,4 +2,9 @@ import "@std/io"
|
|||||||
|
|
||||||
Init :: struct {
|
Init :: struct {
|
||||||
io io.Io
|
io io.Io
|
||||||
|
args Args
|
||||||
|
}
|
||||||
|
|
||||||
|
Args :: struct {
|
||||||
|
values [][;0]u8
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user