1const std = @import("std");
2const string = []const u8;
3const extras = @import("extras");
4const nio = @import("nio");
5
6pub const Parser = struct {
7 any: nio.AnyReadable,
8 allocator: std.mem.Allocator,
9 temp: std.ArrayListUnmanaged(u8) = .empty,
10 idx: usize = 0,
11 end: bool = false,
12 line: usize = 1,
13 col: usize = 1,
14 data: std.ArrayListUnmanaged(u8) = .empty,
15 strings_map: std.StringArrayHashMapUnmanaged(usize) = .empty,
16 string_tag: u8,
17
18 pub fn init(allocator: std.mem.Allocator, any: nio.AnyReadable, string_tag: u8) Parser {
19 return .{
20 .any = any,
21 .allocator = allocator,
22 .string_tag = string_tag,
23 };
24 }
25
26 pub fn deinit(p: *Parser) void {
27 p.temp.deinit(p.allocator);
28 p.data.deinit(p.allocator);
29 p.strings_map.deinit(p.allocator);
30 }
31
32 pub inline fn avail(p: *Parser) usize {
33 return p.temp.items.len - p.idx;
34 }
35
36 pub inline fn slice(p: *Parser) []const u8 {
37 return p.temp.items[p.idx..];
38 }
39
40 pub fn eat(p: *Parser, comptime test_s: string) !?void {
41 if (test_s.len == 1) {
42 _ = try p.eatByte(test_s[0]);
43 return;
44 }
45 try p.peekAmt(test_s.len) orelse return null;
46 if (std.mem.eql(u8, p.slice()[0..test_s.len], test_s)) {
47 p.idx += test_s.len;
48 return;
49 }
50 return null;
51 }
52
53 fn peekAmt(p: *Parser, amt: usize) !?void {
54 if (p.avail() >= amt) return;
55 const buf_size = std.heap.page_size_min;
56 const diff_amt = amt - p.avail();
57 std.debug.assert(diff_amt <= buf_size);
58 var buf: [buf_size]u8 = undefined;
59 const len = try p.any.readAll(&buf);
60 if (len == 0) p.end = true;
61 if (len == 0) return null;
62 try p.temp.appendSlice(p.allocator, buf[0..len]);
63 if (amt > len) return null;
64 }
65
66 pub fn eatByte(p: *Parser, test_c: u8) !?u8 {
67 try p.peekAmt(1) orelse return null;
68 if (p.slice()[0] == test_c) {
69 p.idx += 1;
70 return test_c;
71 }
72 return null;
73 }
74
75 pub fn eatRange(p: *Parser, comptime from: u8, comptime to: u8) !?u8 {
76 try p.peekAmt(1) orelse return null;
77 if (p.slice()[0] >= from and p.slice()[0] <= to) {
78 defer p.idx += 1;
79 return p.slice()[0];
80 }
81 return null;
82 }
83
84 pub fn eatAnyScalar(p: *Parser, test_s: string) !?u8 {
85 std.debug.assert(extras.matchesAll(u8, test_s, std.ascii.isAscii));
86 try p.peekAmt(1) orelse return null;
87 if (std.mem.indexOfScalar(u8, test_s, p.slice()[0])) |idx| {
88 p.idx += 1;
89 return test_s[idx];
90 }
91 return null;
92 }
93
94 pub fn shift(p: *Parser) !u21 {
95 try p.peekAmt(1) orelse return error.EndOfStream;
96 const len = std.unicode.utf8ByteSequenceLength(p.slice()[0]) catch return error.MalformedJson;
97 try p.peekAmt(len) orelse return error.EndOfStream;
98 defer p.idx += len;
99 return std.unicode.utf8Decode(p.slice()[0..len]) catch return error.MalformedJson;
100 }
101
102 pub fn shiftBytesN(p: *Parser, comptime n: usize) ![n]u8 {
103 try p.peekAmt(n) orelse return error.EndOfStream;
104 defer p.idx += n;
105 return p.slice()[0..n].*;
106 }
107
108 pub fn trimByte(p: *Parser, test_c: u8) !usize {
109 var amt: usize = 0;
110 while (true) {
111 const s = p.slice();
112 if (s.len == 0) break;
113 if (s[0] != test_c) break;
114 p.idx += 1;
115 amt += 1;
116 }
117 return amt;
118 }
119
120 pub fn eatUntil(p: *Parser, test_c: u8) !?[2]usize {
121 const start = p.idx;
122 while (true) {
123 try p.peekAmt(1) orelse return null;
124 const amt = std.mem.indexOfScalar(u8, p.slice(), test_c) orelse {
125 const left = p.avail();
126 p.idx += left;
127 continue;
128 };
129 p.idx += amt;
130 p.idx += 1;
131 const end = p.idx;
132 return .{ start, end };
133 }
134 }
135
136 pub fn eatUntilStr(p: *Parser, test_s: []const u8) !?[2]usize {
137 const start = p.idx;
138 while (true) {
139 try p.peekAmt(1) orelse return null;
140 const amt = std.mem.indexOf(u8, p.slice(), test_s) orelse {
141 const left = p.avail();
142 p.idx += left;
143 continue;
144 };
145 p.idx += amt;
146 p.idx += test_s.len;
147 const end = p.idx;
148 return .{ start, end };
149 }
150 }
151
152 // tag(u8) + len(u32) + bytes(N)
153 pub fn addStr(p: *Parser, alloc: std.mem.Allocator, str: string) !usize {
154 const adapter: AdapterStr = .{ .p = p };
155 const res = try p.strings_map.getOrPutAdapted(alloc, str, adapter);
156 if (res.found_existing) return res.value_ptr.*;
157 errdefer p.strings_map.orderedRemoveAt(res.index);
158 const r = p.data.items.len;
159 const l = str.len;
160 try p.data.ensureUnusedCapacity(alloc, 1 + 4 + l);
161 p.data.appendAssumeCapacity(p.string_tag);
162 p.data.appendSliceAssumeCapacity(&std.mem.toBytes(@as(u32, @intCast(l))));
163 p.data.appendSliceAssumeCapacity(str);
164 res.value_ptr.* = r;
165 return r;
166 }
167
168 const AdapterStr = struct {
169 p: *const Parser,
170
171 pub fn hash(ctx: @This(), a: string) u32 {
172 _ = ctx;
173 var hasher = std.hash.Wyhash.init(0);
174 hasher.update(a);
175 return @truncate(hasher.final());
176 }
177
178 pub fn eql(ctx: @This(), a: string, _: string, b_index: usize) bool {
179 const i = ctx.p.strings_map.values()[b_index];
180 std.debug.assert(ctx.p.data.items[i] == ctx.p.string_tag);
181 const l: u32 = @bitCast(ctx.p.data.items[i..][1..][0..4].*);
182 const b = ctx.p.data.items[i..][1..][4..][0..l];
183 return std.mem.eql(u8, a, b);
184 }
185 };
186
187 /// Similar to addStr but lets you change the tag so that new index types can be aliases and use the same intern storage
188 pub fn AddStrGeneric(comptime tag: u8) type {
189 return struct {
190 pub fn add(p: *Parser, alloc: std.mem.Allocator, str: string) !usize {
191 const adapter: Adapter = .{ .p = p };
192 const res = try p.strings_map.getOrPutAdapted(alloc, str, adapter);
193 if (res.found_existing) return res.value_ptr.*;
194 errdefer p.strings_map.orderedRemoveAt(res.index);
195 const r = p.data.items.len;
196 const l = str.len;
197 try p.data.ensureUnusedCapacity(alloc, 1 + 4 + l);
198 p.data.appendAssumeCapacity(tag);
199 p.data.appendSliceAssumeCapacity(&std.mem.toBytes(@as(u32, @intCast(l))));
200 p.data.appendSliceAssumeCapacity(str);
201 res.value_ptr.* = r;
202 return r;
203 }
204
205 const Adapter = struct {
206 p: *const Parser,
207
208 pub fn hash(ctx: @This(), a: string) u32 {
209 _ = ctx;
210 var hasher = std.hash.Wyhash.init(0);
211 hasher.update(a);
212 return @truncate(hasher.final());
213 }
214
215 pub fn eql(ctx: @This(), a: string, _: string, b_index: usize) bool {
216 const i = ctx.p.strings_map.values()[b_index];
217 std.debug.assert(ctx.p.data.items[i] == tag);
218 const l: u32 = @bitCast(ctx.p.data.items[i..][1..][0..4].*);
219 const b = ctx.p.data.items[i..][1..][4..][0..l];
220 return std.mem.eql(u8, a, b);
221 }
222 };
223 };
224 }
225};