626 lines
21 KiB
Plaintext
626 lines
21 KiB
Plaintext
|
|
// Parse a JSON string into the given Type.
|
|
// All members of Type that are not present in the JSON are kept at their default values.
|
|
// All fields in the JSON that have no corresponding member in Type are ignored by default
|
|
// but you can pass ignore_unknown = false to fail instead.
|
|
json_parse_string :: (content: string, $T: Type, ignore_unknown := true, rename := rename_by_note) -> success: bool, T {
|
|
result: T;
|
|
if !content then return false, result;
|
|
|
|
info := type_info(T);
|
|
remainder, success := parse_value(content, cast(*u8)*result, info, ignore_unknown, "", rename=rename);
|
|
if !success return false, result;
|
|
|
|
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
|
if remainder.count {
|
|
log_error("Unexpected trailing characters: %", remainder);
|
|
return false, result;
|
|
}
|
|
|
|
return true, result;
|
|
}
|
|
|
|
json_parse_file :: (filename: string, $T: Type, ignore_unknown := true, rename := rename_by_note) -> success: bool, T {
|
|
file_data, success := read_entire_file(filename);
|
|
result: T;
|
|
if !success {
|
|
log_error("Could not read file: %", filename);
|
|
return false, result;
|
|
}
|
|
defer free(file_data);
|
|
|
|
if (context.log_level >= .VERBOSE) {
|
|
log("Read file: %", success);
|
|
}
|
|
success, result = json_parse_string(file_data, T, ignore_unknown, rename=rename);
|
|
return success, result;
|
|
}
|
|
|
|
json_write_native :: (builder: *String_Builder, data: *void, info: *Type_Info, indent_char := "\t", ignore := ignore_by_note, rename := rename_by_note, level := 0) {
|
|
if info.type == {
|
|
case .BOOL;
|
|
append(builder, ifx <<(cast(*bool) data) "true" else "false");
|
|
case .INTEGER; #through;
|
|
case .FLOAT;
|
|
any_val: Any;
|
|
any_val.type = info;
|
|
any_val.value_pointer = data;
|
|
print_item_to_builder(builder, any_val);
|
|
case .ENUM;
|
|
any_val: Any;
|
|
any_val.type = info;
|
|
any_val.value_pointer = data;
|
|
|
|
append(builder, #char "\"");
|
|
print_item_to_builder(builder, any_val);
|
|
append(builder, #char "\"");
|
|
case .STRING;
|
|
json_append_escaped(builder, <<(cast(*string) data));
|
|
case .ARRAY;
|
|
info_array := cast(*Type_Info_Array) info;
|
|
element_size := info_array.element_type.runtime_size;
|
|
assert(element_size != -1);
|
|
|
|
stride := element_size;
|
|
array_data := data;
|
|
array_count := info_array.array_count;
|
|
if info_array.array_count == -1 {
|
|
array_count = << cast(*s64) data;
|
|
|
|
array_dest: **void = data + 8;
|
|
array_data = << array_dest;
|
|
}
|
|
|
|
append(builder, "[");
|
|
if array_data {
|
|
if indent_char.count {
|
|
append(builder, "\n");
|
|
for 0..level append(builder, indent_char);
|
|
}
|
|
for 0..array_count-1 {
|
|
json_write_native(builder, array_data, info_array.element_type, indent_char, ignore, rename, level + 1);
|
|
if it != array_count - 1 append(builder, ",");
|
|
array_data += stride;
|
|
}
|
|
}
|
|
if indent_char.count {
|
|
append(builder, "\n");
|
|
for 0..level-1 append(builder, indent_char);
|
|
}
|
|
append(builder, "]");
|
|
case .STRUCT;
|
|
struct_info := cast(*Type_Info_Struct) info;
|
|
if is_generic_json_value(info) {
|
|
value := cast(*JSON_Value) data;
|
|
json_write_json_value(builder, <<value, indent_char, level);
|
|
} else {
|
|
append(builder, #char "{");
|
|
first := true;
|
|
json_write_native_members(builder, data, struct_info.members, indent_char, ignore, rename, level, *first);
|
|
if indent_char.count {
|
|
append(builder, "\n");
|
|
for 0..level-1 append(builder, indent_char);
|
|
}
|
|
append(builder, "}");
|
|
}
|
|
case .POINTER;
|
|
ptr_info := cast(*Type_Info_Pointer) info;
|
|
ptr := << cast(**void) data;
|
|
if ptr {
|
|
json_write_native(builder, ptr, ptr_info.pointer_to, indent_char, ignore, rename, level);
|
|
} else {
|
|
append(builder, "null");
|
|
}
|
|
case;
|
|
assert(false, "Unsupported type: %", info.type);
|
|
}
|
|
}
|
|
|
|
#scope_file
|
|
|
|
json_write_native_members :: (builder: *String_Builder, data: *void, members: [] Type_Info_Struct_Member, indent_char := "\t", ignore := ignore_by_note, rename: Rename_Proc, level := 0, first: *bool) {
|
|
for * member: members {
|
|
if member.flags & .CONSTANT continue;
|
|
if ignore(member) continue;
|
|
if (member.type.type == .STRUCT && member.flags & .USING) {
|
|
info := cast(*Type_Info_Struct) member.type;
|
|
json_write_native_members(builder, data + member.offset_in_bytes, info.members, indent_char, ignore, rename, level, first);
|
|
} else {
|
|
if !<<first append(builder, ",");
|
|
<<first = false;
|
|
|
|
if indent_char.count {
|
|
append(builder, "\n");
|
|
for 0..level append(builder, indent_char);
|
|
}
|
|
|
|
renamed_name := rename(member);
|
|
name := ifx renamed_name.count > 0 renamed_name else member.name;
|
|
|
|
json_append_escaped(builder, name);
|
|
append(builder, ": ");
|
|
json_write_native(builder, data + member.offset_in_bytes, member.type, indent_char, ignore, rename, level + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
is_generic_json_value_or_pointer_to_it :: (info: *Type_Info) -> bool {
|
|
value_info := info;
|
|
if info.type == .POINTER {
|
|
pointer_info := cast(*Type_Info_Pointer) info;
|
|
value_info = pointer_info.pointer_to;
|
|
}
|
|
|
|
return is_generic_json_value(info);
|
|
}
|
|
|
|
is_generic_json_value :: (info: *Type_Info) -> bool {
|
|
return info == type_info(JSON_Value);
|
|
}
|
|
|
|
parse_value :: (to_parse: string, slot: *u8, info: *Type_Info, ignore_unknown: bool, field_name: string, rename: Rename_Proc) -> remainder: string, success: bool {
|
|
remainder := trim_left(to_parse, WHITESPACE_CHARS);
|
|
success := true;
|
|
|
|
prepare_slot :: (expected_type: Type_Info_Tag, info: *Type_Info, slot: *u8, to_parse: string) -> *u8, success: bool, is_generic: bool, info: *Type_Info {
|
|
value_info := info;
|
|
if info.type == .POINTER {
|
|
pointer_info := cast(*Type_Info_Pointer) info;
|
|
value_info = pointer_info.pointer_to;
|
|
}
|
|
|
|
if info.type == .ENUM {
|
|
info_enum := cast(*Type_Info_Enum)info;
|
|
value_info = info_enum.internal_type;
|
|
}
|
|
|
|
is_generic := is_generic_json_value(value_info);
|
|
|
|
if !is_generic && value_info.type != expected_type {
|
|
teaser := to_parse;
|
|
if teaser.count > 50 teaser.count = 50;
|
|
builder: String_Builder;
|
|
print_type_to_builder(*builder, info);
|
|
type_name := builder_to_string(*builder,, temp);
|
|
log_error("Cannot parse % value into type \"%\". Remaining input is: %…", expected_type, type_name, teaser);
|
|
return null, false, false, value_info;
|
|
}
|
|
|
|
if info.type == .POINTER {
|
|
value_slot := alloc(value_info.runtime_size);
|
|
initializer: (*void) #no_context;
|
|
if value_info.type == .STRUCT {
|
|
struct_info := cast(*Type_Info_Struct) value_info;
|
|
initializer = struct_info.initializer;
|
|
}
|
|
if initializer {
|
|
initializer(value_slot);
|
|
} else {
|
|
memset(value_slot, 0, value_info.runtime_size);
|
|
}
|
|
<<cast(**u8)slot = value_slot;
|
|
return value_slot, true, is_generic, value_info;
|
|
} else {
|
|
return slot, true, is_generic, value_info;
|
|
}
|
|
}
|
|
|
|
is_generic: bool;
|
|
if remainder[0] == {
|
|
case #char "n";
|
|
remainder, success = expect_and_slice(remainder, "null");
|
|
if !success return remainder, false;
|
|
if slot {
|
|
if info.type == .POINTER {
|
|
<<cast(**void) slot = null;
|
|
} else {
|
|
builder: String_Builder;
|
|
print_type_to_builder(*builder, info);
|
|
type_name := builder_to_string(*builder,, temp);
|
|
log_error("Got NULL value for non-pointer type \"%\" of field \"%\". Keeping default value instead.", type_name, field_name);
|
|
}
|
|
}
|
|
return remainder, true;
|
|
case #char "t";
|
|
remainder, success = expect_and_slice(remainder, "true");
|
|
if !success return remainder, false;
|
|
if slot {
|
|
value_slot: *u8;
|
|
value_slot, success, is_generic = prepare_slot(.BOOL, info, slot, to_parse);
|
|
if success {
|
|
if is_generic {
|
|
json_set(cast(*JSON_Value)value_slot, true);
|
|
} else {
|
|
<<cast(*bool)value_slot = true;
|
|
}
|
|
}
|
|
}
|
|
case #char "f";
|
|
remainder, success = expect_and_slice(remainder, "false");
|
|
if !success return remainder, false;
|
|
if slot {
|
|
value_slot: *u8;
|
|
value_slot, success, is_generic = prepare_slot(.BOOL, info, slot, to_parse);
|
|
if success {
|
|
if is_generic {
|
|
json_set(cast(*JSON_Value)value_slot, false);
|
|
} else {
|
|
<<cast(*bool)value_slot = false;
|
|
}
|
|
}
|
|
}
|
|
case #char "\"";
|
|
if slot && info && info.type == .ENUM {
|
|
info_enum := cast(*Type_Info_Enum)info;
|
|
value_slot: *u8;
|
|
value_slot, success, is_generic = prepare_slot(.INTEGER, info_enum.internal_type, slot, to_parse);
|
|
remainder, success = parse_enum_string(remainder, value_slot, info_enum);
|
|
} else {
|
|
value: string;
|
|
value, remainder, success = parse_string(remainder);
|
|
stored := false;
|
|
defer if !stored free(value);
|
|
if success && slot {
|
|
value_slot: *u8;
|
|
value_slot, success, is_generic = prepare_slot(.STRING, info, slot, to_parse);
|
|
if success {
|
|
if is_generic {
|
|
json_set(cast(*JSON_Value)value_slot, value);
|
|
} else {
|
|
<<cast(*string)value_slot = value;
|
|
}
|
|
stored = true;
|
|
}
|
|
}
|
|
}
|
|
case #char "[";
|
|
value_slot: *u8;
|
|
value_info: *Type_Info;
|
|
if slot {
|
|
value_slot, success, is_generic, value_info = prepare_slot(.ARRAY, info, slot, to_parse);
|
|
}
|
|
if success {
|
|
if is_generic {
|
|
value: [] JSON_Value;
|
|
value, remainder, success = parse_array(remainder);
|
|
json_set(cast(*JSON_Value)value_slot, value);
|
|
} else {
|
|
remainder, success = parse_array(remainder, value_slot, cast(*Type_Info_Array) value_info, ignore_unknown, rename=rename);
|
|
}
|
|
}
|
|
case #char "{";
|
|
value_slot: *u8;
|
|
value_info: *Type_Info;
|
|
if slot {
|
|
value_slot, success, is_generic, value_info = prepare_slot(.STRUCT, info, slot, to_parse);
|
|
}
|
|
if success {
|
|
if is_generic {
|
|
value := New(JSON_Object);
|
|
<<value, remainder, success = parse_object(remainder);
|
|
json_set(cast(*JSON_Value)value_slot, value);
|
|
} else {
|
|
remainder, success = parse_object(remainder, value_slot, cast(*Type_Info_Struct) value_info, ignore_unknown, rename=rename);
|
|
}
|
|
}
|
|
case;
|
|
if slot == null || info.type == .FLOAT || is_generic_json_value_or_pointer_to_it(info) {
|
|
float_value: float64;
|
|
float_value, success, remainder = string_to_float64(remainder);
|
|
if success && slot {
|
|
value_slot: *u8;
|
|
value_info: *Type_Info;
|
|
value_slot, success, is_generic, value_info = prepare_slot(.FLOAT, info, slot, to_parse);
|
|
if success {
|
|
if is_generic {
|
|
json_set(cast(*JSON_Value)value_slot, float_value);
|
|
} else {
|
|
if value_info.runtime_size == 4 {
|
|
(<< cast(*float) slot) = cast(float) float_value;
|
|
} else {
|
|
assert(value_info.runtime_size == 8);
|
|
(<< cast(*float64) slot) = float_value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if slot {
|
|
value_slot: *u8;
|
|
value_info: *Type_Info;
|
|
value_slot, success, is_generic, value_info = prepare_slot(.INTEGER, info, slot, to_parse);
|
|
if success {
|
|
if is_generic {
|
|
int_value: s64;
|
|
int_value, success, remainder = string_to_int(remainder, T = s64);
|
|
if success {
|
|
json_set(cast(*JSON_Value)value_slot, int_value);
|
|
} else {
|
|
log_error("Could not parse \"%\" as an integer.", to_parse);
|
|
}
|
|
} else {
|
|
info_int := cast(*Type_Info_Integer) value_info;
|
|
success, remainder = parse_and_write_integer(info_int, value_slot, to_parse);
|
|
}
|
|
}
|
|
} else {
|
|
int_value: s64;
|
|
int_value, success, remainder = string_to_int(remainder, T = s64);
|
|
if !success {
|
|
log_error("Could not parse \"%\" as an integer.", to_parse);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return remainder, success;
|
|
}
|
|
|
|
parse_enum_string :: (str: string, slot: *u8, info_enum: *Type_Info_Enum) -> remainder: string, success: bool {
|
|
value, remainder, success := parse_string(str);
|
|
defer free(value);
|
|
if !success return remainder, false;
|
|
|
|
// Parse by members' names
|
|
normalize_enum_value :: inline (name: string) -> string #expand {
|
|
normalized := trim(name);
|
|
if normalized.count > info_enum.name.count && starts_with(normalized, info_enum.name) && normalized[info_enum.name.count] == #char "." {
|
|
normalized = slice(normalized, info_enum.name.count+1, normalized.count-info_enum.name.count-1);
|
|
} else if starts_with(normalized, ".") {
|
|
normalized = slice(normalized, 1, normalized.count-1);
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
int_info := info_enum.internal_type;
|
|
int_value: s64;
|
|
if info_enum.enum_type_flags & .FLAGS {
|
|
values := split(value, "|",, temp);
|
|
|
|
for v: values {
|
|
name := normalize_enum_value(v);
|
|
found_name := false;
|
|
for info_enum.names {
|
|
if name == it {
|
|
found_name = true;
|
|
int_value |= info_enum.values[it_index];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if !found_name {
|
|
log_error("Enum \"%\" does not contain a member named \"%\".", info_enum.name, name);
|
|
success = false;
|
|
}
|
|
}
|
|
} else {
|
|
success = false;
|
|
name := normalize_enum_value(value);
|
|
for info_enum.names {
|
|
if name == it {
|
|
int_value = info_enum.values[it_index];
|
|
success = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if !success {
|
|
log_error("Enum \"%\" does not contain a member named \"%\".", info_enum.name, name);
|
|
}
|
|
}
|
|
|
|
if success {
|
|
if int_info.signed {
|
|
valid, low, high := Reflection.range_check_and_store(int_value, int_info, slot);
|
|
if !valid {
|
|
log_error("The value '%' is out of range. (It must be between % and %.)", int_value, low, high);
|
|
return remainder, false;
|
|
}
|
|
} else {
|
|
valid, low, high := Reflection.range_check_and_store(cast(u64) int_value, int_info, slot);
|
|
if !valid {
|
|
log_error("The value '%' is out of range. (It must be between % and %.)", int_value, low, high);
|
|
return remainder, false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return remainder, success;
|
|
}
|
|
|
|
parse_array :: (str: string, slot: *u8, info: *Type_Info_Array, ignore_unknown: bool, rename: Rename_Proc) -> remainder: string, success: bool {
|
|
element_size: int;
|
|
if slot {
|
|
element_size = info.element_type.runtime_size;
|
|
assert(element_size != -1, "Unknown element size");
|
|
}
|
|
|
|
assert(str[0] == #char "[", "Invalid object start %", str);
|
|
remainder := advance(str);
|
|
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
|
if remainder[0] == #char "]" {
|
|
remainder = advance(remainder);
|
|
// @Robustness: Do we need to zero out the array?
|
|
return remainder, true;
|
|
}
|
|
|
|
|
|
if slot {
|
|
array: Resizable_Array;
|
|
|
|
initializer: (*void) #no_context;
|
|
if info.element_type.type == .STRUCT {
|
|
struct_info := cast(*Type_Info_Struct) info.element_type;
|
|
initializer = struct_info.initializer;
|
|
}
|
|
|
|
while true {
|
|
maybe_grow(*array, element_size);
|
|
element_data := array.data + array.count * element_size;
|
|
if initializer {
|
|
initializer(element_data);
|
|
} else {
|
|
memset(element_data, 0, element_size);
|
|
}
|
|
|
|
success: bool;
|
|
remainder, success = parse_value(remainder, element_data, info.element_type, ignore_unknown, "", rename=rename);
|
|
if !success return remainder, false;
|
|
|
|
array.count += 1;
|
|
|
|
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
|
if remainder[0] != #char "," break;
|
|
remainder = advance(remainder);
|
|
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
|
}
|
|
|
|
if info.array_type == .VIEW {
|
|
view := (cast(*Array_View_64) slot);
|
|
view.count = array.count;
|
|
view.data = array.data;
|
|
} else if info.array_count == -1 {
|
|
// Resizable array
|
|
<<(cast(*Resizable_Array) slot) = array;
|
|
} else {
|
|
// Fixed-size array
|
|
if (info.array_count != array.count) {
|
|
log_error("Expected array of size %, but found array of size %\n", info.array_count, array.count);
|
|
return remainder, false;
|
|
}
|
|
|
|
memcpy(slot, array.data, array.count * element_size);
|
|
}
|
|
} else {
|
|
while true {
|
|
success: bool;
|
|
remainder, success = parse_value(remainder, null, null, ignore_unknown, "", rename=rename);
|
|
if !success return remainder, false;
|
|
|
|
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
|
if remainder[0] != #char "," break;
|
|
remainder = advance(remainder);
|
|
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
|
}
|
|
}
|
|
|
|
if remainder[0] != #char "]" return remainder, false;
|
|
remainder = advance(remainder);
|
|
return remainder, true;
|
|
}
|
|
|
|
Member_Offset :: struct {
|
|
member: *Type_Info_Struct_Member;
|
|
offset_in_bytes: s64;
|
|
}
|
|
|
|
// This procedure is somewhat copied from Basic.get_field.
|
|
fill_member_table :: (table: *Table(string, Member_Offset), info: *Type_Info_Struct, rename: Rename_Proc, base_offset := 0) {
|
|
for * member: info.members {
|
|
offset := base_offset + member.offset_in_bytes;
|
|
name := rename(member);
|
|
assert(!table_find_pointer(table, name), "Redeclaration of member \"%\": % vs. %", name, <<member, <<table_find_pointer(table, name));
|
|
table_set(table, name, .{member, offset});
|
|
if (member.flags & .USING) && (member.type.type == .STRUCT) {
|
|
fill_member_table(table, cast(*Type_Info_Struct)member.type, rename, offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
parse_object :: (str: string, slot: *u8, info: *Type_Info_Struct, ignore_unknown: bool, rename: Rename_Proc) -> remainder: string, success: bool {
|
|
assert(str[0] == #char "{", "Invalid object start %", str);
|
|
remainder := advance(str);
|
|
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
|
if remainder[0] == #char "}" {
|
|
remainder = advance(remainder);
|
|
return remainder, true;
|
|
}
|
|
|
|
// @Speed: Building this table every time is pretty silly.
|
|
// We should probably either not build it at all or cache it somewhere.
|
|
member_table: Table(string, Member_Offset);
|
|
init(*member_table);
|
|
defer deinit(*member_table);
|
|
|
|
if info fill_member_table(*member_table, info, rename);
|
|
|
|
while true {
|
|
if remainder[0] != #char "\"" return remainder, false;
|
|
|
|
key: string;
|
|
success: bool;
|
|
key, remainder, success = parse_string(remainder);
|
|
if !success return remainder, false;
|
|
defer free(key);
|
|
|
|
member_found, member_offset := table_find_new(*member_table, key);
|
|
|
|
member_slot: *u8;
|
|
member_info: *Type_Info;
|
|
if member_found {
|
|
member_slot = slot + member_offset.offset_in_bytes;
|
|
member_info = member_offset.member.type;
|
|
} else if !ignore_unknown {
|
|
log_error("Missing member % in %", key, <<info);
|
|
return remainder, false;
|
|
}
|
|
|
|
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
|
if remainder[0] != #char ":" return remainder, false;
|
|
remainder = advance(remainder);
|
|
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
|
remainder, success = parse_value(remainder, member_slot, member_info, ignore_unknown, key, rename);
|
|
if !success return remainder, false;
|
|
|
|
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
|
if remainder[0] != #char "," break;
|
|
remainder = advance(remainder);
|
|
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
|
}
|
|
|
|
if remainder[0] != #char "}" return remainder, false;
|
|
remainder = advance(remainder);
|
|
return remainder, true;
|
|
}
|
|
|
|
parse_and_write_integer :: (info: *Type_Info_Integer, pointer: *void, string_value: string) -> bool, remainder: string {
|
|
if info.signed {
|
|
success, remainder := parse_and_write_integer(info, pointer, string_value, signed = true);
|
|
return success, remainder;
|
|
} else {
|
|
success, remainder := parse_and_write_integer(info, pointer, string_value, signed = false);
|
|
return success, remainder;
|
|
}
|
|
}
|
|
|
|
parse_and_write_integer :: (info: *Type_Info_Integer, pointer: *void, string_value: string, $signed: bool) -> bool, remainder: string {
|
|
#if signed {
|
|
Int_Type :: s64;
|
|
} else {
|
|
Int_Type :: u64;
|
|
}
|
|
|
|
int_value, int_success, remainder := string_to_int(string_value, T = Int_Type);
|
|
if !int_success {
|
|
#if signed {
|
|
log_error("Could not parse \"%\" as an integer.", string_value);
|
|
} else {
|
|
log_error("Could not parse \"%\" as an unsigned integer.", string_value);
|
|
}
|
|
return false, remainder;
|
|
}
|
|
|
|
valid, low, high := Reflection.range_check_and_store(int_value, info, pointer);
|
|
|
|
if !valid {
|
|
log_error("The value '%' is out of range. (It must be between % and %.)", int_value, low, high);
|
|
return false, remainder;
|
|
}
|
|
|
|
return true, remainder;
|
|
}
|
|
|
|
Reflection :: #import "Reflection";
|
|
|