encoding/json start
This commit is contained in:
parent
8b013ce0fa
commit
325d8e2e81
1 changed files with 309 additions and 0 deletions
309
encoding/json.jai
Normal file
309
encoding/json.jai
Normal file
|
|
@ -0,0 +1,309 @@
|
||||||
|
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";
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in a new issue