const std = @import("std");
const debug = std.debug;
const ArenaAllocator = std.heap.ArenaAllocator;
const ArrayList = std.ArrayList;
const StringArrayHashMap = std.StringArrayHashMap;
const Allocator = std.mem.Allocator;
const StringifyOptions = @import("./stringify.zig").StringifyOptions;
const stringify = @import("./stringify.zig").stringify;
const JsonScanner = @import("./scanner.zig").Scanner;
const AllocWhen = @import("./scanner.zig").AllocWhen;
const Token = @import("./scanner.zig").Token;
const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger;
pub const ValueTree = struct {
arena: *ArenaAllocator,
root: Value,
pub fn deinit(self: *ValueTree) void {
self.arena.deinit();
self.arena.child_allocator.destroy(self.arena);
}
};
pub const ObjectMap = StringArrayHashMap(Value);
pub const Array = ArrayList(Value);
pub const Value = union(enum) {
null,
bool: bool,
integer: i64,
float: f64,
number_string: []const u8,
string: []const u8,
array: Array,
object: ObjectMap,
pub fn jsonStringify(
value: @This(),
options: StringifyOptions,
out_stream: anytype,
) @TypeOf(out_stream).Error!void {
switch (value) {
.null => try stringify(null, options, out_stream),
.bool => |inner| try stringify(inner, options, out_stream),
.integer => |inner| try stringify(inner, options, out_stream),
.float => |inner| try stringify(inner, options, out_stream),
.number_string => |inner| try out_stream.writeAll(inner),
.string => |inner| try stringify(inner, options, out_stream),
.array => |inner| try stringify(inner.items, options, out_stream),
.object => |inner| {
try out_stream.writeByte('{');
var field_output = false;
var child_options = options;
child_options.whitespace.indent_level += 1;
var it = inner.iterator();
while (it.next()) |entry| {
if (!field_output) {
field_output = true;
} else {
try out_stream.writeByte(',');
}
try child_options.whitespace.outputIndent(out_stream);
try stringify(entry.key_ptr.*, options, out_stream);
try out_stream.writeByte(':');
if (child_options.whitespace.separator) {
try out_stream.writeByte(' ');
}
try stringify(entry.value_ptr.*, child_options, out_stream);
}
if (field_output) {
try options.whitespace.outputIndent(out_stream);
}
try out_stream.writeByte('}');
},
}
}
pub fn dump(self: Value) void {
std.debug.getStderrMutex().lock();
defer std.debug.getStderrMutex().unlock();
const stderr = std.io.getStdErr().writer();
stringify(self, .{}, stderr) catch return;
}
};
pub const Parser = struct {
allocator: Allocator,
state: State,
alloc_when: AllocWhen,
stack: Array,
const State = enum {
object_key,
object_value,
array_value,
simple,
};
pub fn init(allocator: Allocator, alloc_when: AllocWhen) Parser {
return Parser{
.allocator = allocator,
.state = .simple,
.alloc_when = alloc_when,
.stack = Array.init(allocator),
};
}
pub fn deinit(p: *Parser) void {
p.stack.deinit();
}
pub fn reset(p: *Parser) void {
p.state = .simple;
p.stack.shrinkRetainingCapacity(0);
}
pub fn parse(p: *Parser, input: []const u8) !ValueTree {
var scanner = JsonScanner.initCompleteInput(p.allocator, input);
defer scanner.deinit();
var arena = try p.allocator.create(ArenaAllocator);
errdefer p.allocator.destroy(arena);
arena.* = ArenaAllocator.init(p.allocator);
errdefer arena.deinit();
const allocator = arena.allocator();
while (true) {
const token = try scanner.nextAlloc(allocator, p.alloc_when);
if (token == .end_of_document) break;
try p.transition(allocator, token);
}
debug.assert(p.stack.items.len == 1);
return ValueTree{
.arena = arena,
.root = p.stack.items[0],
};
}
fn transition(p: *Parser, allocator: Allocator, token: Token) !void {
switch (p.state) {
.object_key => switch (token) {
.object_end => {
if (p.stack.items.len == 1) {
return;
}
var value = p.stack.pop();
try p.pushToParent(&value);
},
.string => |s| {
try p.stack.append(Value{ .string = s });
p.state = .object_value;
},
.allocated_string => |s| {
try p.stack.append(Value{ .string = s });
p.state = .object_value;
},
else => unreachable,
},
.object_value => {
var object = &p.stack.items[p.stack.items.len - 2].object;
var key = p.stack.items[p.stack.items.len - 1].string;
switch (token) {
.object_begin => {
try p.stack.append(Value{ .object = ObjectMap.init(allocator) });
p.state = .object_key;
},
.array_begin => {
try p.stack.append(Value{ .array = Array.init(allocator) });
p.state = .array_value;
},
.string => |s| {
try object.put(key, Value{ .string = s });
_ = p.stack.pop();
p.state = .object_key;
},
.allocated_string => |s| {
try object.put(key, Value{ .string = s });
_ = p.stack.pop();
p.state = .object_key;
},
.number => |slice| {
try object.put(key, try p.parseNumber(slice));
_ = p.stack.pop();
p.state = .object_key;
},
.allocated_number => |slice| {
try object.put(key, try p.parseNumber(slice));
_ = p.stack.pop();
p.state = .object_key;
},
.true => {
try object.put(key, Value{ .bool = true });
_ = p.stack.pop();
p.state = .object_key;
},
.false => {
try object.put(key, Value{ .bool = false });
_ = p.stack.pop();
p.state = .object_key;
},
.null => {
try object.put(key, .null);
_ = p.stack.pop();
p.state = .object_key;
},
.object_end, .array_end, .end_of_document => unreachable,
.partial_number, .partial_string, .partial_string_escaped_1, .partial_string_escaped_2, .partial_string_escaped_3, .partial_string_escaped_4 => unreachable,
}
},
.array_value => {
var array = &p.stack.items[p.stack.items.len - 1].array;
switch (token) {
.array_end => {
if (p.stack.items.len == 1) {
return;
}
var value = p.stack.pop();
try p.pushToParent(&value);
},
.object_begin => {
try p.stack.append(Value{ .object = ObjectMap.init(allocator) });
p.state = .object_key;
},
.array_begin => {
try p.stack.append(Value{ .array = Array.init(allocator) });
p.state = .array_value;
},
.string => |s| {
try array.append(Value{ .string = s });
},
.allocated_string => |s| {
try array.append(Value{ .string = s });
},
.number => |slice| {
try array.append(try p.parseNumber(slice));
},
.allocated_number => |slice| {
try array.append(try p.parseNumber(slice));
},
.true => {
try array.append(Value{ .bool = true });
},
.false => {
try array.append(Value{ .bool = false });
},
.null => {
try array.append(.null);
},
.object_end, .end_of_document => unreachable,
.partial_number, .partial_string, .partial_string_escaped_1, .partial_string_escaped_2, .partial_string_escaped_3, .partial_string_escaped_4 => unreachable,
}
},
.simple => switch (token) {
.object_begin => {
try p.stack.append(Value{ .object = ObjectMap.init(allocator) });
p.state = .object_key;
},
.array_begin => {
try p.stack.append(Value{ .array = Array.init(allocator) });
p.state = .array_value;
},
.string => |s| {
try p.stack.append(Value{ .string = s });
},
.allocated_string => |s| {
try p.stack.append(Value{ .string = s });
},
.number => |slice| {
try p.stack.append(try p.parseNumber(slice));
},
.allocated_number => |slice| {
try p.stack.append(try p.parseNumber(slice));
},
.true => {
try p.stack.append(Value{ .bool = true });
},
.false => {
try p.stack.append(Value{ .bool = false });
},
.null => {
try p.stack.append(.null);
},
.object_end, .array_end, .end_of_document => unreachable,
.partial_number, .partial_string, .partial_string_escaped_1, .partial_string_escaped_2, .partial_string_escaped_3, .partial_string_escaped_4 => unreachable,
},
}
}
fn pushToParent(p: *Parser, value: *const Value) !void {
switch (p.stack.items[p.stack.items.len - 1]) {
.string => |key| {
_ = p.stack.pop();
var object = &p.stack.items[p.stack.items.len - 1].object;
try object.put(key, value.*);
p.state = .object_key;
},
.array => |*array| {
try array.append(value.*);
p.state = .array_value;
},
else => {
unreachable;
},
}
}
fn parseNumber(p: *Parser, slice: []const u8) !Value {
_ = p;
return if (isNumberFormattedLikeAnInteger(slice))
Value{
.integer = std.fmt.parseInt(i64, slice, 10) catch |e| switch (e) {
error.Overflow => return Value{ .number_string = slice },
error.InvalidCharacter => |err| return err,
},
}
else
Value{ .float = try std.fmt.parseFloat(f64, slice) };
}
};
test {
_ = @import("dynamic_test.zig");
}