| 1 | const std = @import("std"); |
| 2 | const string = []const u8; |
| 3 | const extras = @import("extras"); |
| 4 | const nio = @import("nio"); |
| 5 | |
| 6 | pub 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 | }; |