| 1 | const std = @import("std"); |
| 2 | const debug = std.debug; |
| 3 | const ArenaAllocator = std.heap.ArenaAllocator; |
| 4 | const StringArrayHashMap = std.array_hash_map.String; |
| 5 | const Allocator = std.mem.Allocator; |
| 6 | const json = std.json; |
| 7 | |
| 8 | const ParseOptions = @import("./static.zig").ParseOptions; |
| 9 | const ParseError = @import("./static.zig").ParseError; |
| 10 | |
| 11 | const isNumberFormattedLikeAnInteger = @import("Scanner.zig").isNumberFormattedLikeAnInteger; |
| 12 | |
| 13 | pub const ObjectMap = StringArrayHashMap(Value); |
| 14 | pub const Array = std.array_list.Managed(Value); |
| 15 | |
| 16 | /// Represents any JSON value, potentially containing other JSON values. |
| 17 | /// A .float value may be an approximation of the original value. |
| 18 | /// Arbitrary precision numbers can be represented by .number_string values. |
| 19 | /// See also `std.json.ParseOptions.parse_numbers`. |
| 20 | pub const Value = union(enum) { |
| 21 | null, |
| 22 | bool: bool, |
| 23 | integer: i64, |
| 24 | float: f64, |
| 25 | number_string: []const u8, |
| 26 | string: []const u8, |
| 27 | array: Array, |
| 28 | object: ObjectMap, |
| 29 | |
| 30 | pub fn parseFromNumberSlice(s: []const u8) Value { |
| 31 | if (!isNumberFormattedLikeAnInteger(s)) { |
| 32 | const f = std.fmt.parseFloat(f64, s) catch unreachable; |
| 33 | if (std.math.isFinite(f)) { |
| 34 | return Value{ .float = f }; |
| 35 | } else { |
| 36 | return Value{ .number_string = s }; |
| 37 | } |
| 38 | } |
| 39 | if (std.fmt.parseInt(i64, s, 10)) |i| { |
| 40 | return Value{ .integer = i }; |
| 41 | } else |e| { |
| 42 | switch (e) { |
| 43 | error.Overflow => return Value{ .number_string = s }, |
| 44 | error.InvalidCharacter => unreachable, |
| 45 | } |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | pub fn dump(v: Value) void { |
| 50 | const stderr = std.debug.lockStderr(&.{}); |
| 51 | defer std.debug.unlockStderr(); |
| 52 | json.Stringify.value(v, .{}, &stderr.file_writer.interface) catch return; |
| 53 | } |
| 54 | |
| 55 | pub fn jsonStringify(value: @This(), jws: anytype) !void { |
| 56 | switch (value) { |
| 57 | .null => try jws.write(null), |
| 58 | .bool => |inner| try jws.write(inner), |
| 59 | .integer => |inner| try jws.write(inner), |
| 60 | .float => |inner| try jws.write(inner), |
| 61 | .number_string => |inner| try jws.print("{s}", .{inner}), |
| 62 | .string => |inner| try jws.write(inner), |
| 63 | .array => |inner| try jws.write(inner.items), |
| 64 | .object => |inner| { |
| 65 | try jws.beginObject(); |
| 66 | var it = inner.iterator(); |
| 67 | while (it.next()) |entry| { |
| 68 | try jws.objectField(entry.key_ptr.*); |
| 69 | try jws.write(entry.value_ptr.*); |
| 70 | } |
| 71 | try jws.endObject(); |
| 72 | }, |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) ParseError(@TypeOf(source.*))!@This() { |
| 77 | // The grammar of the stack is: |
| 78 | // (.array | .object .string)* |
| 79 | var stack = Array.init(allocator); |
| 80 | defer stack.deinit(); |
| 81 | |
| 82 | while (true) { |
| 83 | // Assert the stack grammar at the top of the stack. |
| 84 | debug.assert(stack.items.len == 0 or |
| 85 | stack.items[stack.items.len - 1] == .array or |
| 86 | (stack.items[stack.items.len - 2] == .object and stack.items[stack.items.len - 1] == .string)); |
| 87 | |
| 88 | switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) { |
| 89 | .allocated_string => |s| { |
| 90 | return try handleCompleteValue(&stack, allocator, source, Value{ .string = s }, options) orelse continue; |
| 91 | }, |
| 92 | .allocated_number => |slice| { |
| 93 | if (options.parse_numbers) { |
| 94 | return try handleCompleteValue(&stack, allocator, source, Value.parseFromNumberSlice(slice), options) orelse continue; |
| 95 | } else { |
| 96 | return try handleCompleteValue(&stack, allocator, source, Value{ .number_string = slice }, options) orelse continue; |
| 97 | } |
| 98 | }, |
| 99 | |
| 100 | .null => return try handleCompleteValue(&stack, allocator, source, .null, options) orelse continue, |
| 101 | .true => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = true }, options) orelse continue, |
| 102 | .false => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = false }, options) orelse continue, |
| 103 | |
| 104 | .object_begin => { |
| 105 | switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) { |
| 106 | .object_end => return try handleCompleteValue(&stack, allocator, source, Value{ .object = .empty }, options) orelse continue, |
| 107 | .allocated_string => |key| { |
| 108 | try stack.appendSlice(&[_]Value{ |
| 109 | Value{ .object = .empty }, |
| 110 | Value{ .string = key }, |
| 111 | }); |
| 112 | }, |
| 113 | else => unreachable, |
| 114 | } |
| 115 | }, |
| 116 | .array_begin => { |
| 117 | try stack.append(Value{ .array = Array.init(allocator) }); |
| 118 | }, |
| 119 | .array_end => return try handleCompleteValue(&stack, allocator, source, stack.pop().?, options) orelse continue, |
| 120 | |
| 121 | else => unreachable, |
| 122 | } |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() { |
| 127 | _ = allocator; |
| 128 | _ = options; |
| 129 | return source; |
| 130 | } |
| 131 | }; |
| 132 | |
| 133 | fn handleCompleteValue(stack: *Array, allocator: Allocator, source: anytype, value_: Value, options: ParseOptions) !?Value { |
| 134 | if (stack.items.len == 0) return value_; |
| 135 | var value = value_; |
| 136 | while (true) { |
| 137 | // Assert the stack grammar at the top of the stack. |
| 138 | debug.assert(stack.items[stack.items.len - 1] == .array or |
| 139 | (stack.items[stack.items.len - 2] == .object and stack.items[stack.items.len - 1] == .string)); |
| 140 | switch (stack.items[stack.items.len - 1]) { |
| 141 | .string => |key| { |
| 142 | // stack: [..., .object, .string] |
| 143 | _ = stack.pop(); |
| 144 | |
| 145 | // stack: [..., .object] |
| 146 | var object = &stack.items[stack.items.len - 1].object; |
| 147 | |
| 148 | const gop = try object.getOrPut(allocator, key); |
| 149 | if (gop.found_existing) { |
| 150 | switch (options.duplicate_field_behavior) { |
| 151 | .use_first => {}, |
| 152 | .@"error" => return error.DuplicateField, |
| 153 | .use_last => { |
| 154 | gop.value_ptr.* = value; |
| 155 | }, |
| 156 | } |
| 157 | } else { |
| 158 | gop.value_ptr.* = value; |
| 159 | } |
| 160 | |
| 161 | // This is an invalid state to leave the stack in, |
| 162 | // so we have to process the next token before we return. |
| 163 | switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) { |
| 164 | .object_end => { |
| 165 | // This object is complete. |
| 166 | value = stack.pop().?; |
| 167 | // Effectively recurse now that we have a complete value. |
| 168 | if (stack.items.len == 0) return value; |
| 169 | continue; |
| 170 | }, |
| 171 | .allocated_string => |next_key| { |
| 172 | // We've got another key. |
| 173 | try stack.append(Value{ .string = next_key }); |
| 174 | // stack: [..., .object, .string] |
| 175 | return null; |
| 176 | }, |
| 177 | else => unreachable, |
| 178 | } |
| 179 | }, |
| 180 | .array => |*array| { |
| 181 | // stack: [..., .array] |
| 182 | try array.append(value); |
| 183 | return null; |
| 184 | }, |
| 185 | else => unreachable, |
| 186 | } |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | test { |
| 191 | _ = @import("dynamic_test.zig"); |
| 192 | } |