diff --git a/encoding/json.jai b/encoding/json.jai index de571fa..58e3c23 100644 --- a/encoding/json.jai +++ b/encoding/json.jai @@ -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)); }