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 ParseOptions = @import("./static.zig").ParseOptions;
const ParseError = @import("./static.zig").ParseError;
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 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 parseFromNumberSlice(s: []const u8) Value {
if (!isNumberFormattedLikeAnInteger(s)) {
const f = std.fmt.parseFloat(f64, s) catch unreachable;
if (std.math.isFinite(f)) {
return Value{ .float = f };
} else {
return Value{ .number_string = s };
}
}
if (std.fmt.parseInt(i64, s, 10)) |i| {
return Value{ .integer = i };
} else |e| {
switch (e) {
error.Overflow => return Value{ .number_string = s },
error.InvalidCharacter => unreachable,
}
}
}
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 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 jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) ParseError(@TypeOf(source.*))!@This() {
_ = options;
var stack = Array.init(allocator);
defer stack.deinit();
while (true) {
debug.assert(stack.items.len == 0 or
stack.items[stack.items.len - 1] == .array or
(stack.items[stack.items.len - 2] == .object and stack.items[stack.items.len - 1] == .string));
switch (try source.nextAlloc(allocator, .alloc_if_needed)) {
inline .string, .allocated_string => |s| {
return try handleCompleteValue(&stack, allocator, source, Value{ .string = s }) orelse continue;
},
inline .number, .allocated_number => |slice| {
return try handleCompleteValue(&stack, allocator, source, Value.parseFromNumberSlice(slice)) orelse continue;
},
.null => return try handleCompleteValue(&stack, allocator, source, .null) orelse continue,
.true => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = true }) orelse continue,
.false => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = false }) orelse continue,
.object_begin => {
switch (try source.nextAlloc(allocator, .alloc_if_needed)) {
.object_end => return try handleCompleteValue(&stack, allocator, source, Value{ .object = ObjectMap.init(allocator) }) orelse continue,
inline .string, .allocated_string => |key| {
try stack.appendSlice(&[_]Value{
Value{ .object = ObjectMap.init(allocator) },
Value{ .string = key },
});
},
else => unreachable,
}
},
.array_begin => {
try stack.append(Value{ .array = Array.init(allocator) });
},
.array_end => return try handleCompleteValue(&stack, allocator, source, stack.pop()) orelse continue,
else => unreachable,
}
}
}
};
fn handleCompleteValue(stack: *Array, allocator: Allocator, source: anytype, value_: Value) !?Value {
if (stack.items.len == 0) return value_;
var value = value_;
while (true) {
debug.assert(stack.items[stack.items.len - 1] == .array or
(stack.items[stack.items.len - 2] == .object and stack.items[stack.items.len - 1] == .string));
switch (stack.items[stack.items.len - 1]) {
.string => |key| {
_ = stack.pop();
var object = &stack.items[stack.items.len - 1].object;
try object.put(key, value);
switch (try source.nextAlloc(allocator, .alloc_if_needed)) {
.object_end => {
value = stack.pop();
if (stack.items.len == 0) return value;
continue;
},
inline .string, .allocated_string => |next_key| {
try stack.append(Value{ .string = next_key });
return null;
},
else => unreachable,
}
},
.array => |*array| {
try array.append(value);
return null;
},
else => unreachable,
}
}
}
test {
_ = @import("dynamic_test.zig");
}