diff options
Diffstat (limited to 'core/flags/internal_rtti.odin')
| -rw-r--r-- | core/flags/internal_rtti.odin | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/core/flags/internal_rtti.odin b/core/flags/internal_rtti.odin new file mode 100644 index 000000000..8a11ebbc1 --- /dev/null +++ b/core/flags/internal_rtti.odin @@ -0,0 +1,548 @@ +//+private +package flags + +import "base:intrinsics" +import "base:runtime" +import "core:fmt" +import "core:mem" +@require import "core:net" +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="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: (cast(^i8) ptr)^ = cast(i8) bounded_int(value, cast(i128)min(i8), cast(i128)max(i8) ) or_return + case i16: (cast(^i16) ptr)^ = cast(i16) bounded_int(value, cast(i128)min(i16), cast(i128)max(i16) ) or_return + case i32: (cast(^i32) ptr)^ = cast(i32) bounded_int(value, cast(i128)min(i32), cast(i128)max(i32) ) or_return + case i64: (cast(^i64) ptr)^ = cast(i64) bounded_int(value, cast(i128)min(i64), cast(i128)max(i64) ) or_return + case i128: (cast(^i128) ptr)^ = value + + case int: (cast(^int) ptr)^ = cast(int) bounded_int(value, cast(i128)min(int), cast(i128)max(int) ) or_return + + case i16le: (cast(^i16le) ptr)^ = cast(i16le) bounded_int(value, cast(i128)min(i16le), cast(i128)max(i16le) ) or_return + case i32le: (cast(^i32le) ptr)^ = cast(i32le) bounded_int(value, cast(i128)min(i32le), cast(i128)max(i32le) ) or_return + case i64le: (cast(^i64le) ptr)^ = cast(i64le) bounded_int(value, cast(i128)min(i64le), cast(i128)max(i64le) ) or_return + case i128le: (cast(^i128le)ptr)^ = cast(i128le) bounded_int(value, cast(i128)min(i128le), cast(i128)max(i128le)) or_return + + case i16be: (cast(^i16be) ptr)^ = cast(i16be) bounded_int(value, cast(i128)min(i16be), cast(i128)max(i16be) ) or_return + case i32be: (cast(^i32be) ptr)^ = cast(i32be) bounded_int(value, cast(i128)min(i32be), cast(i128)max(i32be) ) or_return + case i64be: (cast(^i64be) ptr)^ = cast(i64be) bounded_int(value, cast(i128)min(i64be), cast(i128)max(i64be) ) or_return + case i128be: (cast(^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: (cast(^u8) ptr)^ = cast(u8) bounded_uint(value, cast(u128)max(u8) ) or_return + case u16: (cast(^u16) ptr)^ = cast(u16) bounded_uint(value, cast(u128)max(u16) ) or_return + case u32: (cast(^u32) ptr)^ = cast(u32) bounded_uint(value, cast(u128)max(u32) ) or_return + case u64: (cast(^u64) ptr)^ = cast(u64) bounded_uint(value, cast(u128)max(u64) ) or_return + case u128: (cast(^u128) ptr)^ = value + + case uint: (cast(^uint) ptr)^ = cast(uint) bounded_uint(value, cast(u128)max(uint) ) or_return + case uintptr: (cast(^uintptr)ptr)^ = cast(uintptr) bounded_uint(value, cast(u128)max(uintptr)) or_return + + case u16le: (cast(^u16le) ptr)^ = cast(u16le) bounded_uint(value, cast(u128)max(u16le) ) or_return + case u32le: (cast(^u32le) ptr)^ = cast(u32le) bounded_uint(value, cast(u128)max(u32le) ) or_return + case u64le: (cast(^u64le) ptr)^ = cast(u64le) bounded_uint(value, cast(u128)max(u64le) ) or_return + case u128le: (cast(^u128le) ptr)^ = cast(u128le) bounded_uint(value, cast(u128)max(u128le) ) or_return + + case u16be: (cast(^u16be) ptr)^ = cast(u16be) bounded_uint(value, cast(u128)max(u16be) ) or_return + case u32be: (cast(^u32be) ptr)^ = cast(u32be) bounded_uint(value, cast(u128)max(u32be) ) or_return + case u64be: (cast(^u64be) ptr)^ = cast(u64be) bounded_uint(value, cast(u128)max(u64be) ) or_return + case u128be: (cast(^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 + } + + (cast(^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: (cast(^f16) ptr)^ = cast(f16) value + case f32: (cast(^f32) ptr)^ = cast(f32) value + case f64: (cast(^f64) ptr)^ = value + + case f16le: (cast(^f16le)ptr)^ = cast(f16le) value + case f32le: (cast(^f32le)ptr)^ = cast(f32le) value + case f64le: (cast(^f64le)ptr)^ = cast(f64le) value + + case f16be: (cast(^f16be)ptr)^ = cast(f16be) value + case f32be: (cast(^f32be)ptr)^ = cast(f32be) value + case f64be: (cast(^f64be)ptr)^ = cast(f64be) value + } + + case runtime.Type_Info_Complex: + value := strconv.parse_complex128(str) or_return + switch type_info.id { + case complex128: (cast(^complex128)ptr)^ = value + case complex64: (cast(^complex64) ptr)^ = cast(complex64)value + case complex32: (cast(^complex32) ptr)^ = cast(complex32)value + } + + case runtime.Type_Info_Quaternion: + value := strconv.parse_quaternion256(str) or_return + switch type_info.id { + case quaternion256: (cast(^quaternion256)ptr)^ = value + case quaternion128: (cast(^quaternion128)ptr)^ = cast(quaternion128)value + case quaternion64: (cast(^quaternion64) ptr)^ = cast(quaternion64)value + } + + case runtime.Type_Info_String: + if specific_type_info.is_cstring { + cstr_ptr := cast(^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 { + (cast(^string)ptr)^ = str + } + + case runtime.Type_Info_Boolean: + value := strconv.parse_bool(str) or_return + switch type_info.id { + case bool: (cast(^bool) ptr)^ = value + case b8: (cast(^b8) ptr)^ = cast(b8) value + case b16: (cast(^b16) ptr)^ = cast(b16) value + case b32: (cast(^b32) ptr)^ = cast(b32) value + case b64: (cast(^b64) ptr)^ = cast(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 := cast(u128)(1 + specific_type_info.upper - specific_type_info.lower) + bit_index : u128 = 0 + #no_bounds_check for string_index : uint = 0; string_index < len(str); string_index += 1 { + if bit_index == max_bit_index { + // The string's too long for this bit_set. + return false + } + + switch str[string_index] { + case '1': + value |= 1 << bit_index + bit_index += 1 + case '0': + bit_index += 1 + continue + case '_': + continue + case: + return false + } + } + + 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: (cast(^u8) ptr)^ = cast(u8) value + case 16: (cast(^u16) ptr)^ = cast(u16) value + case 32: (cast(^u32) ptr)^ = cast(u32) value + case 64: (cast(^u64) ptr)^ = cast(u64) value + case 128: (cast(^u128) ptr)^ = cast(u128) value + } + } + + case: + fmt.panicf("Unsupported base data type: %v", specific_type_info) + } + + return true +} + +// This proc exists to make error handling easier, since everything in the base +// type one above works on booleans. It's a simple parsing error if it's false. +// +// However, here we have to be more careful about how we handle errors, +// especially with files. +// +// We want to provide as informative as an error as we can. +@(optimization_mode="size", disabled=NO_CORE_NAMED_TYPES) +parse_and_set_pointer_by_named_type :: proc(ptr: rawptr, str: string, data_type: typeid, arg_tag: string, out_error: ^Error) { + // Core types currently supported: + // + // - os.Handle + // - time.Time + // - datetime.DateTime + // - net.Host_Or_Endpoint + + GENERIC_RFC_3339_ERROR :: "Invalid RFC 3339 string. Try this format: `yyyy-mm-ddThh:mm:ssZ`, for example `2024-02-29T16:30:00Z`." + + out_error^ = nil + + if data_type == os.Handle { + // NOTE: `os` is hopefully available everywhere, even if it might panic on some calls. + wants_read := false + wants_write := false + mode: int + + if file, ok := get_struct_subtag(arg_tag, SUBTAG_FILE); ok { + for i := 0; i < len(file); i += 1 { + #no_bounds_check switch file[i] { + case 'r': wants_read = true + case 'w': wants_write = true + case 'c': mode |= os.O_CREATE + case 'a': mode |= os.O_APPEND + case 't': mode |= os.O_TRUNC + } + } + } + + // Sane default. + // owner/group/other: r--r--r-- + perms: int = 0o444 + + if wants_read && wants_write { + mode |= os.O_RDWR + perms |= 0o200 + } else if wants_write { + mode |= os.O_WRONLY + perms |= 0o200 + } else { + mode |= os.O_RDONLY + } + + if permstr, ok := get_struct_subtag(arg_tag, SUBTAG_PERMS); ok { + if value, parse_ok := strconv.parse_u64_of_base(permstr, 8); parse_ok { + perms = cast(int)value + } + } + + handle, errno := os.open(str, mode, perms) + if errno != 0 { + // NOTE(Feoramund): os.Errno is system-dependent, and there's + // currently no good way to translate them all into strings. + // + // The upcoming `os2` package will hopefully solve this. + // + // We can at least provide the number for now, so the user can look + // it up. + out_error^ = Open_File_Error { + str, + errno, + mode, + perms, + } + return + } + + (cast(^os.Handle)ptr)^ = handle + return + } + + when IMPORTING_TIME { + if data_type == time.Time { + // NOTE: The leap second data is discarded. + res, consumed := time.rfc3339_to_time_utc(str) + if consumed == 0 { + // The RFC 3339 parsing facilities provide no indication as to what + // went wrong, so just treat it as a regular parsing error. + out_error^ = Parse_Error { + .Bad_Value, + GENERIC_RFC_3339_ERROR, + } + return + } + + (cast(^time.Time)ptr)^ = res + return + } else if data_type == datetime.DateTime { + // NOTE: The UTC offset and leap second data are discarded. + res, _, _, consumed := time.rfc3339_to_components(str) + if consumed == 0 { + out_error^ = Parse_Error { + .Bad_Value, + GENERIC_RFC_3339_ERROR, + } + return + } + + (cast(^datetime.DateTime)ptr)^ = res + return + } + } + + when IMPORTING_NET { + if data_type == net.Host_Or_Endpoint { + addr, net_error := net.parse_hostname_or_endpoint(str) + if net_error != nil { + // We pass along `net.Error` here. + out_error^ = Parse_Error { + net_error, + "Invalid Host/Endpoint.", + } + return + } + + (cast(^net.Host_Or_Endpoint)ptr)^ = addr + return + } + } + + out_error ^= Parse_Error { + // The caller will add more details. + .Unsupported_Type, + "", + } +} + +@(optimization_mode="size") +set_unbounded_integer_by_type :: proc(ptr: rawptr, value: $T, data_type: typeid) where intrinsics.type_is_integer(T) { + switch data_type { + case i8: (cast(^i8) ptr)^ = cast(i8) value + case i16: (cast(^i16) ptr)^ = cast(i16) value + case i32: (cast(^i32) ptr)^ = cast(i32) value + case i64: (cast(^i64) ptr)^ = cast(i64) value + case i128: (cast(^i128) ptr)^ = cast(i128) value + + case int: (cast(^int) ptr)^ = cast(int) value + + case i16le: (cast(^i16le) ptr)^ = cast(i16le) value + case i32le: (cast(^i32le) ptr)^ = cast(i32le) value + case i64le: (cast(^i64le) ptr)^ = cast(i64le) value + case i128le: (cast(^i128le) ptr)^ = cast(i128le) value + + case i16be: (cast(^i16be) ptr)^ = cast(i16be) value + case i32be: (cast(^i32be) ptr)^ = cast(i32be) value + case i64be: (cast(^i64be) ptr)^ = cast(i64be) value + case i128be: (cast(^i128be) ptr)^ = cast(i128be) value + + case u8: (cast(^u8) ptr)^ = cast(u8) value + case u16: (cast(^u16) ptr)^ = cast(u16) value + case u32: (cast(^u32) ptr)^ = cast(u32) value + case u64: (cast(^u64) ptr)^ = cast(u64) value + case u128: (cast(^u128) ptr)^ = cast(u128) value + + case uint: (cast(^uint) ptr)^ = cast(uint) value + case uintptr: (cast(^uintptr)ptr)^ = cast(uintptr) value + + case u16le: (cast(^u16le) ptr)^ = cast(u16le) value + case u32le: (cast(^u32le) ptr)^ = cast(u32le) value + case u64le: (cast(^u64le) ptr)^ = cast(u64le) value + case u128le: (cast(^u128le) ptr)^ = cast(u128le) value + + case u16be: (cast(^u16be) ptr)^ = cast(u16be) value + case u32be: (cast(^u32be) ptr)^ = cast(u32be) value + case u64be: (cast(^u64be) ptr)^ = cast(u64be) value + case u128be: (cast(^u128be) ptr)^ = cast(u128be) value + + case rune: (cast(^rune) ptr)^ = cast(rune) value + + case: + fmt.panicf("Unsupported integer backing type: %v", data_type) + } +} + +@(optimization_mode="size") +parse_and_set_pointer_by_type :: proc(ptr: rawptr, str: string, type_info: ^runtime.Type_Info, arg_tag: string) -> (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 := cast(rawptr)( + cast(uintptr)ptr.data + + cast(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 cast(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, tag_ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS) + if !tag_ok { + continue + } + + pos_subtag, pos_ok := get_struct_subtag(args_tag, SUBTAG_POS) + if !pos_ok { + continue + } + + value, parse_ok := strconv.parse_u64_of_base(pos_subtag, 10) + if parse_ok && cast(int)value == pos { + return field, i, true + } + } + + return +} |