Merge pull request #6435 from mtarik34b/improve-flags-bitset-support

Improve `bit_set` support in the `flags` package
This commit is contained in:
gingerBill
2026-06-23 12:50:21 +01:00
committed by GitHub
4 changed files with 156 additions and 16 deletions

View File

@@ -11,13 +11,21 @@ Command-Line Syntax:
Arguments are treated differently depending on how they're formatted.
The format is similar to the Odin binary's way of handling compiler flags.
type handling
------------ ------------------------
<positional> depends on struct layout
-<flag> set a bool true
-<flag:option> set flag to option
-<flag=option> set flag to option, alternative syntax
-<map>:<key>=<value> set map[key] to value
type handling
------------ ------------------------
<positional> depends on struct layout
-<flag> set a bool true
-<flag:option> set flag to option
-<flag=option> set flag to option, alternative syntax
-<flag:option1,opt...> set bit_set flag to one or more options
-<flag=option1,opt...> set bit_set flag to one or more options, alternative syntax
-<map>:<key>=<value> set map[key] to value
Underscores (`_`) in a flag will be replaced with dashes (`-`).
Bit sets may be set with a strictly comma-separated list of options.
Bit sets may also be set with a binary string of 0s and 1s, and will be parsed from left to right, from least significant bit to most significant bit. Underscores are allowed and will be ignored. However, starting with an underscore is disallowed.
Unhandled Arguments:

View File

