master
  1const std = @import("std");
  2const debug = std.debug;
  3const ArenaAllocator = std.heap.ArenaAllocator;
  4const StringArrayHashMap = std.StringArrayHashMap;
  5const Allocator = std.mem.Allocator;
  6const json = std.json;
  7
  8const ParseOptions = @import("./static.zig").ParseOptions;
  9const ParseError = @import("./static.zig").ParseError;
 10
 11const isNumberFormattedLikeAnInteger = @import("Scanner.zig").isNumberFormattedLikeAnInteger;
 12
 13pub const ObjectMap = StringArrayHashMap(Value);
 14pub 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`.
 20pub 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 w, _ = std.debug.lockStderrWriter(&.{});
 51        defer std.debug.unlockStderrWriter();
 52
 53        json.Stringify.value(v, .{}, w) catch return;
 54    }
 55
 56    pub fn jsonStringify(value: @This(), jws: anytype) !void {
 57        switch (value) {
 58            .null => try jws.write(null),
 59            .bool => |inner| try jws.write(inner),
 60            .integer => |inner| try jws.write(inner),
 61            .float => |inner| try jws.write(inner),
 62            .number_string => |inner| try jws.print("{s}", .{inner}),
 63            .string => |inner| try jws.write(inner),
 64            .array => |inner| try jws.write(inner.items),
 65            .object => |inner| {
 66                try jws.beginObject();
 67                var it = inner.iterator();
 68                while (it.next()) |entry| {
 69                    try jws.objectField(entry.key_ptr.*);
 70                    try jws.write(entry.value_ptr.*);
 71                }
 72                try jws.endObject();
 73            },
 74        }
 75    }
 76
 77    pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) ParseError(@TypeOf(source.*))!@This() {
 78        // The grammar of the stack is:
 79        //  (.array | .object .string)*
 80        var stack = Array.init(allocator);
 81        defer stack.deinit();
 82
 83        while (true) {
 84            // Assert the stack grammar at the top of the stack.
 85            debug.assert(stack.items.len == 0 or
 86                stack.items[stack.items.len - 1] == .array or
 87                (stack.items[stack.items.len - 2] == .object and stack.items[stack.items.len - 1] == .string));
 88
 89            switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
 90                .allocated_string => |s| {
 91                    return try handleCompleteValue(&stack, allocator, source, Value{ .string = s }, options) orelse continue;
 92                },
 93                .allocated_number => |slice| {
 94                    if (options.parse_numbers) {
 95                        return try handleCompleteValue(&stack, allocator, source, Value.parseFromNumberSlice(slice), options) orelse continue;
 96                    } else {
 97                        return try handleCompleteValue(&stack, allocator, source, Value{ .number_string = slice }, options) orelse continue;
 98                    }
 99                },
100
101                .null => return try handleCompleteValue(&stack, allocator, source, .null, options) orelse continue,
102                .true => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = true }, options) orelse continue,
103                .false => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = false }, options) orelse continue,
104
105                .object_begin => {
106                    switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
107                        .object_end => return try handleCompleteValue(&stack, allocator, source, Value{ .object = ObjectMap.init(allocator) }, options) orelse continue,
108                        .allocated_string => |key| {
109                            try stack.appendSlice(&[_]Value{
110                                Value{ .object = ObjectMap.init(allocator) },
111                                Value{ .string = key },
112                            });
113                        },
114                        else => unreachable,
115                    }
116                },
117                .array_begin => {
118                    try stack.append(Value{ .array = Array.init(allocator) });
119                },
120                .array_end => return try handleCompleteValue(&stack, allocator, source, stack.pop().?, options) orelse continue,
121
122                else => unreachable,
123            }
124        }
125    }
126
127    pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() {
128        _ = allocator;
129        _ = options;
130        return source;
131    }
132};
133
134fn handleCompleteValue(stack: *Array, allocator: Allocator, source: anytype, value_: Value, options: ParseOptions) !?Value {
135    if (stack.items.len == 0) return value_;
136    var value = value_;
137    while (true) {
138        // Assert the stack grammar at the top of the stack.
139        debug.assert(stack.items[stack.items.len - 1] == .array or
140            (stack.items[stack.items.len - 2] == .object and stack.items[stack.items.len - 1] == .string));
141        switch (stack.items[stack.items.len - 1]) {
142            .string => |key| {
143                // stack: [..., .object, .string]
144                _ = stack.pop();
145
146                // stack: [..., .object]
147                var object = &stack.items[stack.items.len - 1].object;
148
149                const gop = try object.getOrPut(key);
150                if (gop.found_existing) {
151                    switch (options.duplicate_field_behavior) {
152                        .use_first => {},
153                        .@"error" => return error.DuplicateField,
154                        .use_last => {
155                            gop.value_ptr.* = value;
156                        },
157                    }
158                } else {
159                    gop.value_ptr.* = value;
160                }
161
162                // This is an invalid state to leave the stack in,
163                // so we have to process the next token before we return.
164                switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
165                    .object_end => {
166                        // This object is complete.
167                        value = stack.pop().?;
168                        // Effectively recurse now that we have a complete value.
169                        if (stack.items.len == 0) return value;
170                        continue;
171                    },
172                    .allocated_string => |next_key| {
173                        // We've got another key.
174                        try stack.append(Value{ .string = next_key });
175                        // stack: [..., .object, .string]
176                        return null;
177                    },
178                    else => unreachable,
179                }
180            },
181            .array => |*array| {
182                // stack: [..., .array]
183                try array.append(value);
184                return null;
185            },
186            else => unreachable,
187        }
188    }
189}
190
191test {
192    _ = @import("dynamic_test.zig");
193}