611 lines
17 KiB
Text
611 lines
17 KiB
Text
Json_Value :: struct {
|
|
kind: enum {
|
|
none;
|
|
bool;
|
|
number;
|
|
string;
|
|
array;
|
|
object;
|
|
};
|
|
|
|
using value: union {
|
|
b: bool;
|
|
n: float64;
|
|
s: string;
|
|
|
|
array: [..]Json_Value;
|
|
|
|
object: struct {
|
|
keys: [..]string;
|
|
values: [..]Json_Value;
|
|
}
|
|
}
|
|
}
|
|
|
|
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_deserialize :: (data: string) -> (Json_Value, bool) {
|
|
p := Parser.{
|
|
data = data.([]u8),
|
|
allocator = context.allocator,
|
|
};
|
|
|
|
// @todo(judah): logging
|
|
return parse_value(*p), p.error.count == 0;
|
|
}
|
|
|
|
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 {
|
|
b: basic.String_Builder;
|
|
|
|
add_indents :: (b: *basic.String_Builder, level: int) #expand {
|
|
if level != 0 {
|
|
for 0..level basic.append(b, " ");
|
|
}
|
|
}
|
|
|
|
if value.kind == {
|
|
case .none;
|
|
basic.append(*b, "null");
|
|
|
|
case .bool;
|
|
if value.b {
|
|
basic.append(*b, "true");
|
|
}
|
|
else {
|
|
basic.append(*b, "false");
|
|
}
|
|
|
|
case .number;
|
|
basic.print_to_builder(*b, "%", value.n);
|
|
|
|
case .string;
|
|
basic.print_to_builder(*b, "\"%\"", value.s);
|
|
|
|
case .array;
|
|
basic.append(*b, "[\n");
|
|
|
|
for value.array {
|
|
add_indents(*b, indent + 2);
|
|
basic.append(*b, to_string(it, indent + 2));
|
|
if it_index < value.array.count {
|
|
basic.append(*b, ",\n");
|
|
}
|
|
}
|
|
|
|
add_indents(*b, indent);
|
|
basic.append(*b, "]");
|
|
|
|
case .object;
|
|
basic.append(*b, "{\n");
|
|
|
|
for value.object.keys {
|
|
add_indents(*b, indent + 2);
|
|
v := value.object.values[it_index];
|
|
basic.print_to_builder(*b, "\"%\": %,\n", it, to_string(v, indent + 2));
|
|
}
|
|
|
|
add_indents(*b, indent);
|
|
basic.append(*b, "}");
|
|
}
|
|
|
|
return basic.builder_to_string(*b);
|
|
}
|
|
|
|
|
|
#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;
|
|
error: string;
|
|
offset: uint;
|
|
line: uint;
|
|
}
|
|
|
|
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 .{ 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;
|
|
str.count = 1;
|
|
return str;
|
|
}
|
|
|
|
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;
|
|
|
|
obj := *object.object;
|
|
obj.keys.allocator = p.allocator;
|
|
obj.values.allocator = p.allocator;
|
|
|
|
while !at_end(p) {
|
|
end := peek_token(p);
|
|
if end.kind == .r_brace break;
|
|
|
|
key_ok, key := expect(p, .str);
|
|
if !key_ok {
|
|
set_error(p, "expected key for object");
|
|
return .{};
|
|
}
|
|
|
|
if !expect(p, .colon) {
|
|
set_error(p, "missing ':' after key");
|
|
return .{};
|
|
}
|
|
|
|
value := parse_value(p);
|
|
if p.error.count != 0 {
|
|
return .{};
|
|
}
|
|
|
|
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 .{};
|
|
}
|
|
}
|
|
|
|
if !expect(p, .r_brace) {
|
|
set_error(p, "missing '}' at end of object");
|
|
return .{};
|
|
}
|
|
|
|
return object;
|
|
|
|
case .l_square;
|
|
array: Json_Value;
|
|
array.kind = .array;
|
|
array.array.allocator = p.allocator;
|
|
|
|
while !at_end(p) {
|
|
end := peek_token(p);
|
|
if end.kind == .r_square break;
|
|
|
|
value := parse_value(p);
|
|
if p.error.count != 0 {
|
|
return .{};
|
|
}
|
|
|
|
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 .{};
|
|
}
|
|
}
|
|
|
|
if !expect(p, .r_square) {
|
|
set_error(p, "missing ']' at end of array");
|
|
return .{};
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
return .{};
|
|
}
|
|
|
|
set_error :: (p: *Parser, message: string, args: ..Any) {
|
|
p.error = basic.tprint("line %, %", p.line, basic.tprint(message, ..args));
|
|
}
|
|
|
|
skip_whitespace :: (p: *Parser) {
|
|
while !at_end(p) {
|
|
c := p.data[p.offset];
|
|
if c == {
|
|
case #char " "; #through;
|
|
case #char "\t"; #through;
|
|
case #char "\r";
|
|
p.offset += 1;
|
|
|
|
case #char "\n";
|
|
p.offset += 1;
|
|
p.line += 1;
|
|
|
|
case;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
capture_while :: (p: *Parser, proc: (u8) -> bool) -> string {
|
|
basic.assert(p.offset < p.data.count.(uint));
|
|
|
|
capture := p.data;
|
|
capture.data += p.offset;
|
|
capture.count = 0;
|
|
|
|
while !at_end(p) {
|
|
c := p.data[p.offset];
|
|
if !proc(c) break;
|
|
p.offset += 1;
|
|
}
|
|
|
|
capture.count = ((p.data.data + p.offset) - capture.data).(sint);
|
|
return capture.(string);
|
|
}
|
|
|
|
at_end :: inline (p: *Parser) -> bool {
|
|
return p.offset >= p.data.count.(uint);
|
|
}
|
|
|
|
basic :: #import "Basic";
|
|
strings :: #import "String";
|
|
|
|
#import,file "../base.jai";
|
|
|
|
|