Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions handler.jai
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@

#add_context jaison: Jaison_Context;

// TODO: since we are adding a context, we may as well use it for the rename proc and float handling...
Jaison_Context :: struct {
type_handlers: [..] JSON_Type_Handler;
}

/*
I am not 100% satisfied with the design of handlers here, but this is the best I could come up with in light of the need for a split between preparing the slot and actually setting the value.
Maybe we don't really need that split and could just handle everything all at once, but that would require more extensive changes.

Because of the split between preparing the slot and actually writing to it, we need to be able to attach some kind of user data in prepare_slot that we can read in get_member_slot or set_value_from_string.
And, since we are potentially way down in some recursive structure, our only option for allocating that user data is most likely to New() it.
So now that we have some user data that may (or may not!) be dynamically allocated, we need a means to free that later, hence the deinit proc.

One plus to doing things this way is that we could override any of the handler procs on a per-instance basis, but how useful that really is is up for debate.
*/

JSON_Type_Handler :: struct {
type: *Type_Info;
user_data: *void;

prepare_slot: (handler: *JSON_Type_Handler, expected_type: Type_Info_Tag, info: *Type_Info, slot: *void) -> success: bool, info: *Type_Info, slot: *void;
get_member_slot: (handler: *JSON_Type_Handler, info: *Type_Info, slot: *void, key: string, index: int) -> success: bool, info: *Type_Info, slot: *void;
set_value_from_string: (handler: *JSON_Type_Handler, info: *Type_Info, slot: *void, string_value: string) -> success: bool;
deinit: (handler: *JSON_Type_Handler);
}

is_valid :: inline (handler: JSON_Type_Handler) -> bool {
return handler.type != null;
}

deinit :: inline (handler: *JSON_Type_Handler) {
if is_valid(handler) {
if handler.deinit handler.deinit(handler);
handler.* = .{};
}
}

get_handler_for_type :: (info: *Type_Info) -> JSON_Type_Handler {
handlers := context.jaison.type_handlers;
if !handlers return .{};

// first iteration searches for specific type, repeating search if type is a variant
while true {
for handler: handlers {
if handler.type == info {
return handler;
}
}
if info.type == .VARIANT {
info = (cast(*Type_Info_Variant) info).variant_of;
continue;
}
break;
}

// second iteration searches for match to polymorph_source_struct
if info.type == .STRUCT {
polymorph_source_struct := (cast(*Type_Info_Struct) info).polymorph_source_struct;
for handler: handlers {
if handler.type == xx polymorph_source_struct {
return handler;
}
}
}

return .{};
}

// With a generic handler interface, you have options now as to whether you want to use a handler for Any_Hash_Table, or generate individual handlers for specific hash table types.
// You can also handle things like Fixed_Array and Fixed_String types, or even bucket arrays and the like.
1 change: 1 addition & 0 deletions module.jai
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ WHITESPACE_CHARS :: " \t\n\r";

#load "generic.jai";
#load "typed.jai";
#load "handler.jai";

