//+private package flags import "base:intrinsics" import "base:runtime" import "core:fmt" import "core:mem" import "core:os" import "core:reflect" import "core:strconv" import "core:strings" @require import "core:time" @require import "core:time/datetime" import "core:unicode/utf8" @(optimization_mode="favor_size") parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info: ^runtime.Type_Info) -> bool { bounded_int :: proc(value, min, max: i128) -> (result: i128, ok: bool) { return value, min <= value && value <= max } bounded_uint :: proc(value, max: u128) -> (result: u128, ok: bool) { return value, value <= max } // NOTE(Feoramund): This procedure has been written with the goal in mind // of generating the least amount of assembly, given that this library is // likely to be called once and forgotten. // // I've rewritten the switch tables below in 3 different ways, and the // current one generates the least amount of code for me on Linux AMD64. // // The other two ways were: // // - the original implementation: use of parametric polymorphism which led // to dozens of functions generated, one for each type. // // - a `value, ok` assignment statement with the `or_return` done at the // end of the switch, instead of inline. // // This seems to be the smallest way for now. #partial switch specific_type_info in type_info.variant { case runtime.Type_Info_Integer: if specific_type_info.signed { value := strconv.parse_i128(str) or_return switch type_info.id { case i8: (^i8) (ptr)^ = cast(i8) bounded_int(value, cast(i128)min(i8), cast(i128)max(i8) ) or_return case i16: (^i16) (ptr)^ = cast(i16) bounded_int(value, cast(i128)min(i16), cast(i128)max(i16) ) or_return case i32: (^i32) (ptr)^ = cast(i32) bounded_int(value, cast(i128)min(i32), cast(i128)max(i32) ) or_return case i64: (^i64) (ptr)^ = cast(i64) bounded_int(value, cast(i128)min(i64), cast(i128)max(i64) ) or_return case i128: (^i128) (ptr)^ = value case int: (^int) (ptr)^ = cast(int) bounded_int(value, cast(i128)min(int), cast(i128)max(int) ) or_return case i16le: (^i16le) (ptr)^ = cast(i16le) bounded_int(value, cast(i128)min(i16le), cast(i128)max(i16le) ) or_return case i32le: (^i32le) (ptr)^ = cast(i32le) bounded_int(value, cast(i128)min(i32le), cast(i128)max(i32le) ) or_return case i64le: (^i64le) (ptr)^ = cast(i64le) bounded_int(value, cast(i128)min(i64le), cast(i128)max(i64le) ) or_return case i128le: (^i128le)(ptr)^ = cast(i128le) bounded_int(value, cast(i128)min(i128le), cast(i128)max(i128le)) or_return case i16be: (^i16be) (ptr)^ = cast(i16be) bounded_int(value, cast(i128)min(i16be), cast(i128)max(i16be) ) or_return case i32be: (^i32be) (ptr)^ = cast(i32be) bounded_int(value, cast(i128)min(i32be), cast(i128)max(i32be) ) or_return case i64be: (^i64be) (ptr)^ = cast(i64be) bounded_int(value, cast(i128)min(i64be), cast(i128)max(i64be) ) or_return case i128be: (^i128be)(ptr)^ = cast(i128be) bounded_int(value, cast(i128)min(i128be), cast(i128)max(i128be)) or_return } } else { value := strconv.parse_u128(str) or_return switch type_info.id { case u8: (^u8) (ptr)^ = cast(u8) bounded_uint(value, cast(u128)max(u8) ) or_return case u16: (^u16) (ptr)^ = cast(u16) bounded_uint(value, cast(u128)max(u16) ) or_return case u32: (^u32) (ptr)^ = cast(u32) bounded_uint(value, cast(u128)max(u32) ) or_return case u64: (^u64) (ptr)^ = cast(u64) bounded_uint(value, cast(u128)max(u64) ) or_return case u128: (^u128) (ptr)^ = value case uint: (^uint) (ptr)^ = cast(uint) bounded_uint(value, cast(u128)max(uint) ) or_return case uintptr: (^uintptr)(ptr)^ = cast(uintptr) bounded_uint(value, cast(u128)max(uintptr)) or_return case u16le: (^u16le) (ptr)^ = cast(u16le) bounded_uint(value, cast(u128)max(u16le) ) or_return case u32le: (^u32le) (ptr)^ = cast(u32le) bounded_uint(value, cast(u128)max(u32le) ) or_return case u64le: (^u64le) (ptr)^ = cast(u64le) bounded_uint(value, cast(u128)max(u64le) ) or_return case u128le: (^u128le) (ptr)^ = cast(u128le) bounded_uint(value, cast(u128)max(u128le) ) or_return case u16be: (^u16be) (ptr)^ = cast(u16be) bounded_uint(value, cast(u128)max(u16be) ) or_return case u32be: (^u32be) (ptr)^ = cast(u32be) bounded_uint(value, cast(u128)max(u32be) ) or_return case u64be: (^u64be) (ptr)^ = cast(u64be) bounded_uint(value, cast(u128)max(u64be) ) or_return case u128be: (^u128be) (ptr)^ = cast(u128be) bounded_uint(value, cast(u128)max(u128be) ) or_return } } case runtime.Type_Info_Rune: if utf8.rune_count_in_string(str) != 1 { return false } (^rune)(ptr)^ = utf8.rune_at_pos(str, 0) case runtime.Type_Info_Float: value := strconv.parse_f64(str) or_return switch type_info.id { case f16: (^f16) (ptr)^ = cast(f16) value case f32: (^f32) (ptr)^ = cast(f32) value case f64: (^f64) (ptr)^ = value case f16le: (^f16le)(ptr)^ = cast(f16le) value case f32le: (^f32le)(ptr)^ = cast(f32le) value case f64le: (^f64le)(ptr)^ = cast(f64le) value case f16be: (^f16be)(ptr)^ = cast(f16be) value 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 { case complex32: (^complex32) (ptr)^ = (complex32)(value) 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 { case quaternion64: (^quaternion64) (ptr)^ = (quaternion64)(value) case quaternion128: (^quaternion128)(ptr)^ = (quaternion128)(value) case quaternion256: (^quaternion256)(ptr)^ = value } case runtime.Type_Info_String: if specific_type_info.is_cstring { cstr_ptr := (^cstring)(ptr) if cstr_ptr != nil { // Prevent memory leaks from us setting this value multiple times. delete(cstr_ptr^) } cstr_ptr^ = strings.clone_to_cstring(str) } else { (^string)(ptr)^ = str } case runtime.Type_Info_Boolean: value := strconv.parse_bool(str) or_return switch type_info.id { case bool: (^bool)(ptr)^ = value case b8: (^b8) (ptr)^ = b8(value) case b16: (^b16) (ptr)^ = b16(value) case b32: (^b32) (ptr)^ = b32(value) case b64: (^b64) (ptr)^ = b64(value) } case runtime.Type_Info_Bit_Set: // Parse a string of 1's and 0's, 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) bit_index := u128(0) #no_bounds_check for string_index in 0.. (error: Error) { #partial switch specific_type_info in type_info.variant { case runtime.Type_Info_Named: if global_custom_type_setter != nil { // The program gets to go first. error_message, handled, alloc_error := global_custom_type_setter(ptr, type_info.id, str, arg_tag) if alloc_error != nil { // There was an allocation error. Bail out. return Parse_Error { alloc_error, "Custom type setter encountered allocation error.", } } if handled { // The program handled the type. if len(error_message) != 0 { // However, there was an error. Pass it along. error = Parse_Error { .Bad_Value, error_message, } } return } } // Might be a named enum. Need to check here first, since we handle all enums. if enum_type_info, is_enum := specific_type_info.base.variant.(runtime.Type_Info_Enum); is_enum { if value, ok := reflect.enum_from_name_any(type_info.id, str); ok { set_unbounded_integer_by_type(ptr, value, enum_type_info.base.id) } else { return Parse_Error { .Bad_Value, fmt.tprintf("Invalid value name. Valid names are: %s", enum_type_info.names), } } } 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. if _, is_indistinct := get_struct_subtag(arg_tag, SUBTAG_INDISTINCT); is_indistinct { return parse_and_set_pointer_by_type(ptr, str, specific_type_info.base, arg_tag) } } } case runtime.Type_Info_Dynamic_Array: ptr := cast(^runtime.Raw_Dynamic_Array)ptr // Try to convert the value first. elem_backing, alloc_error := mem.alloc_bytes(specific_type_info.elem.size, specific_type_info.elem.align) if alloc_error != nil { return Parse_Error { alloc_error, "Failed to allocate element backing for dynamic array.", } } defer delete(elem_backing) parse_and_set_pointer_by_type(raw_data(elem_backing), str, specific_type_info.elem, arg_tag) or_return if !runtime.__dynamic_array_resize(ptr, specific_type_info.elem.size, specific_type_info.elem.align, ptr.len + 1) { // NOTE: This is purely an assumption that it's OOM. // Regardless, the resize failed. return Parse_Error { runtime.Allocator_Error.Out_Of_Memory, "Failed to resize dynamic array.", } } subptr := rawptr( uintptr(ptr.data) + uintptr((ptr.len - 1) * specific_type_info.elem.size)) mem.copy(subptr, raw_data(elem_backing), len(elem_backing)) case runtime.Type_Info_Enum: // This is a nameless enum. // The code here is virtually the same as above for named enums. if value, ok := reflect.enum_from_name_any(type_info.id, str); ok { set_unbounded_integer_by_type(ptr, value, specific_type_info.base.id) } else { return Parse_Error { .Bad_Value, fmt.tprintf("Invalid value name. Valid names are: %s", specific_type_info.names), } } case: if !parse_and_set_pointer_by_base_type(ptr, str, type_info) { return Parse_Error { // The caller will add more details. .Bad_Value, "", } } } return } get_struct_subtag :: get_subtag get_field_name :: proc(field: reflect.Struct_Field) -> string { if args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok { if name_subtag, name_ok := get_struct_subtag(args_tag, SUBTAG_NAME); name_ok { return name_subtag } } name, _ := strings.replace_all(field.name, "_", "-", context.temp_allocator) return name } get_field_pos :: proc(field: reflect.Struct_Field) -> (int, bool) { if args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok { if pos_subtag, pos_ok := get_struct_subtag(args_tag, SUBTAG_POS); pos_ok { if value, parse_ok := strconv.parse_u64_of_base(pos_subtag, 10); parse_ok { return int(value), true } } } return 0, false } // Get a struct field by its field name or `name` subtag. get_field_by_name :: proc(model: ^$T, name: string) -> (result: reflect.Struct_Field, index: int, error: Error) { for field, i in reflect.struct_fields_zipped(T) { if get_field_name(field) == name { return field, i, nil } } error = Parse_Error { .Missing_Flag, fmt.tprintf("Unable to find any flag named `%s`.", name), } return } // Get a struct field by its `pos` subtag. get_field_by_pos :: proc(model: ^$T, pos: int) -> (result: reflect.Struct_Field, index: int, ok: bool) { for field, i in reflect.struct_fields_zipped(T) { args_tag := reflect.struct_tag_lookup(field.tag, TAG_ARGS) or_continue pos_subtag := get_struct_subtag(args_tag, SUBTAG_POS) or_continue value, parse_ok := strconv.parse_u64_of_base(pos_subtag, 10) if parse_ok && cast(int)value == pos { return field, i, true } } return }