jc/encoding/json.jai
2025-05-12 22:03:57 -06:00

309 lines
6.9 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_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";