feat: preprocess native argv into sentinel slices

This commit is contained in:
2026-10-23 12:07:23 +02:00
parent eddca5c3bf
commit 8ebbf72388
8 changed files with 167 additions and 93 deletions
+2 -2
View File
@@ -289,7 +289,7 @@ exactly once. Bare functions named `memcopy` or `memset` remain ordinary user fu
- `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` contains exactly `io io.Io` and `args Args`, 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`, borrowing native `argv` and its `argc` count without allocation or copying; index 0 is the executable name when present. `argument_count(args Args) usize` returns the count, and `argument(args Args, index usize) ?[;0]u8` scans only the requested argument and returns a borrowed sentinel slice (including empty arguments), or null out of range; borrowed views remain valid while the supplied storage lives - `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
@@ -300,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 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 - 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
View File
@@ -24,14 +24,15 @@ main func(init process.Init) void {
} }
``` ```
`Init` contains `io io.Io` and `args process.Args`. `Args.values` is a borrowed `Init` contains `io io.Io` and `args process.Args`. `Args.values` has type
`[][*;0]u8` view of native `argv`, with its length taken from `argc`: it includes `[][;0]u8`: use `init.args.values.len` for the count and
the executable name at index 0 when present, without copying strings or the `init.args.values[index]` for a borrowed, zero-terminated argument slice.
pointer array. `process.argument_count(init.args)` returns that count, and Index 0 is the executable name when present, and empty arguments are empty
`process.argument(init.args, index)` returns a borrowed `?[;0]u8` (null when out slices. Startup scans each native argument once and allocates only the slice
of range). The helper scans only the requested zero-terminated argument; empty descriptors; argument strings are never copied. The native entry wrapper owns
arguments remain empty slices. The slices are valid while their supplied the descriptors until `main` returns, freeing them on both success and error.
argument storage lives. 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
+3 -3
View File
@@ -1764,15 +1764,15 @@ configure_entry_point :: proc(checker: ^Checker) {
args_item, args_ok := types.node(&checker.module.types, args_type) args_item, args_ok := types.node(&checker.module.types, args_type)
args_fields := types.fields_for(&checker.module.types, args_type) args_fields := types.fields_for(&checker.module.types, args_type)
values_name := symbol.intern(checker.symbols, "values") values_name := symbol.intern(checker.symbols, "values")
argument_pointer := types.pointer(&checker.module.types, types.U8, false, true, true, 0) argument_slice := types.slice(&checker.module.types, types.U8, false, true, 0)
argument_values := types.slice(&checker.module.types, argument_pointer, false) argument_values := types.slice(&checker.module.types, argument_slice, false)
if !args_ok || args_item.kind != .Struct || args_item.tuple || args_item.c_layout || 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) || 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) { !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 Args must be an auto-layout record containing exactly 'values [][*;0]u8'", "@std/process Args must be an auto-layout record containing exactly 'values [][;0]u8'",
) )
return return
} }
+3
View File
@@ -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,
+79 -2
View File
@@ -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")
@@ -2746,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] {
@@ -2880,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(
@@ -2895,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 {
+13 -14
View File
@@ -1784,6 +1784,7 @@ append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, alloca
instructions.allocator = allocator instructions.allocator = allocator
param_types: []types.Type 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_type := main_function.locals[param_index].type
init_fields := types.fields_for(&module.types, init_type) init_fields := types.fields_for(&module.types, init_type)
@@ -1797,7 +1798,7 @@ append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, alloca
args_index := 1 - io_index args_index := 1 - io_index
args_type := types.resolve_alias(init_fields[args_index].type, &module.types) 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) values_type := types.resolve_alias(types.fields_for(&module.types, args_type)[0].type, &module.types)
argument_pointer := types.child_type(values_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) argv_type := types.pointer(&module.types, argument_pointer, false, true)
param_types = make([]types.Type, 2, allocator) param_types = make([]types.Type, 2, allocator)
param_types[0] = types.C_INT param_types[0] = types.C_INT
@@ -1809,23 +1810,14 @@ append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, alloca
diagnostic=source.INVALID_DIAGNOSTIC, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
} }
count := ir.instruction_id(len(instructions)) prepared_args = ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{ append(&instructions, ir.Instruction{
op=.Scalar_Cast, type=types.USIZE, target=ir.INVALID_REF, op=.Prepare_Args, type=values_type, target=ir.INVALID_REF,
a=ir.Instruction_Id(0), b=ir.INVALID_INSTRUCTION, a=ir.Instruction_Id(0), b=ir.Instruction_Id(1),
diagnostic=source.INVALID_DIAGNOSTIC,
})
slice_args := make([]ir.Instruction_Id, 2, allocator)
slice_args[0] = ir.INVALID_INSTRUCTION
slice_args[1] = count
values := ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Slice, type=values_type, args=slice_args, target=ir.INVALID_REF,
a=ir.Instruction_Id(1), b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
values_args := make([]ir.Instruction_Id, 1, allocator) values_args := make([]ir.Instruction_Id, 1, allocator)
values_args[0] = values values_args[0] = prepared_args
process_args := ir.instruction_id(len(instructions)) process_args := ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{ append(&instructions, ir.Instruction{
op=.Aggregate, type=args_type, args=values_args, target=ir.INVALID_REF, op=.Aggregate, type=args_type, args=values_args, target=ir.INVALID_REF,
@@ -1867,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)
+57 -47
View File
@@ -3534,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 }
@@ -3578,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,
}, },
@@ -3603,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)
@@ -3941,7 +3964,7 @@ 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"
Args :: struct { values [][*;0]u8 } Args :: struct { values [][;0]u8 }
Init :: struct { io io.Io, args Args } Init :: struct { io io.Io, args Args }
`, `,
}, },
@@ -3950,7 +3973,7 @@ Init :: struct { io io.Io, args Args }
@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"
Args :: struct { values [][*;0]u8 } Args :: struct { values [][;0]u8 }
Init :: struct { io io.Io, args Args, extra i32 } Init :: struct { io io.Io, args Args, extra i32 }
`, `,
}, },
@@ -3959,7 +3982,7 @@ Init :: struct { io io.Io, args Args, extra 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"
Args :: struct { values [][*;0]u8 } Args :: struct { values [][;0]u8 }
Init :: struct { io io.Io } Init :: struct { io io.Io }
`, `,
}, },
@@ -3968,7 +3991,7 @@ Init :: struct { io io.Io }
@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"
Args :: struct { values [][*]u8 } Args :: struct { values [][]u8 }
Init :: struct { io io.Io, args Args } Init :: struct { io io.Io, args Args }
`, `,
}, },
@@ -3977,7 +4000,7 @@ Init :: struct { io io.Io, args Args }
@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"
Args :: struct { values [][*;0]u8, extra i32 } Args :: struct { values [][;0]u8, extra i32 }
Init :: struct { io io.Io, args Args } Init :: struct { io io.Io, args Args }
`, `,
}, },
@@ -3986,7 +4009,7 @@ Init :: struct { io io.Io, args Args }
@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"
Args :: struct { values [][*;0]u8 } Args :: struct { values [][;0]u8 }
Init :: struct { io io.Io, args i32 } Init :: struct { io io.Io, args i32 }
`, `,
}, },
@@ -4030,7 +4053,7 @@ main func(init process.Init) void { _ = init }
} }
@(test) @(test)
process_init_supplies_native_arguments_and_helpers_borrow_explicit_storage :: proc(t: ^testing.T) { process_init_supplies_native_argument_slices :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-process-args" directory := "/tmp/brolang-test-process-args"
main_path := "/tmp/brolang-test-process-args/main.bro" main_path := "/tmp/brolang-test-process-args/main.bro"
output := "/tmp/brolang-test-process-args/app" output := "/tmp/brolang-test-process-args/app"
@@ -4038,43 +4061,23 @@ process_init_supplies_native_arguments_and_helpers_borrow_explicit_storage :: pr
io :: import "@std/io" io :: import "@std/io"
mem :: import "@std/mem" mem :: import "@std/mem"
supplied_args_work func() bool {
text [3;0]mut u8 := ['r', 'u', 'n']
pointers [1][*;0]u8 :: [(&text).ptr]
args :: process.Args { values = pointers[..] }
if process.argument_count(args) != 1 { return false }
if process.argument(args, 0) |value| {
text[0] = 'f'
if !mem.eql(value, "fun") { return false }
} else { return false }
if process.argument(args, 1) |_| { return false }
empty :: process.Args { values = pointers[0..0] }
if process.argument_count(empty) != 0 { return false }
if process.argument(empty, 0) |_| { return false }
return true
}
main func(init process.Init) i32 { main func(init process.Init) i32 {
count :: process.argument_count(init.args) count :: init.args.values.len
if count != 1 and count != 4 { return 1 } if count != 1 and count != 4 and count != 16385 { return 1 }
if process.argument(init.args, 0) |executable| { if !mem.eql(init.args.values[0], "/tmp/brolang-test-process-args/app") { return 2 }
if !mem.eql(executable, "/tmp/brolang-test-process-args/app") { return 2 }
} else { return 3 }
if process.argument(init.args, count) |_| { return 4 }
if process.argument(init.args, maxval!(usize)) |_| { return 5 }
if count == 4 { if count == 4 {
if process.argument(init.args, 1) |operation| { if !mem.eql(init.args.values[1], "run") { return 3 }
if !mem.eql(operation, "run") { return 6 } if init.args.values[2].len != 0 { return 4 }
} else { return 7 } if !mem.eql(init.args.values[3], "two words") { return 5 }
if process.argument(init.args, 2) |empty| {
if empty.len != 0 { return 8 }
} else { return 9 }
if process.argument(init.args, 3) |spaced| {
if !mem.eql(spaced, "two words") { return 10 }
} else { return 11 }
} }
if !supplied_args_work() { return 12 } if count == 16385 {
io.write_all(io.stdout(init.io), "args-ok\n") catch |_| { return 13 } 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 return 0
} }
` `
@@ -4085,9 +4088,16 @@ main func(init process.Init) i32 {
testing.expect_value(t, compiler_core.compile_package( testing.expect_value(t, compiler_core.compile_package(
directory, output, nil, target.DEFAULT, cimport.Options{}, ".", directory, output, nil, target.DEFAULT, cimport.Options{}, ".",
), 0) ), 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{ commands := [][]string{
{output}, {output},
{output, "run", "", "two words"}, {output, "run", "", "two words"},
large_command,
} }
for command in commands { for command in commands {
state, stdout, stderr, error := os2.process_exec( state, stdout, stderr, error := os2.process_exec(
@@ -4126,16 +4136,16 @@ process_init_startup_respects_reordered_fields :: proc(t: ^testing.T) {
@hide system func() Io { return Io {value = 37} } @hide system func() Io { return Io {value = 37} }
` `
process_source := `io :: import "@std/io" process_source := `io :: import "@std/io"
Args :: struct { values [][*;0]u8 } Args :: struct { values [][;0]u8 }
Init :: struct { args Args, io io.Io } Init :: struct { args Args, io io.Io }
` `
main_source := `process :: import "@std/process" main_source := `process :: import "@std/process"
main func(init process.Init) i32 { main func(init process.Init) i32 {
if init.io.value != 37 or init.args.values.len != 3 { return 1 } if init.io.value != 37 or init.args.values.len != 3 { return 1 }
empty :: init.args.values[1] empty :: init.args.values[1]
if empty[0] != 0 { return 2 } if empty.len != 0 { return 2 }
spaced :: init.args.values[2] spaced :: init.args.values[2]
if spaced[0] != 'a' or spaced[1] != ' ' or spaced[2] != 'b' or spaced[3] != 0 { return 3 } if spaced.len != 3 or spaced[0] != 'a' or spaced[1] != ' ' or spaced[2] != 'b' { return 3 }
return 0 return 0
} }
` `
+1 -17
View File
@@ -6,21 +6,5 @@ Init :: struct {
} }
Args :: struct { Args :: struct {
values [][*;0]u8 values [][;0]u8
}
argument_count func(args Args) usize {
return args.values.len
}
argument func(args Args, index usize) ?[;0]u8 {
if index >= argument_count(args) {
return null
}
pointer :: args.values[index]
length usize := 0
while pointer[length] != 0 {
length += 1
}
return pointer[..length;0]
} }