mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-10 13:21:37 -07:00
Add package core:flags
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
//+private
|
||||
package flags
|
||||
|
||||
import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
import "core:container/bit_array"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
import "core:reflect"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
|
||||
// Push a positional argument onto a data struct, checking for specified
|
||||
// positionals first before adding it to a fallback field.
|
||||
@(optimization_mode="size")
|
||||
push_positional :: #force_no_inline proc (model: ^$T, parser: ^Parser, arg: string) -> (error: Error) {
|
||||
if bit_array.get(&parser.filled_pos, parser.filled_pos.max_index) {
|
||||
// The max index is set, which means we're out of space.
|
||||
// Add one free bit by setting the index above to false.
|
||||
bit_array.set(&parser.filled_pos, 1 + parser.filled_pos.max_index, false)
|
||||
}
|
||||
|
||||
pos: int = ---
|
||||
{
|
||||
iter := bit_array.make_iterator(&parser.filled_pos)
|
||||
ok: bool
|
||||
pos, ok = bit_array.iterate_by_unset(&iter)
|
||||
|
||||
// This may be an allocator error.
|
||||
assert(ok, "Unable to find a free spot in the positional bit_array.")
|
||||
}
|
||||
|
||||
field, index, has_pos_assigned := get_field_by_pos(model, pos)
|
||||
|
||||
if !has_pos_assigned {
|
||||
when intrinsics.type_has_field(T, INTERNAL_VARIADIC_FLAG) {
|
||||
// Add it to the fallback array.
|
||||
field = reflect.struct_field_by_name(T, INTERNAL_VARIADIC_FLAG)
|
||||
} else {
|
||||
return Parse_Error {
|
||||
.Extra_Positional,
|
||||
fmt.tprintf("Got extra positional argument `%s` with nowhere to store it.", arg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ptr := cast(rawptr)(cast(uintptr)model + field.offset)
|
||||
args_tag, _ := reflect.struct_tag_lookup(field.tag, TAG_ARGS)
|
||||
field_name := get_field_name(field)
|
||||
error = parse_and_set_pointer_by_type(ptr, arg, field.type, args_tag)
|
||||
#partial switch &specific_error in error {
|
||||
case Parse_Error:
|
||||
specific_error.message = fmt.tprintf("Unable to set positional #%i (%s) of type %v to `%s`.%s%s",
|
||||
pos,
|
||||
field_name,
|
||||
field.type,
|
||||
arg,
|
||||
" " if len(specific_error.message) > 0 else "",
|
||||
specific_error.message)
|
||||
case nil:
|
||||
bit_array.set(&parser.filled_pos, pos)
|
||||
bit_array.set(&parser.fields_set, index)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
register_field :: proc(parser: ^Parser, field: reflect.Struct_Field, index: int) {
|
||||
if pos, ok := get_field_pos(field); ok {
|
||||
bit_array.set(&parser.filled_pos, pos)
|
||||
}
|
||||
|
||||
bit_array.set(&parser.fields_set, index)
|
||||
}
|
||||
|
||||
// Set a `-flag` argument, Odin-style.
|
||||
@(optimization_mode="size")
|
||||
set_odin_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (error: Error) {
|
||||
// We make a special case for help requests.
|
||||
switch name {
|
||||
case RESERVED_HELP_FLAG, RESERVED_HELP_FLAG_SHORT:
|
||||
return Help_Request{}
|
||||
}
|
||||
|
||||
field, index := get_field_by_name(model, name) or_return
|
||||
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Boolean:
|
||||
ptr := cast(^bool)(cast(uintptr)model + field.offset)
|
||||
ptr^ = true
|
||||
case:
|
||||
return Parse_Error {
|
||||
.Bad_Value,
|
||||
fmt.tprintf("Unable to set `%s` of type %v to true.", name, field.type),
|
||||
}
|
||||
}
|
||||
|
||||
register_field(parser, field, index)
|
||||
return
|
||||
}
|
||||
|
||||
// Set a `-flag` argument, UNIX-style.
|
||||
@(optimization_mode="size")
|
||||
set_unix_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (future_args: int, error: Error) {
|
||||
// We make a special case for help requests.
|
||||
switch name {
|
||||
case RESERVED_HELP_FLAG, RESERVED_HELP_FLAG_SHORT:
|
||||
return 0, Help_Request{}
|
||||
}
|
||||
|
||||
field, index := get_field_by_name(model, name) or_return
|
||||
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Boolean:
|
||||
ptr := cast(^bool)(cast(uintptr)model + field.offset)
|
||||
ptr^ = true
|
||||
case runtime.Type_Info_Dynamic_Array:
|
||||
future_args = 1
|
||||
if tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
|
||||
if length, is_variadic := get_struct_subtag(tag, SUBTAG_VARIADIC); is_variadic {
|
||||
// Variadic arrays may specify how many arguments they consume at once.
|
||||
// Otherwise, they take everything that's left.
|
||||
if value, value_ok := strconv.parse_u64_of_base(length, 10); value_ok {
|
||||
future_args = cast(int)value
|
||||
} else {
|
||||
future_args = max(int)
|
||||
}
|
||||
}
|
||||
}
|
||||
case:
|
||||
// `--flag`, waiting on its value.
|
||||
future_args = 1
|
||||
}
|
||||
|
||||
register_field(parser, field, index)
|
||||
return
|
||||
}
|
||||
|
||||
// Set a `-flag:option` argument.
|
||||
@(optimization_mode="size")
|
||||
set_option :: proc(model: ^$T, parser: ^Parser, name, option: string) -> (error: Error) {
|
||||
field, index := get_field_by_name(model, name) or_return
|
||||
|
||||
if len(option) == 0 {
|
||||
return Parse_Error {
|
||||
.No_Value,
|
||||
fmt.tprintf("Setting `%s` to an empty value is meaningless.", name),
|
||||
}
|
||||
}
|
||||
|
||||
// Guard against incorrect syntax.
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Map:
|
||||
return Parse_Error {
|
||||
.No_Value,
|
||||
fmt.tprintf("Unable to set `%s` of type %v to `%s`. Are you missing an `=`? The correct format is `map:key=value`.", name, field.type, option),
|
||||
}
|
||||
}
|
||||
|
||||
ptr := cast(rawptr)(cast(uintptr)model + field.offset)
|
||||
args_tag := reflect.struct_tag_get(field.tag, TAG_ARGS)
|
||||
error = parse_and_set_pointer_by_type(ptr, option, field.type, args_tag)
|
||||
#partial switch &specific_error in error {
|
||||
case Parse_Error:
|
||||
specific_error.message = fmt.tprintf("Unable to set `%s` of type %v to `%s`.%s%s",
|
||||
name,
|
||||
field.type,
|
||||
option,
|
||||
" " if len(specific_error.message) > 0 else "",
|
||||
specific_error.message)
|
||||
case nil:
|
||||
register_field(parser, field, index)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Set a `-map:key=value` argument.
|
||||
@(optimization_mode="size")
|
||||
set_key_value :: proc(model: ^$T, parser: ^Parser, name, key, value: string) -> (error: Error) {
|
||||
field, index := get_field_by_name(model, name) or_return
|
||||
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Map:
|
||||
key := key
|
||||
key_ptr := cast(rawptr)&key
|
||||
key_cstr: cstring
|
||||
if reflect.is_cstring(specific_type_info.key) {
|
||||
// We clone the key here, because it's liable to be a slice of an
|
||||
// Odin string, and we need to put a NUL terminator in it.
|
||||
key_cstr = strings.clone_to_cstring(key)
|
||||
key_ptr = &key_cstr
|
||||
}
|
||||
defer if key_cstr != nil {
|
||||
delete(key_cstr)
|
||||
}
|
||||
|
||||
raw_map := (^runtime.Raw_Map)(cast(uintptr)model + field.offset)
|
||||
|
||||
hash := specific_type_info.map_info.key_hasher(key_ptr, runtime.map_seed(raw_map^))
|
||||
|
||||
backing_alloc := false
|
||||
elem_backing: []byte
|
||||
value_ptr: rawptr
|
||||
|
||||
if raw_map.allocator.procedure == nil {
|
||||
raw_map.allocator = context.allocator
|
||||
} else {
|
||||
value_ptr = runtime.__dynamic_map_get(raw_map,
|
||||
specific_type_info.map_info,
|
||||
hash,
|
||||
key_ptr,
|
||||
)
|
||||
}
|
||||
|
||||
if value_ptr == nil {
|
||||
alloc_error: runtime.Allocator_Error = ---
|
||||
elem_backing, alloc_error = mem.alloc_bytes(specific_type_info.value.size, specific_type_info.value.align)
|
||||
if elem_backing == nil {
|
||||
return Parse_Error {
|
||||
alloc_error,
|
||||
"Failed to allocate element backing for map value.",
|
||||
}
|
||||
}
|
||||
|
||||
backing_alloc = true
|
||||
value_ptr = raw_data(elem_backing)
|
||||
}
|
||||
|
||||
args_tag, _ := reflect.struct_tag_lookup(field.tag, TAG_ARGS)
|
||||
error = parse_and_set_pointer_by_type(value_ptr, value, specific_type_info.value, args_tag)
|
||||
#partial switch &specific_error in error {
|
||||
case Parse_Error:
|
||||
specific_error.message = fmt.tprintf("Unable to set `%s` of type %v with key=value: `%s`=`%s`.%s%s",
|
||||
name,
|
||||
field.type,
|
||||
key,
|
||||
value,
|
||||
" " if len(specific_error.message) > 0 else "",
|
||||
specific_error.message)
|
||||
}
|
||||
|
||||
if backing_alloc {
|
||||
runtime.__dynamic_map_set(raw_map,
|
||||
specific_type_info.map_info,
|
||||
hash,
|
||||
key_ptr,
|
||||
value_ptr,
|
||||
)
|
||||
|
||||
delete(elem_backing)
|
||||
}
|
||||
|
||||
register_field(parser, field, index)
|
||||
return
|
||||
}
|
||||
|
||||
return Parse_Error {
|
||||
.Bad_Value,
|
||||
fmt.tprintf("Unable to set `%s` of type %v with key=value: `%s`=`%s`.", name, field.type, key, value),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user