deserialization and serialization is usable

This commit is contained in:
Judah Caruso 2025-05-16 00:03:25 -06:00
parent 3f2d499025
commit 838b950b12

View file

@ -22,11 +22,102 @@ Json_Value :: struct {
} }
} }
json_marshal :: (value: $T) -> string { json_serialize :: (value: $T) -> Json_Value {
return ""; json_serialize_with_info :: (info: *Type_Info, value: *void) -> Json_Value {
if value == null {
return .{ kind = .none };
} }
json_unmarshal :: (data: string) -> (Json_Value, bool) { if info.type == {
case .BOOL;
return .{ kind = .bool, b = value.(*bool).* };
case .INTEGER;
i_info := info.(*Type_Info_Integer);
if i_info.signed {
if i_info.runtime_size == {
case 1; return .{ kind = .number, n = (value.( *s8).*).(float64) };
case 2; return .{ kind = .number, n = (value.(*s16).*).(float64) };
case 4; return .{ kind = .number, n = (value.(*s32).*).(float64) };
case 8; return .{ kind = .number, n = (value.(*s64).*).(float64) };
case; basic.assert(false, "unknown float size: %", info.runtime_size);
}
}
else {
if i_info.runtime_size == {
case 1; return .{ kind = .number, n = (value.( *u8).*).(float64) };
case 2; return .{ kind = .number, n = (value.(*u16).*).(float64) };
case 4; return .{ kind = .number, n = (value.(*u32).*).(float64) };
case 8; return .{ kind = .number, n = (value.(*u64).*).(float64) };
case; basic.assert(false, "unknown float size: %", info.runtime_size);
}
}
case .FLOAT;
if info.runtime_size == {
case 4; return .{ kind = .number, n = (value.(*float32).*).(float64) };
case 8; return .{ kind = .number, n = (value.(*float64).*) };
case; basic.assert(false, "unknown float size: %", info.runtime_size);
}
case .STRING;
return .{ kind = .string, s = (value.(*string)).* };
case .STRUCT;
json_obj: Json_Value;
json_obj.kind = .object;
obj := *json_obj.object;
vptr := value.(*u8);
s_info := info.(*Type_Info_Struct);
for s_info.members if it.offset_in_bytes >= 0 {
name := get_json_member_name(it);
basic.array_add(*obj.keys, name);
field_value := vptr + it.offset_in_bytes;
basic.array_add(*obj.values, json_serialize_with_info(it.type, field_value));
}
return json_obj;
case .ARRAY;
json_array: Json_Value;
json_array.kind = .array;
arr := *json_array.array;
vptr := value.(*u8);
a_info := info.(*Type_Info_Array);
el_info := a_info.element_type;
if a_info.array_type == {
case .FIXED;
for 0..a_info.array_count - 1 {
el_ptr := vptr + (it * el_info.runtime_size);
basic.array_add(arr, json_serialize_with_info(el_info, el_ptr));
}
case .VIEW; #through;
case .RESIZABLE;
data := vptr.(*[]u8);
for 0..data.count - 1 {
el_ptr := data.data + (it * el_info.runtime_size);
basic.array_add(arr, json_serialize_with_info(el_info, el_ptr));
}
}
return json_array;
}
basic.assert(false, "unhandled type in marshal: %", info.type);
return .{};
}
return json_serialize_with_info(T.(*Type_Info), *value);
}
json_deserialize :: (data: string) -> (Json_Value, bool) {
p := Parser.{ p := Parser.{
data = data.([]u8), data = data.([]u8),
allocator = context.allocator, allocator = context.allocator,
@ -36,9 +127,144 @@ json_unmarshal :: (data: string) -> (Json_Value, bool) {
return parse_value(*p), p.error.count == 0; return parse_value(*p), p.error.count == 0;
} }
json_unmarshal :: (data: string, $T: Type) -> (T, bool) { json_deserialize :: ($T: Type, data: string) -> (T, bool) {
zero: T; json_deserialize_with_info :: (info: *Type_Info, json: Json_Value, ptr: *void) -> bool {
return zero, false; if json.kind == {
case .none; return true;
case .bool;
basic.assert(info.type == .BOOL, "cannot deserialize % into %", json.b, info.type); // @errors
ptr.(*bool).* = json.b;
case .number;
if info.type == {
case .INTEGER;
i_info := info.(*Type_Info_Integer);
if i_info.signed {
// @todo(judah): bounds check to make sure this is safe
if i_info.runtime_size == {
case 1; ptr.(*s8 ).* = json.n.(s8 );
case 2; ptr.(*s16).* = json.n.(s16);
case 4; ptr.(*s32).* = json.n.(s32);
case 8; ptr.(*s64).* = json.n.(s64);
case; basic.assert(false, "unhandled int size % in deserialize", i_info.runtime_size);
}
}
else {
if i_info.runtime_size == {
case 1; ptr.(*u8 ).* = json.n.(u8 );
case 2; ptr.(*u16).* = json.n.(u16);
case 4; ptr.(*u32).* = json.n.(u32);
case 8; ptr.(*u64).* = json.n.(u64);
case; basic.assert(false, "unhandled uint size % in deserialize", i_info.runtime_size);
}
}
case .FLOAT;
if info.runtime_size == {
case 4; ptr.(*float32).* = json.n.(float32);
case 8; ptr.(*float64).* = json.n.(float64);
case; basic.assert(false, "unhandled float size % in deserialize", info.runtime_size);
}
}
return true;
case .string;
basic.assert(info.type == .STRING, "cannot deserialize '%' into %", json.s, info.type); // @errors
sptr := ptr.(*string);
sptr.data = basic.copy_string(json.s).data;
sptr.count = json.s.count;
return true;
case .object;
basic.assert(info.type == .STRUCT, "cannot deserialize json object into %", info.type); // @errors
vptr := ptr.(*u8);
s_info := info.(*Type_Info_Struct);
for mem: s_info.members {
found := false;
name := get_json_member_name(mem);
value: Json_Value;
for json.object.keys if it == name { // @todo(judah): handle custom names in notes
found = true;
value = json.object.values[it_index];
break;
}
if !found continue;
field_ptr := vptr + mem.offset_in_bytes;
if !json_deserialize_with_info(mem.type, value, field_ptr) {
return false;
}
}
return true;
case .array;
basic.assert(info.type == .ARRAY, "cannot deserialize json array into %", info.type); // @errors
vptr := ptr.(*u8);
a_info := info.(*Type_Info_Array);
el_info := a_info.element_type;
if a_info.array_type == {
case .FIXED;
basic.assert(json.array.count <= a_info.array_count);
for 0..a_info.array_count - 1 {
el_ptr := vptr + (it * el_info.runtime_size);
value := json.array[it];
if !json_deserialize_with_info(el_info, value, el_ptr) {
return false;
}
}
case .VIEW; #through;
case .RESIZABLE;
data := basic.alloc(json.array.count * el_info.runtime_size).(*u8);
if json.array.count == 0 {
return true;
}
// @todo(judah): verify the json array is homogenous
for json.array {
el_ptr := data + (it_index * el_info.runtime_size);
if !json_deserialize_with_info(el_info, it, el_ptr) {
return false;
}
}
if a_info.array_type == .VIEW {
out := ptr.(*[]u8);
out.data = data;
out.count = json.array.count;
}
else {
out := ptr.(*[..]u8);
out.data = data;
out.count = json.array.count;
out.allocated = json.array.count;
}
return true;
}
return true;
}
basic.assert(false, "unhandled json value: %", json.kind);
return false;
}
json, ok := json_deserialize(data);
if !ok return zero_of(T), ok;
out: T;
ok = json_deserialize_with_info(T.(*Type_Info), json, *out);
return out, ok;
} }
to_string :: (value: Json_Value, indent := 0) -> string { to_string :: (value: Json_Value, indent := 0) -> string {
@ -101,6 +327,17 @@ to_string :: (value: Json_Value, indent := 0) -> string {
#scope_file; #scope_file;
get_json_member_name :: (member: Type_Info_Struct_Member) -> string {
name := member.name;
for member.notes if strings.contains(it, "json:") {
name = .{ data = it.data + 5, count = it.count - 5 };
name = strings.replace(name, "\\_", " ");
break;
}
return name;
}
Parser :: struct { Parser :: struct {
allocator: Allocator; allocator: Allocator;
data: []u8; data: []u8;
@ -109,13 +346,100 @@ Parser :: struct {
line: uint; line: uint;
} }
parse_value :: (p: *Parser) -> Json_Value { Token :: struct {
kind: enum {
invalid;
eof;
true_;
false_;
null_;
number;
str;
colon :: #char ":";
comma :: #char ",";
l_brace :: #char "{";
r_brace :: #char "}";
l_square :: #char "[";
r_square :: #char "]";
};
str: string;
}
get_next_token :: (p: *Parser) -> Token {
skip_whitespace(p); skip_whitespace(p);
if at_end(p) { if at_end(p) {
return .{}; return .{ kind = .eof };
} }
c := p.data[p.offset];
if c == {
case #char ":"; #through;
case #char ","; #through;
case #char "{"; #through;
case #char "}"; #through;
case #char "["; #through;
case #char "]";
p.offset += 1;
return .{ kind = xx c };
}
// Comments
if c == #char "/" && p.offset + 1 < p.data.count.(uint) && p.data[p.offset+1] == #char "/" {
capture_while(p, c => c != #char "\n");
return get_next_token(p);
}
// Strings
// @todo(judah): handle escaped quotes
if c == #char "\"" {
p.offset += 1; // skip "
str := capture_while(p, s => s != #char "\"");
p.offset += 1; // skip "
return .{ kind = .str, str = str };
}
// Numbers
if (c >= #char "0" && c <= #char "9") || c == #char "." {
number := capture_while(p, n => (n >= #char "0" && n <= #char "9") || n == #char ".");
return .{ kind = .number, str = number };
}
// true, false, null
if (c >= #char "a" && c <= #char "z") {
keyword := capture_while(p, n => (n >= #char "a" && n <= #char "z"));
if keyword == {
case "true";
return .{ kind = .true_ };
case "false";
return .{ kind = .false_ };
case "null";
return .{ kind = .null_ };
case;
return .{ kind = .invalid, str = keyword };
}
}
return .{ kind = .eof };
}
peek_token :: (p: *Parser) -> Token {
copy := p.*; // @todo(judah): this is bad?
return get_next_token(*copy);
}
expect :: (p: *Parser, kinds: ..type_of(Token.kind)) -> (bool, Token) {
tok := get_next_token(p);
for kinds if it == tok.kind {
return true, tok;
}
return false, tok;
}
parse_value :: (p: *Parser) -> Json_Value {
to_string :: (c: u8) -> string #expand { to_string :: (c: u8) -> string #expand {
str: string; str: string;
str.data = *c; str.data = *c;
@ -123,28 +447,54 @@ parse_value :: (p: *Parser) -> Json_Value {
return str; return str;
} }
c := p.data[p.offset]; ok, tok := expect(p, .l_brace, .l_square, .str, .number, .true_, .false_, .null_);
if c == { if !ok {
case #char "{"; set_error(p, "expected value, found '%'", tok.str);
p.offset += 1; // skip { return .{};
}
if tok.kind == {
case .number;
number := tok.str;
// @todo(judah): parse_float works a bit weird
f, ok := strings.parse_float(*number);
if !ok {
set_error(p, "malformed number '%'", tok.str);
return .{};
}
return .{ kind = .number, n = f };
case .true_; #through;
case .false_;
return .{ kind = .bool, b = tok.kind == .true_ };
case .null_;
return .{ kind = .none };
case .str;
return .{ kind = .string, s = tok.str };
case .l_brace;
object: Json_Value; object: Json_Value;
object.kind = .object; object.kind = .object;
object.object.keys.allocator = p.allocator;
object.object.values.allocator = p.allocator;
skip_whitespace(p); obj := *object.object;
obj.keys.allocator = p.allocator;
obj.values.allocator = p.allocator;
while !at_end(p) { while !at_end(p) {
c = p.data[p.offset]; end := peek_token(p);
if c == #char "}" break; if end.kind == .r_brace break;
key := parse_value(p); key_ok, key := expect(p, .str);
if p.error.count != 0 || key.kind != .string { if !key_ok {
set_error(p, "expected key for object"); set_error(p, "expected key for object");
return .{}; return .{};
} }
if !expect(p, #char ":") { if !expect(p, .colon) {
set_error(p, "missing ':' after key"); set_error(p, "missing ':' after key");
return .{}; return .{};
} }
@ -154,109 +504,61 @@ parse_value :: (p: *Parser) -> Json_Value {
return .{}; return .{};
} }
if !expect(p, #char ",") { basic.array_add(*obj.keys, key.str);
set_error(p, "expected ',' after value in object"); basic.array_add(*obj.values, value);
end = peek_token(p);
if end.kind == .r_brace break;
if !expect(p, .comma) {
set_error(p, "missing ',' after value");
return .{};
}
}
if !expect(p, .r_brace) {
set_error(p, "missing '}' at end of object");
return .{}; return .{};
} }
basic.array_add(*object.object.keys, key.s);
basic.array_add(*object.object.values, value);
}
p.offset += 1; // skip }
return object; return object;
case #char "["; case .l_square;
p.offset += 1; // skip [
array: Json_Value; array: Json_Value;
array.kind = .array; array.kind = .array;
array.array.allocator = p.allocator; array.array.allocator = p.allocator;
skip_whitespace(p);
while !at_end(p) { while !at_end(p) {
c = p.data[p.offset]; end := peek_token(p);
if c == #char "]" break; if end.kind == .r_square break;
v := parse_value(p); value := parse_value(p);
if p.error.count != 0 { if p.error.count != 0 {
set_error(p, "expected value for array");
return .{}; return .{};
} }
if !expect(p, #char ",") { basic.array_add(*array.array, value);
set_error(p, "expected ',' after value in array");
end = peek_token(p);
if end.kind == .r_square break;
if !expect(p, .comma) {
set_error(p, "missing ',' after value");
return .{}; return .{};
} }
basic.array_add(*array.array, v);
} }
p.offset += 1; // skip ] if !expect(p, .r_square) {
set_error(p, "missing ']' at end of array");
return .{};
}
return array; return array;
case #char "\"";
p.offset += 1;
str := capture_while(p, s => s != #char "\"");
basic.assert(str.count != 0);
p.offset += 1;
return .{ kind = .string, s = str };
case;
// number
if (c >= #char "0" && c <= #char "9") || c == #char "." {
number := capture_while(p, n => (n >= #char "0" && n <= #char "9") || n == #char ".");
basic.assert(number.count != 0);
// @todo(judah): parse_float works a bit weird
f, ok := strings.parse_float(*number);
if !ok {
set_error(p, "malformed number '%'", number);
return .{};
}
return .{ kind = .number, n = f };
}
// true, false, null
if (c >= #char "a" && c <= #char "z") {
keyword := capture_while(p, k => k >= #char "a" && k <= #char "z");
basic.assert(keyword.count != 0);
if keyword == {
case "true";
return .{ kind = .bool, b = true };
case "false";
return .{ kind = .bool, b = false };
case "null";
return .{ kind = .none };
}
set_error(p, "expected number, true, false, null but found '%'", keyword);
return .{};
}
set_error(p, "expected number, true, false, null but found '%'", to_string(c));
return .{};
} }
return .{}; return .{};
} }
expect :: (p: *Parser, c: u8) -> bool {
if at_end(p) || p.data[p.offset] != c {
return false;
}
p.offset += 1;
skip_whitespace(p);
return true;
}
set_error :: (p: *Parser, message: string, args: ..Any) { set_error :: (p: *Parser, message: string, args: ..Any) {
p.error = basic.tprint("line %, %", p.line, basic.tprint(message, ..args)); p.error = basic.tprint("line %, %", p.line, basic.tprint(message, ..args));
} }