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_marshal :: (value: $T) -> string { return ""; } json_unmarshal :: (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_unmarshal :: (data: string, $T: Type) -> (T, bool) { zero: T; return zero, false; } 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; Parser :: struct { allocator: Allocator; data: []u8; error: string; offset: uint; line: uint; } parse_value :: (p: *Parser) -> Json_Value { skip_whitespace(p); if at_end(p) { return .{}; } to_string :: (c: u8) -> string #expand { str: string; str.data = *c; str.count = 1; return str; } c := p.data[p.offset]; if c == { case #char "{"; p.offset += 1; // skip { object: Json_Value; object.kind = .object; object.object.keys.allocator = p.allocator; object.object.values.allocator = p.allocator; skip_whitespace(p); while !at_end(p) { c = p.data[p.offset]; if c == #char "}" break; key := parse_value(p); if p.error.count != 0 || key.kind != .string { set_error(p, "expected key for object"); return .{}; } if !expect(p, #char ":") { set_error(p, "missing ':' after key"); return .{}; } value := parse_value(p); if p.error.count != 0 { return .{}; } if !expect(p, #char ",") { set_error(p, "expected ',' after value in object"); return .{}; } basic.array_add(*object.object.keys, key.s); basic.array_add(*object.object.values, value); } p.offset += 1; // skip } return object; case #char "["; p.offset += 1; // skip [ 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; v := 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"); 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); return .{}; } set_error(p, "expected number, true, false, null but found '%'", to_string(c)); 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) { 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";