1const std = @import("std");
2const string = []const u8;
3const extras = @import("extras");
4const tracer = @import("tracer");
5const intrusive_parser = @import("intrusive-parser");
6const nio = @import("nio");
7
8pub const Error = error{ OutOfMemory, EndOfStream, MalformedJson };
9pub const ObjectHashMap = std.AutoArrayHashMapUnmanaged(StringIndex, ValueIndex);
10
11pub fn parse(alloc: std.mem.Allocator, path: string, inreadable: anytype, options: Parser.Options) (Instance(@TypeOf(inreadable)).ReadError || Error)!Document {
12 const t = tracer.trace(@src(), "", .{});
13 defer t.end();
14
15 _ = path;
16
17 var p = try Parser.init(alloc, inreadable.anyReadable(), options);
18 defer p.deinit();
19
20 const root = try parseElementPrecise(alloc, &p, Instance(@TypeOf(inreadable)).ReadError || Error);
21 if (p.parser.avail() > 0) return error.MalformedJson;
22 const data = try p.parser.data.toOwnedSlice(alloc);
23
24 return .{
25 .root = root,
26 .extras = data,
27 };
28}
29
30fn Instance(T: type) type {
31 return switch (@typeInfo(T)) {
32 .pointer => |info| info.child,
33 else => T,
34 };
35}
36
37pub fn parseFromSlice(alloc: std.mem.Allocator, path: string, input: string, options: Parser.Options) !Document {
38 var fbs: nio.FixedBufferStream(string) = .init(input);
39 return parse(alloc, path, &fbs, options);
40}
41
42fn parseElementPrecise(alloc: std.mem.Allocator, p: *Parser, comptime E: type) E!ValueIndex {
43 return parseElement(alloc, p) catch |err| @errorCast(err);
44}
45
46fn parseElement(alloc: std.mem.Allocator, p: *Parser) anyerror!ValueIndex {
47 const t = tracer.trace(@src(), "", .{});
48 defer t.end();
49
50 try parseWs(p);
51 const v = try parseValue(alloc, p);
52 parseWs(p) catch |err| switch (err) {
53 error.EndOfStream => {},
54 else => |e| return e,
55 };
56 return v;
57}
58
59fn parseValue(alloc: std.mem.Allocator, p: *Parser) anyerror!ValueIndex {
60 const t = tracer.trace(@src(), "", .{});
61 defer t.end();
62
63 if (try p.parser.eat("null")) |_| return @enumFromInt(1);
64 if (try p.parser.eat("true")) |_| return @enumFromInt(2);
65 if (try p.parser.eat("false")) |_| return @enumFromInt(3);
66 if (try parseNumber(alloc, p)) |v| return v;
67 if (try parseString(alloc, p)) |v| return @enumFromInt(@intFromEnum(v));
68 if (try parseArray(alloc, p)) |v| return v;
69 if (try parseObject(alloc, p)) |v| return v;
70 return error.MalformedJson;
71}
72
73fn parseObject(alloc: std.mem.Allocator, p: *Parser) anyerror!?ValueIndex {
74 const t = tracer.trace(@src(), "", .{});
75 defer t.end();
76
77 p.depth += 1;
78 defer p.depth -= 1;
79 if (p.depth > p.maximum_depth) return error.MalformedJson;
80
81 _ = try p.parser.eatByte('{') orelse return null;
82 try parseWs(p);
83
84 var sfa = std.heap.stackFallback(std.heap.page_size_min, alloc);
85 const alloc_local = sfa.get();
86 var members = ObjectHashMap{};
87 defer members.deinit(alloc_local);
88
89 if (try p.parser.eatByte('}')) |_| {
90 return .empty_object;
91 }
92 while (true) {
93 const key = try parseString(alloc, p) orelse return error.MalformedJson;
94 try parseWs(p);
95 _ = try p.parser.eatByte(':') orelse return error.MalformedJson;
96 try parseWs(p);
97 const value = try parseValue(alloc, p);
98 try members.put(alloc_local, key, value);
99 try parseWs(p);
100 _ = try p.parser.eatByte(',') orelse {
101 _ = try p.parser.eatByte('}') orelse return error.MalformedJson;
102 break;
103 };
104 try parseWs(p);
105 if (!p.support_trailing_commas) continue;
106 if (try p.parser.eatByte('}')) |_| break;
107 }
108 if (members.entries.len == 0) {
109 return .empty_object;
110 }
111 return try p.addObject(&members);
112}
113
114fn parseArray(alloc: std.mem.Allocator, p: *Parser) anyerror!?ValueIndex {
115 const t = tracer.trace(@src(), "", .{});
116 defer t.end();
117
118 p.depth += 1;
119 defer p.depth -= 1;
120 if (p.depth > p.maximum_depth) return error.MalformedJson;
121
122 _ = try p.parser.eatByte('[') orelse return null;
123 try parseWs(p);
124
125 var sfa = std.heap.stackFallback(std.heap.page_size_min, alloc);
126 const alloc_local = sfa.get();
127 var elements: std.ArrayListUnmanaged(ValueIndex) = .empty;
128 defer elements.deinit(alloc_local);
129
130 if (try p.parser.eatByte(']')) |_| {
131 return .empty_array;
132 }
133 while (true) {
134 const elem = try parseValue(alloc, p);
135 try elements.append(alloc_local, elem);
136 try parseWs(p);
137 _ = try p.parser.eatByte(',') orelse {
138 _ = try p.parser.eatByte(']') orelse return error.MalformedJson;
139 break;
140 };
141 try parseWs(p);
142 if (!p.support_trailing_commas) continue;
143 if (try p.parser.eatByte(']')) |_| break;
144 }
145 if (elements.items.len == 0) {
146 return .empty_array;
147 }
148 return try p.addArray(elements.items);
149}
150
151fn parseString(alloc: std.mem.Allocator, p: *Parser) anyerror!?StringIndex {
152 const t = tracer.trace(@src(), "", .{});
153 defer t.end();
154
155 var stack_fallback = std.heap.stackFallback(std.heap.page_size_min, alloc);
156 var characters = std.array_list.Managed(u8).init(stack_fallback.get());
157 defer characters.deinit();
158
159 _ = try p.parser.eatByte('"') orelse return null;
160
161 while (true) {
162 const c = try p.parser.shift();
163 if (c == '"') {
164 break;
165 }
166 if (c != '\\') {
167 if (c < 0x20) return error.MalformedJson;
168 const l = std.unicode.utf8CodepointSequenceLength(c) catch unreachable;
169 const b = p.parser.temp.items[p.parser.idx - l ..][0..l];
170 try characters.appendSlice(b);
171 continue;
172 }
173 switch (try p.parser.shift()) {
174 inline 0x22, 0x5C, 0x2F => |d| try characters.append(d),
175 'b' => try characters.append(0x8),
176 'f' => try characters.append(0xC),
177 'n' => try characters.append(0xA),
178 'r' => try characters.append(0xD),
179 't' => try characters.append(0x9),
180 'u' => {
181 var o: u32 = 0;
182 var d = try p.parser.shiftBytesN(4);
183 if (!extras.matchesAll(u8, &d, std.ascii.isHex)) return error.MalformedJson;
184 d = @bitCast(@byteSwap(@as(u32, @bitCast(d))));
185 for (d, 0..) |e, i| o += (e * std.math.pow(u32, 2, @intCast(i)));
186 //
187 if (o > std.math.maxInt(u21)) return error.MalformedJson;
188 var b: [4]u8 = undefined;
189 const l = std.unicode.utf8Encode(@intCast(o), &b) catch return error.MalformedJson;
190 try characters.appendSlice(b[0..l]);
191 },
192 else => return error.MalformedJson,
193 }
194 }
195 return try p.addStr(characters.items);
196}
197
198fn parseNumber(alloc: std.mem.Allocator, p: *Parser) anyerror!?ValueIndex {
199 const t = tracer.trace(@src(), "", .{});
200 defer t.end();
201
202 var stack_fallback = std.heap.stackFallback(std.heap.page_size_min, alloc);
203 var characters = std.array_list.Managed(u8).init(stack_fallback.get());
204 defer characters.deinit();
205
206 if (try p.parser.eatByte('-')) |c| {
207 try characters.append(c);
208 }
209 if (try p.parser.eatByte('0')) |c| {
210 try characters.append(c);
211 if (try p.parser.eatRange('1', '9')) |_| return error.MalformedJson;
212 }
213 while (try p.parser.eatRange('0', '9')) |d| {
214 try characters.append(d);
215 }
216 if (characters.items.len == 0) {
217 return null;
218 }
219 if (characters.items.len == 1 and characters.items[0] == '-') {
220 return error.MalformedJson;
221 }
222 if (try p.parser.eatByte('.')) |c| {
223 try characters.append(c);
224 const l = characters.items.len;
225 while (try p.parser.eatRange('0', '9')) |d| {
226 try characters.append(d);
227 }
228 if (characters.items.len == l) return error.MalformedJson;
229 }
230 if (try p.parser.eatAnyScalar("eE")) |_| {
231 try characters.append('e');
232 try characters.append(try p.parser.eatAnyScalar("+-") orelse '+');
233 const l = characters.items.len;
234 while (try p.parser.eatRange('0', '9')) |d| {
235 try characters.append(d);
236 }
237 if (characters.items.len == l) return error.MalformedJson;
238 }
239
240 return try p.addNumber(characters.items);
241}
242
243fn parseWs(p: *Parser) !void {
244 const t = tracer.trace(@src(), "", .{});
245 defer t.end();
246
247 while (true) {
248 if (try p.parser.eatByte(0x20)) |_| continue; // space
249 if (try p.parser.eatByte(0x0A)) |_| continue; // NL
250 if (try p.parser.eatByte(0x0D)) |_| continue; // CR
251 if (try p.parser.eatByte(0x09)) |_| continue; // TAB
252 break;
253 }
254}
255
256pub const Parser = struct {
257 parser: intrusive_parser.Parser,
258 numbers_map: std.StringArrayHashMapUnmanaged(NumberIndex) = .{},
259 depth: u16 = 0,
260
261 support_trailing_commas: bool,
262 maximum_depth: u16,
263
264 pub fn init(allocator: std.mem.Allocator, any: nio.AnyReadable, options: Options) !Parser {
265 var p: Parser = .{
266 .parser = intrusive_parser.Parser.init(allocator, any, @intFromEnum(Value.Tag.string)),
267 .support_trailing_commas = options.support_trailing_commas,
268 .maximum_depth = options.maximum_depth,
269 };
270 comptime std.debug.assert(@intFromEnum(Value.zero) == 0);
271 try p.parser.data.ensureUnusedCapacity(allocator, 4096);
272 p.parser.data.appendAssumeCapacity(@intFromEnum(Value.Tag.zero));
273 p.parser.data.appendAssumeCapacity(@intFromEnum(Value.Tag.null));
274 p.parser.data.appendAssumeCapacity(@intFromEnum(Value.Tag.true));
275 p.parser.data.appendAssumeCapacity(@intFromEnum(Value.Tag.false));
276 _ = try p.addStr("");
277 std.debug.assert(try p.addArray(&.{}) == .empty_array);
278 std.debug.assert(try p.addObject(&ObjectHashMap{}) == .empty_object);
279
280 return p;
281 }
282
283 pub fn deinit(p: *Parser) void {
284 defer p.numbers_map.deinit(p.parser.allocator);
285 defer p.parser.deinit();
286 }
287
288 pub const Options = struct {
289 support_trailing_commas: bool,
290 maximum_depth: u16,
291 };
292
293 // tag(u8) + len(u32) + member_keys(N * u32) + member_values(N * u32)
294 pub fn addObject(p: *Parser, members: *const ObjectHashMap) !ValueIndex {
295 const t = tracer.trace(@src(), "({d})", .{members.entries.len});
296 defer t.end();
297
298 const alloc = p.parser.allocator;
299 const r = p.parser.data.items.len;
300 const l = members.entries.len;
301 if (l > std.math.maxInt(u32)) return error.MalformedJson;
302 try p.parser.data.ensureUnusedCapacity(alloc, 1 + 4 + (l * 4 * 2));
303 p.parser.data.appendAssumeCapacity(@intFromEnum(Value.Tag.object));
304 p.parser.data.appendSliceAssumeCapacity(&std.mem.toBytes(@as(u32, @intCast(l))));
305 p.parser.data.appendSliceAssumeCapacity(std.mem.sliceAsBytes(members.keys()));
306 p.parser.data.appendSliceAssumeCapacity(std.mem.sliceAsBytes(members.values()));
307 return @enumFromInt(r);
308 }
309
310 pub fn addObjectFromConst(p: *Parser, members: []const struct { StringIndex, ValueIndex }) !ValueIndex {
311 const t = tracer.trace(@src(), "({d})", .{members.len});
312 defer t.end();
313
314 const alloc = p.parser.allocator;
315 var map = ObjectHashMap{};
316 defer map.deinit(alloc);
317 try map.ensureTotalCapacity(alloc, members.len);
318 for (members) |member| map.putAssumeCapacity(member[0], member[1]);
319 return p.addObject(&map);
320 }
321
322 // tag(u8) + len(u32) + items(N * u32)
323 pub fn addArray(p: *Parser, items: []const ValueIndex) !ValueIndex {
324 const t = tracer.trace(@src(), "({d})", .{items.len});
325 defer t.end();
326
327 const alloc = p.parser.allocator;
328 const r = p.parser.data.items.len;
329 const l = items.len;
330 if (l > std.math.maxInt(u32)) return error.MalformedJson;
331 try p.parser.data.ensureUnusedCapacity(alloc, 1 + 4 + (l * 4));
332 p.parser.data.appendAssumeCapacity(@intFromEnum(Value.Tag.array));
333 p.parser.data.appendSliceAssumeCapacity(&std.mem.toBytes(@as(u32, @intCast(l))));
334 p.parser.data.appendSliceAssumeCapacity(std.mem.sliceAsBytes(items));
335 return @enumFromInt(r);
336 }
337
338 // tag(u8) + len(u32) + bytes(N)
339 pub fn addStr(p: *Parser, str: string) !StringIndex {
340 const t = tracer.trace(@src(), "({d})", .{str.len});
341 defer t.end();
342
343 const alloc = p.parser.allocator;
344 return @enumFromInt(try p.parser.addStr(alloc, str));
345 }
346
347 // tag(u8) + len(u32) + bytes(N)
348 pub fn addStrV(p: *Parser, str: string) !ValueIndex {
349 return @enumFromInt(@intFromEnum(try p.addStr(str)));
350 }
351
352 const Adapter = struct {
353 p: *const Parser,
354
355 pub fn hash(ctx: @This(), a: string) u32 {
356 _ = ctx;
357 var hasher = std.hash.Wyhash.init(0);
358 hasher.update(a);
359 return @truncate(hasher.final());
360 }
361
362 pub fn eql(ctx: @This(), a: string, _: string, b_index: usize) bool {
363 const sidx = ctx.p.strings_map.values()[b_index];
364 const b = ctx.p.getStr(sidx);
365 return std.mem.eql(u8, a, b);
366 }
367 };
368
369 pub fn addNumber(p: *Parser, v: []const u8) !ValueIndex {
370 const t = tracer.trace(@src(), "({s})", .{v});
371 defer t.end();
372
373 const alloc = p.parser.allocator;
374 const adapter: AdapterNum = .{ .p = p };
375 const res = try p.numbers_map.getOrPutAdapted(alloc, v, adapter);
376 if (res.found_existing) return @enumFromInt(@intFromEnum(res.value_ptr.*));
377 errdefer p.numbers_map.orderedRemoveAt(res.index);
378 const r = p.parser.data.items.len;
379 const l = v.len;
380 try p.parser.data.ensureUnusedCapacity(alloc, 1 + 4 + l);
381 p.parser.data.appendAssumeCapacity(@intFromEnum(Value.Tag.number));
382 p.parser.data.appendSliceAssumeCapacity(&std.mem.toBytes(@as(u32, @intCast(l))));
383 p.parser.data.appendSliceAssumeCapacity(v);
384 res.value_ptr.* = @enumFromInt(r);
385 return @enumFromInt(r);
386 }
387
388 const AdapterNum = struct {
389 p: *const Parser,
390
391 pub fn hash(ctx: @This(), a: string) u32 {
392 _ = ctx;
393 var hasher = std.hash.Wyhash.init(0);
394 hasher.update(a);
395 return @truncate(hasher.final());
396 }
397
398 pub fn eql(ctx: @This(), a: string, _: string, b_index: usize) bool {
399 const sidx = ctx.p.numbers_map.values()[b_index];
400 const i: u32 = @intFromEnum(sidx);
401 std.debug.assert(@as(Value.Tag, @enumFromInt(ctx.p.parser.data.items[i])) == .number);
402 const l: u32 = @bitCast(ctx.p.parser.data.items[i..][1..][0..4].*);
403 const b = ctx.p.parser.data.items[i..][1..][4..][0..l];
404 return std.mem.eql(u8, a, b);
405 }
406 };
407};
408
409pub threadlocal var doc: ?*const Document = null;
410
411pub const Document = struct {
412 extras: []const u8,
413 root: ValueIndex,
414
415 pub fn deinit(this: *const Document, alloc: std.mem.Allocator) void {
416 alloc.free(this.extras);
417 }
418
419 pub fn acquire(this: *const Document) void {
420 std.debug.assert(doc == null);
421 doc = this;
422 }
423
424 pub fn release(this: *const Document) void {
425 std.debug.assert(doc == this);
426 doc = null;
427 }
428
429 pub fn nprint(this: *const Document, writer: anytype) !void {
430 return nio.fmt.format(writer, "{}", .{this.root});
431 }
432
433 pub fn stringify(this: *const Document, writer: anytype, space: Space, indent: u8) Instance(@TypeOf(writer)).WriteError!void {
434 const fill = space.fill();
435 try writer.writeNTimes(fill, indent);
436 return this.root.stringify(writer, space, indent) catch |err| @errorCast(err);
437 }
438};
439
440pub const ValueIndex = enum(u32) {
441 zero = 0,
442 null = 1,
443 true = 2,
444 false = 3,
445 empty_string = 4,
446 empty_array = 9,
447 empty_object = 14,
448 _,
449
450 pub fn nprint(this: ValueIndex, writer: anytype) !void {
451 return nio.fmt.format(writer, "{}", .{this.v()});
452 }
453
454 fn stringify(this: ValueIndex, writer: anytype, space: Space, indent: u8) !void {
455 return this.v().stringify(writer, space, indent);
456 }
457
458 pub fn v(this: ValueIndex) Value {
459 std.debug.assert(this != .zero);
460 std.debug.assert(doc != null); // make sure to call Document.acquire()
461 return switch (@as(Value.Tag, @enumFromInt(doc.?.extras[@intFromEnum(this)]))) {
462 .zero => .zero,
463 .null => .null,
464 .true => .true,
465 .false => .false,
466 inline .object, .array, .string, .number => |t| @unionInit(Value, @tagName(t), @enumFromInt(@intFromEnum(this))),
467 };
468 }
469
470 pub fn string(this: ValueIndex) []const u8 {
471 return this.v().string.to();
472 }
473
474 pub fn array(this: ValueIndex) Array {
475 return this.v().array.to();
476 }
477
478 pub fn object(this: ValueIndex) ObjectIndex {
479 return this.v().object;
480 }
481
482 pub fn number(this: ValueIndex) NumberIndex {
483 return this.v().number;
484 }
485
486 pub fn boolean(this: ValueIndex) bool {
487 return switch (this) {
488 .true => true,
489 .false => false,
490 else => unreachable,
491 };
492 }
493};
494
495pub const Value = union(enum(u8)) {
496 zero,
497 null,
498 true,
499 false,
500 object: ObjectIndex,
501 array: ArrayIndex,
502 string: StringIndex,
503 number: NumberIndex,
504
505 const Tag = std.meta.Tag(@This());
506
507 pub fn nprint(this: Value, writer: anytype) !void {
508 return switch (this) {
509 .zero => unreachable,
510 .null => nio.fmt.format(writer, "null", .{}),
511 .true => nio.fmt.format(writer, "true", .{}),
512 .false => nio.fmt.format(writer, "false", .{}),
513 inline .object, .array, .string, .number => |t| nio.fmt.format(writer, "{}", .{t}),
514 };
515 }
516
517 fn stringify(this: Value, writer: anytype, space: Space, indent: u8) anyerror!void {
518 return switch (this) {
519 .zero => unreachable,
520 .null => writer.writeAll("null"),
521 .true => writer.writeAll("true"),
522 .false => writer.writeAll("false"),
523 inline .object, .array => |t| t.stringify(writer, space, indent),
524 inline .string, .number => |t| t.stringify(writer),
525 };
526 }
527};
528
529pub const Array = []align(1) const ValueIndex;
530
531pub const StringIndex = enum(u32) {
532 _,
533
534 pub fn nprint(this: StringIndex, writer: anytype) !void {
535 try writer.writeAll("\"");
536 try writer.writeAll(this.to());
537 try writer.writeAll("\"");
538 }
539
540 fn stringify(this: StringIndex, writer: anytype) anyerror!void {
541 try writer.writeAll("\"");
542 try writer.writeAll(this.to());
543 try writer.writeAll("\"");
544 }
545
546 pub fn to(this: StringIndex) []const u8 {
547 var d = doc.?.extras.ptr[@intFromEnum(this)..];
548 std.debug.assert(@as(Value.Tag, @enumFromInt(d[0])) == .string);
549 const len: u32 = @bitCast(d[1..5].*);
550 return d[5..][0..len];
551 }
552};
553
554pub const ArrayIndex = enum(u32) {
555 _,
556
557 pub fn nprint(this: ArrayIndex, writer: anytype) !void {
558 const items = this.to();
559 try writer.writeAll("[");
560 for (items, 0..) |item, i| {
561 if (i > 0) try writer.writeAll(",");
562 try writer.print("{}", .{item});
563 }
564 try writer.writeAll("]");
565 }
566
567 fn stringify(this: ArrayIndex, writer: anytype, space: Space, indent: u8) anyerror!void {
568 const fill = space.fill();
569 const items = this.to();
570 try writer.writeAll("[");
571 for (items, 0..) |item, i| {
572 if (i > 0) try writer.writeAll(",");
573 if (fill.len > 0) try writer.writeAll("\n");
574 try writer.writeNTimes(fill, indent + 1);
575 try item.stringify(writer, space, indent + 1);
576 }
577 if (fill.len > 0) try writer.writeAll("\n");
578 try writer.writeNTimes(fill, indent);
579 try writer.writeAll("]");
580 }
581
582 pub fn to(this: ArrayIndex) Array {
583 var d = doc.?.extras.ptr[@intFromEnum(this)..];
584 std.debug.assert(@as(Value.Tag, @enumFromInt(d[0])) == .array);
585 const len: u32 = @bitCast(d[1..5].*);
586 const e: [*]align(1) const ValueIndex = @ptrCast(d[5..]);
587 return e[0..len];
588 }
589};
590
591pub const ObjectIndex = enum(u32) {
592 _,
593
594 pub fn nprint(this: ObjectIndex, writer: anytype) !void {
595 const keys, const values = this.to();
596 try writer.writeAll("{");
597 for (keys, values, 0..) |k, v, i| {
598 if (i > 0) try writer.writeAll(",");
599 try writer.print("{}", .{k});
600 try writer.writeAll(":");
601 try writer.print("{}", .{v});
602 }
603 try writer.writeAll("}");
604 }
605
606 fn stringify(this: ObjectIndex, writer: anytype, space: Space, indent: u8) anyerror!void {
607 const fill = space.fill();
608 const keys, const values = this.to();
609 try writer.writeAll("{");
610 for (keys, values, 0..) |k, v, i| {
611 if (i > 0) try writer.writeAll(",");
612 if (fill.len > 0) try writer.writeAll("\n");
613 try writer.writeNTimes(fill, indent + 1);
614 try k.stringify(writer);
615 try writer.writeAll(":");
616 try v.stringify(writer, space, indent + 1);
617 }
618 if (fill.len > 0) try writer.writeAll("\n");
619 try writer.writeNTimes(fill, indent);
620 try writer.writeAll("}");
621 }
622
623 pub fn to(this: ObjectIndex) struct { []align(1) const StringIndex, Array } {
624 var d = doc.?.extras.ptr[@intFromEnum(this)..];
625 std.debug.assert(@as(Value.Tag, @enumFromInt(d[0])) == .object);
626 const len: u32 = @bitCast(d[1..5].*);
627 const k: [*]align(1) const StringIndex = @ptrCast(d[5..]);
628 const v: [*]align(1) const ValueIndex = @ptrCast(k + len);
629 return .{ k[0..len], v[0..len] };
630 }
631
632 pub fn getAny(this: ObjectIndex, needle: []const u8) ?ValueIndex {
633 const keys, const values = this.to();
634 for (keys, values) |k, v| {
635 if (std.mem.eql(u8, needle, k.to())) {
636 if (v.v() == .null) {
637 return null;
638 }
639 return v;
640 }
641 }
642 return null;
643 }
644
645 pub fn get(this: ObjectIndex, needle: []const u8, comptime tag: Value.Tag) ?ValueIndex {
646 const keys, const values = this.to();
647 for (keys, values) |k, v| {
648 if (std.mem.eql(u8, needle, k.to())) {
649 if (v.v() == .null) {
650 return null;
651 }
652 if (v.v() == tag) {
653 return v;
654 }
655 }
656 }
657 return null;
658 }
659
660 pub fn getO(this: ObjectIndex, needle: []const u8) ?ObjectIndex {
661 return if (this.get(needle, .object)) |v| v.object() else null;
662 }
663
664 pub fn getA(this: ObjectIndex, needle: []const u8) ?Array {
665 return if (this.get(needle, .array)) |v| v.array() else null;
666 }
667
668 pub fn getS(this: ObjectIndex, needle: []const u8) ?[]const u8 {
669 return if (this.get(needle, .string)) |v| v.string() else null;
670 }
671
672 pub fn getN(this: ObjectIndex, needle: []const u8) ?NumberIndex {
673 return if (this.get(needle, .number)) |v| v.number() else null;
674 }
675
676 pub fn getB(this: ObjectIndex, needle: []const u8) ?bool {
677 if (this.get(needle, .true)) |v| return v.boolean();
678 if (this.get(needle, .false)) |v| return v.boolean();
679 return null;
680 }
681};
682
683pub const NumberIndex = enum(u32) {
684 _,
685
686 pub fn nprint(this: NumberIndex, writer: anytype) !void {
687 try writer.writeAll(this.to());
688 }
689
690 fn stringify(this: NumberIndex, writer: anytype) anyerror!void {
691 try writer.writeAll(this.to());
692 }
693
694 pub fn to(this: NumberIndex) []const u8 {
695 var d = doc.?.extras.ptr[@intFromEnum(this)..];
696 std.debug.assert(@as(Value.Tag, @enumFromInt(d[0])) == .number);
697 const len: u32 = @bitCast(d[1..5].*);
698 return d[5..][0..len];
699 }
700
701 pub fn get(this: NumberIndex, comptime T: type) T {
702 return switch (@typeInfo(T)) {
703 .int => extras.parseDigits(T, this.to(), 10) catch unreachable,
704 .float => std.fmt.parseFloat(T, this.to()) catch unreachable,
705 else => @compileError("not a number type"),
706 };
707 }
708};
709
710const Space = union(enum) {
711 count: u8,
712 custom: []const u8,
713
714 pub fn fill(s: Space) []const u8 {
715 return switch (s) {
716 .count => |c| " "[0..@min(c, 10)],
717 .custom => |f| f[0..@min(f.len, 10)],
718 };
719 }
720};
721
722pub fn stringify(writer: anytype, value: anytype, options: std.json.Stringify.Options) (extras.Pointee(@TypeOf(writer)).WriteError || error{Unexpected})!void {
723 const T = @TypeOf(value);
724 if (comptime extras.isZigString(T)) {
725 if (extras.matchesAll(u8, value, std.ascii.isAscii)) {
726 if (extras.matchesAll(u8, value, std.ascii.isPrint)) {
727 try writer.writevAll(&.{ &.{'"'}, value, &.{'"'} });
728 } else {
729 try writer.writeAll("\"");
730 for (value) |c| {
731 try writer.writeAll(switch (c) {
732 0x08 => "\\b",
733 0x09 => "\\t",
734 0x0a => "\\n",
735 0x0c => "\\f",
736 0x0d => "\\r",
737 0x20...0x21 => &.{c},
738 0x22 => "\\\"",
739 0x23...0x7e => &.{c},
740 else => "\\u00" ++ extras.to_hex([_]u8{c}),
741 });
742 }
743 try writer.writeAll("\"");
744 }
745 } else {
746 var view = std.unicode.Utf8View.init(value) catch {
747 try writer.writeAll("\"");
748 for (value) |c| {
749 try writer.writeAll(switch (c) {
750 else => "\\u00" ++ extras.to_hex([_]u8{c}),
751 });
752 }
753 try writer.writeAll("\"");
754 return;
755 };
756 var iter = view.iterator();
757 try writer.writeAll("\"");
758 while (iter.nextCodepointSlice()) |sl| {
759 const cp = std.unicode.utf8Decode(sl) catch unreachable;
760 if (cp < 128) {
761 const c: u8 = @intCast(cp);
762 try writer.writeAll(switch (c) {
763 0x08 => "\\b",
764 0x09 => "\\t",
765 0x0a => "\\n",
766 0x0c => "\\f",
767 0x0d => "\\r",
768 0x20...0x21 => &.{c},
769 0x22 => "\\\"",
770 0x23...0x7e => &.{c},
771 else => "\\u00" ++ extras.to_hex([_]u8{c}),
772 });
773 continue;
774 }
775 try writer.writeAll(sl);
776 }
777 try writer.writeAll("\"");
778 return;
779 }
780 return;
781 }
782 if (comptime extras.isArrayOf(u8)(T)) {
783 return stringify(writer, &value, options);
784 }
785 if (comptime extras.isSlice(T)) {
786 try writer.writeAll("[");
787 for (value, 0..) |item, i| {
788 if (i > 0) try writer.writeAll(",");
789 try stringify(writer, item, options);
790 }
791 try writer.writeAll("]");
792 return;
793 }
794 switch (@typeInfo(T)) {
795 .@"struct" => |info| {
796 if (@hasDecl(T, "stringifyJson")) {
797 return T.stringifyJson(value, writer, options, @This());
798 }
799 try writer.writeAll("{");
800 inline for (info.fields, 0..) |field, i| blk: {
801 const field_value = @field(value, field.name);
802 if (((@typeInfo(field.type) == .optional and field_value == null) or @typeInfo(field.type) == .null) and !options.emit_null_optional_fields) break :blk;
803 if (i > 0) try writer.writeAll(",");
804 try stringify(writer, field.name, options);
805 try writer.writeAll(":");
806 try stringify(writer, field_value, options);
807 }
808 try writer.writeAll("}");
809 },
810 .comptime_int, .int => {
811 return nio.fmt.formatInt(value, 10, .lower, .{}, writer);
812 },
813 .optional => {
814 if (value) |v| {
815 try stringify(writer, v, options);
816 } else {
817 try writer.writeAll("null");
818 }
819 },
820 .bool => {
821 try writer.writeAll(if (value) "true" else "false");
822 },
823 .@"union" => {
824 if (@hasDecl(T, "stringifyJson")) {
825 return T.stringifyJson(value, writer, options, @This());
826 }
827 switch (value) {
828 inline else => |v, t| {
829 try writer.writeAll("{");
830 try stringify(writer, @tagName(t), options);
831 try writer.writeAll(":");
832 try stringify(writer, v, options);
833 try writer.writeAll("}");
834 },
835 }
836 },
837 .void => {
838 try writer.writeAll("{}");
839 },
840 .@"enum" => {
841 try T.stringifyJson(value, writer, options, @This());
842 },
843 .null => {
844 try writer.writeAll("null");
845 },
846 else => @compileError(@typeName(T)),
847 }
848}
849
850pub fn stringifyAlloc(allocator: std.mem.Allocator, value: anytype, options: std.json.Stringify.Options) ![]u8 {
851 var writer: nio.AllocatingWriter = .init(allocator);
852 defer writer.deinit();
853 try writer.ensureUnusedCapacity(256);
854 try stringify(&writer, value, options);
855 return writer.toOwnedSlice();
856}