mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 22:04:21 +00:00
194 lines
7.8 KiB
Zig
194 lines
7.8 KiB
Zig
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 json = std.json;
|
|
|
|
const ParseOptions = @import("./static.zig").ParseOptions;
|
|
const ParseError = @import("./static.zig").ParseError;
|
|
|
|
const isNumberFormattedLikeAnInteger = @import("Scanner.zig").isNumberFormattedLikeAnInteger;
|
|
|
|
pub const ObjectMap = StringArrayHashMap(Value);
|
|
pub const Array = ArrayList(Value);
|
|
|
|
/// Represents any JSON value, potentially containing other JSON values.
|
|
/// A .float value may be an approximation of the original value.
|
|
/// Arbitrary precision numbers can be represented by .number_string values.
|
|
/// See also `std.json.ParseOptions.parse_numbers`.
|
|
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(v: Value) void {
|
|
const w = std.debug.lockStderrWriter(&.{});
|
|
defer std.debug.unlockStderrWriter();
|
|
|
|
json.Stringify.value(v, .{}, w) catch return;
|
|
}
|
|
|
|
pub fn jsonStringify(value: @This(), jws: anytype) !void {
|
|
switch (value) {
|
|
.null => try jws.write(null),
|
|
.bool => |inner| try jws.write(inner),
|
|
.integer => |inner| try jws.write(inner),
|
|
.float => |inner| try jws.write(inner),
|
|
.number_string => |inner| try jws.print("{s}", .{inner}),
|
|
.string => |inner| try jws.write(inner),
|
|
.array => |inner| try jws.write(inner.items),
|
|
.object => |inner| {
|
|
try jws.beginObject();
|
|
var it = inner.iterator();
|
|
while (it.next()) |entry| {
|
|
try jws.objectField(entry.key_ptr.*);
|
|
try jws.write(entry.value_ptr.*);
|
|
}
|
|
try jws.endObject();
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) ParseError(@TypeOf(source.*))!@This() {
|
|
// The grammar of the stack is:
|
|
// (.array | .object .string)*
|
|
var stack = Array.init(allocator);
|
|
defer stack.deinit();
|
|
|
|
while (true) {
|
|
// Assert the stack grammar at the top of the stack.
|
|
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.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
|
|
.allocated_string => |s| {
|
|
return try handleCompleteValue(&stack, allocator, source, Value{ .string = s }, options) orelse continue;
|
|
},
|
|
.allocated_number => |slice| {
|
|
if (options.parse_numbers) {
|
|
return try handleCompleteValue(&stack, allocator, source, Value.parseFromNumberSlice(slice), options) orelse continue;
|
|
} else {
|
|
return try handleCompleteValue(&stack, allocator, source, Value{ .number_string = slice }, options) orelse continue;
|
|
}
|
|
},
|
|
|
|
.null => return try handleCompleteValue(&stack, allocator, source, .null, options) orelse continue,
|
|
.true => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = true }, options) orelse continue,
|
|
.false => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = false }, options) orelse continue,
|
|
|
|
.object_begin => {
|
|
switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
|
|
.object_end => return try handleCompleteValue(&stack, allocator, source, Value{ .object = ObjectMap.init(allocator) }, options) orelse continue,
|
|
.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().?, options) orelse continue,
|
|
|
|
else => unreachable,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() {
|
|
_ = allocator;
|
|
_ = options;
|
|
return source;
|
|
}
|
|
};
|
|
|
|
fn handleCompleteValue(stack: *Array, allocator: Allocator, source: anytype, value_: Value, options: ParseOptions) !?Value {
|
|
if (stack.items.len == 0) return value_;
|
|
var value = value_;
|
|
while (true) {
|
|
// Assert the stack grammar at the top of the stack.
|
|
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: [..., .object, .string]
|
|
_ = stack.pop();
|
|
|
|
// stack: [..., .object]
|
|
var object = &stack.items[stack.items.len - 1].object;
|
|
|
|
const gop = try object.getOrPut(key);
|
|
if (gop.found_existing) {
|
|
switch (options.duplicate_field_behavior) {
|
|
.use_first => {},
|
|
.@"error" => return error.DuplicateField,
|
|
.use_last => {
|
|
gop.value_ptr.* = value;
|
|
},
|
|
}
|
|
} else {
|
|
gop.value_ptr.* = value;
|
|
}
|
|
|
|
// This is an invalid state to leave the stack in,
|
|
// so we have to process the next token before we return.
|
|
switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
|
|
.object_end => {
|
|
// This object is complete.
|
|
value = stack.pop().?;
|
|
// Effectively recurse now that we have a complete value.
|
|
if (stack.items.len == 0) return value;
|
|
continue;
|
|
},
|
|
.allocated_string => |next_key| {
|
|
// We've got another key.
|
|
try stack.append(Value{ .string = next_key });
|
|
// stack: [..., .object, .string]
|
|
return null;
|
|
},
|
|
else => unreachable,
|
|
}
|
|
},
|
|
.array => |*array| {
|
|
// stack: [..., .array]
|
|
try array.append(value);
|
|
return null;
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
}
|
|
|
|
test {
|
|
_ = @import("dynamic_test.zig");
|
|
}
|