diff options
| author | Feoramund <161657516+Feoramund@users.noreply.github.com> | 2024-06-06 18:35:43 -0400 |
|---|---|---|
| committer | Feoramund <161657516+Feoramund@users.noreply.github.com> | 2024-06-07 13:16:13 -0400 |
| commit | edb685f04be9aef407dc8abac7bb76fb51184f9f (patch) | |
| tree | 63f48c951de7a9d751895a3cd72194e241bd38c0 /core/flags/internal_assignment.odin | |
| parent | 08612423b9afffb7499d8db2c69715df1627bd50 (diff) | |
Add package `core:flags`
Diffstat (limited to 'core/flags/internal_assignment.odin')
| -rw-r--r-- | core/flags/internal_assignment.odin | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/core/flags/internal_assignment.odin b/core/flags/internal_assignment.odin new file mode 100644 index 000000000..1e715998d --- /dev/null +++ b/core/flags/internal_assignment.odin @@ -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), + } +} |