From 2551ea0a4a3b581eeecf7f0d474006d7180cb2a5 Mon Sep 17 00:00:00 2001 From: rexim Date: Tue, 20 Jan 2026 22:16:31 +0700 Subject: [PATCH 1/2] Add experimental support for tables of pointers Tries to address #15 Example: ```jai main :: () { json_string := "{\"foo\": 69, \"bar\": 420}"; success, json := json_parse_string(json_string, Table(string, *int)); if !success exit(69); for json { print("% => %\n", it_index, it.*); } } #import "jaison"; ``` Why Tables of Pointers only: Currently all the `Type_Info` handling is happening at Runtime. But supporting Tables of Values that differ in size requires polymorphing such operations as `table_add()` which means we need to know the `Type_Info`-s at compile time. Until the type.jai is refactored to pass all the necessary `Type_Info` with `$` we can only offer that. Unless I'm missing something. Please let me know if I do. If I get a spare minute in the foreseeable future, I can spend it on doing the refactoring to bring the support for more Table kinds, but if somebody else wants to tuckle it I won't mind it. Please let me know if you want me to make any changes to the patch. Thank you for making this very useful library! --- typed.jai | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 5 deletions(-) diff --git a/typed.jai b/typed.jai index a50ee81..24e9f90 100644 --- a/typed.jai +++ b/typed.jai @@ -178,7 +178,7 @@ parse_value :: (to_parse: string, slot: *u8, info: *Type_Info, ignore_unknown: b 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, is_table: bool { value_info := info; if info.type == .POINTER { pointer_info := cast(*Type_Info_Pointer) info; @@ -190,6 +190,35 @@ parse_value :: (to_parse: string, slot: *u8, info: *Type_Info, ignore_unknown: b value_info = info_enum.internal_type; } + // @Incomplete: Add support for Tables of Any Types. + // Currently all the `Type_Info` handling is happening at + // Runtime. But supporting Tables of Values that differ in + // size requires polymorphing such operations as `table_add()` + // which means we need to know the `Type_Info`-s at compile + // time. Until the type.jai is refactored to pass all the + // necessary `Type_Info` with `$` we can only offer Table of + // Pointers. + // - rexim 2026-01-20 + is_type_info_table_of_pointers :: (info: *Type_Info) -> (value_info: *Type_Info_Pointer) { + if (info.type != .STRUCT) return null; + struct_info := cast(*Type_Info_Struct)info; + if (struct_info.name != "Table") return null; + for struct_info.specified_parameters { + if it.name == "Value_Type" { + if (it.type.type == .TYPE) { + value_info := (cast(**Type_Info)(struct_info.constant_storage.data + it.offset_into_constant_storage)).*; + if value_info.type != .POINTER return null; + return cast(*Type_Info_Pointer)value_info; + } + } + } + return null; + } + member_info := is_type_info_table_of_pointers(value_info); + if member_info { + return slot, true, false, member_info, true; + } + is_generic := is_generic_json_value(value_info); if !is_generic && value_info.type != expected_type { @@ -199,7 +228,7 @@ parse_value :: (to_parse: string, slot: *u8, info: *Type_Info, ignore_unknown: b 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; + return null, false, false, value_info, false; } if info.type == .POINTER { @@ -215,9 +244,9 @@ 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; + return value_slot, true, is_generic, value_info, false; } else { - return slot, true, is_generic, value_info; + return slot, true, is_generic, value_info, false; } } @@ -339,14 +368,17 @@ parse_value :: (to_parse: string, slot: *u8, info: *Type_Info, ignore_unknown: b case #char "{"; value_slot: *u8; value_info: *Type_Info; + is_table: bool; if slot { - value_slot, success, is_generic, value_info = prepare_slot(.STRUCT, info, slot, to_parse); + value_slot, success, is_generic, value_info, is_table = 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 if is_table { + remainder, success = parse_object_as_table_of_pointers(remainder, value_slot, cast(*Type_Info_Pointer)value_info, ignore_unknown, rename=rename, float_handling=float_handling); } else { remainder, success = parse_object(remainder, value_slot, cast(*Type_Info_Struct) value_info, ignore_unknown, rename=rename, float_handling=float_handling); } @@ -599,6 +631,49 @@ fill_member_table :: (table: *Table(string, Member_Offset), info: *Type_Info_Str } } +parse_object_as_table_of_pointers :: (str: string, slot: *u8, value_info: *Type_Info_Pointer, 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; + } + + table := cast(*Table(string, *void))slot; + + while true { + if !remainder || remainder[0] != #char "\"" return remainder, false; + + key: string; + success: bool; + key, remainder, success = parse_string(remainder); + if !success return remainder, false; + defer free(key); + + remainder = trim_left(remainder, WHITESPACE_CHARS); + if !remainder || remainder[0] != #char ":" return remainder, false; + remainder = advance(remainder); + + member_info := value_info.pointer_to; + member_slot := NewArray(member_info.runtime_size, u8).data; + remainder, success = parse_value(remainder, member_slot, member_info, ignore_unknown, key, rename, float_handling=float_handling); + if !success return remainder, false; + + table_add(table, copy_string(key), member_slot); + + 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); From b8efed61ee3ef0c99e4350da170003e9961782c1 Mon Sep 17 00:00:00 2001 From: rexim Date: Wed, 21 Jan 2026 00:55:10 +0700 Subject: [PATCH 2/2] Move table of pointers check to after the expected type check Since Table is a .STRUCT anyway expecting a .STRUCT when encountring a JSON object should work out well in this case. --- typed.jai | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/typed.jai b/typed.jai index 24e9f90..b7dde6f 100644 --- a/typed.jai +++ b/typed.jai @@ -190,6 +190,18 @@ parse_value :: (to_parse: string, slot: *u8, info: *Type_Info, ignore_unknown: b 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, false; + } + // @Incomplete: Add support for Tables of Any Types. // Currently all the `Type_Info` handling is happening at // Runtime. But supporting Tables of Values that differ in @@ -219,18 +231,6 @@ parse_value :: (to_parse: string, slot: *u8, info: *Type_Info, ignore_unknown: b return slot, true, false, member_info, true; } - 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, false; - } - if info.type == .POINTER { value_slot := alloc(value_info.runtime_size); initializer: (*void) #no_context;