deserialization and serialization is usable
This commit is contained in:
parent
3f2d499025
commit
838b950b12
1 changed files with 397 additions and 95 deletions
|
|
@ -22,11 +22,102 @@ Json_Value :: struct {
|
|||
}
|
||||
}
|
||||
|
||||
json_marshal :: (value: $T) -> string {
|
||||
return "";
|
||||
json_serialize :: (value: $T) -> Json_Value {
|
||||
json_serialize_with_info :: (info: *Type_Info, value: *void) -> Json_Value {
|
||||
if value == null {
|
||||
return .{ kind = .none };
|
||||
}
|
||||
|
||||
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_unmarshal :: (data: string) -> (Json_Value, bool) {
|
||||
json_deserialize :: (data: string) -> (Json_Value, bool) {
|
||||
p := Parser.{
|
||||
data = data.([]u8),
|
||||
allocator = context.allocator,
|
||||
|
|
@ -36,9 +127,144 @@ json_unmarshal :: (data: string) -> (Json_Value, bool) {
|
|||
return parse_value(*p), p.error.count == 0;
|
||||
}
|
||||
|
||||
json_unmarshal :: (data: string, $T: Type) -> (T, bool) {
|
||||
zero: T;
|
||||
return zero, false;
|
||||
json_deserialize :: ($T: Type, data: string) -> (T, bool) {
|
||||
json_deserialize_with_info :: (info: *Type_Info, json: Json_Value, ptr: *void) -> bool {
|
||||
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 {
|
||||
|
|
@ -101,6 +327,17 @@ to_string :: (value: Json_Value, indent := 0) -> string {
|
|||
|
||||
#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 {
|
||||
allocator: Allocator;
|
||||
data: []u8;
|
||||
|
|
@ -109,13 +346,100 @@ Parser :: struct {
|
|||
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);
|
||||
|
||||
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 {
|
||||
str: string;
|
||||
str.data = *c;
|
||||
|
|
@ -123,28 +447,54 @@ parse_value :: (p: *Parser) -> Json_Value {
|
|||
return str;
|
||||
}
|
||||
|
||||
c := p.data[p.offset];
|
||||
if c == {
|
||||
case #char "{";
|
||||
p.offset += 1; // skip {
|
||||
ok, tok := expect(p, .l_brace, .l_square, .str, .number, .true_, .false_, .null_);
|
||||
if !ok {
|
||||
set_error(p, "expected value, found '%'", tok.str);
|
||||
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.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) {
|
||||
c = p.data[p.offset];
|
||||
if c == #char "}" break;
|
||||
end := peek_token(p);
|
||||
if end.kind == .r_brace break;
|
||||
|
||||
key := parse_value(p);
|
||||
if p.error.count != 0 || key.kind != .string {
|
||||
key_ok, key := expect(p, .str);
|
||||
if !key_ok {
|
||||
set_error(p, "expected key for object");
|
||||
return .{};
|
||||
}
|
||||
|
||||
if !expect(p, #char ":") {
|
||||
if !expect(p, .colon) {
|
||||
set_error(p, "missing ':' after key");
|
||||
return .{};
|
||||
}
|
||||
|
|
@ -154,109 +504,61 @@ parse_value :: (p: *Parser) -> Json_Value {
|
|||
return .{};
|
||||
}
|
||||
|
||||
if !expect(p, #char ",") {
|
||||
set_error(p, "expected ',' after value in object");
|
||||
basic.array_add(*obj.keys, key.str);
|
||||
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 .{};
|
||||
}
|
||||
|
||||
basic.array_add(*object.object.keys, key.s);
|
||||
basic.array_add(*object.object.values, value);
|
||||
}
|
||||
|
||||
p.offset += 1; // skip }
|
||||
if !expect(p, .r_brace) {
|
||||
set_error(p, "missing '}' at end of object");
|
||||
return .{};
|
||||
}
|
||||
|
||||
return object;
|
||||
|
||||
case #char "[";
|
||||
p.offset += 1; // skip [
|
||||
|
||||
case .l_square;
|
||||
array: Json_Value;
|
||||
array.kind = .array;
|
||||
array.array.allocator = p.allocator;
|
||||
|
||||
skip_whitespace(p);
|
||||
while !at_end(p) {
|
||||
c = p.data[p.offset];
|
||||
if c == #char "]" break;
|
||||
end := peek_token(p);
|
||||
if end.kind == .r_square break;
|
||||
|
||||
v := parse_value(p);
|
||||
value := parse_value(p);
|
||||
if p.error.count != 0 {
|
||||
set_error(p, "expected value for array");
|
||||
return .{};
|
||||
}
|
||||
|
||||
if !expect(p, #char ",") {
|
||||
set_error(p, "expected ',' after value in array");
|
||||
basic.array_add(*array.array, value);
|
||||
|
||||
end = peek_token(p);
|
||||
if end.kind == .r_square break;
|
||||
|
||||
if !expect(p, .comma) {
|
||||
set_error(p, "missing ',' after value");
|
||||
return .{};
|
||||
}
|
||||
|
||||
basic.array_add(*array.array, v);
|
||||
}
|
||||
|
||||
p.offset += 1; // skip ]
|
||||
|
||||
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);
|
||||
if !expect(p, .r_square) {
|
||||
set_error(p, "missing ']' at end of array");
|
||||
return .{};
|
||||
}
|
||||
|
||||
set_error(p, "expected number, true, false, null but found '%'", to_string(c));
|
||||
return .{};
|
||||
return array;
|
||||
}
|
||||
|
||||
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) {
|
||||
p.error = basic.tprint("line %, %", p.line, basic.tprint(message, ..args));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue