mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 05:44:20 +00:00
`std.Io.tty.Config.detect` may be an expensive check (e.g. involving syscalls), and doing it every time we need to print isn't really necessary; under normal usage, we can compute the value once and cache it for the whole program's execution. Since anyone outputting to stderr may reasonably want this information (in fact they are very likely to), it makes sense to cache it and return it from `lockStderrWriter`. Call sites who do not need it will experience no significant overhead, and can just ignore the TTY config with a `const w, _` destructure.
193 lines
7.8 KiB
Zig
193 lines
7.8 KiB
Zig
const std = @import("std");
|
|
const debug = std.debug;
|
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
|
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 = std.array_list.Managed(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");
|
|
}
|