mirror of
https://github.com/odin-lang/Odin.git
synced 2026-07-11 02:19:30 +00:00
Improve bit_set support in the flags package
This commit is contained in:
@@ -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 0's and 1's, 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:
|
||||
|
||||
@@ -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."`,
|
||||
|
||||
@@ -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 {
|
||||
@@ -157,10 +157,11 @@ parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info:
|
||||
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 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
|
||||
}
|
||||
}
|
||||
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)
|
||||
|
||||
@@ -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[:])
|
||||
|
||||
Reference in New Issue
Block a user