@@ -20,6 +20,14 @@ Optimization_Level :: enum {
Ludicrous_Speed,
}
Vet_Flag :: enum {
Unused,
Unused_Variables,
Unused_Imports,
Shadowing,
Using_Stmt,
}
// It's simple but powerful.
my_custom_type_setter :: proc(
data: rawptr,
@@ -83,6 +91,8 @@ main :: proc() {
schedule: datetime.DateTime `usage:"Launch tasks at this time."`,
opt: Optimization_Level `usage:"Optimization level."`,
vet_flags: bit_set[Vet_Flag] `usage:"Vet flags. Example usage:
-vet-flags:Unused,Shadowing"`,
todo: [dynamic]string `usage:"Todo items."`,
accuracy: Fixed_Point1_1 `args:"required" usage:"Lenience in FLOP calculations."`,

View File

@@ -110,7 +110,7 @@ parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info:
case f32be: (^f32be)(ptr)^ = cast(f32be) value
case f64be: (^f64be)(ptr)^ = cast(f64be) value
}
case runtime.Type_Info_Complex:
value := strconv.parse_complex128(str) or_return
switch type_info.id {
@@ -118,7 +118,7 @@ parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info:
case complex64: (^complex64) (ptr)^ = (complex64)(value)
case complex128: (^complex128)(ptr)^ = value
}
case runtime.Type_Info_Quaternion:
value := strconv.parse_quaternion256(str) or_return
switch type_info.id {
@@ -152,15 +152,16 @@ parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info:
}
case runtime.Type_Info_Bit_Set:
// Parse a string of 1's and 0's, from left to right,
// Parse a string of 1s and 0s, from left to right,
// least significant bit to most significant bit.
value: u128
// NOTE: `upper` is inclusive, i.e: `0..=31`
max_bit_index := u128(1 + specific_type_info.upper - specific_type_info.lower)
underscores := u128(strings.count(str, "_"))
bit_index_limit := u128(1 + specific_type_info.upper - specific_type_info.lower) + underscores
bit_index := u128(0)
#no_bounds_check for string_index in 0..<uint(len(str)) {
if bit_index == max_bit_index {
if bit_index == bit_index_limit {
// The string's too long for this bit_set.
return false
}
@@ -421,7 +422,7 @@ parse_and_set_pointer_by_type :: proc(ptr: rawptr, str: string, type_info: ^runt
}
} else {
parse_and_set_pointer_by_named_type(ptr, str, type_info.id, arg_tag, &error)
if error != nil {
// So far, it's none of the types that we recognize.
// Check to see if we can set it by base type, if allowed.
@@ -471,6 +472,70 @@ parse_and_set_pointer_by_type :: proc(ptr: rawptr, str: string, type_info: ^runt
}
}
case runtime.Type_Info_Bit_Set:
if str[0] == '0' || str[0] == '1' {
if !parse_and_set_pointer_by_base_type(ptr, str, type_info) {
return Parse_Error {
// The caller will add more details.
.Bad_Value,
"",
}
} else {
error = nil
return
}
}
value: u128
et := runtime.type_info_base(specific_type_info.elem)
if enum_type_info, is_enum := et.variant.(runtime.Type_Info_Enum); is_enum {
names, _ := strings.split(str, ",", context.temp_allocator)
valid_names := enum_type_info.names
underlying_values := enum_type_info.values
#no_bounds_check outer_loop: for name in names {
found: bool
#no_bounds_check for valid_name, index in valid_names {
if name == valid_name {
shift := u128(underlying_values[index]) - u128(specific_type_info.lower)
value |= u128(1 << shift)
found = true
continue outer_loop
}
}
if !found {
return Parse_Error {
.Bad_Value,
fmt.tprintf(
"Invalid value name: `%s`. Valid names are: %s",
name,
valid_names,
),
}
}
}
} else {
return Parse_Error {
// The caller will add more details.
.Bad_Value,
"",
}
}
if specific_type_info.underlying != nil {
set_unbounded_integer_by_type(ptr, value, specific_type_info.underlying.id)
} else {
switch 8*type_info.size {
case 8: (^u8) (ptr)^ = cast(u8) value
case 16: (^u16) (ptr)^ = cast(u16) value
case 32: (^u32) (ptr)^ = cast(u32) value
case 64: (^u64) (ptr)^ = cast(u64) value
case 128: (^u128)(ptr)^ = value
}
}
error = nil
case:
if type_info.id == ^os.File {
parse_and_set_pointer_by_named_type(ptr, str, type_info.id, arg_tag, &error)

View File

@@ -307,7 +307,7 @@ test_all_bit_sets :: proc(t: ^testing.T) {
"-a:10101",
"-b:0000_0000_0000_0001",
"-c:11",
"-d:___1",
"-d:1",
"-e:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
"-f:1",
"-g:01",
@@ -333,6 +333,65 @@ test_all_bit_sets :: proc(t: ^testing.T) {
testing.expect_value(t, err.reason, flags.Parse_Error_Reason.Bad_Value)
}
}
{
// Starting a binary string with underscore is disallowed.
args := [?]string { "-d:_1" }
result := flags.parse(&s, args[:])
err, ok := result.(flags.Parse_Error)
testing.expectf(t, ok, "unexpected result: %v", result)
if ok {
testing.expect_value(t, err.reason, flags.Parse_Error_Reason.Bad_Value)
}
}
E2 :: enum {
Option_A=5,
Option_B,
Option_C=3,
Option_D,
}
R :: struct {
a: bit_set[E2],
}
r: R
{
// Least significant bit > 0
args := [?]string {
"-a:Option_A,Option_D",
}
result := flags.parse(&r, args[:])
testing.expect_value(t, result, nil)
testing.expect_value(t, r.a, bit_set[E2]{E2.Option_A, E2.Option_D})
}
{
args := [?]string { "-a:Option_Non_Existent" }
result := flags.parse(&r, args[:])
err, ok := result.(flags.Parse_Error)
testing.expectf(t, ok, "unexpected result: %v", result)
if ok {
testing.expect_value(t, err.reason, flags.Parse_Error_Reason.Bad_Value)
}
}
{
// Value names list must be strictly comma-separated.
args := [?]string { "-a:Option_A, Option_B" }
result := flags.parse(&r, args[:])
err, ok := result.(flags.Parse_Error)
testing.expectf(t, ok, "unexpected result: %v", result)
if ok {
testing.expect_value(t, err.reason, flags.Parse_Error_Reason.Bad_Value)
}
}
{
// Value names list must not end with a comma.
args := [?]string { "-a:Option_A,Option_B," }
result := flags.parse(&r, args[:])
err, ok := result.(flags.Parse_Error)
testing.expectf(t, ok, "unexpected result: %v", result)
if ok {
testing.expect_value(t, err.reason, flags.Parse_Error_Reason.Bad_Value)
}
}
}
@(test)
@@ -1288,13 +1347,11 @@ test_distinct_types :: proc(t: ^testing.T) {
unmodified_i: I,
}
s: S
{
args := [?]string {"-base-i:1"}
result := flags.parse(&s, args[:])
testing.expect_value(t, result, nil)
}
{
args := [?]string {"-unmodified-i:1"}
result := flags.parse(&s, args[:])