json_append_escaped :: (builder: *String_Builder, str: string) {
remaining := str;
Expand Down
165 changes: 141 additions & 24 deletions typed.jai
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ json_parse_file :: (filename: string, $T: Type, ignore_unknown := true, 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, float_handling: Special_Float_Handling, level := 0) {
json_write_native :: (builder: *String_Builder, data: *void, info: *Type_Info, indent_char := "\t", ignore := ignore_by_note, rename := rename_by_note, float_handling := Special_Float_Handling.SUPPORT_NULL, level := 0) {
if info.type == {
case .BOOL;
append(builder, ifx (.*)(cast(*bool) data) "true" else "false");
Expand Down Expand Up @@ -177,8 +177,37 @@ parse_value :: (to_parse: string, slot: *u8, info: *Type_Info, ignore_unknown: b
remainder := trim_left(to_parse, WHITESPACE_CHARS);
if !remainder return "", false;
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 {

prepare_slot :: (expected_type: Type_Info_Tag, info: *Type_Info, slot: *u8, to_parse: string) -> *u8, success: bool, is_generic: bool, info: *Type_Info, handler: JSON_Type_Handler {
log_type_mismatch_error :: () #expand {
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);
}

handler := get_handler_for_type(info);
if is_valid(handler) {
// To prevent extra complications with other primitive types, handlers are only permitted for JSON objects, arrays, and strings.
if expected_type == {
case .STRING;
case .STRUCT;
case .ARRAY;
case;
// TODO: better error message specific to handler case..?
log_type_mismatch_error();
}

success, value_info, value_slot := handler.prepare_slot(*handler, expected_type, info, slot);
if !success {
log_type_mismatch_error();
return null, false, false, value_info, .{};
}
return value_slot, true, false, value_info, handler;
}

value_info := info;
if info.type == .POINTER {
pointer_info := cast(*Type_Info_Pointer) info;
Expand All @@ -193,13 +222,8 @@ parse_value :: (to_parse: string, slot: *u8, info: *Type_Info, ignore_unknown: b
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;
log_type_mismatch_error();
return null, false, false, value_info, .{};
}

if info.type == .POINTER {
Expand All @@ -215,9 +239,12 @@ parse_value :: (to_parse: string, slot: *u8, info: *Type_Info, ignore_unknown: b
memset(value_slot, 0, value_info.runtime_size);
}
(.*)cast(**u8)slot = value_slot;
return value_slot, true, is_generic, value_info;

// needs to be called recursively in case we have a pointer to some type that requires a handler.
value_slot, success:, is_generic, value_info, handler = prepare_slot(expected_type, value_info, value_slot, to_parse);
return value_slot, success, is_generic, value_info, handler;
} else {
return slot, true, is_generic, value_info;
return slot, true, is_generic, value_info, .{};
}
}

Expand Down Expand Up @@ -308,27 +335,36 @@ parse_value :: (to_parse: string, slot: *u8, info: *Type_Info, ignore_unknown: b
log_error("Found unsupported string value \"%\" for field \"%\" of type %.", value, field_name, type_name);
}
} else {
value_slot: *u8;
value_slot, success, is_generic = prepare_slot(.STRING, info, slot, to_parse);
value_slot: *u8;
value_info: *Type_Info;
handler: JSON_Type_Handler;
defer deinit(*handler);
value_slot, success, is_generic, value_info, handler = prepare_slot(.STRING, info, slot, to_parse);
if success {
if is_generic {
if is_valid(handler) {
success = handler.set_value_from_string(*handler, value_info, value_slot, value);
} else if is_generic {
json_set(cast(*JSON_Value)value_slot, value);
} else {
(.*)cast(*string)value_slot = value;
}
stored = true;
stored = success;
}
}
}
}
case #char "[";
value_slot: *u8;
value_info: *Type_Info;
value_slot: *u8;
value_info: *Type_Info;
handler: JSON_Type_Handler;
defer deinit(*handler);
if slot {
value_slot, success, is_generic, value_info = prepare_slot(.ARRAY, info, slot, to_parse);
value_slot, success, is_generic, value_info, handler = prepare_slot(.ARRAY, info, slot, to_parse);
}
if success {
if is_generic {
if is_valid(handler) {
remainder, success = parse_array_with_handler(*handler, remainder, value_slot, value_info, ignore_unknown, rename=rename, float_handling=float_handling);
} else if is_generic {
value: [] JSON_Value;
value, remainder, success = parse_array(remainder);
json_set(cast(*JSON_Value)value_slot, value);
Expand All @@ -337,13 +373,17 @@ parse_value :: (to_parse: string, slot: *u8, info: *Type_Info, ignore_unknown: b
}
}
case #char "{";
value_slot: *u8;
value_info: *Type_Info;
value_slot: *u8;
value_info: *Type_Info;
handler: JSON_Type_Handler;
defer deinit(*handler);
if slot {
value_slot, success, is_generic, value_info = prepare_slot(.STRUCT, info, slot, to_parse);
value_slot, success, is_generic, value_info, handler = prepare_slot(.STRUCT, info, slot, to_parse);
}
if success {
if is_generic {
if is_valid(handler) {
remainder, success = parse_object_with_handler(*handler, remainder, value_slot, value_info, ignore_unknown, rename=rename, float_handling=float_handling);
} else if is_generic {
value := New(JSON_Object);
value.*, remainder, success = parse_object(remainder);
json_set(cast(*JSON_Value)value_slot, value);
Expand Down Expand Up @@ -462,6 +502,44 @@ parse_enum_string :: (str: string, slot: *u8, info_enum: *Type_Info_Enum) -> rem
return remainder, success;
}

parse_array_with_handler :: (handler: *JSON_Type_Handler, str: string, slot: *u8, info: *Type_Info, ignore_unknown: bool, rename: Rename_Proc, float_handling: Special_Float_Handling) -> remainder: string, success: bool {
assert(str[0] == #char "{", "Invalid object start %", str);
remainder := advance(str);
remainder = trim_left(remainder, WHITESPACE_CHARS);
if !remainder return "", false;

if remainder[0] == #char "}" {
remainder = advance(remainder);
return remainder, true;
}

index := 0;
while true {
defer index +=1;

if !remainder || remainder[0] != #char "\"" return remainder, false;

success, member_info, member_slot := handler.get_member_slot(handler, info, slot, "", index);
if !success return remainder, false;

remainder = trim_left(remainder, WHITESPACE_CHARS);
if !remainder || remainder[0] != #char ":" return remainder, false;
remainder = advance(remainder);

remainder, success = parse_value(remainder, member_slot, member_info, ignore_unknown, "", rename, float_handling=float_handling);
if !success return remainder, false;

remainder = trim_left(remainder, WHITESPACE_CHARS);
if !remainder || remainder[0] != #char "," break;
remainder = advance(remainder);
remainder = trim_left(remainder, WHITESPACE_CHARS);
}

if !remainder || remainder[0] != #char "}" return remainder, false;
remainder = advance(remainder);
return remainder, true;
}

parse_array :: (str: string, slot: *u8, info: *Type_Info_Array, ignore_unknown: bool, rename: Rename_Proc, float_handling: Special_Float_Handling) -> remainder: string, success: bool {
element_size: int;
if slot {
Expand Down Expand Up @@ -599,6 +677,45 @@ fill_member_table :: (table: *Table(string, Member_Offset), info: *Type_Info_Str
}
}

parse_object_with_handler :: (handler: *JSON_Type_Handler, str: string, slot: *u8, info: *Type_Info, ignore_unknown: bool, rename: Rename_Proc, float_handling: Special_Float_Handling) -> remainder: string, success: bool {
assert(str[0] == #char "{", "Invalid object start %", str);
remainder := advance(str);
remainder = trim_left(remainder, WHITESPACE_CHARS);
if !remainder return "", false;

if remainder[0] == #char "}" {
remainder = advance(remainder);
return remainder, true;
}

index := 0;
while true {
defer index +=1;

if !remainder || remainder[0] != #char "\"" return remainder, false;

key, remainder=, success := parse_string(remainder,, temp);
if !success return remainder, false;

success=, member_info, member_slot := handler.get_member_slot(handler, info, slot, key, index);

remainder = trim_left(remainder, WHITESPACE_CHARS);
if !remainder || remainder[0] != #char ":" return remainder, false;
remainder = advance(remainder);

remainder, success = parse_value(remainder, member_slot, member_info, ignore_unknown, key, rename, float_handling=float_handling);
if !success return remainder, false;

remainder = trim_left(remainder, WHITESPACE_CHARS);
if !remainder || remainder[0] != #char "," break;
remainder = advance(remainder);
remainder = trim_left(remainder, WHITESPACE_CHARS);
}

if !remainder || remainder[0] != #char "}" return remainder, false;
remainder = advance(remainder);
return remainder, true;
}

parse_object :: (str: string, slot: *u8, info: *Type_Info_Struct, ignore_unknown: bool, rename: Rename_Proc, float_handling: Special_Float_Handling) -> remainder: string, success: bool {
assert(str[0] == #char "{", "Invalid object start %", str);
Expand Down