package json import "core:mem" import "core:math" import "core:reflect" import "core:strconv" import "core:strings" import "core:runtime" Unmarshal_Data_Error :: enum { Invalid_Data, Invalid_Parameter, Non_Pointer_Parameter, Multiple_Use_Field, } Unsupported_Type_Error :: struct { id: typeid, token: Token, } Unmarshal_Error :: union { Error, Unmarshal_Data_Error, Unsupported_Type_Error, } unmarshal_any :: proc(data: []byte, v: any, spec := DEFAULT_SPECIFICATION, allocator := context.allocator) -> Unmarshal_Error { v := v if v == nil || v.id == nil { return .Invalid_Parameter } v = reflect.any_base(v) ti := type_info_of(v.id) if !reflect.is_pointer(ti) || ti.id == rawptr { return .Non_Pointer_Parameter } PARSE_INTEGERS :: true if !is_valid(data, spec, PARSE_INTEGERS) { return .Invalid_Data } p := make_parser(data, spec, PARSE_INTEGERS, allocator) data := any{(^rawptr)(v.data)^, ti.variant.(reflect.Type_Info_Pointer).elem.id} if v.data == nil { return .Invalid_Parameter } context.allocator = p.allocator if p.spec == .MJSON { #partial switch p.curr_token.kind { case .Ident, .String: return unmarshal_object(&p, data, .EOF) } } return unmarshal_value(&p, data) } unmarshal :: proc(data: []byte, ptr: ^$T, spec := DEFAULT_SPECIFICATION, allocator := context.allocator) -> Unmarshal_Error { return unmarshal_any(data, ptr, spec, allocator) } unmarshal_string :: proc(data: string, ptr: ^$T, spec := DEFAULT_SPECIFICATION, allocator := context.allocator) -> Unmarshal_Error { return unmarshal_any(transmute([]byte)data, ptr, spec, allocator) } @(private) assign_bool :: proc(val: any, b: bool) -> bool { v := reflect.any_core(val) switch dst in &v { case bool: dst = bool(b) case b8: dst = b8 (b) case b16: dst = b16 (b) case b32: dst = b32 (b) case b64: dst = b64 (b) case: return false } return true } @(private) assign_int :: proc(val: any, i: $T) -> bool { v := reflect.any_core(val) switch dst in &v { case i8: dst = i8 (i) case i16: dst = i16 (i) case i16le: dst = i16le (i) case i16be: dst = i16be (i) case i32: dst = i32 (i) case i32le: dst = i32le (i) case i32be: dst = i32be (i) case i64: dst = i64 (i) case i64le: dst = i64le (i) case i64be: dst = i64be (i) case i128: dst = i128 (i) case i128le: dst = i128le (i) case i128be: dst = i128be (i) case u8: dst = u8 (i) case u16: dst = u16 (i) case u16le: dst = u16le (i) case u16be: dst = u16be (i) case u32: dst = u32 (i) case u32le: dst = u32le (i) case u32be: dst = u32be (i) case u64: dst = u64 (i) case u64le: dst = u64le (i) case u64be: dst = u64be (i) case u128: dst = u128 (i) case u128le: dst = u128le (i) case u128be: dst = u128be (i) case int: dst = int (i) case uint: dst = uint (i) case uintptr: dst = uintptr(i) case: return false } return true } @(private) assign_float :: proc(val: any, f: $T) -> bool { v := reflect.any_core(val) switch dst in &v { case f16: dst = f16 (f) case f16le: dst = f16le(f) case f16be: dst = f16be(f) case f32: dst = f32 (f) case f32le: dst = f32le(f) case f32be: dst = f32be(f) case f64: dst = f64 (f) case f64le: dst = f64le(f) case f64be: dst = f64be(f) case complex32: dst = complex(f16(f), 0) case complex64: dst = complex(f32(f), 0) case complex128: dst = complex(f64(f), 0) case quaternion64: dst = quaternion(f16(f), 0, 0, 0) case quaternion128: dst = quaternion(f32(f), 0, 0, 0) case quaternion256: dst = quaternion(f64(f), 0, 0, 0) case: return false } return true } @(private) unmarshal_string_token :: proc(p: ^Parser, val: any, str: string, ti: ^reflect.Type_Info) -> bool { val := val switch dst in &val { case string: dst = str return true case cstring: if str == "" { dst = strings.clone_to_cstring("", p.allocator) } else { // NOTE: This is valid because 'clone_string' appends a NUL terminator dst = cstring(raw_data(str)) } return true } #partial switch variant in ti.variant { case reflect.Type_Info_Enum: for name, i in variant.names { if name == str { assign_int(val, variant.values[i]) return true } } // TODO(bill): should this be an error or not? return true case reflect.Type_Info_Integer: i := strconv.parse_i128(str) or_return if assign_int(val, i) { return true } if assign_float(val, i) { return true } case reflect.Type_Info_Float: f := strconv.parse_f64(str) or_return if assign_int(val, f) { return true } if assign_float(val, f) { return true } } return false } @(private) unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) { UNSUPPORTED_TYPE := Unsupported_Type_Error{v.id, p.curr_token} token := p.curr_token v := v ti := reflect.type_info_base(type_info_of(v.id)) // NOTE: If it's a union with only one variant, then treat it as that variant if u, ok := ti.variant.(reflect.Type_Info_Union); ok && len(u.variants) == 1 && token.kind != .Null { variant := u.variants[0] v.id = variant.id ti = reflect.type_info_base(variant) if !(u.maybe && reflect.is_pointer(variant)) { tag := any{rawptr(uintptr(v.data) + u.tag_offset), u.tag_type.id} assign_int(tag, 1) } } #partial switch token.kind { case .Null: mem.zero(v.data, ti.size) advance_token(p) return case .False, .True: advance_token(p) if assign_bool(v, token.kind == .True) { return } return UNSUPPORTED_TYPE case .Integer: advance_token(p) i, _ := strconv.parse_i128(token.text) if assign_int(v, i) { return } if assign_float(v, i) { return } return UNSUPPORTED_TYPE case .Float: advance_token(p) f, _ := strconv.parse_f64(token.text) if assign_float(v, f) { return } if i, fract := math.modf(f); fract == 0 { if assign_int(v, i) { return } if assign_float(v, i) { return } } return UNSUPPORTED_TYPE case .Ident: advance_token(p) if p.spec == .MJSON { if unmarshal_string_token(p, any{v.data, ti.id}, token.text, ti) { return nil } } return UNSUPPORTED_TYPE case .String: advance_token(p) str := unquote_string(token, p.spec, p.allocator) or_return if unmarshal_string_token(p, any{v.data, ti.id}, str, ti) { return nil } delete(str, p.allocator) return UNSUPPORTED_TYPE case .Open_Brace: return unmarshal_object(p, v, .Close_Brace) case .Open_Bracket: return unmarshal_array(p, v) case: if p.spec != .JSON { #partial switch token.kind { case .Infinity: advance_token(p) f: f64 = 0h7ff0000000000000 if token.text[0] == '-' { f = 0hfff0000000000000 } if assign_float(v, f) { return } return UNSUPPORTED_TYPE case .NaN: advance_token(p) f: f64 = 0h7ff7ffffffffffff if token.text[0] == '-' { f = 0hfff7ffffffffffff } if assign_float(v, f) { return } return UNSUPPORTED_TYPE } } } advance_token(p) return UNSUPPORTED_TYPE } @(private) unmarshal_expect_token :: proc(p: ^Parser, kind: Token_Kind, loc := #caller_location) -> Token { prev := p.curr_token err := expect_token(p, kind) assert(err == nil, "unmarshal_expect_token") return prev } @(private) unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unmarshal_Error) { UNSUPPORTED_TYPE := Unsupported_Type_Error{v.id, p.curr_token} if end_token == .Close_Brace { assert(expect_token(p, .Open_Brace) == nil) } v := v v = reflect.any_base(v) ti := type_info_of(v.id) #partial switch t in ti.variant { case reflect.Type_Info_Struct: if t.is_raw_union { return UNSUPPORTED_TYPE } struct_loop: for p.curr_token.kind != end_token { key, _ := parse_object_key(p, p.allocator) defer delete(key, p.allocator) unmarshal_expect_token(p, .Colon) fields := reflect.struct_fields_zipped(ti.id) field_used := make([]bool, len(fields), context.temp_allocator) use_field_idx := -1 for field, field_idx in fields { tag_value := string(reflect.struct_tag_get(field.tag, "json")) if key == tag_value { use_field_idx = field_idx break } } if use_field_idx < 0 { for field, field_idx in fields { if key == field.name { use_field_idx = field_idx break } } } if use_field_idx >= 0 { if field_used[use_field_idx] { return .Multiple_Use_Field } field_used[use_field_idx] = true offset := fields[use_field_idx].offset type := fields[use_field_idx].type name := fields[use_field_idx].name field_ptr := rawptr(uintptr(v.data) + offset) field := any{field_ptr, type.id} unmarshal_value(p, field) or_return if parse_comma(p) { break struct_loop } continue struct_loop } return Unsupported_Type_Error{v.id, p.curr_token} } case reflect.Type_Info_Map: if !reflect.is_string(t.key) { return UNSUPPORTED_TYPE } raw_map := (^mem.Raw_Map)(v.data) if raw_map.entries.allocator.procedure == nil { raw_map.entries.allocator = p.allocator } header := runtime.__get_map_header_runtime(raw_map, t) elem_backing := bytes_make(t.value.size, t.value.align, p.allocator) or_return defer delete(elem_backing, p.allocator) map_backing_value := any{raw_data(elem_backing), t.value.id} map_loop: for p.curr_token.kind != end_token { key, _ := parse_object_key(p, p.allocator) unmarshal_expect_token(p, .Colon) mem.zero_slice(elem_backing) if err := unmarshal_value(p, map_backing_value); err != nil { delete(key, p.allocator) return err } hash := runtime.Map_Hash { hash = runtime.default_hasher_string(&key, 0), key_ptr = &key, } key_cstr: cstring if reflect.is_cstring(t.key) { key_cstr = cstring(raw_data(key)) hash.key_ptr = &key_cstr } set_ptr := runtime.__dynamic_map_set(header, hash, map_backing_value.data) if set_ptr == nil { delete(key, p.allocator) } if parse_comma(p) { break map_loop } } case reflect.Type_Info_Enumerated_Array: index_type := reflect.type_info_base(t.index) enum_type := index_type.variant.(reflect.Type_Info_Enum) enumerated_array_loop: for p.curr_token.kind != end_token { key, _ := parse_object_key(p, p.allocator) unmarshal_expect_token(p, .Colon) defer delete(key, p.allocator) index := -1 for name, i in enum_type.names { if key == name { index = int(enum_type.values[i] - t.min_value) break } } if index < 0 || index >= t.count { return UNSUPPORTED_TYPE } index_ptr := rawptr(uintptr(v.data) + uintptr(index*t.elem_size)) index_any := any{index_ptr, t.elem.id} unmarshal_value(p, index_any) or_return if parse_comma(p) { break enumerated_array_loop } } return nil case: return UNSUPPORTED_TYPE } if end_token == .Close_Brace { assert(expect_token(p, .Close_Brace) == nil) } return } @(private) unmarshal_count_array :: proc(p: ^Parser) -> (length: uintptr) { p_backup := p^ p.allocator = mem.nil_allocator() unmarshal_expect_token(p, .Open_Bracket) array_length_loop: for p.curr_token.kind != .Close_Bracket { _, _ = parse_value(p) length += 1 if parse_comma(p) { break } } p^ = p_backup return } @(private) unmarshal_array :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) { assign_array :: proc(p: ^Parser, base: rawptr, elem: ^reflect.Type_Info, length: uintptr) -> Unmarshal_Error { unmarshal_expect_token(p, .Open_Bracket) for idx: uintptr = 0; p.curr_token.kind != .Close_Bracket; idx += 1 { assert(idx < length) elem_ptr := rawptr(uintptr(base) + idx*uintptr(elem.size)) elem := any{elem_ptr, elem.id} unmarshal_value(p, elem) or_return if parse_comma(p) { break } } unmarshal_expect_token(p, .Close_Bracket) return nil } UNSUPPORTED_TYPE := Unsupported_Type_Error{v.id, p.curr_token} ti := reflect.type_info_base(type_info_of(v.id)) length := unmarshal_count_array(p) #partial switch t in ti.variant { case reflect.Type_Info_Slice: raw := (^mem.Raw_Slice)(v.data) data := bytes_make(t.elem.size * int(length), t.elem.align, p.allocator) or_return raw.data = raw_data(data) raw.len = int(length) return assign_array(p, raw.data, t.elem, length) case reflect.Type_Info_Dynamic_Array: raw := (^mem.Raw_Dynamic_Array)(v.data) data := bytes_make(t.elem.size * int(length), t.elem.align, p.allocator) or_return raw.data = raw_data(data) raw.len = int(length) raw.cap = int(length) raw.allocator = p.allocator return assign_array(p, raw.data, t.elem, length) case reflect.Type_Info_Array: // NOTE(bill): Allow lengths which are less than the dst array if int(length) > t.count { return UNSUPPORTED_TYPE } return assign_array(p, v.data, t.elem, length) case reflect.Type_Info_Enumerated_Array: // NOTE(bill): Allow lengths which are less than the dst array if int(length) > t.count { return UNSUPPORTED_TYPE } return assign_array(p, v.data, t.elem, length) case reflect.Type_Info_Complex: // NOTE(bill): Allow lengths which are less than the dst array if int(length) > 2 { return UNSUPPORTED_TYPE } switch ti.id { case complex32: return assign_array(p, v.data, type_info_of(f16), 2) case complex64: return assign_array(p, v.data, type_info_of(f32), 2) case complex128: return assign_array(p, v.data, type_info_of(f64), 2) } return UNSUPPORTED_TYPE } return UNSUPPORTED_TYPE }