| author | |
| committer | |
| log | 70994b13df94ac4a3392decef498724d0e0a0a28 |
| tree | 98f0565fa62409dc61bd9a22abb81bbdb153a7fc |
| parent | 5d2faeb8f3acbcf28e08f1bd126e1cd8191afd07 |
| parent | e4abdf5a133ac4822644da1dabd5437ac751d78d |
48 files changed, 3011 insertions(+), 3307 deletions(-)
doc/langref.html.in+1-1| ... | ... | @@ -7987,7 +7987,7 @@ AsmInput <- COLON AsmInputList AsmClobbers? |
| 7987 | 7987 | |
| 7988 | 7988 | AsmInputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN Expr RPAREN |
| 7989 | 7989 | |
| 7990 | AsmClobbers <- COLON StringList | |
| 7990 | AsmClobbers <- COLON Expr | |
| 7991 | 7991 | |
| 7992 | 7992 | # *** Helper grammar *** |
| 7993 | 7993 | BreakLabel <- COLON IDENTIFIER |
lib/compiler/resinator/main.zig+7-5| ... | ... | @@ -292,12 +292,14 @@ pub fn main() !void { |
| 292 | 292 | }; |
| 293 | 293 | defer depfile.close(); |
| 294 | 294 | |
| 295 | const depfile_writer = depfile.deprecatedWriter(); | |
| 296 | var depfile_buffered_writer = std.io.bufferedWriter(depfile_writer); | |
| 295 | var depfile_buffer: [1024]u8 = undefined; | |
| 296 | var depfile_writer = depfile.writer(&depfile_buffer); | |
| 297 | 297 | switch (options.depfile_fmt) { |
| 298 | 298 | .json => { |
| 299 | var write_stream = std.json.writeStream(depfile_buffered_writer.writer(), .{ .whitespace = .indent_2 }); | |
| 300 | defer write_stream.deinit(); | |
| 299 | var write_stream: std.json.Stringify = .{ | |
| 300 | .writer = &depfile_writer.interface, | |
| 301 | .options = .{ .whitespace = .indent_2 }, | |
| 302 | }; | |
| 301 | 303 | |
| 302 | 304 | try write_stream.beginArray(); |
| 303 | 305 | for (dependencies_list.items) |dep_path| { |
| ... | ... | @@ -306,7 +308,7 @@ pub fn main() !void { |
| 306 | 308 | try write_stream.endArray(); |
| 307 | 309 | }, |
| 308 | 310 | } |
| 309 | try depfile_buffered_writer.flush(); | |
| 311 | try depfile_writer.interface.flush(); | |
| 310 | 312 | } |
| 311 | 313 | } |
| 312 | 314 |
lib/compiler/test_runner.zig+6-6| ... | ... | @@ -10,10 +10,10 @@ pub const std_options: std.Options = .{ |
| 10 | 10 | }; |
| 11 | 11 | |
| 12 | 12 | var log_err_count: usize = 0; |
| 13 | var fba_buffer: [8192]u8 = undefined; | |
| 14 | 13 | var fba = std.heap.FixedBufferAllocator.init(&fba_buffer); |
| 15 | var stdin_buffer: [std.heap.page_size_min]u8 align(std.heap.page_size_min) = undefined; | |
| 16 | var stdout_buffer: [std.heap.page_size_min]u8 align(std.heap.page_size_min) = undefined; | |
| 14 | var fba_buffer: [8192]u8 = undefined; | |
| 15 | var stdin_buffer: [4096]u8 = undefined; | |
| 16 | var stdout_buffer: [4096]u8 = undefined; | |
| 17 | 17 | |
| 18 | 18 | const crippled = switch (builtin.zig_backend) { |
| 19 | 19 | .stage2_powerpc, |
| ... | ... | @@ -68,8 +68,8 @@ pub fn main() void { |
| 68 | 68 | |
| 69 | 69 | fn mainServer() !void { |
| 70 | 70 | @disableInstrumentation(); |
| 71 | var stdin_reader = std.fs.File.stdin().reader(&stdin_buffer); | |
| 72 | var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); | |
| 71 | var stdin_reader = std.fs.File.stdin().readerStreaming(&stdin_buffer); | |
| 72 | var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer); | |
| 73 | 73 | var server = try std.zig.Server.init(.{ |
| 74 | 74 | .in = &stdin_reader.interface, |
| 75 | 75 | .out = &stdout_writer.interface, |
| ... | ... | @@ -104,7 +104,7 @@ fn mainServer() !void { |
| 104 | 104 | defer testing.allocator.free(expected_panic_msgs); |
| 105 | 105 | |
| 106 | 106 | for (test_fns, names, expected_panic_msgs) |test_fn, *name, *expected_panic_msg| { |
| 107 | name.* = @as(u32, @intCast(string_bytes.items.len)); | |
| 107 | name.* = @intCast(string_bytes.items.len); | |
| 108 | 108 | try string_bytes.ensureUnusedCapacity(testing.allocator, test_fn.name.len + 1); |
| 109 | 109 | string_bytes.appendSliceAssumeCapacity(test_fn.name); |
| 110 | 110 | string_bytes.appendAssumeCapacity(0); |
lib/std/Build/Cache/Path.zig+5-3| ... | ... | @@ -161,17 +161,19 @@ pub fn formatEscapeString(path: Path, writer: *std.io.Writer) std.io.Writer.Erro |
| 161 | 161 | } |
| 162 | 162 | } |
| 163 | 163 | |
| 164 | /// Deprecated, use double quoted escape to print paths. | |
| 164 | 165 | pub fn fmtEscapeChar(path: Path) std.fmt.Formatter(Path, formatEscapeChar) { |
| 165 | 166 | return .{ .data = path }; |
| 166 | 167 | } |
| 167 | 168 | |
| 169 | /// Deprecated, use double quoted escape to print paths. | |
| 168 | 170 | pub fn formatEscapeChar(path: Path, writer: *std.io.Writer) std.io.Writer.Error!void { |
| 169 | 171 | if (path.root_dir.path) |p| { |
| 170 | try std.zig.charEscape(p, writer); | |
| 171 | if (path.sub_path.len > 0) try std.zig.charEscape(fs.path.sep_str, writer); | |
| 172 | for (p) |byte| try std.zig.charEscape(byte, writer); | |
| 173 | if (path.sub_path.len > 0) try writer.writeByte(fs.path.sep); | |
| 172 | 174 | } |
| 173 | 175 | if (path.sub_path.len > 0) { |
| 174 | try std.zig.charEscape(path.sub_path, writer); | |
| 176 | for (path.sub_path) |byte| try std.zig.charEscape(byte, writer); | |
| 175 | 177 | } |
| 176 | 178 | } |
| 177 | 179 |
lib/std/Build/Step/Run.zig+7-5| ... | ... | @@ -1122,10 +1122,12 @@ fn runCommand( |
| 1122 | 1122 | // Wine's excessive stderr logging is only situationally helpful. Disable it by default, but |
| 1123 | 1123 | // allow the user to override it (e.g. with `WINEDEBUG=err+all`) if desired. |
| 1124 | 1124 | if (env_map.get("WINEDEBUG") == null) { |
| 1125 | // We don't own `env_map` at this point, so turn it into a copy before modifying it. | |
| 1126 | env_map = arena.create(EnvMap) catch @panic("OOM"); | |
| 1127 | env_map.hash_map = try env_map.hash_map.cloneWithAllocator(arena); | |
| 1128 | try env_map.put("WINEDEBUG", "-all"); | |
| 1125 | // We don't own `env_map` at this point, so create a copy in order to modify it. | |
| 1126 | const new_env_map = arena.create(EnvMap) catch @panic("OOM"); | |
| 1127 | new_env_map.hash_map = try env_map.hash_map.cloneWithAllocator(arena); | |
| 1128 | try new_env_map.put("WINEDEBUG", "-all"); | |
| 1129 | ||
| 1130 | env_map = new_env_map; | |
| 1129 | 1131 | } |
| 1130 | 1132 | } else { |
| 1131 | 1133 | return failForeign(run, "-fwine", argv[0], exe); |
| ... | ... | @@ -1737,7 +1739,7 @@ fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { |
| 1737 | 1739 | .tag = tag, |
| 1738 | 1740 | .bytes_len = 0, |
| 1739 | 1741 | }; |
| 1740 | try file.writeAll(std.mem.asBytes(&header)); | |
| 1742 | try file.writeAll(@ptrCast(&header)); | |
| 1741 | 1743 | } |
| 1742 | 1744 | |
| 1743 | 1745 | fn sendRunTestMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag, index: u32) !void { |
lib/std/Io/Reader.zig+42-21| ... | ... | @@ -990,9 +990,9 @@ pub fn discardDelimiterLimit(r: *Reader, delimiter: u8, limit: Limit) DiscardDel |
| 990 | 990 | /// Returns `error.EndOfStream` if and only if there are fewer than `n` bytes |
| 991 | 991 | /// remaining. |
| 992 | 992 | /// |
| 993 | /// Asserts buffer capacity is at least `n`. | |
| 993 | /// If the end of stream is not encountered, asserts buffer capacity is at | |
| 994 | /// least `n`. | |
| 994 | 995 | pub fn fill(r: *Reader, n: usize) Error!void { |
| 995 | assert(n <= r.buffer.len); | |
| 996 | 996 | if (r.seek + n <= r.end) { |
| 997 | 997 | @branchHint(.likely); |
| 998 | 998 | return; |
| ... | ... | @@ -1108,9 +1108,9 @@ pub fn takeVarInt(r: *Reader, comptime Int: type, endian: std.builtin.Endian, n: |
| 1108 | 1108 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. |
| 1109 | 1109 | /// |
| 1110 | 1110 | /// See also: |
| 1111 | /// * `peekStructReference` | |
| 1111 | /// * `peekStructPointer` | |
| 1112 | 1112 | /// * `takeStruct` |
| 1113 | pub fn takeStructReference(r: *Reader, comptime T: type) Error!*align(1) T { | |
| 1113 | pub fn takeStructPointer(r: *Reader, comptime T: type) Error!*align(1) T { | |
| 1114 | 1114 | // Only extern and packed structs have defined in-memory layout. |
| 1115 | 1115 | comptime assert(@typeInfo(T).@"struct".layout != .auto); |
| 1116 | 1116 | return @ptrCast(try r.takeArray(@sizeOf(T))); |
| ... | ... | @@ -1122,9 +1122,9 @@ pub fn takeStructReference(r: *Reader, comptime T: type) Error!*align(1) T { |
| 1122 | 1122 | /// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. |
| 1123 | 1123 | /// |
| 1124 | 1124 | /// See also: |
| 1125 | /// * `takeStructReference` | |
| 1125 | /// * `takeStructPointer` | |
| 1126 | 1126 | /// * `peekStruct` |
| 1127 | pub fn peekStructReference(r: *Reader, comptime T: type) Error!*align(1) T { | |
| 1127 | pub fn peekStructPointer(r: *Reader, comptime T: type) Error!*align(1) T { | |
| 1128 | 1128 | // Only extern and packed structs have defined in-memory layout. |
| 1129 | 1129 | comptime assert(@typeInfo(T).@"struct".layout != .auto); |
| 1130 | 1130 | return @ptrCast(try r.peekArray(@sizeOf(T))); |
| ... | ... | @@ -1136,19 +1136,19 @@ pub fn peekStructReference(r: *Reader, comptime T: type) Error!*align(1) T { |
| 1136 | 1136 | /// when `endian` is comptime-known and matches the host endianness. |
| 1137 | 1137 | /// |
| 1138 | 1138 | /// See also: |
| 1139 | /// * `takeStructReference` | |
| 1139 | /// * `takeStructPointer` | |
| 1140 | 1140 | /// * `peekStruct` |
| 1141 | 1141 | pub inline fn takeStruct(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T { |
| 1142 | 1142 | switch (@typeInfo(T)) { |
| 1143 | 1143 | .@"struct" => |info| switch (info.layout) { |
| 1144 | 1144 | .auto => @compileError("ill-defined memory layout"), |
| 1145 | 1145 | .@"extern" => { |
| 1146 | var res = (try r.takeStructReference(T)).*; | |
| 1146 | var res = (try r.takeStructPointer(T)).*; | |
| 1147 | 1147 | if (native_endian != endian) std.mem.byteSwapAllFields(T, &res); |
| 1148 | 1148 | return res; |
| 1149 | 1149 | }, |
| 1150 | 1150 | .@"packed" => { |
| 1151 | return takeInt(r, info.backing_integer.?, endian); | |
| 1151 | return @bitCast(try takeInt(r, info.backing_integer.?, endian)); | |
| 1152 | 1152 | }, |
| 1153 | 1153 | }, |
| 1154 | 1154 | else => @compileError("not a struct"), |
| ... | ... | @@ -1162,18 +1162,18 @@ pub inline fn takeStruct(r: *Reader, comptime T: type, endian: std.builtin.Endia |
| 1162 | 1162 | /// |
| 1163 | 1163 | /// See also: |
| 1164 | 1164 | /// * `takeStruct` |
| 1165 | /// * `peekStructReference` | |
| 1165 | /// * `peekStructPointer` | |
| 1166 | 1166 | pub inline fn peekStruct(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T { |
| 1167 | 1167 | switch (@typeInfo(T)) { |
| 1168 | 1168 | .@"struct" => |info| switch (info.layout) { |
| 1169 | 1169 | .auto => @compileError("ill-defined memory layout"), |
| 1170 | 1170 | .@"extern" => { |
| 1171 | var res = (try r.peekStructReference(T)).*; | |
| 1171 | var res = (try r.peekStructPointer(T)).*; | |
| 1172 | 1172 | if (native_endian != endian) std.mem.byteSwapAllFields(T, &res); |
| 1173 | 1173 | return res; |
| 1174 | 1174 | }, |
| 1175 | 1175 | .@"packed" => { |
| 1176 | return peekInt(r, info.backing_integer.?, endian); | |
| 1176 | return @bitCast(try peekInt(r, info.backing_integer.?, endian)); | |
| 1177 | 1177 | }, |
| 1178 | 1178 | }, |
| 1179 | 1179 | else => @compileError("not a struct"), |
| ... | ... | @@ -1557,27 +1557,27 @@ test takeVarInt { |
| 1557 | 1557 | try testing.expectError(error.EndOfStream, r.takeVarInt(u16, .little, 1)); |
| 1558 | 1558 | } |
| 1559 | 1559 | |
| 1560 | test takeStructReference { | |
| 1560 | test takeStructPointer { | |
| 1561 | 1561 | var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 }); |
| 1562 | 1562 | const S = extern struct { a: u8, b: u16 }; |
| 1563 | 1563 | switch (native_endian) { |
| 1564 | .little => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.takeStructReference(S)).*), | |
| 1565 | .big => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.takeStructReference(S)).*), | |
| 1564 | .little => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.takeStructPointer(S)).*), | |
| 1565 | .big => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.takeStructPointer(S)).*), | |
| 1566 | 1566 | } |
| 1567 | try testing.expectError(error.EndOfStream, r.takeStructReference(S)); | |
| 1567 | try testing.expectError(error.EndOfStream, r.takeStructPointer(S)); | |
| 1568 | 1568 | } |
| 1569 | 1569 | |
| 1570 | test peekStructReference { | |
| 1570 | test peekStructPointer { | |
| 1571 | 1571 | var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 }); |
| 1572 | 1572 | const S = extern struct { a: u8, b: u16 }; |
| 1573 | 1573 | switch (native_endian) { |
| 1574 | 1574 | .little => { |
| 1575 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStructReference(S)).*); | |
| 1576 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStructReference(S)).*); | |
| 1575 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStructPointer(S)).*); | |
| 1576 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStructPointer(S)).*); | |
| 1577 | 1577 | }, |
| 1578 | 1578 | .big => { |
| 1579 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStructReference(S)).*); | |
| 1580 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStructReference(S)).*); | |
| 1579 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStructPointer(S)).*); | |
| 1580 | try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStructPointer(S)).*); | |
| 1581 | 1581 | }, |
| 1582 | 1582 | } |
| 1583 | 1583 | } |
| ... | ... | @@ -1724,6 +1724,27 @@ test "takeDelimiterInclusive when it rebases" { |
| 1724 | 1724 | } |
| 1725 | 1725 | } |
| 1726 | 1726 | |
| 1727 | test "takeStruct and peekStruct packed" { | |
| 1728 | var r: Reader = .fixed(&.{ 0b11110000, 0b00110011 }); | |
| 1729 | const S = packed struct(u16) { a: u2, b: u6, c: u7, d: u1 }; | |
| 1730 | ||
| 1731 | try testing.expectEqual(@as(S, .{ | |
| 1732 | .a = 0b11, | |
| 1733 | .b = 0b001100, | |
| 1734 | .c = 0b1110000, | |
| 1735 | .d = 0b1, | |
| 1736 | }), try r.peekStruct(S, .big)); | |
| 1737 | ||
| 1738 | try testing.expectEqual(@as(S, .{ | |
| 1739 | .a = 0b11, | |
| 1740 | .b = 0b001100, | |
| 1741 | .c = 0b1110000, | |
| 1742 | .d = 0b1, | |
| 1743 | }), try r.takeStruct(S, .big)); | |
| 1744 | ||
| 1745 | try testing.expectError(error.EndOfStream, r.takeStruct(S, .little)); | |
| 1746 | } | |
| 1747 | ||
| 1727 | 1748 | /// Provides a `Reader` implementation by passing data from an underlying |
| 1728 | 1749 | /// reader through `Hasher.update`. |
| 1729 | 1750 | /// |
lib/std/Io/Writer.zig+17-15| ... | ... | @@ -867,18 +867,11 @@ pub inline fn writeSliceEndian( |
| 867 | 867 | } |
| 868 | 868 | } |
| 869 | 869 | |
| 870 | /// Asserts that the buffer storage capacity is at least enough to store `@sizeOf(Elem)` | |
| 871 | /// | |
| 872 | /// Asserts that the buffer is aligned enough for `@alignOf(Elem)`. | |
| 873 | 870 | pub fn writeSliceSwap(w: *Writer, Elem: type, slice: []const Elem) Error!void { |
| 874 | var i: usize = 0; | |
| 875 | while (i < slice.len) { | |
| 876 | const dest_bytes = try w.writableSliceGreedy(@sizeOf(Elem)); | |
| 877 | const dest: []Elem = @alignCast(@ptrCast(dest_bytes[0 .. dest_bytes.len - dest_bytes.len % @sizeOf(Elem)])); | |
| 878 | const copy_len = @min(dest.len, slice.len - i); | |
| 879 | @memcpy(dest[0..copy_len], slice[i..][0..copy_len]); | |
| 880 | i += copy_len; | |
| 881 | std.mem.byteSwapAllElements(Elem, dest); | |
| 871 | for (slice) |elem| { | |
| 872 | var tmp = elem; | |
| 873 | std.mem.byteSwapAllFields(Elem, &tmp); | |
| 874 | try w.writeAll(@ptrCast(&tmp)); | |
| 882 | 875 | } |
| 883 | 876 | } |
| 884 | 877 | |
| ... | ... | @@ -1141,8 +1134,8 @@ pub fn printValue( |
| 1141 | 1134 | else => invalidFmtError(fmt, value), |
| 1142 | 1135 | }, |
| 1143 | 1136 | 't' => switch (@typeInfo(T)) { |
| 1144 | .error_set => return w.writeAll(@errorName(value)), | |
| 1145 | .@"enum", .@"union" => return w.writeAll(@tagName(value)), | |
| 1137 | .error_set => return w.alignBufferOptions(@errorName(value), options), | |
| 1138 | .@"enum", .@"union" => return w.alignBufferOptions(@tagName(value), options), | |
| 1146 | 1139 | else => invalidFmtError(fmt, value), |
| 1147 | 1140 | }, |
| 1148 | 1141 | else => {}, |
| ... | ... | @@ -2152,6 +2145,14 @@ test "bytes.hex" { |
| 2152 | 2145 | try testing.expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros}); |
| 2153 | 2146 | } |
| 2154 | 2147 | |
| 2148 | test "padding" { | |
| 2149 | const foo: enum { foo } = .foo; | |
| 2150 | try testing.expectFmt("tag: |foo |\n", "tag: |{t:<4}|\n", .{foo}); | |
| 2151 | ||
| 2152 | const bar: error{bar} = error.bar; | |
| 2153 | try testing.expectFmt("error: |bar |\n", "error: |{t:<4}|\n", .{bar}); | |
| 2154 | } | |
| 2155 | ||
| 2155 | 2156 | test fixed { |
| 2156 | 2157 | { |
| 2157 | 2158 | var buf: [255]u8 = undefined; |
| ... | ... | @@ -2650,9 +2651,10 @@ test writeStruct { |
| 2650 | 2651 | } |
| 2651 | 2652 | |
| 2652 | 2653 | test writeSliceEndian { |
| 2653 | var buffer: [4]u8 align(2) = undefined; | |
| 2654 | var buffer: [5]u8 align(2) = undefined; | |
| 2654 | 2655 | var w: Writer = .fixed(&buffer); |
| 2656 | try w.writeByte('x'); | |
| 2655 | 2657 | const array: [2]u16 = .{ 0x1234, 0x5678 }; |
| 2656 | 2658 | try writeSliceEndian(&w, u16, &array, .big); |
| 2657 | try testing.expectEqualSlices(u8, &.{ 0x12, 0x34, 0x56, 0x78 }, &buffer); | |
| 2659 | try testing.expectEqualSlices(u8, &.{ 'x', 0x12, 0x34, 0x56, 0x78 }, &buffer); | |
| 2658 | 2660 | } |
lib/std/hash.zig-2| ... | ... | @@ -31,8 +31,6 @@ pub const CityHash64 = cityhash.CityHash64; |
| 31 | 31 | const wyhash = @import("hash/wyhash.zig"); |
| 32 | 32 | pub const Wyhash = wyhash.Wyhash; |
| 33 | 33 | |
| 34 | pub const RapidHash = @import("hash/RapidHash.zig"); | |
| 35 | ||
| 36 | 34 | const xxhash = @import("hash/xxhash.zig"); |
| 37 | 35 | pub const XxHash3 = xxhash.XxHash3; |
| 38 | 36 | pub const XxHash64 = xxhash.XxHash64; |
lib/std/hash/RapidHash.zig deleted-125| ... | ... | @@ -1,125 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | ||
| 3 | const readInt = std.mem.readInt; | |
| 4 | const assert = std.debug.assert; | |
| 5 | const expect = std.testing.expect; | |
| 6 | const expectEqual = std.testing.expectEqual; | |
| 7 | ||
| 8 | const RAPID_SEED: u64 = 0xbdd89aa982704029; | |
| 9 | const RAPID_SECRET: [3]u64 = .{ 0x2d358dccaa6c78a5, 0x8bb84b93962eacc9, 0x4b33a62ed433d4a3 }; | |
| 10 | ||
| 11 | pub fn hash(seed: u64, input: []const u8) u64 { | |
| 12 | const sc = RAPID_SECRET; | |
| 13 | const len = input.len; | |
| 14 | var a: u64 = 0; | |
| 15 | var b: u64 = 0; | |
| 16 | var k = input; | |
| 17 | var is: [3]u64 = .{ seed, 0, 0 }; | |
| 18 | ||
| 19 | is[0] ^= mix(seed ^ sc[0], sc[1]) ^ len; | |
| 20 | ||
| 21 | if (len <= 16) { | |
| 22 | if (len >= 4) { | |
| 23 | const d: u64 = ((len & 24) >> @intCast(len >> 3)); | |
| 24 | const e = len - 4; | |
| 25 | a = (r32(k) << 32) | r32(k[e..]); | |
| 26 | b = ((r32(k[d..]) << 32) | r32(k[(e - d)..])); | |
| 27 | } else if (len > 0) | |
| 28 | a = (@as(u64, k[0]) << 56) | (@as(u64, k[len >> 1]) << 32) | @as(u64, k[len - 1]); | |
| 29 | } else { | |
| 30 | var remain = len; | |
| 31 | if (len > 48) { | |
| 32 | is[1] = is[0]; | |
| 33 | is[2] = is[0]; | |
| 34 | while (remain >= 96) { | |
| 35 | inline for (0..6) |i| { | |
| 36 | const m1 = r64(k[8 * i * 2 ..]); | |
| 37 | const m2 = r64(k[8 * (i * 2 + 1) ..]); | |
| 38 | is[i % 3] = mix(m1 ^ sc[i % 3], m2 ^ is[i % 3]); | |
| 39 | } | |
| 40 | k = k[96..]; | |
| 41 | remain -= 96; | |
| 42 | } | |
| 43 | if (remain >= 48) { | |
| 44 | inline for (0..3) |i| { | |
| 45 | const m1 = r64(k[8 * i * 2 ..]); | |
| 46 | const m2 = r64(k[8 * (i * 2 + 1) ..]); | |
| 47 | is[i] = mix(m1 ^ sc[i], m2 ^ is[i]); | |
| 48 | } | |
| 49 | k = k[48..]; | |
| 50 | remain -= 48; | |
| 51 | } | |
| 52 | ||
| 53 | is[0] ^= is[1] ^ is[2]; | |
| 54 | } | |
| 55 | ||
| 56 | if (remain > 16) { | |
| 57 | is[0] = mix(r64(k) ^ sc[2], r64(k[8..]) ^ is[0] ^ sc[1]); | |
| 58 | if (remain > 32) { | |
| 59 | is[0] = mix(r64(k[16..]) ^ sc[2], r64(k[24..]) ^ is[0]); | |
| 60 | } | |
| 61 | } | |
| 62 | ||
| 63 | a = r64(input[len - 16 ..]); | |
| 64 | b = r64(input[len - 8 ..]); | |
| 65 | } | |
| 66 | ||
| 67 | a ^= sc[1]; | |
| 68 | b ^= is[0]; | |
| 69 | mum(&a, &b); | |
| 70 | return mix(a ^ sc[0] ^ len, b ^ sc[1]); | |
| 71 | } | |
| 72 | ||
| 73 | test "RapidHash.hash" { | |
| 74 | const bytes: []const u8 = "abcdefgh" ** 128; | |
| 75 | ||
| 76 | const sizes: [13]u64 = .{ 0, 1, 2, 3, 4, 8, 16, 32, 64, 128, 256, 512, 1024 }; | |
| 77 | ||
| 78 | const outcomes: [13]u64 = .{ | |
| 79 | 0x5a6ef77074ebc84b, | |
| 80 | 0xc11328477bc0f5d1, | |
| 81 | 0x5644ac035e40d569, | |
| 82 | 0x347080fbf5fcd81, | |
| 83 | 0x56b66b8dc802bcc, | |
| 84 | 0xb6bf9055973aac7c, | |
| 85 | 0xed56d62eead1e402, | |
| 86 | 0xc19072d767da8ffb, | |
| 87 | 0x89bb40a9928a4f0d, | |
| 88 | 0xe0af7c5e7b6e29fd, | |
| 89 | 0x9a3ed35fbedfa11a, | |
| 90 | 0x4c684b2119ca19fb, | |
| 91 | 0x4b575f5bf25600d6, | |
| 92 | }; | |
| 93 | ||
| 94 | var success: bool = true; | |
| 95 | for (sizes, outcomes) |s, e| { | |
| 96 | const r = hash(RAPID_SEED, bytes[0..s]); | |
| 97 | ||
| 98 | expectEqual(e, r) catch |err| { | |
| 99 | std.debug.print("Failed on {d}: {!}\n", .{ s, err }); | |
| 100 | success = false; | |
| 101 | }; | |
| 102 | } | |
| 103 | try expect(success); | |
| 104 | } | |
| 105 | ||
| 106 | inline fn mum(a: *u64, b: *u64) void { | |
| 107 | const r = @as(u128, a.*) * b.*; | |
| 108 | a.* = @truncate(r); | |
| 109 | b.* = @truncate(r >> 64); | |
| 110 | } | |
| 111 | ||
| 112 | inline fn mix(a: u64, b: u64) u64 { | |
| 113 | var copy_a = a; | |
| 114 | var copy_b = b; | |
| 115 | mum(&copy_a, &copy_b); | |
| 116 | return copy_a ^ copy_b; | |
| 117 | } | |
| 118 | ||
| 119 | inline fn r64(p: []const u8) u64 { | |
| 120 | return readInt(u64, p[0..8], .little); | |
| 121 | } | |
| 122 | ||
| 123 | inline fn r32(p: []const u8) u64 { | |
| 124 | return readInt(u32, p[0..4], .little); | |
| 125 | } |
lib/std/hash/benchmark.zig-6| ... | ... | @@ -59,12 +59,6 @@ const hashes = [_]Hash{ |
| 59 | 59 | .ty = hash.crc.Crc32, |
| 60 | 60 | .name = "crc32", |
| 61 | 61 | }, |
| 62 | Hash{ | |
| 63 | .ty = hash.RapidHash, | |
| 64 | .name = "rapidhash", | |
| 65 | .has_iterative_api = false, | |
| 66 | .init_u64 = 0, | |
| 67 | }, | |
| 68 | 62 | Hash{ |
| 69 | 63 | .ty = hash.CityHash32, |
| 70 | 64 | .name = "cityhash-32", |
lib/std/json.zig+17-24| ... | ... | @@ -44,7 +44,7 @@ test Value { |
| 44 | 44 | test Stringify { |
| 45 | 45 | var out: std.io.Writer.Allocating = .init(testing.allocator); |
| 46 | 46 | var write_stream: Stringify = .{ |
| 47 | .writer = &out.interface, | |
| 47 | .writer = &out.writer, | |
| 48 | 48 | .options = .{ .whitespace = .indent_2 }, |
| 49 | 49 | }; |
| 50 | 50 | defer out.deinit(); |
| ... | ... | @@ -66,18 +66,18 @@ pub const Value = @import("json/dynamic.zig").Value; |
| 66 | 66 | |
| 67 | 67 | pub const ArrayHashMap = @import("json/hashmap.zig").ArrayHashMap; |
| 68 | 68 | |
| 69 | pub const validate = @import("json/scanner.zig").validate; | |
| 70 | pub const Error = @import("json/scanner.zig").Error; | |
| 71 | pub const reader = @import("json/scanner.zig").reader; | |
| 72 | pub const default_buffer_size = @import("json/scanner.zig").default_buffer_size; | |
| 73 | pub const Token = @import("json/scanner.zig").Token; | |
| 74 | pub const TokenType = @import("json/scanner.zig").TokenType; | |
| 75 | pub const Diagnostics = @import("json/scanner.zig").Diagnostics; | |
| 76 | pub const AllocWhen = @import("json/scanner.zig").AllocWhen; | |
| 77 | pub const default_max_value_len = @import("json/scanner.zig").default_max_value_len; | |
| 78 | pub const Reader = @import("json/scanner.zig").Reader; | |
| 79 | pub const Scanner = @import("json/scanner.zig").Scanner; | |
| 80 | pub const isNumberFormattedLikeAnInteger = @import("json/scanner.zig").isNumberFormattedLikeAnInteger; | |
| 69 | pub const Scanner = @import("json/Scanner.zig"); | |
| 70 | pub const validate = Scanner.validate; | |
| 71 | pub const Error = Scanner.Error; | |
| 72 | pub const reader = Scanner.reader; | |
| 73 | pub const default_buffer_size = Scanner.default_buffer_size; | |
| 74 | pub const Token = Scanner.Token; | |
| 75 | pub const TokenType = Scanner.TokenType; | |
| 76 | pub const Diagnostics = Scanner.Diagnostics; | |
| 77 | pub const AllocWhen = Scanner.AllocWhen; | |
| 78 | pub const default_max_value_len = Scanner.default_max_value_len; | |
| 79 | pub const Reader = Scanner.Reader; | |
| 80 | pub const isNumberFormattedLikeAnInteger = Scanner.isNumberFormattedLikeAnInteger; | |
| 81 | 81 | |
| 82 | 82 | pub const ParseOptions = @import("json/static.zig").ParseOptions; |
| 83 | 83 | pub const Parsed = @import("json/static.zig").Parsed; |
| ... | ... | @@ -101,10 +101,10 @@ pub fn fmt(value: anytype, options: Stringify.Options) Formatter(@TypeOf(value)) |
| 101 | 101 | |
| 102 | 102 | test fmt { |
| 103 | 103 | const expectFmt = std.testing.expectFmt; |
| 104 | try expectFmt("123", "{}", .{fmt(@as(u32, 123), .{})}); | |
| 104 | try expectFmt("123", "{f}", .{fmt(@as(u32, 123), .{})}); | |
| 105 | 105 | try expectFmt( |
| 106 | 106 | \\{"num":927,"msg":"hello","sub":{"mybool":true}} |
| 107 | , "{}", .{fmt(struct { | |
| 107 | , "{f}", .{fmt(struct { | |
| 108 | 108 | num: u32, |
| 109 | 109 | msg: []const u8, |
| 110 | 110 | sub: struct { |
| ... | ... | @@ -123,14 +123,7 @@ pub fn Formatter(comptime T: type) type { |
| 123 | 123 | value: T, |
| 124 | 124 | options: Stringify.Options, |
| 125 | 125 | |
| 126 | pub fn format( | |
| 127 | self: @This(), | |
| 128 | comptime fmt_spec: []const u8, | |
| 129 | options: std.fmt.FormatOptions, | |
| 130 | writer: *std.io.Writer, | |
| 131 | ) !void { | |
| 132 | comptime std.debug.assert(fmt_spec.len == 0); | |
| 133 | _ = options; | |
| 126 | pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { | |
| 134 | 127 | try Stringify.value(self.value, self.options, writer); |
| 135 | 128 | } |
| 136 | 129 | }; |
| ... | ... | @@ -138,7 +131,7 @@ pub fn Formatter(comptime T: type) type { |
| 138 | 131 | |
| 139 | 132 | test { |
| 140 | 133 | _ = @import("json/test.zig"); |
| 141 | _ = @import("json/scanner.zig"); | |
| 134 | _ = Scanner; | |
| 142 | 135 | _ = @import("json/dynamic.zig"); |
| 143 | 136 | _ = @import("json/hashmap.zig"); |
| 144 | 137 | _ = @import("json/static.zig"); |
lib/std/json/Scanner.zig created+1767| ... | ... | @@ -0,0 +1,1767 @@ |
| 1 | //! The lowest level parsing API in this package; | |
| 2 | //! supports streaming input with a low memory footprint. | |
| 3 | //! The memory requirement is `O(d)` where d is the nesting depth of `[]` or `{}` containers in the input. | |
| 4 | //! Specifically `d/8` bytes are required for this purpose, | |
| 5 | //! with some extra buffer according to the implementation of `std.ArrayList`. | |
| 6 | //! | |
| 7 | //! This scanner can emit partial tokens; see `std.json.Token`. | |
| 8 | //! The input to this class is a sequence of input buffers that you must supply one at a time. | |
| 9 | //! Call `feedInput()` with the first buffer, then call `next()` repeatedly until `error.BufferUnderrun` is returned. | |
| 10 | //! Then call `feedInput()` again and so forth. | |
| 11 | //! Call `endInput()` when the last input buffer has been given to `feedInput()`, either immediately after calling `feedInput()`, | |
| 12 | //! or when `error.BufferUnderrun` requests more data and there is no more. | |
| 13 | //! Be sure to call `next()` after calling `endInput()` until `Token.end_of_document` has been returned. | |
| 14 | //! | |
| 15 | //! Notes on standards compliance: https://datatracker.ietf.org/doc/html/rfc8259 | |
| 16 | //! * RFC 8259 requires JSON documents be valid UTF-8, | |
| 17 | //! but makes an allowance for systems that are "part of a closed ecosystem". | |
| 18 | //! I have no idea what that's supposed to mean in the context of a standard specification. | |
| 19 | //! This implementation requires inputs to be valid UTF-8. | |
| 20 | //! * RFC 8259 contradicts itself regarding whether lowercase is allowed in \u hex digits, | |
| 21 | //! but this is probably a bug in the spec, and it's clear that lowercase is meant to be allowed. | |
| 22 | //! (RFC 5234 defines HEXDIG to only allow uppercase.) | |
| 23 | //! * When RFC 8259 refers to a "character", I assume they really mean a "Unicode scalar value". | |
| 24 | //! See http://www.unicode.org/glossary/#unicode_scalar_value . | |
| 25 | //! * RFC 8259 doesn't explicitly disallow unpaired surrogate halves in \u escape sequences, | |
| 26 | //! but vaguely implies that \u escapes are for encoding Unicode "characters" (i.e. Unicode scalar values?), | |
| 27 | //! which would mean that unpaired surrogate halves are forbidden. | |
| 28 | //! By contrast ECMA-404 (a competing(/compatible?) JSON standard, which JavaScript's JSON.parse() conforms to) | |
| 29 | //! explicitly allows unpaired surrogate halves. | |
| 30 | //! This implementation forbids unpaired surrogate halves in \u sequences. | |
| 31 | //! If a high surrogate half appears in a \u sequence, | |
| 32 | //! then a low surrogate half must immediately follow in \u notation. | |
| 33 | //! * RFC 8259 allows implementations to "accept non-JSON forms or extensions". | |
| 34 | //! This implementation does not accept any of that. | |
| 35 | //! * RFC 8259 allows implementations to put limits on "the size of texts", | |
| 36 | //! "the maximum depth of nesting", "the range and precision of numbers", | |
| 37 | //! and "the length and character contents of strings". | |
| 38 | //! This low-level implementation does not limit these, | |
| 39 | //! except where noted above, and except that nesting depth requires memory allocation. | |
| 40 | //! Note that this low-level API does not interpret numbers numerically, | |
| 41 | //! but simply emits their source form for some higher level code to make sense of. | |
| 42 | //! * This low-level implementation allows duplicate object keys, | |
| 43 | //! and key/value pairs are emitted in the order they appear in the input. | |
| 44 | ||
| 45 | const Scanner = @This(); | |
| 46 | const std = @import("std"); | |
| 47 | ||
| 48 | const Allocator = std.mem.Allocator; | |
| 49 | const ArrayList = std.ArrayList; | |
| 50 | const assert = std.debug.assert; | |
| 51 | const BitStack = std.BitStack; | |
| 52 | ||
| 53 | state: State = .value, | |
| 54 | string_is_object_key: bool = false, | |
| 55 | stack: BitStack, | |
| 56 | value_start: usize = undefined, | |
| 57 | utf16_code_units: [2]u16 = undefined, | |
| 58 | ||
| 59 | input: []const u8 = "", | |
| 60 | cursor: usize = 0, | |
| 61 | is_end_of_input: bool = false, | |
| 62 | diagnostics: ?*Diagnostics = null, | |
| 63 | ||
| 64 | /// The allocator is only used to track `[]` and `{}` nesting levels. | |
| 65 | pub fn initStreaming(allocator: Allocator) @This() { | |
| 66 | return .{ | |
| 67 | .stack = BitStack.init(allocator), | |
| 68 | }; | |
| 69 | } | |
| 70 | /// Use this if your input is a single slice. | |
| 71 | /// This is effectively equivalent to: | |
| 72 | /// ``` | |
| 73 | /// initStreaming(allocator); | |
| 74 | /// feedInput(complete_input); | |
| 75 | /// endInput(); | |
| 76 | /// ``` | |
| 77 | pub fn initCompleteInput(allocator: Allocator, complete_input: []const u8) @This() { | |
| 78 | return .{ | |
| 79 | .stack = BitStack.init(allocator), | |
| 80 | .input = complete_input, | |
| 81 | .is_end_of_input = true, | |
| 82 | }; | |
| 83 | } | |
| 84 | pub fn deinit(self: *@This()) void { | |
| 85 | self.stack.deinit(); | |
| 86 | self.* = undefined; | |
| 87 | } | |
| 88 | ||
| 89 | pub fn enableDiagnostics(self: *@This(), diagnostics: *Diagnostics) void { | |
| 90 | diagnostics.cursor_pointer = &self.cursor; | |
| 91 | self.diagnostics = diagnostics; | |
| 92 | } | |
| 93 | ||
| 94 | /// Call this whenever you get `error.BufferUnderrun` from `next()`. | |
| 95 | /// When there is no more input to provide, call `endInput()`. | |
| 96 | pub fn feedInput(self: *@This(), input: []const u8) void { | |
| 97 | assert(self.cursor == self.input.len); // Not done with the last input slice. | |
| 98 | if (self.diagnostics) |diag| { | |
| 99 | diag.total_bytes_before_current_input += self.input.len; | |
| 100 | // This usually goes "negative" to measure how far before the beginning | |
| 101 | // of the new buffer the current line started. | |
| 102 | diag.line_start_cursor -%= self.cursor; | |
| 103 | } | |
| 104 | self.input = input; | |
| 105 | self.cursor = 0; | |
| 106 | self.value_start = 0; | |
| 107 | } | |
| 108 | /// Call this when you will no longer call `feedInput()` anymore. | |
| 109 | /// This can be called either immediately after the last `feedInput()`, | |
| 110 | /// or at any time afterward, such as when getting `error.BufferUnderrun` from `next()`. | |
| 111 | /// Don't forget to call `next*()` after `endInput()` until you get `.end_of_document`. | |
| 112 | pub fn endInput(self: *@This()) void { | |
| 113 | self.is_end_of_input = true; | |
| 114 | } | |
| 115 | ||
| 116 | pub const NextError = Error || Allocator.Error || error{BufferUnderrun}; | |
| 117 | pub const AllocError = Error || Allocator.Error || error{ValueTooLong}; | |
| 118 | pub const PeekError = Error || error{BufferUnderrun}; | |
| 119 | pub const SkipError = Error || Allocator.Error; | |
| 120 | pub const AllocIntoArrayListError = AllocError || error{BufferUnderrun}; | |
| 121 | ||
| 122 | /// Equivalent to `nextAllocMax(allocator, when, default_max_value_len);` | |
| 123 | /// This function is only available after `endInput()` (or `initCompleteInput()`) has been called. | |
| 124 | /// See also `std.json.Token` for documentation of `nextAlloc*()` function behavior. | |
| 125 | pub fn nextAlloc(self: *@This(), allocator: Allocator, when: AllocWhen) AllocError!Token { | |
| 126 | return self.nextAllocMax(allocator, when, default_max_value_len); | |
| 127 | } | |
| 128 | ||
| 129 | /// This function is only available after `endInput()` (or `initCompleteInput()`) has been called. | |
| 130 | /// See also `std.json.Token` for documentation of `nextAlloc*()` function behavior. | |
| 131 | pub fn nextAllocMax(self: *@This(), allocator: Allocator, when: AllocWhen, max_value_len: usize) AllocError!Token { | |
| 132 | assert(self.is_end_of_input); // This function is not available in streaming mode. | |
| 133 | const token_type = self.peekNextTokenType() catch |e| switch (e) { | |
| 134 | error.BufferUnderrun => unreachable, | |
| 135 | else => |err| return err, | |
| 136 | }; | |
| 137 | switch (token_type) { | |
| 138 | .number, .string => { | |
| 139 | var value_list = ArrayList(u8).init(allocator); | |
| 140 | errdefer { | |
| 141 | value_list.deinit(); | |
| 142 | } | |
| 143 | if (self.allocNextIntoArrayListMax(&value_list, when, max_value_len) catch |e| switch (e) { | |
| 144 | error.BufferUnderrun => unreachable, | |
| 145 | else => |err| return err, | |
| 146 | }) |slice| { | |
| 147 | return if (token_type == .number) | |
| 148 | Token{ .number = slice } | |
| 149 | else | |
| 150 | Token{ .string = slice }; | |
| 151 | } else { | |
| 152 | return if (token_type == .number) | |
| 153 | Token{ .allocated_number = try value_list.toOwnedSlice() } | |
| 154 | else | |
| 155 | Token{ .allocated_string = try value_list.toOwnedSlice() }; | |
| 156 | } | |
| 157 | }, | |
| 158 | ||
| 159 | // Simple tokens never alloc. | |
| 160 | .object_begin, | |
| 161 | .object_end, | |
| 162 | .array_begin, | |
| 163 | .array_end, | |
| 164 | .true, | |
| 165 | .false, | |
| 166 | .null, | |
| 167 | .end_of_document, | |
| 168 | => return self.next() catch |e| switch (e) { | |
| 169 | error.BufferUnderrun => unreachable, | |
| 170 | else => |err| return err, | |
| 171 | }, | |
| 172 | } | |
| 173 | } | |
| 174 | ||
| 175 | /// Equivalent to `allocNextIntoArrayListMax(value_list, when, default_max_value_len);` | |
| 176 | pub fn allocNextIntoArrayList(self: *@This(), value_list: *ArrayList(u8), when: AllocWhen) AllocIntoArrayListError!?[]const u8 { | |
| 177 | return self.allocNextIntoArrayListMax(value_list, when, default_max_value_len); | |
| 178 | } | |
| 179 | /// The next token type must be either `.number` or `.string`. See `peekNextTokenType()`. | |
| 180 | /// When allocation is not necessary with `.alloc_if_needed`, | |
| 181 | /// this method returns the content slice from the input buffer, and `value_list` is not touched. | |
| 182 | /// When allocation is necessary or with `.alloc_always`, this method concatenates partial tokens into the given `value_list`, | |
| 183 | /// and returns `null` once the final `.number` or `.string` token has been written into it. | |
| 184 | /// In case of an `error.BufferUnderrun`, partial values will be left in the given value_list. | |
| 185 | /// The given `value_list` is never reset by this method, so an `error.BufferUnderrun` situation | |
| 186 | /// can be resumed by passing the same array list in again. | |
| 187 | /// This method does not indicate whether the token content being returned is for a `.number` or `.string` token type; | |
| 188 | /// the caller of this method is expected to know which type of token is being processed. | |
| 189 | pub fn allocNextIntoArrayListMax(self: *@This(), value_list: *ArrayList(u8), when: AllocWhen, max_value_len: usize) AllocIntoArrayListError!?[]const u8 { | |
| 190 | while (true) { | |
| 191 | const token = try self.next(); | |
| 192 | switch (token) { | |
| 193 | // Accumulate partial values. | |
| 194 | .partial_number, .partial_string => |slice| { | |
| 195 | try appendSlice(value_list, slice, max_value_len); | |
| 196 | }, | |
| 197 | .partial_string_escaped_1 => |buf| { | |
| 198 | try appendSlice(value_list, buf[0..], max_value_len); | |
| 199 | }, | |
| 200 | .partial_string_escaped_2 => |buf| { | |
| 201 | try appendSlice(value_list, buf[0..], max_value_len); | |
| 202 | }, | |
| 203 | .partial_string_escaped_3 => |buf| { | |
| 204 | try appendSlice(value_list, buf[0..], max_value_len); | |
| 205 | }, | |
| 206 | .partial_string_escaped_4 => |buf| { | |
| 207 | try appendSlice(value_list, buf[0..], max_value_len); | |
| 208 | }, | |
| 209 | ||
| 210 | // Return complete values. | |
| 211 | .number => |slice| { | |
| 212 | if (when == .alloc_if_needed and value_list.items.len == 0) { | |
| 213 | // No alloc necessary. | |
| 214 | return slice; | |
| 215 | } | |
| 216 | try appendSlice(value_list, slice, max_value_len); | |
| 217 | // The token is complete. | |
| 218 | return null; | |
| 219 | }, | |
| 220 | .string => |slice| { | |
| 221 | if (when == .alloc_if_needed and value_list.items.len == 0) { | |
| 222 | // No alloc necessary. | |
| 223 | return slice; | |
| 224 | } | |
| 225 | try appendSlice(value_list, slice, max_value_len); | |
| 226 | // The token is complete. | |
| 227 | return null; | |
| 228 | }, | |
| 229 | ||
| 230 | .object_begin, | |
| 231 | .object_end, | |
| 232 | .array_begin, | |
| 233 | .array_end, | |
| 234 | .true, | |
| 235 | .false, | |
| 236 | .null, | |
| 237 | .end_of_document, | |
| 238 | => unreachable, // Only .number and .string token types are allowed here. Check peekNextTokenType() before calling this. | |
| 239 | ||
| 240 | .allocated_number, .allocated_string => unreachable, | |
| 241 | } | |
| 242 | } | |
| 243 | } | |
| 244 | ||
| 245 | /// This function is only available after `endInput()` (or `initCompleteInput()`) has been called. | |
| 246 | /// If the next token type is `.object_begin` or `.array_begin`, | |
| 247 | /// this function calls `next()` repeatedly until the corresponding `.object_end` or `.array_end` is found. | |
| 248 | /// If the next token type is `.number` or `.string`, | |
| 249 | /// this function calls `next()` repeatedly until the (non `.partial_*`) `.number` or `.string` token is found. | |
| 250 | /// If the next token type is `.true`, `.false`, or `.null`, this function calls `next()` once. | |
| 251 | /// The next token type must not be `.object_end`, `.array_end`, or `.end_of_document`; | |
| 252 | /// see `peekNextTokenType()`. | |
| 253 | pub fn skipValue(self: *@This()) SkipError!void { | |
| 254 | assert(self.is_end_of_input); // This function is not available in streaming mode. | |
| 255 | switch (self.peekNextTokenType() catch |e| switch (e) { | |
| 256 | error.BufferUnderrun => unreachable, | |
| 257 | else => |err| return err, | |
| 258 | }) { | |
| 259 | .object_begin, .array_begin => { | |
| 260 | self.skipUntilStackHeight(self.stackHeight()) catch |e| switch (e) { | |
| 261 | error.BufferUnderrun => unreachable, | |
| 262 | else => |err| return err, | |
| 263 | }; | |
| 264 | }, | |
| 265 | .number, .string => { | |
| 266 | while (true) { | |
| 267 | switch (self.next() catch |e| switch (e) { | |
| 268 | error.BufferUnderrun => unreachable, | |
| 269 | else => |err| return err, | |
| 270 | }) { | |
| 271 | .partial_number, | |
| 272 | .partial_string, | |
| 273 | .partial_string_escaped_1, | |
| 274 | .partial_string_escaped_2, | |
| 275 | .partial_string_escaped_3, | |
| 276 | .partial_string_escaped_4, | |
| 277 | => continue, | |
| 278 | ||
| 279 | .number, .string => break, | |
| 280 | ||
| 281 | else => unreachable, | |
| 282 | } | |
| 283 | } | |
| 284 | }, | |
| 285 | .true, .false, .null => { | |
| 286 | _ = self.next() catch |e| switch (e) { | |
| 287 | error.BufferUnderrun => unreachable, | |
| 288 | else => |err| return err, | |
| 289 | }; | |
| 290 | }, | |
| 291 | ||
| 292 | .object_end, .array_end, .end_of_document => unreachable, // Attempt to skip a non-value token. | |
| 293 | } | |
| 294 | } | |
| 295 | ||
| 296 | /// Skip tokens until an `.object_end` or `.array_end` token results in a `stackHeight()` equal the given stack height. | |
| 297 | /// Unlike `skipValue()`, this function is available in streaming mode. | |
| 298 | pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: usize) NextError!void { | |
| 299 | while (true) { | |
| 300 | switch (try self.next()) { | |
| 301 | .object_end, .array_end => { | |
| 302 | if (self.stackHeight() == terminal_stack_height) break; | |
| 303 | }, | |
| 304 | .end_of_document => unreachable, | |
| 305 | else => continue, | |
| 306 | } | |
| 307 | } | |
| 308 | } | |
| 309 | ||
| 310 | /// The depth of `{}` or `[]` nesting levels at the current position. | |
| 311 | pub fn stackHeight(self: *const @This()) usize { | |
| 312 | return self.stack.bit_len; | |
| 313 | } | |
| 314 | ||
| 315 | /// Pre allocate memory to hold the given number of nesting levels. | |
| 316 | /// `stackHeight()` up to the given number will not cause allocations. | |
| 317 | pub fn ensureTotalStackCapacity(self: *@This(), height: usize) Allocator.Error!void { | |
| 318 | try self.stack.ensureTotalCapacity(height); | |
| 319 | } | |
| 320 | ||
| 321 | /// See `std.json.Token` for documentation of this function. | |
| 322 | pub fn next(self: *@This()) NextError!Token { | |
| 323 | state_loop: while (true) { | |
| 324 | switch (self.state) { | |
| 325 | .value => { | |
| 326 | switch (try self.skipWhitespaceExpectByte()) { | |
| 327 | // Object, Array | |
| 328 | '{' => { | |
| 329 | try self.stack.push(OBJECT_MODE); | |
| 330 | self.cursor += 1; | |
| 331 | self.state = .object_start; | |
| 332 | return .object_begin; | |
| 333 | }, | |
| 334 | '[' => { | |
| 335 | try self.stack.push(ARRAY_MODE); | |
| 336 | self.cursor += 1; | |
| 337 | self.state = .array_start; | |
| 338 | return .array_begin; | |
| 339 | }, | |
| 340 | ||
| 341 | // String | |
| 342 | '"' => { | |
| 343 | self.cursor += 1; | |
| 344 | self.value_start = self.cursor; | |
| 345 | self.state = .string; | |
| 346 | continue :state_loop; | |
| 347 | }, | |
| 348 | ||
| 349 | // Number | |
| 350 | '1'...'9' => { | |
| 351 | self.value_start = self.cursor; | |
| 352 | self.cursor += 1; | |
| 353 | self.state = .number_int; | |
| 354 | continue :state_loop; | |
| 355 | }, | |
| 356 | '0' => { | |
| 357 | self.value_start = self.cursor; | |
| 358 | self.cursor += 1; | |
| 359 | self.state = .number_leading_zero; | |
| 360 | continue :state_loop; | |
| 361 | }, | |
| 362 | '-' => { | |
| 363 | self.value_start = self.cursor; | |
| 364 | self.cursor += 1; | |
| 365 | self.state = .number_minus; | |
| 366 | continue :state_loop; | |
| 367 | }, | |
| 368 | ||
| 369 | // literal values | |
| 370 | 't' => { | |
| 371 | self.cursor += 1; | |
| 372 | self.state = .literal_t; | |
| 373 | continue :state_loop; | |
| 374 | }, | |
| 375 | 'f' => { | |
| 376 | self.cursor += 1; | |
| 377 | self.state = .literal_f; | |
| 378 | continue :state_loop; | |
| 379 | }, | |
| 380 | 'n' => { | |
| 381 | self.cursor += 1; | |
| 382 | self.state = .literal_n; | |
| 383 | continue :state_loop; | |
| 384 | }, | |
| 385 | ||
| 386 | else => return error.SyntaxError, | |
| 387 | } | |
| 388 | }, | |
| 389 | ||
| 390 | .post_value => { | |
| 391 | if (try self.skipWhitespaceCheckEnd()) return .end_of_document; | |
| 392 | ||
| 393 | const c = self.input[self.cursor]; | |
| 394 | if (self.string_is_object_key) { | |
| 395 | self.string_is_object_key = false; | |
| 396 | switch (c) { | |
| 397 | ':' => { | |
| 398 | self.cursor += 1; | |
| 399 | self.state = .value; | |
| 400 | continue :state_loop; | |
| 401 | }, | |
| 402 | else => return error.SyntaxError, | |
| 403 | } | |
| 404 | } | |
| 405 | ||
| 406 | switch (c) { | |
| 407 | '}' => { | |
| 408 | if (self.stack.pop() != OBJECT_MODE) return error.SyntaxError; | |
| 409 | self.cursor += 1; | |
| 410 | // stay in .post_value state. | |
| 411 | return .object_end; | |
| 412 | }, | |
| 413 | ']' => { | |
| 414 | if (self.stack.pop() != ARRAY_MODE) return error.SyntaxError; | |
| 415 | self.cursor += 1; | |
| 416 | // stay in .post_value state. | |
| 417 | return .array_end; | |
| 418 | }, | |
| 419 | ',' => { | |
| 420 | switch (self.stack.peek()) { | |
| 421 | OBJECT_MODE => { | |
| 422 | self.state = .object_post_comma; | |
| 423 | }, | |
| 424 | ARRAY_MODE => { | |
| 425 | self.state = .value; | |
| 426 | }, | |
| 427 | } | |
| 428 | self.cursor += 1; | |
| 429 | continue :state_loop; | |
| 430 | }, | |
| 431 | else => return error.SyntaxError, | |
| 432 | } | |
| 433 | }, | |
| 434 | ||
| 435 | .object_start => { | |
| 436 | switch (try self.skipWhitespaceExpectByte()) { | |
| 437 | '"' => { | |
| 438 | self.cursor += 1; | |
| 439 | self.value_start = self.cursor; | |
| 440 | self.state = .string; | |
| 441 | self.string_is_object_key = true; | |
| 442 | continue :state_loop; | |
| 443 | }, | |
| 444 | '}' => { | |
| 445 | self.cursor += 1; | |
| 446 | _ = self.stack.pop(); | |
| 447 | self.state = .post_value; | |
| 448 | return .object_end; | |
| 449 | }, | |
| 450 | else => return error.SyntaxError, | |
| 451 | } | |
| 452 | }, | |
| 453 | .object_post_comma => { | |
| 454 | switch (try self.skipWhitespaceExpectByte()) { | |
| 455 | '"' => { | |
| 456 | self.cursor += 1; | |
| 457 | self.value_start = self.cursor; | |
| 458 | self.state = .string; | |
| 459 | self.string_is_object_key = true; | |
| 460 | continue :state_loop; | |
| 461 | }, | |
| 462 | else => return error.SyntaxError, | |
| 463 | } | |
| 464 | }, | |
| 465 | ||
| 466 | .array_start => { | |
| 467 | switch (try self.skipWhitespaceExpectByte()) { | |
| 468 | ']' => { | |
| 469 | self.cursor += 1; | |
| 470 | _ = self.stack.pop(); | |
| 471 | self.state = .post_value; | |
| 472 | return .array_end; | |
| 473 | }, | |
| 474 | else => { | |
| 475 | self.state = .value; | |
| 476 | continue :state_loop; | |
| 477 | }, | |
| 478 | } | |
| 479 | }, | |
| 480 | ||
| 481 | .number_minus => { | |
| 482 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(false); | |
| 483 | switch (self.input[self.cursor]) { | |
| 484 | '0' => { | |
| 485 | self.cursor += 1; | |
| 486 | self.state = .number_leading_zero; | |
| 487 | continue :state_loop; | |
| 488 | }, | |
| 489 | '1'...'9' => { | |
| 490 | self.cursor += 1; | |
| 491 | self.state = .number_int; | |
| 492 | continue :state_loop; | |
| 493 | }, | |
| 494 | else => return error.SyntaxError, | |
| 495 | } | |
| 496 | }, | |
| 497 | .number_leading_zero => { | |
| 498 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(true); | |
| 499 | switch (self.input[self.cursor]) { | |
| 500 | '.' => { | |
| 501 | self.cursor += 1; | |
| 502 | self.state = .number_post_dot; | |
| 503 | continue :state_loop; | |
| 504 | }, | |
| 505 | 'e', 'E' => { | |
| 506 | self.cursor += 1; | |
| 507 | self.state = .number_post_e; | |
| 508 | continue :state_loop; | |
| 509 | }, | |
| 510 | else => { | |
| 511 | self.state = .post_value; | |
| 512 | return Token{ .number = self.takeValueSlice() }; | |
| 513 | }, | |
| 514 | } | |
| 515 | }, | |
| 516 | .number_int => { | |
| 517 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 518 | switch (self.input[self.cursor]) { | |
| 519 | '0'...'9' => continue, | |
| 520 | '.' => { | |
| 521 | self.cursor += 1; | |
| 522 | self.state = .number_post_dot; | |
| 523 | continue :state_loop; | |
| 524 | }, | |
| 525 | 'e', 'E' => { | |
| 526 | self.cursor += 1; | |
| 527 | self.state = .number_post_e; | |
| 528 | continue :state_loop; | |
| 529 | }, | |
| 530 | else => { | |
| 531 | self.state = .post_value; | |
| 532 | return Token{ .number = self.takeValueSlice() }; | |
| 533 | }, | |
| 534 | } | |
| 535 | } | |
| 536 | return self.endOfBufferInNumber(true); | |
| 537 | }, | |
| 538 | .number_post_dot => { | |
| 539 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(false); | |
| 540 | switch (self.input[self.cursor]) { | |
| 541 | '0'...'9' => { | |
| 542 | self.cursor += 1; | |
| 543 | self.state = .number_frac; | |
| 544 | continue :state_loop; | |
| 545 | }, | |
| 546 | else => return error.SyntaxError, | |
| 547 | } | |
| 548 | }, | |
| 549 | .number_frac => { | |
| 550 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 551 | switch (self.input[self.cursor]) { | |
| 552 | '0'...'9' => continue, | |
| 553 | 'e', 'E' => { | |
| 554 | self.cursor += 1; | |
| 555 | self.state = .number_post_e; | |
| 556 | continue :state_loop; | |
| 557 | }, | |
| 558 | else => { | |
| 559 | self.state = .post_value; | |
| 560 | return Token{ .number = self.takeValueSlice() }; | |
| 561 | }, | |
| 562 | } | |
| 563 | } | |
| 564 | return self.endOfBufferInNumber(true); | |
| 565 | }, | |
| 566 | .number_post_e => { | |
| 567 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(false); | |
| 568 | switch (self.input[self.cursor]) { | |
| 569 | '0'...'9' => { | |
| 570 | self.cursor += 1; | |
| 571 | self.state = .number_exp; | |
| 572 | continue :state_loop; | |
| 573 | }, | |
| 574 | '+', '-' => { | |
| 575 | self.cursor += 1; | |
| 576 | self.state = .number_post_e_sign; | |
| 577 | continue :state_loop; | |
| 578 | }, | |
| 579 | else => return error.SyntaxError, | |
| 580 | } | |
| 581 | }, | |
| 582 | .number_post_e_sign => { | |
| 583 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(false); | |
| 584 | switch (self.input[self.cursor]) { | |
| 585 | '0'...'9' => { | |
| 586 | self.cursor += 1; | |
| 587 | self.state = .number_exp; | |
| 588 | continue :state_loop; | |
| 589 | }, | |
| 590 | else => return error.SyntaxError, | |
| 591 | } | |
| 592 | }, | |
| 593 | .number_exp => { | |
| 594 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 595 | switch (self.input[self.cursor]) { | |
| 596 | '0'...'9' => continue, | |
| 597 | else => { | |
| 598 | self.state = .post_value; | |
| 599 | return Token{ .number = self.takeValueSlice() }; | |
| 600 | }, | |
| 601 | } | |
| 602 | } | |
| 603 | return self.endOfBufferInNumber(true); | |
| 604 | }, | |
| 605 | ||
| 606 | .string => { | |
| 607 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 608 | switch (self.input[self.cursor]) { | |
| 609 | 0...0x1f => return error.SyntaxError, // Bare ASCII control code in string. | |
| 610 | ||
| 611 | // ASCII plain text. | |
| 612 | 0x20...('"' - 1), ('"' + 1)...('\\' - 1), ('\\' + 1)...0x7F => continue, | |
| 613 | ||
| 614 | // Special characters. | |
| 615 | '"' => { | |
| 616 | const result = Token{ .string = self.takeValueSlice() }; | |
| 617 | self.cursor += 1; | |
| 618 | self.state = .post_value; | |
| 619 | return result; | |
| 620 | }, | |
| 621 | '\\' => { | |
| 622 | const slice = self.takeValueSlice(); | |
| 623 | self.cursor += 1; | |
| 624 | self.state = .string_backslash; | |
| 625 | if (slice.len > 0) return Token{ .partial_string = slice }; | |
| 626 | continue :state_loop; | |
| 627 | }, | |
| 628 | ||
| 629 | // UTF-8 validation. | |
| 630 | // See http://unicode.org/mail-arch/unicode-ml/y2003-m02/att-0467/01-The_Algorithm_to_Valide_an_UTF-8_String | |
| 631 | 0xC2...0xDF => { | |
| 632 | self.cursor += 1; | |
| 633 | self.state = .string_utf8_last_byte; | |
| 634 | continue :state_loop; | |
| 635 | }, | |
| 636 | 0xE0 => { | |
| 637 | self.cursor += 1; | |
| 638 | self.state = .string_utf8_second_to_last_byte_guard_against_overlong; | |
| 639 | continue :state_loop; | |
| 640 | }, | |
| 641 | 0xE1...0xEC, 0xEE...0xEF => { | |
| 642 | self.cursor += 1; | |
| 643 | self.state = .string_utf8_second_to_last_byte; | |
| 644 | continue :state_loop; | |
| 645 | }, | |
| 646 | 0xED => { | |
| 647 | self.cursor += 1; | |
| 648 | self.state = .string_utf8_second_to_last_byte_guard_against_surrogate_half; | |
| 649 | continue :state_loop; | |
| 650 | }, | |
| 651 | 0xF0 => { | |
| 652 | self.cursor += 1; | |
| 653 | self.state = .string_utf8_third_to_last_byte_guard_against_overlong; | |
| 654 | continue :state_loop; | |
| 655 | }, | |
| 656 | 0xF1...0xF3 => { | |
| 657 | self.cursor += 1; | |
| 658 | self.state = .string_utf8_third_to_last_byte; | |
| 659 | continue :state_loop; | |
| 660 | }, | |
| 661 | 0xF4 => { | |
| 662 | self.cursor += 1; | |
| 663 | self.state = .string_utf8_third_to_last_byte_guard_against_too_large; | |
| 664 | continue :state_loop; | |
| 665 | }, | |
| 666 | 0x80...0xC1, 0xF5...0xFF => return error.SyntaxError, // Invalid UTF-8. | |
| 667 | } | |
| 668 | } | |
| 669 | if (self.is_end_of_input) return error.UnexpectedEndOfInput; | |
| 670 | const slice = self.takeValueSlice(); | |
| 671 | if (slice.len > 0) return Token{ .partial_string = slice }; | |
| 672 | return error.BufferUnderrun; | |
| 673 | }, | |
| 674 | .string_backslash => { | |
| 675 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 676 | switch (self.input[self.cursor]) { | |
| 677 | '"', '\\', '/' => { | |
| 678 | // Since these characters now represent themselves literally, | |
| 679 | // we can simply begin the next plaintext slice here. | |
| 680 | self.value_start = self.cursor; | |
| 681 | self.cursor += 1; | |
| 682 | self.state = .string; | |
| 683 | continue :state_loop; | |
| 684 | }, | |
| 685 | 'b' => { | |
| 686 | self.cursor += 1; | |
| 687 | self.value_start = self.cursor; | |
| 688 | self.state = .string; | |
| 689 | return Token{ .partial_string_escaped_1 = [_]u8{0x08} }; | |
| 690 | }, | |
| 691 | 'f' => { | |
| 692 | self.cursor += 1; | |
| 693 | self.value_start = self.cursor; | |
| 694 | self.state = .string; | |
| 695 | return Token{ .partial_string_escaped_1 = [_]u8{0x0c} }; | |
| 696 | }, | |
| 697 | 'n' => { | |
| 698 | self.cursor += 1; | |
| 699 | self.value_start = self.cursor; | |
| 700 | self.state = .string; | |
| 701 | return Token{ .partial_string_escaped_1 = [_]u8{'\n'} }; | |
| 702 | }, | |
| 703 | 'r' => { | |
| 704 | self.cursor += 1; | |
| 705 | self.value_start = self.cursor; | |
| 706 | self.state = .string; | |
| 707 | return Token{ .partial_string_escaped_1 = [_]u8{'\r'} }; | |
| 708 | }, | |
| 709 | 't' => { | |
| 710 | self.cursor += 1; | |
| 711 | self.value_start = self.cursor; | |
| 712 | self.state = .string; | |
| 713 | return Token{ .partial_string_escaped_1 = [_]u8{'\t'} }; | |
| 714 | }, | |
| 715 | 'u' => { | |
| 716 | self.cursor += 1; | |
| 717 | self.state = .string_backslash_u; | |
| 718 | continue :state_loop; | |
| 719 | }, | |
| 720 | else => return error.SyntaxError, | |
| 721 | } | |
| 722 | }, | |
| 723 | .string_backslash_u => { | |
| 724 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 725 | const c = self.input[self.cursor]; | |
| 726 | switch (c) { | |
| 727 | '0'...'9' => { | |
| 728 | self.utf16_code_units[0] = @as(u16, c - '0') << 12; | |
| 729 | }, | |
| 730 | 'A'...'F' => { | |
| 731 | self.utf16_code_units[0] = @as(u16, c - 'A' + 10) << 12; | |
| 732 | }, | |
| 733 | 'a'...'f' => { | |
| 734 | self.utf16_code_units[0] = @as(u16, c - 'a' + 10) << 12; | |
| 735 | }, | |
| 736 | else => return error.SyntaxError, | |
| 737 | } | |
| 738 | self.cursor += 1; | |
| 739 | self.state = .string_backslash_u_1; | |
| 740 | continue :state_loop; | |
| 741 | }, | |
| 742 | .string_backslash_u_1 => { | |
| 743 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 744 | const c = self.input[self.cursor]; | |
| 745 | switch (c) { | |
| 746 | '0'...'9' => { | |
| 747 | self.utf16_code_units[0] |= @as(u16, c - '0') << 8; | |
| 748 | }, | |
| 749 | 'A'...'F' => { | |
| 750 | self.utf16_code_units[0] |= @as(u16, c - 'A' + 10) << 8; | |
| 751 | }, | |
| 752 | 'a'...'f' => { | |
| 753 | self.utf16_code_units[0] |= @as(u16, c - 'a' + 10) << 8; | |
| 754 | }, | |
| 755 | else => return error.SyntaxError, | |
| 756 | } | |
| 757 | self.cursor += 1; | |
| 758 | self.state = .string_backslash_u_2; | |
| 759 | continue :state_loop; | |
| 760 | }, | |
| 761 | .string_backslash_u_2 => { | |
| 762 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 763 | const c = self.input[self.cursor]; | |
| 764 | switch (c) { | |
| 765 | '0'...'9' => { | |
| 766 | self.utf16_code_units[0] |= @as(u16, c - '0') << 4; | |
| 767 | }, | |
| 768 | 'A'...'F' => { | |
| 769 | self.utf16_code_units[0] |= @as(u16, c - 'A' + 10) << 4; | |
| 770 | }, | |
| 771 | 'a'...'f' => { | |
| 772 | self.utf16_code_units[0] |= @as(u16, c - 'a' + 10) << 4; | |
| 773 | }, | |
| 774 | else => return error.SyntaxError, | |
| 775 | } | |
| 776 | self.cursor += 1; | |
| 777 | self.state = .string_backslash_u_3; | |
| 778 | continue :state_loop; | |
| 779 | }, | |
| 780 | .string_backslash_u_3 => { | |
| 781 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 782 | const c = self.input[self.cursor]; | |
| 783 | switch (c) { | |
| 784 | '0'...'9' => { | |
| 785 | self.utf16_code_units[0] |= c - '0'; | |
| 786 | }, | |
| 787 | 'A'...'F' => { | |
| 788 | self.utf16_code_units[0] |= c - 'A' + 10; | |
| 789 | }, | |
| 790 | 'a'...'f' => { | |
| 791 | self.utf16_code_units[0] |= c - 'a' + 10; | |
| 792 | }, | |
| 793 | else => return error.SyntaxError, | |
| 794 | } | |
| 795 | self.cursor += 1; | |
| 796 | if (std.unicode.utf16IsHighSurrogate(self.utf16_code_units[0])) { | |
| 797 | self.state = .string_surrogate_half; | |
| 798 | continue :state_loop; | |
| 799 | } else if (std.unicode.utf16IsLowSurrogate(self.utf16_code_units[0])) { | |
| 800 | return error.SyntaxError; // Unexpected low surrogate half. | |
| 801 | } else { | |
| 802 | self.value_start = self.cursor; | |
| 803 | self.state = .string; | |
| 804 | return partialStringCodepoint(self.utf16_code_units[0]); | |
| 805 | } | |
| 806 | }, | |
| 807 | .string_surrogate_half => { | |
| 808 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 809 | switch (self.input[self.cursor]) { | |
| 810 | '\\' => { | |
| 811 | self.cursor += 1; | |
| 812 | self.state = .string_surrogate_half_backslash; | |
| 813 | continue :state_loop; | |
| 814 | }, | |
| 815 | else => return error.SyntaxError, // Expected low surrogate half. | |
| 816 | } | |
| 817 | }, | |
| 818 | .string_surrogate_half_backslash => { | |
| 819 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 820 | switch (self.input[self.cursor]) { | |
| 821 | 'u' => { | |
| 822 | self.cursor += 1; | |
| 823 | self.state = .string_surrogate_half_backslash_u; | |
| 824 | continue :state_loop; | |
| 825 | }, | |
| 826 | else => return error.SyntaxError, // Expected low surrogate half. | |
| 827 | } | |
| 828 | }, | |
| 829 | .string_surrogate_half_backslash_u => { | |
| 830 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 831 | switch (self.input[self.cursor]) { | |
| 832 | 'D', 'd' => { | |
| 833 | self.cursor += 1; | |
| 834 | self.utf16_code_units[1] = 0xD << 12; | |
| 835 | self.state = .string_surrogate_half_backslash_u_1; | |
| 836 | continue :state_loop; | |
| 837 | }, | |
| 838 | else => return error.SyntaxError, // Expected low surrogate half. | |
| 839 | } | |
| 840 | }, | |
| 841 | .string_surrogate_half_backslash_u_1 => { | |
| 842 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 843 | const c = self.input[self.cursor]; | |
| 844 | switch (c) { | |
| 845 | 'C'...'F' => { | |
| 846 | self.cursor += 1; | |
| 847 | self.utf16_code_units[1] |= @as(u16, c - 'A' + 10) << 8; | |
| 848 | self.state = .string_surrogate_half_backslash_u_2; | |
| 849 | continue :state_loop; | |
| 850 | }, | |
| 851 | 'c'...'f' => { | |
| 852 | self.cursor += 1; | |
| 853 | self.utf16_code_units[1] |= @as(u16, c - 'a' + 10) << 8; | |
| 854 | self.state = .string_surrogate_half_backslash_u_2; | |
| 855 | continue :state_loop; | |
| 856 | }, | |
| 857 | else => return error.SyntaxError, // Expected low surrogate half. | |
| 858 | } | |
| 859 | }, | |
| 860 | .string_surrogate_half_backslash_u_2 => { | |
| 861 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 862 | const c = self.input[self.cursor]; | |
| 863 | switch (c) { | |
| 864 | '0'...'9' => { | |
| 865 | self.cursor += 1; | |
| 866 | self.utf16_code_units[1] |= @as(u16, c - '0') << 4; | |
| 867 | self.state = .string_surrogate_half_backslash_u_3; | |
| 868 | continue :state_loop; | |
| 869 | }, | |
| 870 | 'A'...'F' => { | |
| 871 | self.cursor += 1; | |
| 872 | self.utf16_code_units[1] |= @as(u16, c - 'A' + 10) << 4; | |
| 873 | self.state = .string_surrogate_half_backslash_u_3; | |
| 874 | continue :state_loop; | |
| 875 | }, | |
| 876 | 'a'...'f' => { | |
| 877 | self.cursor += 1; | |
| 878 | self.utf16_code_units[1] |= @as(u16, c - 'a' + 10) << 4; | |
| 879 | self.state = .string_surrogate_half_backslash_u_3; | |
| 880 | continue :state_loop; | |
| 881 | }, | |
| 882 | else => return error.SyntaxError, | |
| 883 | } | |
| 884 | }, | |
| 885 | .string_surrogate_half_backslash_u_3 => { | |
| 886 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 887 | const c = self.input[self.cursor]; | |
| 888 | switch (c) { | |
| 889 | '0'...'9' => { | |
| 890 | self.utf16_code_units[1] |= c - '0'; | |
| 891 | }, | |
| 892 | 'A'...'F' => { | |
| 893 | self.utf16_code_units[1] |= c - 'A' + 10; | |
| 894 | }, | |
| 895 | 'a'...'f' => { | |
| 896 | self.utf16_code_units[1] |= c - 'a' + 10; | |
| 897 | }, | |
| 898 | else => return error.SyntaxError, | |
| 899 | } | |
| 900 | self.cursor += 1; | |
| 901 | self.value_start = self.cursor; | |
| 902 | self.state = .string; | |
| 903 | const code_point = std.unicode.utf16DecodeSurrogatePair(&self.utf16_code_units) catch unreachable; | |
| 904 | return partialStringCodepoint(code_point); | |
| 905 | }, | |
| 906 | ||
| 907 | .string_utf8_last_byte => { | |
| 908 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 909 | switch (self.input[self.cursor]) { | |
| 910 | 0x80...0xBF => { | |
| 911 | self.cursor += 1; | |
| 912 | self.state = .string; | |
| 913 | continue :state_loop; | |
| 914 | }, | |
| 915 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 916 | } | |
| 917 | }, | |
| 918 | .string_utf8_second_to_last_byte => { | |
| 919 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 920 | switch (self.input[self.cursor]) { | |
| 921 | 0x80...0xBF => { | |
| 922 | self.cursor += 1; | |
| 923 | self.state = .string_utf8_last_byte; | |
| 924 | continue :state_loop; | |
| 925 | }, | |
| 926 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 927 | } | |
| 928 | }, | |
| 929 | .string_utf8_second_to_last_byte_guard_against_overlong => { | |
| 930 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 931 | switch (self.input[self.cursor]) { | |
| 932 | 0xA0...0xBF => { | |
| 933 | self.cursor += 1; | |
| 934 | self.state = .string_utf8_last_byte; | |
| 935 | continue :state_loop; | |
| 936 | }, | |
| 937 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 938 | } | |
| 939 | }, | |
| 940 | .string_utf8_second_to_last_byte_guard_against_surrogate_half => { | |
| 941 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 942 | switch (self.input[self.cursor]) { | |
| 943 | 0x80...0x9F => { | |
| 944 | self.cursor += 1; | |
| 945 | self.state = .string_utf8_last_byte; | |
| 946 | continue :state_loop; | |
| 947 | }, | |
| 948 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 949 | } | |
| 950 | }, | |
| 951 | .string_utf8_third_to_last_byte => { | |
| 952 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 953 | switch (self.input[self.cursor]) { | |
| 954 | 0x80...0xBF => { | |
| 955 | self.cursor += 1; | |
| 956 | self.state = .string_utf8_second_to_last_byte; | |
| 957 | continue :state_loop; | |
| 958 | }, | |
| 959 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 960 | } | |
| 961 | }, | |
| 962 | .string_utf8_third_to_last_byte_guard_against_overlong => { | |
| 963 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 964 | switch (self.input[self.cursor]) { | |
| 965 | 0x90...0xBF => { | |
| 966 | self.cursor += 1; | |
| 967 | self.state = .string_utf8_second_to_last_byte; | |
| 968 | continue :state_loop; | |
| 969 | }, | |
| 970 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 971 | } | |
| 972 | }, | |
| 973 | .string_utf8_third_to_last_byte_guard_against_too_large => { | |
| 974 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 975 | switch (self.input[self.cursor]) { | |
| 976 | 0x80...0x8F => { | |
| 977 | self.cursor += 1; | |
| 978 | self.state = .string_utf8_second_to_last_byte; | |
| 979 | continue :state_loop; | |
| 980 | }, | |
| 981 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 982 | } | |
| 983 | }, | |
| 984 | ||
| 985 | .literal_t => { | |
| 986 | switch (try self.expectByte()) { | |
| 987 | 'r' => { | |
| 988 | self.cursor += 1; | |
| 989 | self.state = .literal_tr; | |
| 990 | continue :state_loop; | |
| 991 | }, | |
| 992 | else => return error.SyntaxError, | |
| 993 | } | |
| 994 | }, | |
| 995 | .literal_tr => { | |
| 996 | switch (try self.expectByte()) { | |
| 997 | 'u' => { | |
| 998 | self.cursor += 1; | |
| 999 | self.state = .literal_tru; | |
| 1000 | continue :state_loop; | |
| 1001 | }, | |
| 1002 | else => return error.SyntaxError, | |
| 1003 | } | |
| 1004 | }, | |
| 1005 | .literal_tru => { | |
| 1006 | switch (try self.expectByte()) { | |
| 1007 | 'e' => { | |
| 1008 | self.cursor += 1; | |
| 1009 | self.state = .post_value; | |
| 1010 | return .true; | |
| 1011 | }, | |
| 1012 | else => return error.SyntaxError, | |
| 1013 | } | |
| 1014 | }, | |
| 1015 | .literal_f => { | |
| 1016 | switch (try self.expectByte()) { | |
| 1017 | 'a' => { | |
| 1018 | self.cursor += 1; | |
| 1019 | self.state = .literal_fa; | |
| 1020 | continue :state_loop; | |
| 1021 | }, | |
| 1022 | else => return error.SyntaxError, | |
| 1023 | } | |
| 1024 | }, | |
| 1025 | .literal_fa => { | |
| 1026 | switch (try self.expectByte()) { | |
| 1027 | 'l' => { | |
| 1028 | self.cursor += 1; | |
| 1029 | self.state = .literal_fal; | |
| 1030 | continue :state_loop; | |
| 1031 | }, | |
| 1032 | else => return error.SyntaxError, | |
| 1033 | } | |
| 1034 | }, | |
| 1035 | .literal_fal => { | |
| 1036 | switch (try self.expectByte()) { | |
| 1037 | 's' => { | |
| 1038 | self.cursor += 1; | |
| 1039 | self.state = .literal_fals; | |
| 1040 | continue :state_loop; | |
| 1041 | }, | |
| 1042 | else => return error.SyntaxError, | |
| 1043 | } | |
| 1044 | }, | |
| 1045 | .literal_fals => { | |
| 1046 | switch (try self.expectByte()) { | |
| 1047 | 'e' => { | |
| 1048 | self.cursor += 1; | |
| 1049 | self.state = .post_value; | |
| 1050 | return .false; | |
| 1051 | }, | |
| 1052 | else => return error.SyntaxError, | |
| 1053 | } | |
| 1054 | }, | |
| 1055 | .literal_n => { | |
| 1056 | switch (try self.expectByte()) { | |
| 1057 | 'u' => { | |
| 1058 | self.cursor += 1; | |
| 1059 | self.state = .literal_nu; | |
| 1060 | continue :state_loop; | |
| 1061 | }, | |
| 1062 | else => return error.SyntaxError, | |
| 1063 | } | |
| 1064 | }, | |
| 1065 | .literal_nu => { | |
| 1066 | switch (try self.expectByte()) { | |
| 1067 | 'l' => { | |
| 1068 | self.cursor += 1; | |
| 1069 | self.state = .literal_nul; | |
| 1070 | continue :state_loop; | |
| 1071 | }, | |
| 1072 | else => return error.SyntaxError, | |
| 1073 | } | |
| 1074 | }, | |
| 1075 | .literal_nul => { | |
| 1076 | switch (try self.expectByte()) { | |
| 1077 | 'l' => { | |
| 1078 | self.cursor += 1; | |
| 1079 | self.state = .post_value; | |
| 1080 | return .null; | |
| 1081 | }, | |
| 1082 | else => return error.SyntaxError, | |
| 1083 | } | |
| 1084 | }, | |
| 1085 | } | |
| 1086 | unreachable; | |
| 1087 | } | |
| 1088 | } | |
| 1089 | ||
| 1090 | /// Seeks ahead in the input until the first byte of the next token (or the end of the input) | |
| 1091 | /// determines which type of token will be returned from the next `next*()` call. | |
| 1092 | /// This function is idempotent, only advancing past commas, colons, and inter-token whitespace. | |
| 1093 | pub fn peekNextTokenType(self: *@This()) PeekError!TokenType { | |
| 1094 | state_loop: while (true) { | |
| 1095 | switch (self.state) { | |
| 1096 | .value => { | |
| 1097 | switch (try self.skipWhitespaceExpectByte()) { | |
| 1098 | '{' => return .object_begin, | |
| 1099 | '[' => return .array_begin, | |
| 1100 | '"' => return .string, | |
| 1101 | '-', '0'...'9' => return .number, | |
| 1102 | 't' => return .true, | |
| 1103 | 'f' => return .false, | |
| 1104 | 'n' => return .null, | |
| 1105 | else => return error.SyntaxError, | |
| 1106 | } | |
| 1107 | }, | |
| 1108 | ||
| 1109 | .post_value => { | |
| 1110 | if (try self.skipWhitespaceCheckEnd()) return .end_of_document; | |
| 1111 | ||
| 1112 | const c = self.input[self.cursor]; | |
| 1113 | if (self.string_is_object_key) { | |
| 1114 | self.string_is_object_key = false; | |
| 1115 | switch (c) { | |
| 1116 | ':' => { | |
| 1117 | self.cursor += 1; | |
| 1118 | self.state = .value; | |
| 1119 | continue :state_loop; | |
| 1120 | }, | |
| 1121 | else => return error.SyntaxError, | |
| 1122 | } | |
| 1123 | } | |
| 1124 | ||
| 1125 | switch (c) { | |
| 1126 | '}' => return .object_end, | |
| 1127 | ']' => return .array_end, | |
| 1128 | ',' => { | |
| 1129 | switch (self.stack.peek()) { | |
| 1130 | OBJECT_MODE => { | |
| 1131 | self.state = .object_post_comma; | |
| 1132 | }, | |
| 1133 | ARRAY_MODE => { | |
| 1134 | self.state = .value; | |
| 1135 | }, | |
| 1136 | } | |
| 1137 | self.cursor += 1; | |
| 1138 | continue :state_loop; | |
| 1139 | }, | |
| 1140 | else => return error.SyntaxError, | |
| 1141 | } | |
| 1142 | }, | |
| 1143 | ||
| 1144 | .object_start => { | |
| 1145 | switch (try self.skipWhitespaceExpectByte()) { | |
| 1146 | '"' => return .string, | |
| 1147 | '}' => return .object_end, | |
| 1148 | else => return error.SyntaxError, | |
| 1149 | } | |
| 1150 | }, | |
| 1151 | .object_post_comma => { | |
| 1152 | switch (try self.skipWhitespaceExpectByte()) { | |
| 1153 | '"' => return .string, | |
| 1154 | else => return error.SyntaxError, | |
| 1155 | } | |
| 1156 | }, | |
| 1157 | ||
| 1158 | .array_start => { | |
| 1159 | switch (try self.skipWhitespaceExpectByte()) { | |
| 1160 | ']' => return .array_end, | |
| 1161 | else => { | |
| 1162 | self.state = .value; | |
| 1163 | continue :state_loop; | |
| 1164 | }, | |
| 1165 | } | |
| 1166 | }, | |
| 1167 | ||
| 1168 | .number_minus, | |
| 1169 | .number_leading_zero, | |
| 1170 | .number_int, | |
| 1171 | .number_post_dot, | |
| 1172 | .number_frac, | |
| 1173 | .number_post_e, | |
| 1174 | .number_post_e_sign, | |
| 1175 | .number_exp, | |
| 1176 | => return .number, | |
| 1177 | ||
| 1178 | .string, | |
| 1179 | .string_backslash, | |
| 1180 | .string_backslash_u, | |
| 1181 | .string_backslash_u_1, | |
| 1182 | .string_backslash_u_2, | |
| 1183 | .string_backslash_u_3, | |
| 1184 | .string_surrogate_half, | |
| 1185 | .string_surrogate_half_backslash, | |
| 1186 | .string_surrogate_half_backslash_u, | |
| 1187 | .string_surrogate_half_backslash_u_1, | |
| 1188 | .string_surrogate_half_backslash_u_2, | |
| 1189 | .string_surrogate_half_backslash_u_3, | |
| 1190 | => return .string, | |
| 1191 | ||
| 1192 | .string_utf8_last_byte, | |
| 1193 | .string_utf8_second_to_last_byte, | |
| 1194 | .string_utf8_second_to_last_byte_guard_against_overlong, | |
| 1195 | .string_utf8_second_to_last_byte_guard_against_surrogate_half, | |
| 1196 | .string_utf8_third_to_last_byte, | |
| 1197 | .string_utf8_third_to_last_byte_guard_against_overlong, | |
| 1198 | .string_utf8_third_to_last_byte_guard_against_too_large, | |
| 1199 | => return .string, | |
| 1200 | ||
| 1201 | .literal_t, | |
| 1202 | .literal_tr, | |
| 1203 | .literal_tru, | |
| 1204 | => return .true, | |
| 1205 | .literal_f, | |
| 1206 | .literal_fa, | |
| 1207 | .literal_fal, | |
| 1208 | .literal_fals, | |
| 1209 | => return .false, | |
| 1210 | .literal_n, | |
| 1211 | .literal_nu, | |
| 1212 | .literal_nul, | |
| 1213 | => return .null, | |
| 1214 | } | |
| 1215 | unreachable; | |
| 1216 | } | |
| 1217 | } | |
| 1218 | ||
| 1219 | const State = enum { | |
| 1220 | value, | |
| 1221 | post_value, | |
| 1222 | ||
| 1223 | object_start, | |
| 1224 | object_post_comma, | |
| 1225 | ||
| 1226 | array_start, | |
| 1227 | ||
| 1228 | number_minus, | |
| 1229 | number_leading_zero, | |
| 1230 | number_int, | |
| 1231 | number_post_dot, | |
| 1232 | number_frac, | |
| 1233 | number_post_e, | |
| 1234 | number_post_e_sign, | |
| 1235 | number_exp, | |
| 1236 | ||
| 1237 | string, | |
| 1238 | string_backslash, | |
| 1239 | string_backslash_u, | |
| 1240 | string_backslash_u_1, | |
| 1241 | string_backslash_u_2, | |
| 1242 | string_backslash_u_3, | |
| 1243 | string_surrogate_half, | |
| 1244 | string_surrogate_half_backslash, | |
| 1245 | string_surrogate_half_backslash_u, | |
| 1246 | string_surrogate_half_backslash_u_1, | |
| 1247 | string_surrogate_half_backslash_u_2, | |
| 1248 | string_surrogate_half_backslash_u_3, | |
| 1249 | ||
| 1250 | // From http://unicode.org/mail-arch/unicode-ml/y2003-m02/att-0467/01-The_Algorithm_to_Valide_an_UTF-8_String | |
| 1251 | string_utf8_last_byte, // State A | |
| 1252 | string_utf8_second_to_last_byte, // State B | |
| 1253 | string_utf8_second_to_last_byte_guard_against_overlong, // State C | |
| 1254 | string_utf8_second_to_last_byte_guard_against_surrogate_half, // State D | |
| 1255 | string_utf8_third_to_last_byte, // State E | |
| 1256 | string_utf8_third_to_last_byte_guard_against_overlong, // State F | |
| 1257 | string_utf8_third_to_last_byte_guard_against_too_large, // State G | |
| 1258 | ||
| 1259 | literal_t, | |
| 1260 | literal_tr, | |
| 1261 | literal_tru, | |
| 1262 | literal_f, | |
| 1263 | literal_fa, | |
| 1264 | literal_fal, | |
| 1265 | literal_fals, | |
| 1266 | literal_n, | |
| 1267 | literal_nu, | |
| 1268 | literal_nul, | |
| 1269 | }; | |
| 1270 | ||
| 1271 | fn expectByte(self: *const @This()) !u8 { | |
| 1272 | if (self.cursor < self.input.len) { | |
| 1273 | return self.input[self.cursor]; | |
| 1274 | } | |
| 1275 | // No byte. | |
| 1276 | if (self.is_end_of_input) return error.UnexpectedEndOfInput; | |
| 1277 | return error.BufferUnderrun; | |
| 1278 | } | |
| 1279 | ||
| 1280 | fn skipWhitespace(self: *@This()) void { | |
| 1281 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 1282 | switch (self.input[self.cursor]) { | |
| 1283 | // Whitespace | |
| 1284 | ' ', '\t', '\r' => continue, | |
| 1285 | '\n' => { | |
| 1286 | if (self.diagnostics) |diag| { | |
| 1287 | diag.line_number += 1; | |
| 1288 | // This will count the newline itself, | |
| 1289 | // which means a straight-forward subtraction will give a 1-based column number. | |
| 1290 | diag.line_start_cursor = self.cursor; | |
| 1291 | } | |
| 1292 | continue; | |
| 1293 | }, | |
| 1294 | else => return, | |
| 1295 | } | |
| 1296 | } | |
| 1297 | } | |
| 1298 | ||
| 1299 | fn skipWhitespaceExpectByte(self: *@This()) !u8 { | |
| 1300 | self.skipWhitespace(); | |
| 1301 | return self.expectByte(); | |
| 1302 | } | |
| 1303 | ||
| 1304 | fn skipWhitespaceCheckEnd(self: *@This()) !bool { | |
| 1305 | self.skipWhitespace(); | |
| 1306 | if (self.cursor >= self.input.len) { | |
| 1307 | // End of buffer. | |
| 1308 | if (self.is_end_of_input) { | |
| 1309 | // End of everything. | |
| 1310 | if (self.stackHeight() == 0) { | |
| 1311 | // We did it! | |
| 1312 | return true; | |
| 1313 | } | |
| 1314 | return error.UnexpectedEndOfInput; | |
| 1315 | } | |
| 1316 | return error.BufferUnderrun; | |
| 1317 | } | |
| 1318 | if (self.stackHeight() == 0) return error.SyntaxError; | |
| 1319 | return false; | |
| 1320 | } | |
| 1321 | ||
| 1322 | fn takeValueSlice(self: *@This()) []const u8 { | |
| 1323 | const slice = self.input[self.value_start..self.cursor]; | |
| 1324 | self.value_start = self.cursor; | |
| 1325 | return slice; | |
| 1326 | } | |
| 1327 | fn takeValueSliceMinusTrailingOffset(self: *@This(), trailing_negative_offset: usize) []const u8 { | |
| 1328 | // Check if the escape sequence started before the current input buffer. | |
| 1329 | // (The algebra here is awkward to avoid unsigned underflow, | |
| 1330 | // but it's just making sure the slice on the next line isn't UB.) | |
| 1331 | if (self.cursor <= self.value_start + trailing_negative_offset) return ""; | |
| 1332 | const slice = self.input[self.value_start .. self.cursor - trailing_negative_offset]; | |
| 1333 | // When trailing_negative_offset is non-zero, setting self.value_start doesn't matter, | |
| 1334 | // because we always set it again while emitting the .partial_string_escaped_*. | |
| 1335 | self.value_start = self.cursor; | |
| 1336 | return slice; | |
| 1337 | } | |
| 1338 | ||
| 1339 | fn endOfBufferInNumber(self: *@This(), allow_end: bool) !Token { | |
| 1340 | const slice = self.takeValueSlice(); | |
| 1341 | if (self.is_end_of_input) { | |
| 1342 | if (!allow_end) return error.UnexpectedEndOfInput; | |
| 1343 | self.state = .post_value; | |
| 1344 | return Token{ .number = slice }; | |
| 1345 | } | |
| 1346 | if (slice.len == 0) return error.BufferUnderrun; | |
| 1347 | return Token{ .partial_number = slice }; | |
| 1348 | } | |
| 1349 | ||
| 1350 | fn endOfBufferInString(self: *@This()) !Token { | |
| 1351 | if (self.is_end_of_input) return error.UnexpectedEndOfInput; | |
| 1352 | const slice = self.takeValueSliceMinusTrailingOffset(switch (self.state) { | |
| 1353 | // Don't include the escape sequence in the partial string. | |
| 1354 | .string_backslash => 1, | |
| 1355 | .string_backslash_u => 2, | |
| 1356 | .string_backslash_u_1 => 3, | |
| 1357 | .string_backslash_u_2 => 4, | |
| 1358 | .string_backslash_u_3 => 5, | |
| 1359 | .string_surrogate_half => 6, | |
| 1360 | .string_surrogate_half_backslash => 7, | |
| 1361 | .string_surrogate_half_backslash_u => 8, | |
| 1362 | .string_surrogate_half_backslash_u_1 => 9, | |
| 1363 | .string_surrogate_half_backslash_u_2 => 10, | |
| 1364 | .string_surrogate_half_backslash_u_3 => 11, | |
| 1365 | ||
| 1366 | // Include everything up to the cursor otherwise. | |
| 1367 | .string, | |
| 1368 | .string_utf8_last_byte, | |
| 1369 | .string_utf8_second_to_last_byte, | |
| 1370 | .string_utf8_second_to_last_byte_guard_against_overlong, | |
| 1371 | .string_utf8_second_to_last_byte_guard_against_surrogate_half, | |
| 1372 | .string_utf8_third_to_last_byte, | |
| 1373 | .string_utf8_third_to_last_byte_guard_against_overlong, | |
| 1374 | .string_utf8_third_to_last_byte_guard_against_too_large, | |
| 1375 | => 0, | |
| 1376 | ||
| 1377 | else => unreachable, | |
| 1378 | }); | |
| 1379 | if (slice.len == 0) return error.BufferUnderrun; | |
| 1380 | return Token{ .partial_string = slice }; | |
| 1381 | } | |
| 1382 | ||
| 1383 | fn partialStringCodepoint(code_point: u21) Token { | |
| 1384 | var buf: [4]u8 = undefined; | |
| 1385 | switch (std.unicode.utf8Encode(code_point, &buf) catch unreachable) { | |
| 1386 | 1 => return Token{ .partial_string_escaped_1 = buf[0..1].* }, | |
| 1387 | 2 => return Token{ .partial_string_escaped_2 = buf[0..2].* }, | |
| 1388 | 3 => return Token{ .partial_string_escaped_3 = buf[0..3].* }, | |
| 1389 | 4 => return Token{ .partial_string_escaped_4 = buf[0..4].* }, | |
| 1390 | else => unreachable, | |
| 1391 | } | |
| 1392 | } | |
| 1393 | ||
| 1394 | /// Scan the input and check for malformed JSON. | |
| 1395 | /// On `SyntaxError` or `UnexpectedEndOfInput`, returns `false`. | |
| 1396 | /// Returns any errors from the allocator as-is, which is unlikely, | |
| 1397 | /// but can be caused by extreme nesting depth in the input. | |
| 1398 | pub fn validate(allocator: Allocator, s: []const u8) Allocator.Error!bool { | |
| 1399 | var scanner = Scanner.initCompleteInput(allocator, s); | |
| 1400 | defer scanner.deinit(); | |
| 1401 | ||
| 1402 | while (true) { | |
| 1403 | const token = scanner.next() catch |err| switch (err) { | |
| 1404 | error.SyntaxError, error.UnexpectedEndOfInput => return false, | |
| 1405 | error.OutOfMemory => return error.OutOfMemory, | |
| 1406 | error.BufferUnderrun => unreachable, | |
| 1407 | }; | |
| 1408 | if (token == .end_of_document) break; | |
| 1409 | } | |
| 1410 | ||
| 1411 | return true; | |
| 1412 | } | |
| 1413 | ||
| 1414 | /// The parsing errors are divided into two categories: | |
| 1415 | /// * `SyntaxError` is for clearly malformed JSON documents, | |
| 1416 | /// such as giving an input document that isn't JSON at all. | |
| 1417 | /// * `UnexpectedEndOfInput` is for signaling that everything's been | |
| 1418 | /// valid so far, but the input appears to be truncated for some reason. | |
| 1419 | /// Note that a completely empty (or whitespace-only) input will give `UnexpectedEndOfInput`. | |
| 1420 | pub const Error = error{ SyntaxError, UnexpectedEndOfInput }; | |
| 1421 | ||
| 1422 | /// Used by `json.reader`. | |
| 1423 | pub const default_buffer_size = 0x1000; | |
| 1424 | ||
| 1425 | /// The tokens emitted by `std.json.Scanner` and `std.json.Reader` `.next*()` functions follow this grammar: | |
| 1426 | /// ``` | |
| 1427 | /// <document> = <value> .end_of_document | |
| 1428 | /// <value> = | |
| 1429 | /// | <object> | |
| 1430 | /// | <array> | |
| 1431 | /// | <number> | |
| 1432 | /// | <string> | |
| 1433 | /// | .true | |
| 1434 | /// | .false | |
| 1435 | /// | .null | |
| 1436 | /// <object> = .object_begin ( <string> <value> )* .object_end | |
| 1437 | /// <array> = .array_begin ( <value> )* .array_end | |
| 1438 | /// <number> = <It depends. See below.> | |
| 1439 | /// <string> = <It depends. See below.> | |
| 1440 | /// ``` | |
| 1441 | /// | |
| 1442 | /// What you get for `<number>` and `<string>` values depends on which `next*()` method you call: | |
| 1443 | /// | |
| 1444 | /// ``` | |
| 1445 | /// next(): | |
| 1446 | /// <number> = ( .partial_number )* .number | |
| 1447 | /// <string> = ( <partial_string> )* .string | |
| 1448 | /// <partial_string> = | |
| 1449 | /// | .partial_string | |
| 1450 | /// | .partial_string_escaped_1 | |
| 1451 | /// | .partial_string_escaped_2 | |
| 1452 | /// | .partial_string_escaped_3 | |
| 1453 | /// | .partial_string_escaped_4 | |
| 1454 | /// | |
| 1455 | /// nextAlloc*(..., .alloc_always): | |
| 1456 | /// <number> = .allocated_number | |
| 1457 | /// <string> = .allocated_string | |
| 1458 | /// | |
| 1459 | /// nextAlloc*(..., .alloc_if_needed): | |
| 1460 | /// <number> = | |
| 1461 | /// | .number | |
| 1462 | /// | .allocated_number | |
| 1463 | /// <string> = | |
| 1464 | /// | .string | |
| 1465 | /// | .allocated_string | |
| 1466 | /// ``` | |
| 1467 | /// | |
| 1468 | /// For all tokens with a `[]const u8`, `[]u8`, or `[n]u8` payload, the payload represents the content of the value. | |
| 1469 | /// For number values, this is the representation of the number exactly as it appears in the input. | |
| 1470 | /// For strings, this is the content of the string after resolving escape sequences. | |
| 1471 | /// | |
| 1472 | /// For `.allocated_number` and `.allocated_string`, the `[]u8` payloads are allocations made with the given allocator. | |
| 1473 | /// You are responsible for managing that memory. `json.Reader.deinit()` does *not* free those allocations. | |
| 1474 | /// | |
| 1475 | /// The `.partial_*` tokens indicate that a value spans multiple input buffers or that a string contains escape sequences. | |
| 1476 | /// To get a complete value in memory, you need to concatenate the values yourself. | |
| 1477 | /// Calling `nextAlloc*()` does this for you, and returns an `.allocated_*` token with the result. | |
| 1478 | /// | |
| 1479 | /// For tokens with a `[]const u8` payload, the payload is a slice into the current input buffer. | |
| 1480 | /// The memory may become undefined during the next call to `json.Scanner.feedInput()` | |
| 1481 | /// or any `json.Reader` method whose return error set includes `json.Error`. | |
| 1482 | /// To keep the value persistently, it recommended to make a copy or to use `.alloc_always`, | |
| 1483 | /// which makes a copy for you. | |
| 1484 | /// | |
| 1485 | /// Note that `.number` and `.string` tokens that follow `.partial_*` tokens may have `0` length to indicate that | |
| 1486 | /// the previously partial value is completed with no additional bytes. | |
| 1487 | /// (This can happen when the break between input buffers happens to land on the exact end of a value. E.g. `"[1234"`, `"]"`.) | |
| 1488 | /// `.partial_*` tokens never have `0` length. | |
| 1489 | /// | |
| 1490 | /// The recommended strategy for using the different `next*()` methods is something like this: | |
| 1491 | /// | |
| 1492 | /// When you're expecting an object key, use `.alloc_if_needed`. | |
| 1493 | /// You often don't need a copy of the key string to persist; you might just check which field it is. | |
| 1494 | /// In the case that the key happens to require an allocation, free it immediately after checking it. | |
| 1495 | /// | |
| 1496 | /// When you're expecting a meaningful string value (such as on the right of a `:`), | |
| 1497 | /// use `.alloc_always` in order to keep the value valid throughout parsing the rest of the document. | |
| 1498 | /// | |
| 1499 | /// When you're expecting a number value, use `.alloc_if_needed`. | |
| 1500 | /// You're probably going to be parsing the string representation of the number into a numeric representation, | |
| 1501 | /// so you need the complete string representation only temporarily. | |
| 1502 | /// | |
| 1503 | /// When you're skipping an unrecognized value, use `skipValue()`. | |
| 1504 | pub const Token = union(enum) { | |
| 1505 | object_begin, | |
| 1506 | object_end, | |
| 1507 | array_begin, | |
| 1508 | array_end, | |
| 1509 | ||
| 1510 | true, | |
| 1511 | false, | |
| 1512 | null, | |
| 1513 | ||
| 1514 | number: []const u8, | |
| 1515 | partial_number: []const u8, | |
| 1516 | allocated_number: []u8, | |
| 1517 | ||
| 1518 | string: []const u8, | |
| 1519 | partial_string: []const u8, | |
| 1520 | partial_string_escaped_1: [1]u8, | |
| 1521 | partial_string_escaped_2: [2]u8, | |
| 1522 | partial_string_escaped_3: [3]u8, | |
| 1523 | partial_string_escaped_4: [4]u8, | |
| 1524 | allocated_string: []u8, | |
| 1525 | ||
| 1526 | end_of_document, | |
| 1527 | }; | |
| 1528 | ||
| 1529 | /// This is only used in `peekNextTokenType()` and gives a categorization based on the first byte of the next token that will be emitted from a `next*()` call. | |
| 1530 | pub const TokenType = enum { | |
| 1531 | object_begin, | |
| 1532 | object_end, | |
| 1533 | array_begin, | |
| 1534 | array_end, | |
| 1535 | true, | |
| 1536 | false, | |
| 1537 | null, | |
| 1538 | number, | |
| 1539 | string, | |
| 1540 | end_of_document, | |
| 1541 | }; | |
| 1542 | ||
| 1543 | /// To enable diagnostics, declare `var diagnostics = Diagnostics{};` then call `source.enableDiagnostics(&diagnostics);` | |
| 1544 | /// where `source` is either a `std.json.Reader` or a `std.json.Scanner` that has just been initialized. | |
| 1545 | /// At any time, notably just after an error, call `getLine()`, `getColumn()`, and/or `getByteOffset()` | |
| 1546 | /// to get meaningful information from this. | |
| 1547 | pub const Diagnostics = struct { | |
| 1548 | line_number: u64 = 1, | |
| 1549 | line_start_cursor: usize = @as(usize, @bitCast(@as(isize, -1))), // Start just "before" the input buffer to get a 1-based column for line 1. | |
| 1550 | total_bytes_before_current_input: u64 = 0, | |
| 1551 | cursor_pointer: *const usize = undefined, | |
| 1552 | ||
| 1553 | /// Starts at 1. | |
| 1554 | pub fn getLine(self: *const @This()) u64 { | |
| 1555 | return self.line_number; | |
| 1556 | } | |
| 1557 | /// Starts at 1. | |
| 1558 | pub fn getColumn(self: *const @This()) u64 { | |
| 1559 | return self.cursor_pointer.* -% self.line_start_cursor; | |
| 1560 | } | |
| 1561 | /// Starts at 0. Measures the byte offset since the start of the input. | |
| 1562 | pub fn getByteOffset(self: *const @This()) u64 { | |
| 1563 | return self.total_bytes_before_current_input + self.cursor_pointer.*; | |
| 1564 | } | |
| 1565 | }; | |
| 1566 | ||
| 1567 | /// See the documentation for `std.json.Token`. | |
| 1568 | pub const AllocWhen = enum { alloc_if_needed, alloc_always }; | |
| 1569 | ||
| 1570 | /// For security, the maximum size allocated to store a single string or number value is limited to 4MiB by default. | |
| 1571 | /// This limit can be specified by calling `nextAllocMax()` instead of `nextAlloc()`. | |
| 1572 | pub const default_max_value_len = 4 * 1024 * 1024; | |
| 1573 | ||
| 1574 | /// All `next*()` methods here handle `error.BufferUnderrun` from `std.json.Scanner`, and then read from the reader. | |
| 1575 | pub const Reader = struct { | |
| 1576 | scanner: Scanner, | |
| 1577 | reader: *std.Io.Reader, | |
| 1578 | ||
| 1579 | /// The allocator is only used to track `[]` and `{}` nesting levels. | |
| 1580 | pub fn init(allocator: Allocator, io_reader: *std.Io.Reader) @This() { | |
| 1581 | return .{ | |
| 1582 | .scanner = Scanner.initStreaming(allocator), | |
| 1583 | .reader = io_reader, | |
| 1584 | }; | |
| 1585 | } | |
| 1586 | pub fn deinit(self: *@This()) void { | |
| 1587 | self.scanner.deinit(); | |
| 1588 | self.* = undefined; | |
| 1589 | } | |
| 1590 | ||
| 1591 | /// Calls `std.json.Scanner.enableDiagnostics`. | |
| 1592 | pub fn enableDiagnostics(self: *@This(), diagnostics: *Diagnostics) void { | |
| 1593 | self.scanner.enableDiagnostics(diagnostics); | |
| 1594 | } | |
| 1595 | ||
| 1596 | pub const NextError = std.Io.Reader.Error || Error || Allocator.Error; | |
| 1597 | pub const SkipError = Reader.NextError; | |
| 1598 | pub const AllocError = Reader.NextError || error{ValueTooLong}; | |
| 1599 | pub const PeekError = std.Io.Reader.Error || Error; | |
| 1600 | ||
| 1601 | /// Equivalent to `nextAllocMax(allocator, when, default_max_value_len);` | |
| 1602 | /// See also `std.json.Token` for documentation of `nextAlloc*()` function behavior. | |
| 1603 | pub fn nextAlloc(self: *@This(), allocator: Allocator, when: AllocWhen) Reader.AllocError!Token { | |
| 1604 | return self.nextAllocMax(allocator, when, default_max_value_len); | |
| 1605 | } | |
| 1606 | /// See also `std.json.Token` for documentation of `nextAlloc*()` function behavior. | |
| 1607 | pub fn nextAllocMax(self: *@This(), allocator: Allocator, when: AllocWhen, max_value_len: usize) Reader.AllocError!Token { | |
| 1608 | const token_type = try self.peekNextTokenType(); | |
| 1609 | switch (token_type) { | |
| 1610 | .number, .string => { | |
| 1611 | var value_list = ArrayList(u8).init(allocator); | |
| 1612 | errdefer { | |
| 1613 | value_list.deinit(); | |
| 1614 | } | |
| 1615 | if (try self.allocNextIntoArrayListMax(&value_list, when, max_value_len)) |slice| { | |
| 1616 | return if (token_type == .number) | |
| 1617 | Token{ .number = slice } | |
| 1618 | else | |
| 1619 | Token{ .string = slice }; | |
| 1620 | } else { | |
| 1621 | return if (token_type == .number) | |
| 1622 | Token{ .allocated_number = try value_list.toOwnedSlice() } | |
| 1623 | else | |
| 1624 | Token{ .allocated_string = try value_list.toOwnedSlice() }; | |
| 1625 | } | |
| 1626 | }, | |
| 1627 | ||
| 1628 | // Simple tokens never alloc. | |
| 1629 | .object_begin, | |
| 1630 | .object_end, | |
| 1631 | .array_begin, | |
| 1632 | .array_end, | |
| 1633 | .true, | |
| 1634 | .false, | |
| 1635 | .null, | |
| 1636 | .end_of_document, | |
| 1637 | => return try self.next(), | |
| 1638 | } | |
| 1639 | } | |
| 1640 | ||
| 1641 | /// Equivalent to `allocNextIntoArrayListMax(value_list, when, default_max_value_len);` | |
| 1642 | pub fn allocNextIntoArrayList(self: *@This(), value_list: *ArrayList(u8), when: AllocWhen) Reader.AllocError!?[]const u8 { | |
| 1643 | return self.allocNextIntoArrayListMax(value_list, when, default_max_value_len); | |
| 1644 | } | |
| 1645 | /// Calls `std.json.Scanner.allocNextIntoArrayListMax` and handles `error.BufferUnderrun`. | |
| 1646 | pub fn allocNextIntoArrayListMax(self: *@This(), value_list: *ArrayList(u8), when: AllocWhen, max_value_len: usize) Reader.AllocError!?[]const u8 { | |
| 1647 | while (true) { | |
| 1648 | return self.scanner.allocNextIntoArrayListMax(value_list, when, max_value_len) catch |err| switch (err) { | |
| 1649 | error.BufferUnderrun => { | |
| 1650 | try self.refillBuffer(); | |
| 1651 | continue; | |
| 1652 | }, | |
| 1653 | else => |other_err| return other_err, | |
| 1654 | }; | |
| 1655 | } | |
| 1656 | } | |
| 1657 | ||
| 1658 | /// Like `std.json.Scanner.skipValue`, but handles `error.BufferUnderrun`. | |
| 1659 | pub fn skipValue(self: *@This()) Reader.SkipError!void { | |
| 1660 | switch (try self.peekNextTokenType()) { | |
| 1661 | .object_begin, .array_begin => { | |
| 1662 | try self.skipUntilStackHeight(self.stackHeight()); | |
| 1663 | }, | |
| 1664 | .number, .string => { | |
| 1665 | while (true) { | |
| 1666 | switch (try self.next()) { | |
| 1667 | .partial_number, | |
| 1668 | .partial_string, | |
| 1669 | .partial_string_escaped_1, | |
| 1670 | .partial_string_escaped_2, | |
| 1671 | .partial_string_escaped_3, | |
| 1672 | .partial_string_escaped_4, | |
| 1673 | => continue, | |
| 1674 | ||
| 1675 | .number, .string => break, | |
| 1676 | ||
| 1677 | else => unreachable, | |
| 1678 | } | |
| 1679 | } | |
| 1680 | }, | |
| 1681 | .true, .false, .null => { | |
| 1682 | _ = try self.next(); | |
| 1683 | }, | |
| 1684 | ||
| 1685 | .object_end, .array_end, .end_of_document => unreachable, // Attempt to skip a non-value token. | |
| 1686 | } | |
| 1687 | } | |
| 1688 | /// Like `std.json.Scanner.skipUntilStackHeight()` but handles `error.BufferUnderrun`. | |
| 1689 | pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: usize) Reader.NextError!void { | |
| 1690 | while (true) { | |
| 1691 | return self.scanner.skipUntilStackHeight(terminal_stack_height) catch |err| switch (err) { | |
| 1692 | error.BufferUnderrun => { | |
| 1693 | try self.refillBuffer(); | |
| 1694 | continue; | |
| 1695 | }, | |
| 1696 | else => |other_err| return other_err, | |
| 1697 | }; | |
| 1698 | } | |
| 1699 | } | |
| 1700 | ||
| 1701 | /// Calls `std.json.Scanner.stackHeight`. | |
| 1702 | pub fn stackHeight(self: *const @This()) usize { | |
| 1703 | return self.scanner.stackHeight(); | |
| 1704 | } | |
| 1705 | /// Calls `std.json.Scanner.ensureTotalStackCapacity`. | |
| 1706 | pub fn ensureTotalStackCapacity(self: *@This(), height: usize) Allocator.Error!void { | |
| 1707 | try self.scanner.ensureTotalStackCapacity(height); | |
| 1708 | } | |
| 1709 | ||
| 1710 | /// See `std.json.Token` for documentation of this function. | |
| 1711 | pub fn next(self: *@This()) Reader.NextError!Token { | |
| 1712 | while (true) { | |
| 1713 | return self.scanner.next() catch |err| switch (err) { | |
| 1714 | error.BufferUnderrun => { | |
| 1715 | try self.refillBuffer(); | |
| 1716 | continue; | |
| 1717 | }, | |
| 1718 | else => |other_err| return other_err, | |
| 1719 | }; | |
| 1720 | } | |
| 1721 | } | |
| 1722 | ||
| 1723 | /// See `std.json.Scanner.peekNextTokenType()`. | |
| 1724 | pub fn peekNextTokenType(self: *@This()) Reader.PeekError!TokenType { | |
| 1725 | while (true) { | |
| 1726 | return self.scanner.peekNextTokenType() catch |err| switch (err) { | |
| 1727 | error.BufferUnderrun => { | |
| 1728 | try self.refillBuffer(); | |
| 1729 | continue; | |
| 1730 | }, | |
| 1731 | else => |other_err| return other_err, | |
| 1732 | }; | |
| 1733 | } | |
| 1734 | } | |
| 1735 | ||
| 1736 | fn refillBuffer(self: *@This()) std.Io.Reader.Error!void { | |
| 1737 | const input = self.reader.peekGreedy(1) catch |err| switch (err) { | |
| 1738 | error.ReadFailed => return error.ReadFailed, | |
| 1739 | error.EndOfStream => return self.scanner.endInput(), | |
| 1740 | }; | |
| 1741 | self.reader.toss(input.len); | |
| 1742 | self.scanner.feedInput(input); | |
| 1743 | } | |
| 1744 | }; | |
| 1745 | ||
| 1746 | const OBJECT_MODE = 0; | |
| 1747 | const ARRAY_MODE = 1; | |
| 1748 | ||
| 1749 | fn appendSlice(list: *std.ArrayList(u8), buf: []const u8, max_value_len: usize) !void { | |
| 1750 | const new_len = std.math.add(usize, list.items.len, buf.len) catch return error.ValueTooLong; | |
| 1751 | if (new_len > max_value_len) return error.ValueTooLong; | |
| 1752 | try list.appendSlice(buf); | |
| 1753 | } | |
| 1754 | ||
| 1755 | /// For the slice you get from a `Token.number` or `Token.allocated_number`, | |
| 1756 | /// this function returns true if the number doesn't contain any fraction or exponent components, and is not `-0`. | |
| 1757 | /// Note, the numeric value encoded by the value may still be an integer, such as `1.0`. | |
| 1758 | /// This function is meant to give a hint about whether integer parsing or float parsing should be used on the value. | |
| 1759 | /// This function will not give meaningful results on non-numeric input. | |
| 1760 | pub fn isNumberFormattedLikeAnInteger(value: []const u8) bool { | |
| 1761 | if (std.mem.eql(u8, value, "-0")) return false; | |
| 1762 | return std.mem.indexOfAny(u8, value, ".eE") == null; | |
| 1763 | } | |
| 1764 | ||
| 1765 | test { | |
| 1766 | _ = @import("./scanner_test.zig"); | |
| 1767 | } |
lib/std/json/Stringify.zig+23-24| ... | ... | @@ -248,7 +248,7 @@ test print { |
| 248 | 248 | \\ ] |
| 249 | 249 | \\} |
| 250 | 250 | ; |
| 251 | try std.testing.expectEqualStrings(expected, out.getWritten()); | |
| 251 | try std.testing.expectEqualStrings(expected, out.buffered()); | |
| 252 | 252 | } |
| 253 | 253 | |
| 254 | 254 | /// An alternative to calling `write` that allows you to write directly to the `.writer` field, e.g. with `.writer.writeAll()`. |
| ... | ... | @@ -577,7 +577,7 @@ pub fn value(v: anytype, options: Options, writer: *Writer) Error!void { |
| 577 | 577 | |
| 578 | 578 | test value { |
| 579 | 579 | var out: std.io.Writer.Allocating = .init(std.testing.allocator); |
| 580 | const writer = &out.interface; | |
| 580 | const writer = &out.writer; | |
| 581 | 581 | defer out.deinit(); |
| 582 | 582 | |
| 583 | 583 | const T = struct { a: i32, b: []const u8 }; |
| ... | ... | @@ -617,9 +617,8 @@ test value { |
| 617 | 617 | /// Caller owns returned memory. |
| 618 | 618 | pub fn valueAlloc(gpa: Allocator, v: anytype, options: Options) error{OutOfMemory}![]u8 { |
| 619 | 619 | var aw: std.io.Writer.Allocating = .init(gpa); |
| 620 | const writer = &aw.interface; | |
| 621 | 620 | defer aw.deinit(); |
| 622 | value(v, options, writer) catch return error.OutOfMemory; | |
| 621 | value(v, options, &aw.writer) catch return error.OutOfMemory; | |
| 623 | 622 | return aw.toOwnedSlice(); |
| 624 | 623 | } |
| 625 | 624 | |
| ... | ... | @@ -634,23 +633,23 @@ test valueAlloc { |
| 634 | 633 | try std.testing.expectEqualStrings(expected, actual); |
| 635 | 634 | } |
| 636 | 635 | |
| 637 | fn outputUnicodeEscape(codepoint: u21, bw: *Writer) Error!void { | |
| 636 | fn outputUnicodeEscape(codepoint: u21, w: *Writer) Error!void { | |
| 638 | 637 | if (codepoint <= 0xFFFF) { |
| 639 | 638 | // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF), |
| 640 | 639 | // then it may be represented as a six-character sequence: a reverse solidus, followed |
| 641 | 640 | // by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point. |
| 642 | try bw.writeAll("\\u"); | |
| 643 | try bw.printInt("x", .{ .width = 4, .fill = '0' }, codepoint); | |
| 641 | try w.writeAll("\\u"); | |
| 642 | try w.printInt(codepoint, 16, .lower, .{ .width = 4, .fill = '0' }); | |
| 644 | 643 | } else { |
| 645 | 644 | assert(codepoint <= 0x10FFFF); |
| 646 | 645 | // To escape an extended character that is not in the Basic Multilingual Plane, |
| 647 | 646 | // the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair. |
| 648 | 647 | const high = @as(u16, @intCast((codepoint - 0x10000) >> 10)) + 0xD800; |
| 649 | 648 | const low = @as(u16, @intCast(codepoint & 0x3FF)) + 0xDC00; |
| 650 | try bw.writeAll("\\u"); | |
| 651 | try bw.printInt("x", .{ .width = 4, .fill = '0' }, high); | |
| 652 | try bw.writeAll("\\u"); | |
| 653 | try bw.printInt("x", .{ .width = 4, .fill = '0' }, low); | |
| 649 | try w.writeAll("\\u"); | |
| 650 | try w.printInt(high, 16, .lower, .{ .width = 4, .fill = '0' }); | |
| 651 | try w.writeAll("\\u"); | |
| 652 | try w.printInt(low, 16, .lower, .{ .width = 4, .fill = '0' }); | |
| 654 | 653 | } |
| 655 | 654 | } |
| 656 | 655 | |
| ... | ... | @@ -723,8 +722,8 @@ test "json write stream" { |
| 723 | 722 | try testBasicWriteStream(&w); |
| 724 | 723 | } |
| 725 | 724 | |
| 726 | fn testBasicWriteStream(w: *Stringify) Error!void { | |
| 727 | w.writer.reset(); | |
| 725 | fn testBasicWriteStream(w: *Stringify) !void { | |
| 726 | w.writer.end = 0; | |
| 728 | 727 | |
| 729 | 728 | try w.beginObject(); |
| 730 | 729 | |
| ... | ... | @@ -755,19 +754,19 @@ fn testBasicWriteStream(w: *Stringify) Error!void { |
| 755 | 754 | \\{ |
| 756 | 755 | \\ "object": { |
| 757 | 756 | \\ "one": 1, |
| 758 | \\ "two": 2e0 | |
| 757 | \\ "two": 2 | |
| 759 | 758 | \\ }, |
| 760 | 759 | \\ "string": "This is a string", |
| 761 | 760 | \\ "array": [ |
| 762 | 761 | \\ "Another string", |
| 763 | 762 | \\ 1, |
| 764 | \\ 3.5e0 | |
| 763 | \\ 3.5 | |
| 765 | 764 | \\ ], |
| 766 | 765 | \\ "int": 10, |
| 767 | \\ "float": 3.5e0 | |
| 766 | \\ "float": 3.5 | |
| 768 | 767 | \\} |
| 769 | 768 | ; |
| 770 | try std.testing.expectEqualStrings(expected, w.writer.getWritten()); | |
| 769 | try std.testing.expectEqualStrings(expected, w.writer.buffered()); | |
| 771 | 770 | } |
| 772 | 771 | |
| 773 | 772 | fn getJsonObject(allocator: std.mem.Allocator) !std.json.Value { |
| ... | ... | @@ -804,12 +803,12 @@ test "stringify basic types" { |
| 804 | 803 | try testStringify("null", @as(?u8, null), .{}); |
| 805 | 804 | try testStringify("null", @as(?*u32, null), .{}); |
| 806 | 805 | try testStringify("42", 42, .{}); |
| 807 | try testStringify("4.2e1", 42.0, .{}); | |
| 806 | try testStringify("42", 42.0, .{}); | |
| 808 | 807 | try testStringify("42", @as(u8, 42), .{}); |
| 809 | 808 | try testStringify("42", @as(u128, 42), .{}); |
| 810 | 809 | try testStringify("9999999999999999", 9999999999999999, .{}); |
| 811 | try testStringify("4.2e1", @as(f32, 42), .{}); | |
| 812 | try testStringify("4.2e1", @as(f64, 42), .{}); | |
| 810 | try testStringify("42", @as(f32, 42), .{}); | |
| 811 | try testStringify("42", @as(f64, 42), .{}); | |
| 813 | 812 | try testStringify("\"ItBroke\"", @as(anyerror, error.ItBroke), .{}); |
| 814 | 813 | try testStringify("\"ItBroke\"", error.ItBroke, .{}); |
| 815 | 814 | } |
| ... | ... | @@ -970,9 +969,9 @@ test "stringify struct with custom stringifier" { |
| 970 | 969 | |
| 971 | 970 | fn testStringify(expected: []const u8, v: anytype, options: Options) !void { |
| 972 | 971 | var buffer: [4096]u8 = undefined; |
| 973 | var bw: Writer = .fixed(&buffer); | |
| 974 | try value(v, options, &bw); | |
| 975 | try std.testing.expectEqualStrings(expected, bw.getWritten()); | |
| 972 | var w: Writer = .fixed(&buffer); | |
| 973 | try value(v, options, &w); | |
| 974 | try std.testing.expectEqualStrings(expected, w.buffered()); | |
| 976 | 975 | } |
| 977 | 976 | |
| 978 | 977 | test "raw streaming" { |
| ... | ... | @@ -996,5 +995,5 @@ test "raw streaming" { |
| 996 | 995 | \\ "long key": "long value" |
| 997 | 996 | \\} |
| 998 | 997 | ; |
| 999 | try std.testing.expectEqualStrings(expected, w.writer.getWritten()); | |
| 998 | try std.testing.expectEqualStrings(expected, w.writer.buffered()); | |
| 1000 | 999 | } |
lib/std/json/dynamic.zig+1-4| ... | ... | @@ -9,10 +9,7 @@ const json = std.json; |
| 9 | 9 | const ParseOptions = @import("./static.zig").ParseOptions; |
| 10 | 10 | const ParseError = @import("./static.zig").ParseError; |
| 11 | 11 | |
| 12 | const JsonScanner = @import("./scanner.zig").Scanner; | |
| 13 | const AllocWhen = @import("./scanner.zig").AllocWhen; | |
| 14 | const Token = @import("./scanner.zig").Token; | |
| 15 | const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger; | |
| 12 | const isNumberFormattedLikeAnInteger = @import("Scanner.zig").isNumberFormattedLikeAnInteger; | |
| 16 | 13 | |
| 17 | 14 | pub const ObjectMap = StringArrayHashMap(Value); |
| 18 | 15 | pub const Array = ArrayList(Value); |
lib/std/json/dynamic_test.zig+12-13| ... | ... | @@ -16,8 +16,7 @@ const parseFromTokenSource = @import("static.zig").parseFromTokenSource; |
| 16 | 16 | const parseFromValueLeaky = @import("static.zig").parseFromValueLeaky; |
| 17 | 17 | const ParseOptions = @import("static.zig").ParseOptions; |
| 18 | 18 | |
| 19 | const jsonReader = @import("scanner.zig").reader; | |
| 20 | const JsonReader = @import("scanner.zig").Reader; | |
| 19 | const Scanner = @import("Scanner.zig"); | |
| 21 | 20 | |
| 22 | 21 | test "json.parser.dynamic" { |
| 23 | 22 | const s = |
| ... | ... | @@ -99,8 +98,8 @@ test "write json then parse it" { |
| 99 | 98 | |
| 100 | 99 | try jw.endObject(); |
| 101 | 100 | |
| 102 | var fbs: std.io.FixedBufferStream = .{ .buffer = fixed_writer.getWritten() }; | |
| 103 | var json_reader = jsonReader(testing.allocator, fbs.reader()); | |
| 101 | var fbs: std.Io.Reader = .fixed(fixed_writer.buffered()); | |
| 102 | var json_reader: Scanner.Reader = .init(testing.allocator, &fbs); | |
| 104 | 103 | defer json_reader.deinit(); |
| 105 | 104 | var parsed = try parseFromTokenSource(Value, testing.allocator, &json_reader, .{}); |
| 106 | 105 | defer parsed.deinit(); |
| ... | ... | @@ -263,7 +262,7 @@ test "Value.jsonStringify" { |
| 263 | 262 | \\ } |
| 264 | 263 | \\] |
| 265 | 264 | ; |
| 266 | try testing.expectEqualStrings(expected, fixed_writer.getWritten()); | |
| 265 | try testing.expectEqualStrings(expected, fixed_writer.buffered()); | |
| 267 | 266 | } |
| 268 | 267 | |
| 269 | 268 | test "parseFromValue(std.json.Value,...)" { |
| ... | ... | @@ -331,8 +330,8 @@ test "polymorphic parsing" { |
| 331 | 330 | test "long object value" { |
| 332 | 331 | const value = "01234567890123456789"; |
| 333 | 332 | const doc = "{\"key\":\"" ++ value ++ "\"}"; |
| 334 | var fbs: std.io.FixedBufferStream = .{ .buffer = doc }; | |
| 335 | var reader = smallBufferJsonReader(testing.allocator, fbs.reader()); | |
| 333 | var fbs: std.Io.Reader = .fixed(doc); | |
| 334 | var reader = smallBufferJsonReader(testing.allocator, &fbs); | |
| 336 | 335 | defer reader.deinit(); |
| 337 | 336 | var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{}); |
| 338 | 337 | defer parsed.deinit(); |
| ... | ... | @@ -364,8 +363,8 @@ test "many object keys" { |
| 364 | 363 | \\ "k5": "v5" |
| 365 | 364 | \\} |
| 366 | 365 | ; |
| 367 | var fbs: std.io.FixedBufferStream = .{ .buffer = doc }; | |
| 368 | var reader = smallBufferJsonReader(testing.allocator, fbs.reader()); | |
| 366 | var fbs: std.Io.Reader = .fixed(doc); | |
| 367 | var reader = smallBufferJsonReader(testing.allocator, &fbs); | |
| 369 | 368 | defer reader.deinit(); |
| 370 | 369 | var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{}); |
| 371 | 370 | defer parsed.deinit(); |
| ... | ... | @@ -379,8 +378,8 @@ test "many object keys" { |
| 379 | 378 | |
| 380 | 379 | test "negative zero" { |
| 381 | 380 | const doc = "-0"; |
| 382 | var fbs: std.io.FixedBufferStream = .{ .buffer = doc }; | |
| 383 | var reader = smallBufferJsonReader(testing.allocator, fbs.reader()); | |
| 381 | var fbs: std.Io.Reader = .fixed(doc); | |
| 382 | var reader = smallBufferJsonReader(testing.allocator, &fbs); | |
| 384 | 383 | defer reader.deinit(); |
| 385 | 384 | var parsed = try parseFromTokenSource(Value, testing.allocator, &reader, .{}); |
| 386 | 385 | defer parsed.deinit(); |
| ... | ... | @@ -388,6 +387,6 @@ test "negative zero" { |
| 388 | 387 | try testing.expect(std.math.isNegativeZero(parsed.value.float)); |
| 389 | 388 | } |
| 390 | 389 | |
| 391 | fn smallBufferJsonReader(allocator: Allocator, io_reader: anytype) JsonReader(16, @TypeOf(io_reader)) { | |
| 392 | return JsonReader(16, @TypeOf(io_reader)).init(allocator, io_reader); | |
| 390 | fn smallBufferJsonReader(allocator: Allocator, io_reader: anytype) Scanner.Reader { | |
| 391 | return .init(allocator, io_reader); | |
| 393 | 392 | } |
lib/std/json/hashmap_test.zig+3-3| ... | ... | @@ -10,7 +10,7 @@ const parseFromTokenSource = @import("static.zig").parseFromTokenSource; |
| 10 | 10 | const parseFromValue = @import("static.zig").parseFromValue; |
| 11 | 11 | const Value = @import("dynamic.zig").Value; |
| 12 | 12 | |
| 13 | const jsonReader = @import("./scanner.zig").reader; | |
| 13 | const Scanner = @import("Scanner.zig"); | |
| 14 | 14 | |
| 15 | 15 | const T = struct { |
| 16 | 16 | i: i32, |
| ... | ... | @@ -39,8 +39,8 @@ test "parse json hashmap while streaming" { |
| 39 | 39 | \\ "xyz": {"i": 1, "s": "w"} |
| 40 | 40 | \\} |
| 41 | 41 | ; |
| 42 | var stream: std.io.FixedBufferStream = .{ .buffer = doc }; | |
| 43 | var json_reader = jsonReader(testing.allocator, stream.reader()); | |
| 42 | var stream: std.Io.Reader = .fixed(doc); | |
| 43 | var json_reader: Scanner.Reader = .init(testing.allocator, &stream); | |
| 44 | 44 | |
| 45 | 45 | var parsed = try parseFromTokenSource( |
| 46 | 46 | ArrayHashMap(T), |
lib/std/json/scanner.zig deleted-1776| ... | ... | @@ -1,1776 +0,0 @@ |
| 1 | // Notes on standards compliance: https://datatracker.ietf.org/doc/html/rfc8259 | |
| 2 | // * RFC 8259 requires JSON documents be valid UTF-8, | |
| 3 | // but makes an allowance for systems that are "part of a closed ecosystem". | |
| 4 | // I have no idea what that's supposed to mean in the context of a standard specification. | |
| 5 | // This implementation requires inputs to be valid UTF-8. | |
| 6 | // * RFC 8259 contradicts itself regarding whether lowercase is allowed in \u hex digits, | |
| 7 | // but this is probably a bug in the spec, and it's clear that lowercase is meant to be allowed. | |
| 8 | // (RFC 5234 defines HEXDIG to only allow uppercase.) | |
| 9 | // * When RFC 8259 refers to a "character", I assume they really mean a "Unicode scalar value". | |
| 10 | // See http://www.unicode.org/glossary/#unicode_scalar_value . | |
| 11 | // * RFC 8259 doesn't explicitly disallow unpaired surrogate halves in \u escape sequences, | |
| 12 | // but vaguely implies that \u escapes are for encoding Unicode "characters" (i.e. Unicode scalar values?), | |
| 13 | // which would mean that unpaired surrogate halves are forbidden. | |
| 14 | // By contrast ECMA-404 (a competing(/compatible?) JSON standard, which JavaScript's JSON.parse() conforms to) | |
| 15 | // explicitly allows unpaired surrogate halves. | |
| 16 | // This implementation forbids unpaired surrogate halves in \u sequences. | |
| 17 | // If a high surrogate half appears in a \u sequence, | |
| 18 | // then a low surrogate half must immediately follow in \u notation. | |
| 19 | // * RFC 8259 allows implementations to "accept non-JSON forms or extensions". | |
| 20 | // This implementation does not accept any of that. | |
| 21 | // * RFC 8259 allows implementations to put limits on "the size of texts", | |
| 22 | // "the maximum depth of nesting", "the range and precision of numbers", | |
| 23 | // and "the length and character contents of strings". | |
| 24 | // This low-level implementation does not limit these, | |
| 25 | // except where noted above, and except that nesting depth requires memory allocation. | |
| 26 | // Note that this low-level API does not interpret numbers numerically, | |
| 27 | // but simply emits their source form for some higher level code to make sense of. | |
| 28 | // * This low-level implementation allows duplicate object keys, | |
| 29 | // and key/value pairs are emitted in the order they appear in the input. | |
| 30 | ||
| 31 | const std = @import("std"); | |
| 32 | ||
| 33 | const Allocator = std.mem.Allocator; | |
| 34 | const ArrayList = std.ArrayList; | |
| 35 | const assert = std.debug.assert; | |
| 36 | const BitStack = std.BitStack; | |
| 37 | ||
| 38 | /// Scan the input and check for malformed JSON. | |
| 39 | /// On `SyntaxError` or `UnexpectedEndOfInput`, returns `false`. | |
| 40 | /// Returns any errors from the allocator as-is, which is unlikely, | |
| 41 | /// but can be caused by extreme nesting depth in the input. | |
| 42 | pub fn validate(allocator: Allocator, s: []const u8) Allocator.Error!bool { | |
| 43 | var scanner = Scanner.initCompleteInput(allocator, s); | |
| 44 | defer scanner.deinit(); | |
| 45 | ||
| 46 | while (true) { | |
| 47 | const token = scanner.next() catch |err| switch (err) { | |
| 48 | error.SyntaxError, error.UnexpectedEndOfInput => return false, | |
| 49 | error.OutOfMemory => return error.OutOfMemory, | |
| 50 | error.BufferUnderrun => unreachable, | |
| 51 | }; | |
| 52 | if (token == .end_of_document) break; | |
| 53 | } | |
| 54 | ||
| 55 | return true; | |
| 56 | } | |
| 57 | ||
| 58 | /// The parsing errors are divided into two categories: | |
| 59 | /// * `SyntaxError` is for clearly malformed JSON documents, | |
| 60 | /// such as giving an input document that isn't JSON at all. | |
| 61 | /// * `UnexpectedEndOfInput` is for signaling that everything's been | |
| 62 | /// valid so far, but the input appears to be truncated for some reason. | |
| 63 | /// Note that a completely empty (or whitespace-only) input will give `UnexpectedEndOfInput`. | |
| 64 | pub const Error = error{ SyntaxError, UnexpectedEndOfInput }; | |
| 65 | ||
| 66 | /// Calls `std.json.Reader` with `std.json.default_buffer_size`. | |
| 67 | pub fn reader(allocator: Allocator, io_reader: anytype) Reader(default_buffer_size, @TypeOf(io_reader)) { | |
| 68 | return Reader(default_buffer_size, @TypeOf(io_reader)).init(allocator, io_reader); | |
| 69 | } | |
| 70 | /// Used by `json.reader`. | |
| 71 | pub const default_buffer_size = 0x1000; | |
| 72 | ||
| 73 | /// The tokens emitted by `std.json.Scanner` and `std.json.Reader` `.next*()` functions follow this grammar: | |
| 74 | /// ``` | |
| 75 | /// <document> = <value> .end_of_document | |
| 76 | /// <value> = | |
| 77 | /// | <object> | |
| 78 | /// | <array> | |
| 79 | /// | <number> | |
| 80 | /// | <string> | |
| 81 | /// | .true | |
| 82 | /// | .false | |
| 83 | /// | .null | |
| 84 | /// <object> = .object_begin ( <string> <value> )* .object_end | |
| 85 | /// <array> = .array_begin ( <value> )* .array_end | |
| 86 | /// <number> = <It depends. See below.> | |
| 87 | /// <string> = <It depends. See below.> | |
| 88 | /// ``` | |
| 89 | /// | |
| 90 | /// What you get for `<number>` and `<string>` values depends on which `next*()` method you call: | |
| 91 | /// | |
| 92 | /// ``` | |
| 93 | /// next(): | |
| 94 | /// <number> = ( .partial_number )* .number | |
| 95 | /// <string> = ( <partial_string> )* .string | |
| 96 | /// <partial_string> = | |
| 97 | /// | .partial_string | |
| 98 | /// | .partial_string_escaped_1 | |
| 99 | /// | .partial_string_escaped_2 | |
| 100 | /// | .partial_string_escaped_3 | |
| 101 | /// | .partial_string_escaped_4 | |
| 102 | /// | |
| 103 | /// nextAlloc*(..., .alloc_always): | |
| 104 | /// <number> = .allocated_number | |
| 105 | /// <string> = .allocated_string | |
| 106 | /// | |
| 107 | /// nextAlloc*(..., .alloc_if_needed): | |
| 108 | /// <number> = | |
| 109 | /// | .number | |
| 110 | /// | .allocated_number | |
| 111 | /// <string> = | |
| 112 | /// | .string | |
| 113 | /// | .allocated_string | |
| 114 | /// ``` | |
| 115 | /// | |
| 116 | /// For all tokens with a `[]const u8`, `[]u8`, or `[n]u8` payload, the payload represents the content of the value. | |
| 117 | /// For number values, this is the representation of the number exactly as it appears in the input. | |
| 118 | /// For strings, this is the content of the string after resolving escape sequences. | |
| 119 | /// | |
| 120 | /// For `.allocated_number` and `.allocated_string`, the `[]u8` payloads are allocations made with the given allocator. | |
| 121 | /// You are responsible for managing that memory. `json.Reader.deinit()` does *not* free those allocations. | |
| 122 | /// | |
| 123 | /// The `.partial_*` tokens indicate that a value spans multiple input buffers or that a string contains escape sequences. | |
| 124 | /// To get a complete value in memory, you need to concatenate the values yourself. | |
| 125 | /// Calling `nextAlloc*()` does this for you, and returns an `.allocated_*` token with the result. | |
| 126 | /// | |
| 127 | /// For tokens with a `[]const u8` payload, the payload is a slice into the current input buffer. | |
| 128 | /// The memory may become undefined during the next call to `json.Scanner.feedInput()` | |
| 129 | /// or any `json.Reader` method whose return error set includes `json.Error`. | |
| 130 | /// To keep the value persistently, it recommended to make a copy or to use `.alloc_always`, | |
| 131 | /// which makes a copy for you. | |
| 132 | /// | |
| 133 | /// Note that `.number` and `.string` tokens that follow `.partial_*` tokens may have `0` length to indicate that | |
| 134 | /// the previously partial value is completed with no additional bytes. | |
| 135 | /// (This can happen when the break between input buffers happens to land on the exact end of a value. E.g. `"[1234"`, `"]"`.) | |
| 136 | /// `.partial_*` tokens never have `0` length. | |
| 137 | /// | |
| 138 | /// The recommended strategy for using the different `next*()` methods is something like this: | |
| 139 | /// | |
| 140 | /// When you're expecting an object key, use `.alloc_if_needed`. | |
| 141 | /// You often don't need a copy of the key string to persist; you might just check which field it is. | |
| 142 | /// In the case that the key happens to require an allocation, free it immediately after checking it. | |
| 143 | /// | |
| 144 | /// When you're expecting a meaningful string value (such as on the right of a `:`), | |
| 145 | /// use `.alloc_always` in order to keep the value valid throughout parsing the rest of the document. | |
| 146 | /// | |
| 147 | /// When you're expecting a number value, use `.alloc_if_needed`. | |
| 148 | /// You're probably going to be parsing the string representation of the number into a numeric representation, | |
| 149 | /// so you need the complete string representation only temporarily. | |
| 150 | /// | |
| 151 | /// When you're skipping an unrecognized value, use `skipValue()`. | |
| 152 | pub const Token = union(enum) { | |
| 153 | object_begin, | |
| 154 | object_end, | |
| 155 | array_begin, | |
| 156 | array_end, | |
| 157 | ||
| 158 | true, | |
| 159 | false, | |
| 160 | null, | |
| 161 | ||
| 162 | number: []const u8, | |
| 163 | partial_number: []const u8, | |
| 164 | allocated_number: []u8, | |
| 165 | ||
| 166 | string: []const u8, | |
| 167 | partial_string: []const u8, | |
| 168 | partial_string_escaped_1: [1]u8, | |
| 169 | partial_string_escaped_2: [2]u8, | |
| 170 | partial_string_escaped_3: [3]u8, | |
| 171 | partial_string_escaped_4: [4]u8, | |
| 172 | allocated_string: []u8, | |
| 173 | ||
| 174 | end_of_document, | |
| 175 | }; | |
| 176 | ||
| 177 | /// This is only used in `peekNextTokenType()` and gives a categorization based on the first byte of the next token that will be emitted from a `next*()` call. | |
| 178 | pub const TokenType = enum { | |
| 179 | object_begin, | |
| 180 | object_end, | |
| 181 | array_begin, | |
| 182 | array_end, | |
| 183 | true, | |
| 184 | false, | |
| 185 | null, | |
| 186 | number, | |
| 187 | string, | |
| 188 | end_of_document, | |
| 189 | }; | |
| 190 | ||
| 191 | /// To enable diagnostics, declare `var diagnostics = Diagnostics{};` then call `source.enableDiagnostics(&diagnostics);` | |
| 192 | /// where `source` is either a `std.json.Reader` or a `std.json.Scanner` that has just been initialized. | |
| 193 | /// At any time, notably just after an error, call `getLine()`, `getColumn()`, and/or `getByteOffset()` | |
| 194 | /// to get meaningful information from this. | |
| 195 | pub const Diagnostics = struct { | |
| 196 | line_number: u64 = 1, | |
| 197 | line_start_cursor: usize = @as(usize, @bitCast(@as(isize, -1))), // Start just "before" the input buffer to get a 1-based column for line 1. | |
| 198 | total_bytes_before_current_input: u64 = 0, | |
| 199 | cursor_pointer: *const usize = undefined, | |
| 200 | ||
| 201 | /// Starts at 1. | |
| 202 | pub fn getLine(self: *const @This()) u64 { | |
| 203 | return self.line_number; | |
| 204 | } | |
| 205 | /// Starts at 1. | |
| 206 | pub fn getColumn(self: *const @This()) u64 { | |
| 207 | return self.cursor_pointer.* -% self.line_start_cursor; | |
| 208 | } | |
| 209 | /// Starts at 0. Measures the byte offset since the start of the input. | |
| 210 | pub fn getByteOffset(self: *const @This()) u64 { | |
| 211 | return self.total_bytes_before_current_input + self.cursor_pointer.*; | |
| 212 | } | |
| 213 | }; | |
| 214 | ||
| 215 | /// See the documentation for `std.json.Token`. | |
| 216 | pub const AllocWhen = enum { alloc_if_needed, alloc_always }; | |
| 217 | ||
| 218 | /// For security, the maximum size allocated to store a single string or number value is limited to 4MiB by default. | |
| 219 | /// This limit can be specified by calling `nextAllocMax()` instead of `nextAlloc()`. | |
| 220 | pub const default_max_value_len = 4 * 1024 * 1024; | |
| 221 | ||
| 222 | /// Connects a `std.io.GenericReader` to a `std.json.Scanner`. | |
| 223 | /// All `next*()` methods here handle `error.BufferUnderrun` from `std.json.Scanner`, and then read from the reader. | |
| 224 | pub fn Reader(comptime buffer_size: usize, comptime ReaderType: type) type { | |
| 225 | return struct { | |
| 226 | scanner: Scanner, | |
| 227 | reader: ReaderType, | |
| 228 | ||
| 229 | buffer: [buffer_size]u8 = undefined, | |
| 230 | ||
| 231 | /// The allocator is only used to track `[]` and `{}` nesting levels. | |
| 232 | pub fn init(allocator: Allocator, io_reader: ReaderType) @This() { | |
| 233 | return .{ | |
| 234 | .scanner = Scanner.initStreaming(allocator), | |
| 235 | .reader = io_reader, | |
| 236 | }; | |
| 237 | } | |
| 238 | pub fn deinit(self: *@This()) void { | |
| 239 | self.scanner.deinit(); | |
| 240 | self.* = undefined; | |
| 241 | } | |
| 242 | ||
| 243 | /// Calls `std.json.Scanner.enableDiagnostics`. | |
| 244 | pub fn enableDiagnostics(self: *@This(), diagnostics: *Diagnostics) void { | |
| 245 | self.scanner.enableDiagnostics(diagnostics); | |
| 246 | } | |
| 247 | ||
| 248 | pub const NextError = ReaderType.Error || Error || Allocator.Error; | |
| 249 | pub const SkipError = NextError; | |
| 250 | pub const AllocError = NextError || error{ValueTooLong}; | |
| 251 | pub const PeekError = ReaderType.Error || Error; | |
| 252 | ||
| 253 | /// Equivalent to `nextAllocMax(allocator, when, default_max_value_len);` | |
| 254 | /// See also `std.json.Token` for documentation of `nextAlloc*()` function behavior. | |
| 255 | pub fn nextAlloc(self: *@This(), allocator: Allocator, when: AllocWhen) AllocError!Token { | |
| 256 | return self.nextAllocMax(allocator, when, default_max_value_len); | |
| 257 | } | |
| 258 | /// See also `std.json.Token` for documentation of `nextAlloc*()` function behavior. | |
| 259 | pub fn nextAllocMax(self: *@This(), allocator: Allocator, when: AllocWhen, max_value_len: usize) AllocError!Token { | |
| 260 | const token_type = try self.peekNextTokenType(); | |
| 261 | switch (token_type) { | |
| 262 | .number, .string => { | |
| 263 | var value_list = ArrayList(u8).init(allocator); | |
| 264 | errdefer { | |
| 265 | value_list.deinit(); | |
| 266 | } | |
| 267 | if (try self.allocNextIntoArrayListMax(&value_list, when, max_value_len)) |slice| { | |
| 268 | return if (token_type == .number) | |
| 269 | Token{ .number = slice } | |
| 270 | else | |
| 271 | Token{ .string = slice }; | |
| 272 | } else { | |
| 273 | return if (token_type == .number) | |
| 274 | Token{ .allocated_number = try value_list.toOwnedSlice() } | |
| 275 | else | |
| 276 | Token{ .allocated_string = try value_list.toOwnedSlice() }; | |
| 277 | } | |
| 278 | }, | |
| 279 | ||
| 280 | // Simple tokens never alloc. | |
| 281 | .object_begin, | |
| 282 | .object_end, | |
| 283 | .array_begin, | |
| 284 | .array_end, | |
| 285 | .true, | |
| 286 | .false, | |
| 287 | .null, | |
| 288 | .end_of_document, | |
| 289 | => return try self.next(), | |
| 290 | } | |
| 291 | } | |
| 292 | ||
| 293 | /// Equivalent to `allocNextIntoArrayListMax(value_list, when, default_max_value_len);` | |
| 294 | pub fn allocNextIntoArrayList(self: *@This(), value_list: *ArrayList(u8), when: AllocWhen) AllocError!?[]const u8 { | |
| 295 | return self.allocNextIntoArrayListMax(value_list, when, default_max_value_len); | |
| 296 | } | |
| 297 | /// Calls `std.json.Scanner.allocNextIntoArrayListMax` and handles `error.BufferUnderrun`. | |
| 298 | pub fn allocNextIntoArrayListMax(self: *@This(), value_list: *ArrayList(u8), when: AllocWhen, max_value_len: usize) AllocError!?[]const u8 { | |
| 299 | while (true) { | |
| 300 | return self.scanner.allocNextIntoArrayListMax(value_list, when, max_value_len) catch |err| switch (err) { | |
| 301 | error.BufferUnderrun => { | |
| 302 | try self.refillBuffer(); | |
| 303 | continue; | |
| 304 | }, | |
| 305 | else => |other_err| return other_err, | |
| 306 | }; | |
| 307 | } | |
| 308 | } | |
| 309 | ||
| 310 | /// Like `std.json.Scanner.skipValue`, but handles `error.BufferUnderrun`. | |
| 311 | pub fn skipValue(self: *@This()) SkipError!void { | |
| 312 | switch (try self.peekNextTokenType()) { | |
| 313 | .object_begin, .array_begin => { | |
| 314 | try self.skipUntilStackHeight(self.stackHeight()); | |
| 315 | }, | |
| 316 | .number, .string => { | |
| 317 | while (true) { | |
| 318 | switch (try self.next()) { | |
| 319 | .partial_number, | |
| 320 | .partial_string, | |
| 321 | .partial_string_escaped_1, | |
| 322 | .partial_string_escaped_2, | |
| 323 | .partial_string_escaped_3, | |
| 324 | .partial_string_escaped_4, | |
| 325 | => continue, | |
| 326 | ||
| 327 | .number, .string => break, | |
| 328 | ||
| 329 | else => unreachable, | |
| 330 | } | |
| 331 | } | |
| 332 | }, | |
| 333 | .true, .false, .null => { | |
| 334 | _ = try self.next(); | |
| 335 | }, | |
| 336 | ||
| 337 | .object_end, .array_end, .end_of_document => unreachable, // Attempt to skip a non-value token. | |
| 338 | } | |
| 339 | } | |
| 340 | /// Like `std.json.Scanner.skipUntilStackHeight()` but handles `error.BufferUnderrun`. | |
| 341 | pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: usize) NextError!void { | |
| 342 | while (true) { | |
| 343 | return self.scanner.skipUntilStackHeight(terminal_stack_height) catch |err| switch (err) { | |
| 344 | error.BufferUnderrun => { | |
| 345 | try self.refillBuffer(); | |
| 346 | continue; | |
| 347 | }, | |
| 348 | else => |other_err| return other_err, | |
| 349 | }; | |
| 350 | } | |
| 351 | } | |
| 352 | ||
| 353 | /// Calls `std.json.Scanner.stackHeight`. | |
| 354 | pub fn stackHeight(self: *const @This()) usize { | |
| 355 | return self.scanner.stackHeight(); | |
| 356 | } | |
| 357 | /// Calls `std.json.Scanner.ensureTotalStackCapacity`. | |
| 358 | pub fn ensureTotalStackCapacity(self: *@This(), height: usize) Allocator.Error!void { | |
| 359 | try self.scanner.ensureTotalStackCapacity(height); | |
| 360 | } | |
| 361 | ||
| 362 | /// See `std.json.Token` for documentation of this function. | |
| 363 | pub fn next(self: *@This()) NextError!Token { | |
| 364 | while (true) { | |
| 365 | return self.scanner.next() catch |err| switch (err) { | |
| 366 | error.BufferUnderrun => { | |
| 367 | try self.refillBuffer(); | |
| 368 | continue; | |
| 369 | }, | |
| 370 | else => |other_err| return other_err, | |
| 371 | }; | |
| 372 | } | |
| 373 | } | |
| 374 | ||
| 375 | /// See `std.json.Scanner.peekNextTokenType()`. | |
| 376 | pub fn peekNextTokenType(self: *@This()) PeekError!TokenType { | |
| 377 | while (true) { | |
| 378 | return self.scanner.peekNextTokenType() catch |err| switch (err) { | |
| 379 | error.BufferUnderrun => { | |
| 380 | try self.refillBuffer(); | |
| 381 | continue; | |
| 382 | }, | |
| 383 | else => |other_err| return other_err, | |
| 384 | }; | |
| 385 | } | |
| 386 | } | |
| 387 | ||
| 388 | fn refillBuffer(self: *@This()) ReaderType.Error!void { | |
| 389 | const input = self.buffer[0..try self.reader.read(self.buffer[0..])]; | |
| 390 | if (input.len > 0) { | |
| 391 | self.scanner.feedInput(input); | |
| 392 | } else { | |
| 393 | self.scanner.endInput(); | |
| 394 | } | |
| 395 | } | |
| 396 | }; | |
| 397 | } | |
| 398 | ||
| 399 | /// The lowest level parsing API in this package; | |
| 400 | /// supports streaming input with a low memory footprint. | |
| 401 | /// The memory requirement is `O(d)` where d is the nesting depth of `[]` or `{}` containers in the input. | |
| 402 | /// Specifically `d/8` bytes are required for this purpose, | |
| 403 | /// with some extra buffer according to the implementation of `std.ArrayList`. | |
| 404 | /// | |
| 405 | /// This scanner can emit partial tokens; see `std.json.Token`. | |
| 406 | /// The input to this class is a sequence of input buffers that you must supply one at a time. | |
| 407 | /// Call `feedInput()` with the first buffer, then call `next()` repeatedly until `error.BufferUnderrun` is returned. | |
| 408 | /// Then call `feedInput()` again and so forth. | |
| 409 | /// Call `endInput()` when the last input buffer has been given to `feedInput()`, either immediately after calling `feedInput()`, | |
| 410 | /// or when `error.BufferUnderrun` requests more data and there is no more. | |
| 411 | /// Be sure to call `next()` after calling `endInput()` until `Token.end_of_document` has been returned. | |
| 412 | pub const Scanner = struct { | |
| 413 | state: State = .value, | |
| 414 | string_is_object_key: bool = false, | |
| 415 | stack: BitStack, | |
| 416 | value_start: usize = undefined, | |
| 417 | utf16_code_units: [2]u16 = undefined, | |
| 418 | ||
| 419 | input: []const u8 = "", | |
| 420 | cursor: usize = 0, | |
| 421 | is_end_of_input: bool = false, | |
| 422 | diagnostics: ?*Diagnostics = null, | |
| 423 | ||
| 424 | /// The allocator is only used to track `[]` and `{}` nesting levels. | |
| 425 | pub fn initStreaming(allocator: Allocator) @This() { | |
| 426 | return .{ | |
| 427 | .stack = BitStack.init(allocator), | |
| 428 | }; | |
| 429 | } | |
| 430 | /// Use this if your input is a single slice. | |
| 431 | /// This is effectively equivalent to: | |
| 432 | /// ``` | |
| 433 | /// initStreaming(allocator); | |
| 434 | /// feedInput(complete_input); | |
| 435 | /// endInput(); | |
| 436 | /// ``` | |
| 437 | pub fn initCompleteInput(allocator: Allocator, complete_input: []const u8) @This() { | |
| 438 | return .{ | |
| 439 | .stack = BitStack.init(allocator), | |
| 440 | .input = complete_input, | |
| 441 | .is_end_of_input = true, | |
| 442 | }; | |
| 443 | } | |
| 444 | pub fn deinit(self: *@This()) void { | |
| 445 | self.stack.deinit(); | |
| 446 | self.* = undefined; | |
| 447 | } | |
| 448 | ||
| 449 | pub fn enableDiagnostics(self: *@This(), diagnostics: *Diagnostics) void { | |
| 450 | diagnostics.cursor_pointer = &self.cursor; | |
| 451 | self.diagnostics = diagnostics; | |
| 452 | } | |
| 453 | ||
| 454 | /// Call this whenever you get `error.BufferUnderrun` from `next()`. | |
| 455 | /// When there is no more input to provide, call `endInput()`. | |
| 456 | pub fn feedInput(self: *@This(), input: []const u8) void { | |
| 457 | assert(self.cursor == self.input.len); // Not done with the last input slice. | |
| 458 | if (self.diagnostics) |diag| { | |
| 459 | diag.total_bytes_before_current_input += self.input.len; | |
| 460 | // This usually goes "negative" to measure how far before the beginning | |
| 461 | // of the new buffer the current line started. | |
| 462 | diag.line_start_cursor -%= self.cursor; | |
| 463 | } | |
| 464 | self.input = input; | |
| 465 | self.cursor = 0; | |
| 466 | self.value_start = 0; | |
| 467 | } | |
| 468 | /// Call this when you will no longer call `feedInput()` anymore. | |
| 469 | /// This can be called either immediately after the last `feedInput()`, | |
| 470 | /// or at any time afterward, such as when getting `error.BufferUnderrun` from `next()`. | |
| 471 | /// Don't forget to call `next*()` after `endInput()` until you get `.end_of_document`. | |
| 472 | pub fn endInput(self: *@This()) void { | |
| 473 | self.is_end_of_input = true; | |
| 474 | } | |
| 475 | ||
| 476 | pub const NextError = Error || Allocator.Error || error{BufferUnderrun}; | |
| 477 | pub const AllocError = Error || Allocator.Error || error{ValueTooLong}; | |
| 478 | pub const PeekError = Error || error{BufferUnderrun}; | |
| 479 | pub const SkipError = Error || Allocator.Error; | |
| 480 | pub const AllocIntoArrayListError = AllocError || error{BufferUnderrun}; | |
| 481 | ||
| 482 | /// Equivalent to `nextAllocMax(allocator, when, default_max_value_len);` | |
| 483 | /// This function is only available after `endInput()` (or `initCompleteInput()`) has been called. | |
| 484 | /// See also `std.json.Token` for documentation of `nextAlloc*()` function behavior. | |
| 485 | pub fn nextAlloc(self: *@This(), allocator: Allocator, when: AllocWhen) AllocError!Token { | |
| 486 | return self.nextAllocMax(allocator, when, default_max_value_len); | |
| 487 | } | |
| 488 | ||
| 489 | /// This function is only available after `endInput()` (or `initCompleteInput()`) has been called. | |
| 490 | /// See also `std.json.Token` for documentation of `nextAlloc*()` function behavior. | |
| 491 | pub fn nextAllocMax(self: *@This(), allocator: Allocator, when: AllocWhen, max_value_len: usize) AllocError!Token { | |
| 492 | assert(self.is_end_of_input); // This function is not available in streaming mode. | |
| 493 | const token_type = self.peekNextTokenType() catch |e| switch (e) { | |
| 494 | error.BufferUnderrun => unreachable, | |
| 495 | else => |err| return err, | |
| 496 | }; | |
| 497 | switch (token_type) { | |
| 498 | .number, .string => { | |
| 499 | var value_list = ArrayList(u8).init(allocator); | |
| 500 | errdefer { | |
| 501 | value_list.deinit(); | |
| 502 | } | |
| 503 | if (self.allocNextIntoArrayListMax(&value_list, when, max_value_len) catch |e| switch (e) { | |
| 504 | error.BufferUnderrun => unreachable, | |
| 505 | else => |err| return err, | |
| 506 | }) |slice| { | |
| 507 | return if (token_type == .number) | |
| 508 | Token{ .number = slice } | |
| 509 | else | |
| 510 | Token{ .string = slice }; | |
| 511 | } else { | |
| 512 | return if (token_type == .number) | |
| 513 | Token{ .allocated_number = try value_list.toOwnedSlice() } | |
| 514 | else | |
| 515 | Token{ .allocated_string = try value_list.toOwnedSlice() }; | |
| 516 | } | |
| 517 | }, | |
| 518 | ||
| 519 | // Simple tokens never alloc. | |
| 520 | .object_begin, | |
| 521 | .object_end, | |
| 522 | .array_begin, | |
| 523 | .array_end, | |
| 524 | .true, | |
| 525 | .false, | |
| 526 | .null, | |
| 527 | .end_of_document, | |
| 528 | => return self.next() catch |e| switch (e) { | |
| 529 | error.BufferUnderrun => unreachable, | |
| 530 | else => |err| return err, | |
| 531 | }, | |
| 532 | } | |
| 533 | } | |
| 534 | ||
| 535 | /// Equivalent to `allocNextIntoArrayListMax(value_list, when, default_max_value_len);` | |
| 536 | pub fn allocNextIntoArrayList(self: *@This(), value_list: *ArrayList(u8), when: AllocWhen) AllocIntoArrayListError!?[]const u8 { | |
| 537 | return self.allocNextIntoArrayListMax(value_list, when, default_max_value_len); | |
| 538 | } | |
| 539 | /// The next token type must be either `.number` or `.string`. See `peekNextTokenType()`. | |
| 540 | /// When allocation is not necessary with `.alloc_if_needed`, | |
| 541 | /// this method returns the content slice from the input buffer, and `value_list` is not touched. | |
| 542 | /// When allocation is necessary or with `.alloc_always`, this method concatenates partial tokens into the given `value_list`, | |
| 543 | /// and returns `null` once the final `.number` or `.string` token has been written into it. | |
| 544 | /// In case of an `error.BufferUnderrun`, partial values will be left in the given value_list. | |
| 545 | /// The given `value_list` is never reset by this method, so an `error.BufferUnderrun` situation | |
| 546 | /// can be resumed by passing the same array list in again. | |
| 547 | /// This method does not indicate whether the token content being returned is for a `.number` or `.string` token type; | |
| 548 | /// the caller of this method is expected to know which type of token is being processed. | |
| 549 | pub fn allocNextIntoArrayListMax(self: *@This(), value_list: *ArrayList(u8), when: AllocWhen, max_value_len: usize) AllocIntoArrayListError!?[]const u8 { | |
| 550 | while (true) { | |
| 551 | const token = try self.next(); | |
| 552 | switch (token) { | |
| 553 | // Accumulate partial values. | |
| 554 | .partial_number, .partial_string => |slice| { | |
| 555 | try appendSlice(value_list, slice, max_value_len); | |
| 556 | }, | |
| 557 | .partial_string_escaped_1 => |buf| { | |
| 558 | try appendSlice(value_list, buf[0..], max_value_len); | |
| 559 | }, | |
| 560 | .partial_string_escaped_2 => |buf| { | |
| 561 | try appendSlice(value_list, buf[0..], max_value_len); | |
| 562 | }, | |
| 563 | .partial_string_escaped_3 => |buf| { | |
| 564 | try appendSlice(value_list, buf[0..], max_value_len); | |
| 565 | }, | |
| 566 | .partial_string_escaped_4 => |buf| { | |
| 567 | try appendSlice(value_list, buf[0..], max_value_len); | |
| 568 | }, | |
| 569 | ||
| 570 | // Return complete values. | |
| 571 | .number => |slice| { | |
| 572 | if (when == .alloc_if_needed and value_list.items.len == 0) { | |
| 573 | // No alloc necessary. | |
| 574 | return slice; | |
| 575 | } | |
| 576 | try appendSlice(value_list, slice, max_value_len); | |
| 577 | // The token is complete. | |
| 578 | return null; | |
| 579 | }, | |
| 580 | .string => |slice| { | |
| 581 | if (when == .alloc_if_needed and value_list.items.len == 0) { | |
| 582 | // No alloc necessary. | |
| 583 | return slice; | |
| 584 | } | |
| 585 | try appendSlice(value_list, slice, max_value_len); | |
| 586 | // The token is complete. | |
| 587 | return null; | |
| 588 | }, | |
| 589 | ||
| 590 | .object_begin, | |
| 591 | .object_end, | |
| 592 | .array_begin, | |
| 593 | .array_end, | |
| 594 | .true, | |
| 595 | .false, | |
| 596 | .null, | |
| 597 | .end_of_document, | |
| 598 | => unreachable, // Only .number and .string token types are allowed here. Check peekNextTokenType() before calling this. | |
| 599 | ||
| 600 | .allocated_number, .allocated_string => unreachable, | |
| 601 | } | |
| 602 | } | |
| 603 | } | |
| 604 | ||
| 605 | /// This function is only available after `endInput()` (or `initCompleteInput()`) has been called. | |
| 606 | /// If the next token type is `.object_begin` or `.array_begin`, | |
| 607 | /// this function calls `next()` repeatedly until the corresponding `.object_end` or `.array_end` is found. | |
| 608 | /// If the next token type is `.number` or `.string`, | |
| 609 | /// this function calls `next()` repeatedly until the (non `.partial_*`) `.number` or `.string` token is found. | |
| 610 | /// If the next token type is `.true`, `.false`, or `.null`, this function calls `next()` once. | |
| 611 | /// The next token type must not be `.object_end`, `.array_end`, or `.end_of_document`; | |
| 612 | /// see `peekNextTokenType()`. | |
| 613 | pub fn skipValue(self: *@This()) SkipError!void { | |
| 614 | assert(self.is_end_of_input); // This function is not available in streaming mode. | |
| 615 | switch (self.peekNextTokenType() catch |e| switch (e) { | |
| 616 | error.BufferUnderrun => unreachable, | |
| 617 | else => |err| return err, | |
| 618 | }) { | |
| 619 | .object_begin, .array_begin => { | |
| 620 | self.skipUntilStackHeight(self.stackHeight()) catch |e| switch (e) { | |
| 621 | error.BufferUnderrun => unreachable, | |
| 622 | else => |err| return err, | |
| 623 | }; | |
| 624 | }, | |
| 625 | .number, .string => { | |
| 626 | while (true) { | |
| 627 | switch (self.next() catch |e| switch (e) { | |
| 628 | error.BufferUnderrun => unreachable, | |
| 629 | else => |err| return err, | |
| 630 | }) { | |
| 631 | .partial_number, | |
| 632 | .partial_string, | |
| 633 | .partial_string_escaped_1, | |
| 634 | .partial_string_escaped_2, | |
| 635 | .partial_string_escaped_3, | |
| 636 | .partial_string_escaped_4, | |
| 637 | => continue, | |
| 638 | ||
| 639 | .number, .string => break, | |
| 640 | ||
| 641 | else => unreachable, | |
| 642 | } | |
| 643 | } | |
| 644 | }, | |
| 645 | .true, .false, .null => { | |
| 646 | _ = self.next() catch |e| switch (e) { | |
| 647 | error.BufferUnderrun => unreachable, | |
| 648 | else => |err| return err, | |
| 649 | }; | |
| 650 | }, | |
| 651 | ||
| 652 | .object_end, .array_end, .end_of_document => unreachable, // Attempt to skip a non-value token. | |
| 653 | } | |
| 654 | } | |
| 655 | ||
| 656 | /// Skip tokens until an `.object_end` or `.array_end` token results in a `stackHeight()` equal the given stack height. | |
| 657 | /// Unlike `skipValue()`, this function is available in streaming mode. | |
| 658 | pub fn skipUntilStackHeight(self: *@This(), terminal_stack_height: usize) NextError!void { | |
| 659 | while (true) { | |
| 660 | switch (try self.next()) { | |
| 661 | .object_end, .array_end => { | |
| 662 | if (self.stackHeight() == terminal_stack_height) break; | |
| 663 | }, | |
| 664 | .end_of_document => unreachable, | |
| 665 | else => continue, | |
| 666 | } | |
| 667 | } | |
| 668 | } | |
| 669 | ||
| 670 | /// The depth of `{}` or `[]` nesting levels at the current position. | |
| 671 | pub fn stackHeight(self: *const @This()) usize { | |
| 672 | return self.stack.bit_len; | |
| 673 | } | |
| 674 | ||
| 675 | /// Pre allocate memory to hold the given number of nesting levels. | |
| 676 | /// `stackHeight()` up to the given number will not cause allocations. | |
| 677 | pub fn ensureTotalStackCapacity(self: *@This(), height: usize) Allocator.Error!void { | |
| 678 | try self.stack.ensureTotalCapacity(height); | |
| 679 | } | |
| 680 | ||
| 681 | /// See `std.json.Token` for documentation of this function. | |
| 682 | pub fn next(self: *@This()) NextError!Token { | |
| 683 | state_loop: while (true) { | |
| 684 | switch (self.state) { | |
| 685 | .value => { | |
| 686 | switch (try self.skipWhitespaceExpectByte()) { | |
| 687 | // Object, Array | |
| 688 | '{' => { | |
| 689 | try self.stack.push(OBJECT_MODE); | |
| 690 | self.cursor += 1; | |
| 691 | self.state = .object_start; | |
| 692 | return .object_begin; | |
| 693 | }, | |
| 694 | '[' => { | |
| 695 | try self.stack.push(ARRAY_MODE); | |
| 696 | self.cursor += 1; | |
| 697 | self.state = .array_start; | |
| 698 | return .array_begin; | |
| 699 | }, | |
| 700 | ||
| 701 | // String | |
| 702 | '"' => { | |
| 703 | self.cursor += 1; | |
| 704 | self.value_start = self.cursor; | |
| 705 | self.state = .string; | |
| 706 | continue :state_loop; | |
| 707 | }, | |
| 708 | ||
| 709 | // Number | |
| 710 | '1'...'9' => { | |
| 711 | self.value_start = self.cursor; | |
| 712 | self.cursor += 1; | |
| 713 | self.state = .number_int; | |
| 714 | continue :state_loop; | |
| 715 | }, | |
| 716 | '0' => { | |
| 717 | self.value_start = self.cursor; | |
| 718 | self.cursor += 1; | |
| 719 | self.state = .number_leading_zero; | |
| 720 | continue :state_loop; | |
| 721 | }, | |
| 722 | '-' => { | |
| 723 | self.value_start = self.cursor; | |
| 724 | self.cursor += 1; | |
| 725 | self.state = .number_minus; | |
| 726 | continue :state_loop; | |
| 727 | }, | |
| 728 | ||
| 729 | // literal values | |
| 730 | 't' => { | |
| 731 | self.cursor += 1; | |
| 732 | self.state = .literal_t; | |
| 733 | continue :state_loop; | |
| 734 | }, | |
| 735 | 'f' => { | |
| 736 | self.cursor += 1; | |
| 737 | self.state = .literal_f; | |
| 738 | continue :state_loop; | |
| 739 | }, | |
| 740 | 'n' => { | |
| 741 | self.cursor += 1; | |
| 742 | self.state = .literal_n; | |
| 743 | continue :state_loop; | |
| 744 | }, | |
| 745 | ||
| 746 | else => return error.SyntaxError, | |
| 747 | } | |
| 748 | }, | |
| 749 | ||
| 750 | .post_value => { | |
| 751 | if (try self.skipWhitespaceCheckEnd()) return .end_of_document; | |
| 752 | ||
| 753 | const c = self.input[self.cursor]; | |
| 754 | if (self.string_is_object_key) { | |
| 755 | self.string_is_object_key = false; | |
| 756 | switch (c) { | |
| 757 | ':' => { | |
| 758 | self.cursor += 1; | |
| 759 | self.state = .value; | |
| 760 | continue :state_loop; | |
| 761 | }, | |
| 762 | else => return error.SyntaxError, | |
| 763 | } | |
| 764 | } | |
| 765 | ||
| 766 | switch (c) { | |
| 767 | '}' => { | |
| 768 | if (self.stack.pop() != OBJECT_MODE) return error.SyntaxError; | |
| 769 | self.cursor += 1; | |
| 770 | // stay in .post_value state. | |
| 771 | return .object_end; | |
| 772 | }, | |
| 773 | ']' => { | |
| 774 | if (self.stack.pop() != ARRAY_MODE) return error.SyntaxError; | |
| 775 | self.cursor += 1; | |
| 776 | // stay in .post_value state. | |
| 777 | return .array_end; | |
| 778 | }, | |
| 779 | ',' => { | |
| 780 | switch (self.stack.peek()) { | |
| 781 | OBJECT_MODE => { | |
| 782 | self.state = .object_post_comma; | |
| 783 | }, | |
| 784 | ARRAY_MODE => { | |
| 785 | self.state = .value; | |
| 786 | }, | |
| 787 | } | |
| 788 | self.cursor += 1; | |
| 789 | continue :state_loop; | |
| 790 | }, | |
| 791 | else => return error.SyntaxError, | |
| 792 | } | |
| 793 | }, | |
| 794 | ||
| 795 | .object_start => { | |
| 796 | switch (try self.skipWhitespaceExpectByte()) { | |
| 797 | '"' => { | |
| 798 | self.cursor += 1; | |
| 799 | self.value_start = self.cursor; | |
| 800 | self.state = .string; | |
| 801 | self.string_is_object_key = true; | |
| 802 | continue :state_loop; | |
| 803 | }, | |
| 804 | '}' => { | |
| 805 | self.cursor += 1; | |
| 806 | _ = self.stack.pop(); | |
| 807 | self.state = .post_value; | |
| 808 | return .object_end; | |
| 809 | }, | |
| 810 | else => return error.SyntaxError, | |
| 811 | } | |
| 812 | }, | |
| 813 | .object_post_comma => { | |
| 814 | switch (try self.skipWhitespaceExpectByte()) { | |
| 815 | '"' => { | |
| 816 | self.cursor += 1; | |
| 817 | self.value_start = self.cursor; | |
| 818 | self.state = .string; | |
| 819 | self.string_is_object_key = true; | |
| 820 | continue :state_loop; | |
| 821 | }, | |
| 822 | else => return error.SyntaxError, | |
| 823 | } | |
| 824 | }, | |
| 825 | ||
| 826 | .array_start => { | |
| 827 | switch (try self.skipWhitespaceExpectByte()) { | |
| 828 | ']' => { | |
| 829 | self.cursor += 1; | |
| 830 | _ = self.stack.pop(); | |
| 831 | self.state = .post_value; | |
| 832 | return .array_end; | |
| 833 | }, | |
| 834 | else => { | |
| 835 | self.state = .value; | |
| 836 | continue :state_loop; | |
| 837 | }, | |
| 838 | } | |
| 839 | }, | |
| 840 | ||
| 841 | .number_minus => { | |
| 842 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(false); | |
| 843 | switch (self.input[self.cursor]) { | |
| 844 | '0' => { | |
| 845 | self.cursor += 1; | |
| 846 | self.state = .number_leading_zero; | |
| 847 | continue :state_loop; | |
| 848 | }, | |
| 849 | '1'...'9' => { | |
| 850 | self.cursor += 1; | |
| 851 | self.state = .number_int; | |
| 852 | continue :state_loop; | |
| 853 | }, | |
| 854 | else => return error.SyntaxError, | |
| 855 | } | |
| 856 | }, | |
| 857 | .number_leading_zero => { | |
| 858 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(true); | |
| 859 | switch (self.input[self.cursor]) { | |
| 860 | '.' => { | |
| 861 | self.cursor += 1; | |
| 862 | self.state = .number_post_dot; | |
| 863 | continue :state_loop; | |
| 864 | }, | |
| 865 | 'e', 'E' => { | |
| 866 | self.cursor += 1; | |
| 867 | self.state = .number_post_e; | |
| 868 | continue :state_loop; | |
| 869 | }, | |
| 870 | else => { | |
| 871 | self.state = .post_value; | |
| 872 | return Token{ .number = self.takeValueSlice() }; | |
| 873 | }, | |
| 874 | } | |
| 875 | }, | |
| 876 | .number_int => { | |
| 877 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 878 | switch (self.input[self.cursor]) { | |
| 879 | '0'...'9' => continue, | |
| 880 | '.' => { | |
| 881 | self.cursor += 1; | |
| 882 | self.state = .number_post_dot; | |
| 883 | continue :state_loop; | |
| 884 | }, | |
| 885 | 'e', 'E' => { | |
| 886 | self.cursor += 1; | |
| 887 | self.state = .number_post_e; | |
| 888 | continue :state_loop; | |
| 889 | }, | |
| 890 | else => { | |
| 891 | self.state = .post_value; | |
| 892 | return Token{ .number = self.takeValueSlice() }; | |
| 893 | }, | |
| 894 | } | |
| 895 | } | |
| 896 | return self.endOfBufferInNumber(true); | |
| 897 | }, | |
| 898 | .number_post_dot => { | |
| 899 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(false); | |
| 900 | switch (self.input[self.cursor]) { | |
| 901 | '0'...'9' => { | |
| 902 | self.cursor += 1; | |
| 903 | self.state = .number_frac; | |
| 904 | continue :state_loop; | |
| 905 | }, | |
| 906 | else => return error.SyntaxError, | |
| 907 | } | |
| 908 | }, | |
| 909 | .number_frac => { | |
| 910 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 911 | switch (self.input[self.cursor]) { | |
| 912 | '0'...'9' => continue, | |
| 913 | 'e', 'E' => { | |
| 914 | self.cursor += 1; | |
| 915 | self.state = .number_post_e; | |
| 916 | continue :state_loop; | |
| 917 | }, | |
| 918 | else => { | |
| 919 | self.state = .post_value; | |
| 920 | return Token{ .number = self.takeValueSlice() }; | |
| 921 | }, | |
| 922 | } | |
| 923 | } | |
| 924 | return self.endOfBufferInNumber(true); | |
| 925 | }, | |
| 926 | .number_post_e => { | |
| 927 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(false); | |
| 928 | switch (self.input[self.cursor]) { | |
| 929 | '0'...'9' => { | |
| 930 | self.cursor += 1; | |
| 931 | self.state = .number_exp; | |
| 932 | continue :state_loop; | |
| 933 | }, | |
| 934 | '+', '-' => { | |
| 935 | self.cursor += 1; | |
| 936 | self.state = .number_post_e_sign; | |
| 937 | continue :state_loop; | |
| 938 | }, | |
| 939 | else => return error.SyntaxError, | |
| 940 | } | |
| 941 | }, | |
| 942 | .number_post_e_sign => { | |
| 943 | if (self.cursor >= self.input.len) return self.endOfBufferInNumber(false); | |
| 944 | switch (self.input[self.cursor]) { | |
| 945 | '0'...'9' => { | |
| 946 | self.cursor += 1; | |
| 947 | self.state = .number_exp; | |
| 948 | continue :state_loop; | |
| 949 | }, | |
| 950 | else => return error.SyntaxError, | |
| 951 | } | |
| 952 | }, | |
| 953 | .number_exp => { | |
| 954 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 955 | switch (self.input[self.cursor]) { | |
| 956 | '0'...'9' => continue, | |
| 957 | else => { | |
| 958 | self.state = .post_value; | |
| 959 | return Token{ .number = self.takeValueSlice() }; | |
| 960 | }, | |
| 961 | } | |
| 962 | } | |
| 963 | return self.endOfBufferInNumber(true); | |
| 964 | }, | |
| 965 | ||
| 966 | .string => { | |
| 967 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 968 | switch (self.input[self.cursor]) { | |
| 969 | 0...0x1f => return error.SyntaxError, // Bare ASCII control code in string. | |
| 970 | ||
| 971 | // ASCII plain text. | |
| 972 | 0x20...('"' - 1), ('"' + 1)...('\\' - 1), ('\\' + 1)...0x7F => continue, | |
| 973 | ||
| 974 | // Special characters. | |
| 975 | '"' => { | |
| 976 | const result = Token{ .string = self.takeValueSlice() }; | |
| 977 | self.cursor += 1; | |
| 978 | self.state = .post_value; | |
| 979 | return result; | |
| 980 | }, | |
| 981 | '\\' => { | |
| 982 | const slice = self.takeValueSlice(); | |
| 983 | self.cursor += 1; | |
| 984 | self.state = .string_backslash; | |
| 985 | if (slice.len > 0) return Token{ .partial_string = slice }; | |
| 986 | continue :state_loop; | |
| 987 | }, | |
| 988 | ||
| 989 | // UTF-8 validation. | |
| 990 | // See http://unicode.org/mail-arch/unicode-ml/y2003-m02/att-0467/01-The_Algorithm_to_Valide_an_UTF-8_String | |
| 991 | 0xC2...0xDF => { | |
| 992 | self.cursor += 1; | |
| 993 | self.state = .string_utf8_last_byte; | |
| 994 | continue :state_loop; | |
| 995 | }, | |
| 996 | 0xE0 => { | |
| 997 | self.cursor += 1; | |
| 998 | self.state = .string_utf8_second_to_last_byte_guard_against_overlong; | |
| 999 | continue :state_loop; | |
| 1000 | }, | |
| 1001 | 0xE1...0xEC, 0xEE...0xEF => { | |
| 1002 | self.cursor += 1; | |
| 1003 | self.state = .string_utf8_second_to_last_byte; | |
| 1004 | continue :state_loop; | |
| 1005 | }, | |
| 1006 | 0xED => { | |
| 1007 | self.cursor += 1; | |
| 1008 | self.state = .string_utf8_second_to_last_byte_guard_against_surrogate_half; | |
| 1009 | continue :state_loop; | |
| 1010 | }, | |
| 1011 | 0xF0 => { | |
| 1012 | self.cursor += 1; | |
| 1013 | self.state = .string_utf8_third_to_last_byte_guard_against_overlong; | |
| 1014 | continue :state_loop; | |
| 1015 | }, | |
| 1016 | 0xF1...0xF3 => { | |
| 1017 | self.cursor += 1; | |
| 1018 | self.state = .string_utf8_third_to_last_byte; | |
| 1019 | continue :state_loop; | |
| 1020 | }, | |
| 1021 | 0xF4 => { | |
| 1022 | self.cursor += 1; | |
| 1023 | self.state = .string_utf8_third_to_last_byte_guard_against_too_large; | |
| 1024 | continue :state_loop; | |
| 1025 | }, | |
| 1026 | 0x80...0xC1, 0xF5...0xFF => return error.SyntaxError, // Invalid UTF-8. | |
| 1027 | } | |
| 1028 | } | |
| 1029 | if (self.is_end_of_input) return error.UnexpectedEndOfInput; | |
| 1030 | const slice = self.takeValueSlice(); | |
| 1031 | if (slice.len > 0) return Token{ .partial_string = slice }; | |
| 1032 | return error.BufferUnderrun; | |
| 1033 | }, | |
| 1034 | .string_backslash => { | |
| 1035 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1036 | switch (self.input[self.cursor]) { | |
| 1037 | '"', '\\', '/' => { | |
| 1038 | // Since these characters now represent themselves literally, | |
| 1039 | // we can simply begin the next plaintext slice here. | |
| 1040 | self.value_start = self.cursor; | |
| 1041 | self.cursor += 1; | |
| 1042 | self.state = .string; | |
| 1043 | continue :state_loop; | |
| 1044 | }, | |
| 1045 | 'b' => { | |
| 1046 | self.cursor += 1; | |
| 1047 | self.value_start = self.cursor; | |
| 1048 | self.state = .string; | |
| 1049 | return Token{ .partial_string_escaped_1 = [_]u8{0x08} }; | |
| 1050 | }, | |
| 1051 | 'f' => { | |
| 1052 | self.cursor += 1; | |
| 1053 | self.value_start = self.cursor; | |
| 1054 | self.state = .string; | |
| 1055 | return Token{ .partial_string_escaped_1 = [_]u8{0x0c} }; | |
| 1056 | }, | |
| 1057 | 'n' => { | |
| 1058 | self.cursor += 1; | |
| 1059 | self.value_start = self.cursor; | |
| 1060 | self.state = .string; | |
| 1061 | return Token{ .partial_string_escaped_1 = [_]u8{'\n'} }; | |
| 1062 | }, | |
| 1063 | 'r' => { | |
| 1064 | self.cursor += 1; | |
| 1065 | self.value_start = self.cursor; | |
| 1066 | self.state = .string; | |
| 1067 | return Token{ .partial_string_escaped_1 = [_]u8{'\r'} }; | |
| 1068 | }, | |
| 1069 | 't' => { | |
| 1070 | self.cursor += 1; | |
| 1071 | self.value_start = self.cursor; | |
| 1072 | self.state = .string; | |
| 1073 | return Token{ .partial_string_escaped_1 = [_]u8{'\t'} }; | |
| 1074 | }, | |
| 1075 | 'u' => { | |
| 1076 | self.cursor += 1; | |
| 1077 | self.state = .string_backslash_u; | |
| 1078 | continue :state_loop; | |
| 1079 | }, | |
| 1080 | else => return error.SyntaxError, | |
| 1081 | } | |
| 1082 | }, | |
| 1083 | .string_backslash_u => { | |
| 1084 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1085 | const c = self.input[self.cursor]; | |
| 1086 | switch (c) { | |
| 1087 | '0'...'9' => { | |
| 1088 | self.utf16_code_units[0] = @as(u16, c - '0') << 12; | |
| 1089 | }, | |
| 1090 | 'A'...'F' => { | |
| 1091 | self.utf16_code_units[0] = @as(u16, c - 'A' + 10) << 12; | |
| 1092 | }, | |
| 1093 | 'a'...'f' => { | |
| 1094 | self.utf16_code_units[0] = @as(u16, c - 'a' + 10) << 12; | |
| 1095 | }, | |
| 1096 | else => return error.SyntaxError, | |
| 1097 | } | |
| 1098 | self.cursor += 1; | |
| 1099 | self.state = .string_backslash_u_1; | |
| 1100 | continue :state_loop; | |
| 1101 | }, | |
| 1102 | .string_backslash_u_1 => { | |
| 1103 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1104 | const c = self.input[self.cursor]; | |
| 1105 | switch (c) { | |
| 1106 | '0'...'9' => { | |
| 1107 | self.utf16_code_units[0] |= @as(u16, c - '0') << 8; | |
| 1108 | }, | |
| 1109 | 'A'...'F' => { | |
| 1110 | self.utf16_code_units[0] |= @as(u16, c - 'A' + 10) << 8; | |
| 1111 | }, | |
| 1112 | 'a'...'f' => { | |
| 1113 | self.utf16_code_units[0] |= @as(u16, c - 'a' + 10) << 8; | |
| 1114 | }, | |
| 1115 | else => return error.SyntaxError, | |
| 1116 | } | |
| 1117 | self.cursor += 1; | |
| 1118 | self.state = .string_backslash_u_2; | |
| 1119 | continue :state_loop; | |
| 1120 | }, | |
| 1121 | .string_backslash_u_2 => { | |
| 1122 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1123 | const c = self.input[self.cursor]; | |
| 1124 | switch (c) { | |
| 1125 | '0'...'9' => { | |
| 1126 | self.utf16_code_units[0] |= @as(u16, c - '0') << 4; | |
| 1127 | }, | |
| 1128 | 'A'...'F' => { | |
| 1129 | self.utf16_code_units[0] |= @as(u16, c - 'A' + 10) << 4; | |
| 1130 | }, | |
| 1131 | 'a'...'f' => { | |
| 1132 | self.utf16_code_units[0] |= @as(u16, c - 'a' + 10) << 4; | |
| 1133 | }, | |
| 1134 | else => return error.SyntaxError, | |
| 1135 | } | |
| 1136 | self.cursor += 1; | |
| 1137 | self.state = .string_backslash_u_3; | |
| 1138 | continue :state_loop; | |
| 1139 | }, | |
| 1140 | .string_backslash_u_3 => { | |
| 1141 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1142 | const c = self.input[self.cursor]; | |
| 1143 | switch (c) { | |
| 1144 | '0'...'9' => { | |
| 1145 | self.utf16_code_units[0] |= c - '0'; | |
| 1146 | }, | |
| 1147 | 'A'...'F' => { | |
| 1148 | self.utf16_code_units[0] |= c - 'A' + 10; | |
| 1149 | }, | |
| 1150 | 'a'...'f' => { | |
| 1151 | self.utf16_code_units[0] |= c - 'a' + 10; | |
| 1152 | }, | |
| 1153 | else => return error.SyntaxError, | |
| 1154 | } | |
| 1155 | self.cursor += 1; | |
| 1156 | if (std.unicode.utf16IsHighSurrogate(self.utf16_code_units[0])) { | |
| 1157 | self.state = .string_surrogate_half; | |
| 1158 | continue :state_loop; | |
| 1159 | } else if (std.unicode.utf16IsLowSurrogate(self.utf16_code_units[0])) { | |
| 1160 | return error.SyntaxError; // Unexpected low surrogate half. | |
| 1161 | } else { | |
| 1162 | self.value_start = self.cursor; | |
| 1163 | self.state = .string; | |
| 1164 | return partialStringCodepoint(self.utf16_code_units[0]); | |
| 1165 | } | |
| 1166 | }, | |
| 1167 | .string_surrogate_half => { | |
| 1168 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1169 | switch (self.input[self.cursor]) { | |
| 1170 | '\\' => { | |
| 1171 | self.cursor += 1; | |
| 1172 | self.state = .string_surrogate_half_backslash; | |
| 1173 | continue :state_loop; | |
| 1174 | }, | |
| 1175 | else => return error.SyntaxError, // Expected low surrogate half. | |
| 1176 | } | |
| 1177 | }, | |
| 1178 | .string_surrogate_half_backslash => { | |
| 1179 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1180 | switch (self.input[self.cursor]) { | |
| 1181 | 'u' => { | |
| 1182 | self.cursor += 1; | |
| 1183 | self.state = .string_surrogate_half_backslash_u; | |
| 1184 | continue :state_loop; | |
| 1185 | }, | |
| 1186 | else => return error.SyntaxError, // Expected low surrogate half. | |
| 1187 | } | |
| 1188 | }, | |
| 1189 | .string_surrogate_half_backslash_u => { | |
| 1190 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1191 | switch (self.input[self.cursor]) { | |
| 1192 | 'D', 'd' => { | |
| 1193 | self.cursor += 1; | |
| 1194 | self.utf16_code_units[1] = 0xD << 12; | |
| 1195 | self.state = .string_surrogate_half_backslash_u_1; | |
| 1196 | continue :state_loop; | |
| 1197 | }, | |
| 1198 | else => return error.SyntaxError, // Expected low surrogate half. | |
| 1199 | } | |
| 1200 | }, | |
| 1201 | .string_surrogate_half_backslash_u_1 => { | |
| 1202 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1203 | const c = self.input[self.cursor]; | |
| 1204 | switch (c) { | |
| 1205 | 'C'...'F' => { | |
| 1206 | self.cursor += 1; | |
| 1207 | self.utf16_code_units[1] |= @as(u16, c - 'A' + 10) << 8; | |
| 1208 | self.state = .string_surrogate_half_backslash_u_2; | |
| 1209 | continue :state_loop; | |
| 1210 | }, | |
| 1211 | 'c'...'f' => { | |
| 1212 | self.cursor += 1; | |
| 1213 | self.utf16_code_units[1] |= @as(u16, c - 'a' + 10) << 8; | |
| 1214 | self.state = .string_surrogate_half_backslash_u_2; | |
| 1215 | continue :state_loop; | |
| 1216 | }, | |
| 1217 | else => return error.SyntaxError, // Expected low surrogate half. | |
| 1218 | } | |
| 1219 | }, | |
| 1220 | .string_surrogate_half_backslash_u_2 => { | |
| 1221 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1222 | const c = self.input[self.cursor]; | |
| 1223 | switch (c) { | |
| 1224 | '0'...'9' => { | |
| 1225 | self.cursor += 1; | |
| 1226 | self.utf16_code_units[1] |= @as(u16, c - '0') << 4; | |
| 1227 | self.state = .string_surrogate_half_backslash_u_3; | |
| 1228 | continue :state_loop; | |
| 1229 | }, | |
| 1230 | 'A'...'F' => { | |
| 1231 | self.cursor += 1; | |
| 1232 | self.utf16_code_units[1] |= @as(u16, c - 'A' + 10) << 4; | |
| 1233 | self.state = .string_surrogate_half_backslash_u_3; | |
| 1234 | continue :state_loop; | |
| 1235 | }, | |
| 1236 | 'a'...'f' => { | |
| 1237 | self.cursor += 1; | |
| 1238 | self.utf16_code_units[1] |= @as(u16, c - 'a' + 10) << 4; | |
| 1239 | self.state = .string_surrogate_half_backslash_u_3; | |
| 1240 | continue :state_loop; | |
| 1241 | }, | |
| 1242 | else => return error.SyntaxError, | |
| 1243 | } | |
| 1244 | }, | |
| 1245 | .string_surrogate_half_backslash_u_3 => { | |
| 1246 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1247 | const c = self.input[self.cursor]; | |
| 1248 | switch (c) { | |
| 1249 | '0'...'9' => { | |
| 1250 | self.utf16_code_units[1] |= c - '0'; | |
| 1251 | }, | |
| 1252 | 'A'...'F' => { | |
| 1253 | self.utf16_code_units[1] |= c - 'A' + 10; | |
| 1254 | }, | |
| 1255 | 'a'...'f' => { | |
| 1256 | self.utf16_code_units[1] |= c - 'a' + 10; | |
| 1257 | }, | |
| 1258 | else => return error.SyntaxError, | |
| 1259 | } | |
| 1260 | self.cursor += 1; | |
| 1261 | self.value_start = self.cursor; | |
| 1262 | self.state = .string; | |
| 1263 | const code_point = std.unicode.utf16DecodeSurrogatePair(&self.utf16_code_units) catch unreachable; | |
| 1264 | return partialStringCodepoint(code_point); | |
| 1265 | }, | |
| 1266 | ||
| 1267 | .string_utf8_last_byte => { | |
| 1268 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1269 | switch (self.input[self.cursor]) { | |
| 1270 | 0x80...0xBF => { | |
| 1271 | self.cursor += 1; | |
| 1272 | self.state = .string; | |
| 1273 | continue :state_loop; | |
| 1274 | }, | |
| 1275 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 1276 | } | |
| 1277 | }, | |
| 1278 | .string_utf8_second_to_last_byte => { | |
| 1279 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1280 | switch (self.input[self.cursor]) { | |
| 1281 | 0x80...0xBF => { | |
| 1282 | self.cursor += 1; | |
| 1283 | self.state = .string_utf8_last_byte; | |
| 1284 | continue :state_loop; | |
| 1285 | }, | |
| 1286 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 1287 | } | |
| 1288 | }, | |
| 1289 | .string_utf8_second_to_last_byte_guard_against_overlong => { | |
| 1290 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1291 | switch (self.input[self.cursor]) { | |
| 1292 | 0xA0...0xBF => { | |
| 1293 | self.cursor += 1; | |
| 1294 | self.state = .string_utf8_last_byte; | |
| 1295 | continue :state_loop; | |
| 1296 | }, | |
| 1297 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 1298 | } | |
| 1299 | }, | |
| 1300 | .string_utf8_second_to_last_byte_guard_against_surrogate_half => { | |
| 1301 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1302 | switch (self.input[self.cursor]) { | |
| 1303 | 0x80...0x9F => { | |
| 1304 | self.cursor += 1; | |
| 1305 | self.state = .string_utf8_last_byte; | |
| 1306 | continue :state_loop; | |
| 1307 | }, | |
| 1308 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 1309 | } | |
| 1310 | }, | |
| 1311 | .string_utf8_third_to_last_byte => { | |
| 1312 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1313 | switch (self.input[self.cursor]) { | |
| 1314 | 0x80...0xBF => { | |
| 1315 | self.cursor += 1; | |
| 1316 | self.state = .string_utf8_second_to_last_byte; | |
| 1317 | continue :state_loop; | |
| 1318 | }, | |
| 1319 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 1320 | } | |
| 1321 | }, | |
| 1322 | .string_utf8_third_to_last_byte_guard_against_overlong => { | |
| 1323 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1324 | switch (self.input[self.cursor]) { | |
| 1325 | 0x90...0xBF => { | |
| 1326 | self.cursor += 1; | |
| 1327 | self.state = .string_utf8_second_to_last_byte; | |
| 1328 | continue :state_loop; | |
| 1329 | }, | |
| 1330 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 1331 | } | |
| 1332 | }, | |
| 1333 | .string_utf8_third_to_last_byte_guard_against_too_large => { | |
| 1334 | if (self.cursor >= self.input.len) return self.endOfBufferInString(); | |
| 1335 | switch (self.input[self.cursor]) { | |
| 1336 | 0x80...0x8F => { | |
| 1337 | self.cursor += 1; | |
| 1338 | self.state = .string_utf8_second_to_last_byte; | |
| 1339 | continue :state_loop; | |
| 1340 | }, | |
| 1341 | else => return error.SyntaxError, // Invalid UTF-8. | |
| 1342 | } | |
| 1343 | }, | |
| 1344 | ||
| 1345 | .literal_t => { | |
| 1346 | switch (try self.expectByte()) { | |
| 1347 | 'r' => { | |
| 1348 | self.cursor += 1; | |
| 1349 | self.state = .literal_tr; | |
| 1350 | continue :state_loop; | |
| 1351 | }, | |
| 1352 | else => return error.SyntaxError, | |
| 1353 | } | |
| 1354 | }, | |
| 1355 | .literal_tr => { | |
| 1356 | switch (try self.expectByte()) { | |
| 1357 | 'u' => { | |
| 1358 | self.cursor += 1; | |
| 1359 | self.state = .literal_tru; | |
| 1360 | continue :state_loop; | |
| 1361 | }, | |
| 1362 | else => return error.SyntaxError, | |
| 1363 | } | |
| 1364 | }, | |
| 1365 | .literal_tru => { | |
| 1366 | switch (try self.expectByte()) { | |
| 1367 | 'e' => { | |
| 1368 | self.cursor += 1; | |
| 1369 | self.state = .post_value; | |
| 1370 | return .true; | |
| 1371 | }, | |
| 1372 | else => return error.SyntaxError, | |
| 1373 | } | |
| 1374 | }, | |
| 1375 | .literal_f => { | |
| 1376 | switch (try self.expectByte()) { | |
| 1377 | 'a' => { | |
| 1378 | self.cursor += 1; | |
| 1379 | self.state = .literal_fa; | |
| 1380 | continue :state_loop; | |
| 1381 | }, | |
| 1382 | else => return error.SyntaxError, | |
| 1383 | } | |
| 1384 | }, | |
| 1385 | .literal_fa => { | |
| 1386 | switch (try self.expectByte()) { | |
| 1387 | 'l' => { | |
| 1388 | self.cursor += 1; | |
| 1389 | self.state = .literal_fal; | |
| 1390 | continue :state_loop; | |
| 1391 | }, | |
| 1392 | else => return error.SyntaxError, | |
| 1393 | } | |
| 1394 | }, | |
| 1395 | .literal_fal => { | |
| 1396 | switch (try self.expectByte()) { | |
| 1397 | 's' => { | |
| 1398 | self.cursor += 1; | |
| 1399 | self.state = .literal_fals; | |
| 1400 | continue :state_loop; | |
| 1401 | }, | |
| 1402 | else => return error.SyntaxError, | |
| 1403 | } | |
| 1404 | }, | |
| 1405 | .literal_fals => { | |
| 1406 | switch (try self.expectByte()) { | |
| 1407 | 'e' => { | |
| 1408 | self.cursor += 1; | |
| 1409 | self.state = .post_value; | |
| 1410 | return .false; | |
| 1411 | }, | |
| 1412 | else => return error.SyntaxError, | |
| 1413 | } | |
| 1414 | }, | |
| 1415 | .literal_n => { | |
| 1416 | switch (try self.expectByte()) { | |
| 1417 | 'u' => { | |
| 1418 | self.cursor += 1; | |
| 1419 | self.state = .literal_nu; | |
| 1420 | continue :state_loop; | |
| 1421 | }, | |
| 1422 | else => return error.SyntaxError, | |
| 1423 | } | |
| 1424 | }, | |
| 1425 | .literal_nu => { | |
| 1426 | switch (try self.expectByte()) { | |
| 1427 | 'l' => { | |
| 1428 | self.cursor += 1; | |
| 1429 | self.state = .literal_nul; | |
| 1430 | continue :state_loop; | |
| 1431 | }, | |
| 1432 | else => return error.SyntaxError, | |
| 1433 | } | |
| 1434 | }, | |
| 1435 | .literal_nul => { | |
| 1436 | switch (try self.expectByte()) { | |
| 1437 | 'l' => { | |
| 1438 | self.cursor += 1; | |
| 1439 | self.state = .post_value; | |
| 1440 | return .null; | |
| 1441 | }, | |
| 1442 | else => return error.SyntaxError, | |
| 1443 | } | |
| 1444 | }, | |
| 1445 | } | |
| 1446 | unreachable; | |
| 1447 | } | |
| 1448 | } | |
| 1449 | ||
| 1450 | /// Seeks ahead in the input until the first byte of the next token (or the end of the input) | |
| 1451 | /// determines which type of token will be returned from the next `next*()` call. | |
| 1452 | /// This function is idempotent, only advancing past commas, colons, and inter-token whitespace. | |
| 1453 | pub fn peekNextTokenType(self: *@This()) PeekError!TokenType { | |
| 1454 | state_loop: while (true) { | |
| 1455 | switch (self.state) { | |
| 1456 | .value => { | |
| 1457 | switch (try self.skipWhitespaceExpectByte()) { | |
| 1458 | '{' => return .object_begin, | |
| 1459 | '[' => return .array_begin, | |
| 1460 | '"' => return .string, | |
| 1461 | '-', '0'...'9' => return .number, | |
| 1462 | 't' => return .true, | |
| 1463 | 'f' => return .false, | |
| 1464 | 'n' => return .null, | |
| 1465 | else => return error.SyntaxError, | |
| 1466 | } | |
| 1467 | }, | |
| 1468 | ||
| 1469 | .post_value => { | |
| 1470 | if (try self.skipWhitespaceCheckEnd()) return .end_of_document; | |
| 1471 | ||
| 1472 | const c = self.input[self.cursor]; | |
| 1473 | if (self.string_is_object_key) { | |
| 1474 | self.string_is_object_key = false; | |
| 1475 | switch (c) { | |
| 1476 | ':' => { | |
| 1477 | self.cursor += 1; | |
| 1478 | self.state = .value; | |
| 1479 | continue :state_loop; | |
| 1480 | }, | |
| 1481 | else => return error.SyntaxError, | |
| 1482 | } | |
| 1483 | } | |
| 1484 | ||
| 1485 | switch (c) { | |
| 1486 | '}' => return .object_end, | |
| 1487 | ']' => return .array_end, | |
| 1488 | ',' => { | |
| 1489 | switch (self.stack.peek()) { | |
| 1490 | OBJECT_MODE => { | |
| 1491 | self.state = .object_post_comma; | |
| 1492 | }, | |
| 1493 | ARRAY_MODE => { | |
| 1494 | self.state = .value; | |
| 1495 | }, | |
| 1496 | } | |
| 1497 | self.cursor += 1; | |
| 1498 | continue :state_loop; | |
| 1499 | }, | |
| 1500 | else => return error.SyntaxError, | |
| 1501 | } | |
| 1502 | }, | |
| 1503 | ||
| 1504 | .object_start => { | |
| 1505 | switch (try self.skipWhitespaceExpectByte()) { | |
| 1506 | '"' => return .string, | |
| 1507 | '}' => return .object_end, | |
| 1508 | else => return error.SyntaxError, | |
| 1509 | } | |
| 1510 | }, | |
| 1511 | .object_post_comma => { | |
| 1512 | switch (try self.skipWhitespaceExpectByte()) { | |
| 1513 | '"' => return .string, | |
| 1514 | else => return error.SyntaxError, | |
| 1515 | } | |
| 1516 | }, | |
| 1517 | ||
| 1518 | .array_start => { | |
| 1519 | switch (try self.skipWhitespaceExpectByte()) { | |
| 1520 | ']' => return .array_end, | |
| 1521 | else => { | |
| 1522 | self.state = .value; | |
| 1523 | continue :state_loop; | |
| 1524 | }, | |
| 1525 | } | |
| 1526 | }, | |
| 1527 | ||
| 1528 | .number_minus, | |
| 1529 | .number_leading_zero, | |
| 1530 | .number_int, | |
| 1531 | .number_post_dot, | |
| 1532 | .number_frac, | |
| 1533 | .number_post_e, | |
| 1534 | .number_post_e_sign, | |
| 1535 | .number_exp, | |
| 1536 | => return .number, | |
| 1537 | ||
| 1538 | .string, | |
| 1539 | .string_backslash, | |
| 1540 | .string_backslash_u, | |
| 1541 | .string_backslash_u_1, | |
| 1542 | .string_backslash_u_2, | |
| 1543 | .string_backslash_u_3, | |
| 1544 | .string_surrogate_half, | |
| 1545 | .string_surrogate_half_backslash, | |
| 1546 | .string_surrogate_half_backslash_u, | |
| 1547 | .string_surrogate_half_backslash_u_1, | |
| 1548 | .string_surrogate_half_backslash_u_2, | |
| 1549 | .string_surrogate_half_backslash_u_3, | |
| 1550 | => return .string, | |
| 1551 | ||
| 1552 | .string_utf8_last_byte, | |
| 1553 | .string_utf8_second_to_last_byte, | |
| 1554 | .string_utf8_second_to_last_byte_guard_against_overlong, | |
| 1555 | .string_utf8_second_to_last_byte_guard_against_surrogate_half, | |
| 1556 | .string_utf8_third_to_last_byte, | |
| 1557 | .string_utf8_third_to_last_byte_guard_against_overlong, | |
| 1558 | .string_utf8_third_to_last_byte_guard_against_too_large, | |
| 1559 | => return .string, | |
| 1560 | ||
| 1561 | .literal_t, | |
| 1562 | .literal_tr, | |
| 1563 | .literal_tru, | |
| 1564 | => return .true, | |
| 1565 | .literal_f, | |
| 1566 | .literal_fa, | |
| 1567 | .literal_fal, | |
| 1568 | .literal_fals, | |
| 1569 | => return .false, | |
| 1570 | .literal_n, | |
| 1571 | .literal_nu, | |
| 1572 | .literal_nul, | |
| 1573 | => return .null, | |
| 1574 | } | |
| 1575 | unreachable; | |
| 1576 | } | |
| 1577 | } | |
| 1578 | ||
| 1579 | const State = enum { | |
| 1580 | value, | |
| 1581 | post_value, | |
| 1582 | ||
| 1583 | object_start, | |
| 1584 | object_post_comma, | |
| 1585 | ||
| 1586 | array_start, | |
| 1587 | ||
| 1588 | number_minus, | |
| 1589 | number_leading_zero, | |
| 1590 | number_int, | |
| 1591 | number_post_dot, | |
| 1592 | number_frac, | |
| 1593 | number_post_e, | |
| 1594 | number_post_e_sign, | |
| 1595 | number_exp, | |
| 1596 | ||
| 1597 | string, | |
| 1598 | string_backslash, | |
| 1599 | string_backslash_u, | |
| 1600 | string_backslash_u_1, | |
| 1601 | string_backslash_u_2, | |
| 1602 | string_backslash_u_3, | |
| 1603 | string_surrogate_half, | |
| 1604 | string_surrogate_half_backslash, | |
| 1605 | string_surrogate_half_backslash_u, | |
| 1606 | string_surrogate_half_backslash_u_1, | |
| 1607 | string_surrogate_half_backslash_u_2, | |
| 1608 | string_surrogate_half_backslash_u_3, | |
| 1609 | ||
| 1610 | // From http://unicode.org/mail-arch/unicode-ml/y2003-m02/att-0467/01-The_Algorithm_to_Valide_an_UTF-8_String | |
| 1611 | string_utf8_last_byte, // State A | |
| 1612 | string_utf8_second_to_last_byte, // State B | |
| 1613 | string_utf8_second_to_last_byte_guard_against_overlong, // State C | |
| 1614 | string_utf8_second_to_last_byte_guard_against_surrogate_half, // State D | |
| 1615 | string_utf8_third_to_last_byte, // State E | |
| 1616 | string_utf8_third_to_last_byte_guard_against_overlong, // State F | |
| 1617 | string_utf8_third_to_last_byte_guard_against_too_large, // State G | |
| 1618 | ||
| 1619 | literal_t, | |
| 1620 | literal_tr, | |
| 1621 | literal_tru, | |
| 1622 | literal_f, | |
| 1623 | literal_fa, | |
| 1624 | literal_fal, | |
| 1625 | literal_fals, | |
| 1626 | literal_n, | |
| 1627 | literal_nu, | |
| 1628 | literal_nul, | |
| 1629 | }; | |
| 1630 | ||
| 1631 | fn expectByte(self: *const @This()) !u8 { | |
| 1632 | if (self.cursor < self.input.len) { | |
| 1633 | return self.input[self.cursor]; | |
| 1634 | } | |
| 1635 | // No byte. | |
| 1636 | if (self.is_end_of_input) return error.UnexpectedEndOfInput; | |
| 1637 | return error.BufferUnderrun; | |
| 1638 | } | |
| 1639 | ||
| 1640 | fn skipWhitespace(self: *@This()) void { | |
| 1641 | while (self.cursor < self.input.len) : (self.cursor += 1) { | |
| 1642 | switch (self.input[self.cursor]) { | |
| 1643 | // Whitespace | |
| 1644 | ' ', '\t', '\r' => continue, | |
| 1645 | '\n' => { | |
| 1646 | if (self.diagnostics) |diag| { | |
| 1647 | diag.line_number += 1; | |
| 1648 | // This will count the newline itself, | |
| 1649 | // which means a straight-forward subtraction will give a 1-based column number. | |
| 1650 | diag.line_start_cursor = self.cursor; | |
| 1651 | } | |
| 1652 | continue; | |
| 1653 | }, | |
| 1654 | else => return, | |
| 1655 | } | |
| 1656 | } | |
| 1657 | } | |
| 1658 | ||
| 1659 | fn skipWhitespaceExpectByte(self: *@This()) !u8 { | |
| 1660 | self.skipWhitespace(); | |
| 1661 | return self.expectByte(); | |
| 1662 | } | |
| 1663 | ||
| 1664 | fn skipWhitespaceCheckEnd(self: *@This()) !bool { | |
| 1665 | self.skipWhitespace(); | |
| 1666 | if (self.cursor >= self.input.len) { | |
| 1667 | // End of buffer. | |
| 1668 | if (self.is_end_of_input) { | |
| 1669 | // End of everything. | |
| 1670 | if (self.stackHeight() == 0) { | |
| 1671 | // We did it! | |
| 1672 | return true; | |
| 1673 | } | |
| 1674 | return error.UnexpectedEndOfInput; | |
| 1675 | } | |
| 1676 | return error.BufferUnderrun; | |
| 1677 | } | |
| 1678 | if (self.stackHeight() == 0) return error.SyntaxError; | |
| 1679 | return false; | |
| 1680 | } | |
| 1681 | ||
| 1682 | fn takeValueSlice(self: *@This()) []const u8 { | |
| 1683 | const slice = self.input[self.value_start..self.cursor]; | |
| 1684 | self.value_start = self.cursor; | |
| 1685 | return slice; | |
| 1686 | } | |
| 1687 | fn takeValueSliceMinusTrailingOffset(self: *@This(), trailing_negative_offset: usize) []const u8 { | |
| 1688 | // Check if the escape sequence started before the current input buffer. | |
| 1689 | // (The algebra here is awkward to avoid unsigned underflow, | |
| 1690 | // but it's just making sure the slice on the next line isn't UB.) | |
| 1691 | if (self.cursor <= self.value_start + trailing_negative_offset) return ""; | |
| 1692 | const slice = self.input[self.value_start .. self.cursor - trailing_negative_offset]; | |
| 1693 | // When trailing_negative_offset is non-zero, setting self.value_start doesn't matter, | |
| 1694 | // because we always set it again while emitting the .partial_string_escaped_*. | |
| 1695 | self.value_start = self.cursor; | |
| 1696 | return slice; | |
| 1697 | } | |
| 1698 | ||
| 1699 | fn endOfBufferInNumber(self: *@This(), allow_end: bool) !Token { | |
| 1700 | const slice = self.takeValueSlice(); | |
| 1701 | if (self.is_end_of_input) { | |
| 1702 | if (!allow_end) return error.UnexpectedEndOfInput; | |
| 1703 | self.state = .post_value; | |
| 1704 | return Token{ .number = slice }; | |
| 1705 | } | |
| 1706 | if (slice.len == 0) return error.BufferUnderrun; | |
| 1707 | return Token{ .partial_number = slice }; | |
| 1708 | } | |
| 1709 | ||
| 1710 | fn endOfBufferInString(self: *@This()) !Token { | |
| 1711 | if (self.is_end_of_input) return error.UnexpectedEndOfInput; | |
| 1712 | const slice = self.takeValueSliceMinusTrailingOffset(switch (self.state) { | |
| 1713 | // Don't include the escape sequence in the partial string. | |
| 1714 | .string_backslash => 1, | |
| 1715 | .string_backslash_u => 2, | |
| 1716 | .string_backslash_u_1 => 3, | |
| 1717 | .string_backslash_u_2 => 4, | |
| 1718 | .string_backslash_u_3 => 5, | |
| 1719 | .string_surrogate_half => 6, | |
| 1720 | .string_surrogate_half_backslash => 7, | |
| 1721 | .string_surrogate_half_backslash_u => 8, | |
| 1722 | .string_surrogate_half_backslash_u_1 => 9, | |
| 1723 | .string_surrogate_half_backslash_u_2 => 10, | |
| 1724 | .string_surrogate_half_backslash_u_3 => 11, | |
| 1725 | ||
| 1726 | // Include everything up to the cursor otherwise. | |
| 1727 | .string, | |
| 1728 | .string_utf8_last_byte, | |
| 1729 | .string_utf8_second_to_last_byte, | |
| 1730 | .string_utf8_second_to_last_byte_guard_against_overlong, | |
| 1731 | .string_utf8_second_to_last_byte_guard_against_surrogate_half, | |
| 1732 | .string_utf8_third_to_last_byte, | |
| 1733 | .string_utf8_third_to_last_byte_guard_against_overlong, | |
| 1734 | .string_utf8_third_to_last_byte_guard_against_too_large, | |
| 1735 | => 0, | |
| 1736 | ||
| 1737 | else => unreachable, | |
| 1738 | }); | |
| 1739 | if (slice.len == 0) return error.BufferUnderrun; | |
| 1740 | return Token{ .partial_string = slice }; | |
| 1741 | } | |
| 1742 | ||
| 1743 | fn partialStringCodepoint(code_point: u21) Token { | |
| 1744 | var buf: [4]u8 = undefined; | |
| 1745 | switch (std.unicode.utf8Encode(code_point, &buf) catch unreachable) { | |
| 1746 | 1 => return Token{ .partial_string_escaped_1 = buf[0..1].* }, | |
| 1747 | 2 => return Token{ .partial_string_escaped_2 = buf[0..2].* }, | |
| 1748 | 3 => return Token{ .partial_string_escaped_3 = buf[0..3].* }, | |
| 1749 | 4 => return Token{ .partial_string_escaped_4 = buf[0..4].* }, | |
| 1750 | else => unreachable, | |
| 1751 | } | |
| 1752 | } | |
| 1753 | }; | |
| 1754 | ||
| 1755 | const OBJECT_MODE = 0; | |
| 1756 | const ARRAY_MODE = 1; | |
| 1757 | ||
| 1758 | fn appendSlice(list: *std.ArrayList(u8), buf: []const u8, max_value_len: usize) !void { | |
| 1759 | const new_len = std.math.add(usize, list.items.len, buf.len) catch return error.ValueTooLong; | |
| 1760 | if (new_len > max_value_len) return error.ValueTooLong; | |
| 1761 | try list.appendSlice(buf); | |
| 1762 | } | |
| 1763 | ||
| 1764 | /// For the slice you get from a `Token.number` or `Token.allocated_number`, | |
| 1765 | /// this function returns true if the number doesn't contain any fraction or exponent components, and is not `-0`. | |
| 1766 | /// Note, the numeric value encoded by the value may still be an integer, such as `1.0`. | |
| 1767 | /// This function is meant to give a hint about whether integer parsing or float parsing should be used on the value. | |
| 1768 | /// This function will not give meaningful results on non-numeric input. | |
| 1769 | pub fn isNumberFormattedLikeAnInteger(value: []const u8) bool { | |
| 1770 | if (std.mem.eql(u8, value, "-0")) return false; | |
| 1771 | return std.mem.indexOfAny(u8, value, ".eE") == null; | |
| 1772 | } | |
| 1773 | ||
| 1774 | test { | |
| 1775 | _ = @import("./scanner_test.zig"); | |
| 1776 | } |
lib/std/json/scanner_test.zig+39-39| ... | ... | @@ -1,13 +1,11 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const JsonScanner = @import("./scanner.zig").Scanner; | |
| 3 | const jsonReader = @import("./scanner.zig").reader; | |
| 4 | const JsonReader = @import("./scanner.zig").Reader; | |
| 5 | const Token = @import("./scanner.zig").Token; | |
| 6 | const TokenType = @import("./scanner.zig").TokenType; | |
| 7 | const Diagnostics = @import("./scanner.zig").Diagnostics; | |
| 8 | const Error = @import("./scanner.zig").Error; | |
| 9 | const validate = @import("./scanner.zig").validate; | |
| 10 | const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger; | |
| 2 | const Scanner = @import("Scanner.zig"); | |
| 3 | const Token = Scanner.Token; | |
| 4 | const TokenType = Scanner.TokenType; | |
| 5 | const Diagnostics = Scanner.Diagnostics; | |
| 6 | const Error = Scanner.Error; | |
| 7 | const validate = Scanner.validate; | |
| 8 | const isNumberFormattedLikeAnInteger = Scanner.isNumberFormattedLikeAnInteger; | |
| 11 | 9 | |
| 12 | 10 | const example_document_str = |
| 13 | 11 | \\{ |
| ... | ... | @@ -36,7 +34,7 @@ fn expectPeekNext(scanner_or_reader: anytype, expected_token_type: TokenType, ex |
| 36 | 34 | } |
| 37 | 35 | |
| 38 | 36 | test "token" { |
| 39 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, example_document_str); | |
| 37 | var scanner = Scanner.initCompleteInput(std.testing.allocator, example_document_str); | |
| 40 | 38 | defer scanner.deinit(); |
| 41 | 39 | |
| 42 | 40 | try expectNext(&scanner, .object_begin); |
| ... | ... | @@ -138,23 +136,25 @@ fn testAllTypes(source: anytype, large_buffer: bool) !void { |
| 138 | 136 | } |
| 139 | 137 | |
| 140 | 138 | test "peek all types" { |
| 141 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, all_types_test_case); | |
| 139 | var scanner = Scanner.initCompleteInput(std.testing.allocator, all_types_test_case); | |
| 142 | 140 | defer scanner.deinit(); |
| 143 | 141 | try testAllTypes(&scanner, true); |
| 144 | 142 | |
| 145 | var stream: std.io.FixedBufferStream = .{ .buffer = all_types_test_case }; | |
| 146 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | |
| 143 | var stream: std.Io.Reader = .fixed(all_types_test_case); | |
| 144 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); | |
| 147 | 145 | defer json_reader.deinit(); |
| 148 | 146 | try testAllTypes(&json_reader, true); |
| 149 | 147 | |
| 150 | var tiny_stream: std.io.FixedBufferStream = .{ .buffer = all_types_test_case }; | |
| 151 | var tiny_json_reader = JsonReader(1, @TypeOf(tiny_stream.reader())).init(std.testing.allocator, tiny_stream.reader()); | |
| 148 | var tiny_buffer: [1]u8 = undefined; | |
| 149 | var tiny_stream: std.testing.Reader = .init(&tiny_buffer, &.{.{ .buffer = all_types_test_case }}); | |
| 150 | tiny_stream.artificial_limit = .limited(1); | |
| 151 | var tiny_json_reader: Scanner.Reader = .init(std.testing.allocator, &tiny_stream.interface); | |
| 152 | 152 | defer tiny_json_reader.deinit(); |
| 153 | 153 | try testAllTypes(&tiny_json_reader, false); |
| 154 | 154 | } |
| 155 | 155 | |
| 156 | 156 | test "token mismatched close" { |
| 157 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, "[102, 111, 111 }"); | |
| 157 | var scanner = Scanner.initCompleteInput(std.testing.allocator, "[102, 111, 111 }"); | |
| 158 | 158 | defer scanner.deinit(); |
| 159 | 159 | try expectNext(&scanner, .array_begin); |
| 160 | 160 | try expectNext(&scanner, Token{ .number = "102" }); |
| ... | ... | @@ -164,15 +164,15 @@ test "token mismatched close" { |
| 164 | 164 | } |
| 165 | 165 | |
| 166 | 166 | test "token premature object close" { |
| 167 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, "{ \"key\": }"); | |
| 167 | var scanner = Scanner.initCompleteInput(std.testing.allocator, "{ \"key\": }"); | |
| 168 | 168 | defer scanner.deinit(); |
| 169 | 169 | try expectNext(&scanner, .object_begin); |
| 170 | 170 | try expectNext(&scanner, Token{ .string = "key" }); |
| 171 | 171 | try std.testing.expectError(error.SyntaxError, scanner.next()); |
| 172 | 172 | } |
| 173 | 173 | |
| 174 | test "JsonScanner basic" { | |
| 175 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, example_document_str); | |
| 174 | test "Scanner basic" { | |
| 175 | var scanner = Scanner.initCompleteInput(std.testing.allocator, example_document_str); | |
| 176 | 176 | defer scanner.deinit(); |
| 177 | 177 | |
| 178 | 178 | while (true) { |
| ... | ... | @@ -181,10 +181,10 @@ test "JsonScanner basic" { |
| 181 | 181 | } |
| 182 | 182 | } |
| 183 | 183 | |
| 184 | test "JsonReader basic" { | |
| 185 | var stream: std.io.FixedBufferStream = .{ .buffer = example_document_str }; | |
| 184 | test "Scanner.Reader basic" { | |
| 185 | var stream: std.Io.Reader = .fixed(example_document_str); | |
| 186 | 186 | |
| 187 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | |
| 187 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); | |
| 188 | 188 | defer json_reader.deinit(); |
| 189 | 189 | |
| 190 | 190 | while (true) { |
| ... | ... | @@ -215,7 +215,7 @@ const number_test_items = blk: { |
| 215 | 215 | |
| 216 | 216 | test "numbers" { |
| 217 | 217 | for (number_test_items) |number_str| { |
| 218 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, number_str); | |
| 218 | var scanner = Scanner.initCompleteInput(std.testing.allocator, number_str); | |
| 219 | 219 | defer scanner.deinit(); |
| 220 | 220 | |
| 221 | 221 | const token = try scanner.next(); |
| ... | ... | @@ -243,10 +243,10 @@ const string_test_cases = .{ |
| 243 | 243 | |
| 244 | 244 | test "strings" { |
| 245 | 245 | inline for (string_test_cases) |tuple| { |
| 246 | var stream: std.io.FixedBufferStream = .{ .buffer = "\"" ++ tuple[0] ++ "\"" }; | |
| 246 | var stream: std.Io.Reader = .fixed("\"" ++ tuple[0] ++ "\""); | |
| 247 | 247 | var arena = std.heap.ArenaAllocator.init(std.testing.allocator); |
| 248 | 248 | defer arena.deinit(); |
| 249 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | |
| 249 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); | |
| 250 | 250 | defer json_reader.deinit(); |
| 251 | 251 | |
| 252 | 252 | const token = try json_reader.nextAlloc(arena.allocator(), .alloc_if_needed); |
| ... | ... | @@ -289,7 +289,7 @@ test "nesting" { |
| 289 | 289 | } |
| 290 | 290 | |
| 291 | 291 | fn expectMaybeError(document_str: []const u8, maybe_error: ?Error) !void { |
| 292 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, document_str); | |
| 292 | var scanner = Scanner.initCompleteInput(std.testing.allocator, document_str); | |
| 293 | 293 | defer scanner.deinit(); |
| 294 | 294 | |
| 295 | 295 | while (true) { |
| ... | ... | @@ -352,12 +352,12 @@ fn expectEqualTokens(expected_token: Token, actual_token: Token) !void { |
| 352 | 352 | } |
| 353 | 353 | |
| 354 | 354 | fn testTinyBufferSize(document_str: []const u8) !void { |
| 355 | var tiny_stream: std.io.FixedBufferStream = .{ .buffer = document_str }; | |
| 356 | var normal_stream: std.io.FixedBufferStream = .{ .buffer = document_str }; | |
| 355 | var tiny_stream: std.Io.Reader = .fixed(document_str); | |
| 356 | var normal_stream: std.Io.Reader = .fixed(document_str); | |
| 357 | 357 | |
| 358 | var tiny_json_reader = JsonReader(1, @TypeOf(tiny_stream.reader())).init(std.testing.allocator, tiny_stream.reader()); | |
| 358 | var tiny_json_reader: Scanner.Reader = .init(std.testing.allocator, &tiny_stream); | |
| 359 | 359 | defer tiny_json_reader.deinit(); |
| 360 | var normal_json_reader = JsonReader(0x1000, @TypeOf(normal_stream.reader())).init(std.testing.allocator, normal_stream.reader()); | |
| 360 | var normal_json_reader: Scanner.Reader = .init(std.testing.allocator, &normal_stream); | |
| 361 | 361 | defer normal_json_reader.deinit(); |
| 362 | 362 | |
| 363 | 363 | expectEqualStreamOfTokens(&normal_json_reader, &tiny_json_reader) catch |err| { |
| ... | ... | @@ -397,13 +397,13 @@ test "validate" { |
| 397 | 397 | } |
| 398 | 398 | |
| 399 | 399 | fn testSkipValue(s: []const u8) !void { |
| 400 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, s); | |
| 400 | var scanner = Scanner.initCompleteInput(std.testing.allocator, s); | |
| 401 | 401 | defer scanner.deinit(); |
| 402 | 402 | try scanner.skipValue(); |
| 403 | 403 | try expectEqualTokens(.end_of_document, try scanner.next()); |
| 404 | 404 | |
| 405 | var stream: std.io.FixedBufferStream = .{ .buffer = s }; | |
| 406 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | |
| 405 | var stream: std.Io.Reader = .fixed(s); | |
| 406 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); | |
| 407 | 407 | defer json_reader.deinit(); |
| 408 | 408 | try json_reader.skipValue(); |
| 409 | 409 | try expectEqualTokens(.end_of_document, try json_reader.next()); |
| ... | ... | @@ -441,7 +441,7 @@ fn testEnsureStackCapacity(do_ensure: bool) !void { |
| 441 | 441 | try input_string.appendNTimes(std.testing.allocator, ']', nestings); |
| 442 | 442 | defer input_string.deinit(std.testing.allocator); |
| 443 | 443 | |
| 444 | var scanner = JsonScanner.initCompleteInput(failing_allocator, input_string.items); | |
| 444 | var scanner = Scanner.initCompleteInput(failing_allocator, input_string.items); | |
| 445 | 445 | defer scanner.deinit(); |
| 446 | 446 | |
| 447 | 447 | if (do_ensure) { |
| ... | ... | @@ -473,17 +473,17 @@ fn testDiagnosticsFromSource(expected_error: ?anyerror, line: u64, col: u64, byt |
| 473 | 473 | try std.testing.expectEqual(byte_offset, diagnostics.getByteOffset()); |
| 474 | 474 | } |
| 475 | 475 | fn testDiagnostics(expected_error: ?anyerror, line: u64, col: u64, byte_offset: u64, s: []const u8) !void { |
| 476 | var scanner = JsonScanner.initCompleteInput(std.testing.allocator, s); | |
| 476 | var scanner = Scanner.initCompleteInput(std.testing.allocator, s); | |
| 477 | 477 | defer scanner.deinit(); |
| 478 | 478 | try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &scanner); |
| 479 | 479 | |
| 480 | var tiny_stream: std.io.FixedBufferStream = .{ .buffer = s }; | |
| 481 | var tiny_json_reader = JsonReader(1, @TypeOf(tiny_stream.reader())).init(std.testing.allocator, tiny_stream.reader()); | |
| 480 | var tiny_stream: std.Io.Reader = .fixed(s); | |
| 481 | var tiny_json_reader: Scanner.Reader = .init(std.testing.allocator, &tiny_stream); | |
| 482 | 482 | defer tiny_json_reader.deinit(); |
| 483 | 483 | try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &tiny_json_reader); |
| 484 | 484 | |
| 485 | var medium_stream: std.io.FixedBufferStream = .{ .buffer = s }; | |
| 486 | var medium_json_reader = JsonReader(5, @TypeOf(medium_stream.reader())).init(std.testing.allocator, medium_stream.reader()); | |
| 485 | var medium_stream: std.Io.Reader = .fixed(s); | |
| 486 | var medium_json_reader: Scanner.Reader = .init(std.testing.allocator, &medium_stream); | |
| 487 | 487 | defer medium_json_reader.deinit(); |
| 488 | 488 | try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &medium_json_reader); |
| 489 | 489 | } |
lib/std/json/static.zig+5-5| ... | ... | @@ -4,11 +4,11 @@ const Allocator = std.mem.Allocator; |
| 4 | 4 | const ArenaAllocator = std.heap.ArenaAllocator; |
| 5 | 5 | const ArrayList = std.ArrayList; |
| 6 | 6 | |
| 7 | const Scanner = @import("./scanner.zig").Scanner; | |
| 8 | const Token = @import("./scanner.zig").Token; | |
| 9 | const AllocWhen = @import("./scanner.zig").AllocWhen; | |
| 10 | const default_max_value_len = @import("./scanner.zig").default_max_value_len; | |
| 11 | const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger; | |
| 7 | const Scanner = @import("Scanner.zig"); | |
| 8 | const Token = Scanner.Token; | |
| 9 | const AllocWhen = Scanner.AllocWhen; | |
| 10 | const default_max_value_len = Scanner.default_max_value_len; | |
| 11 | const isNumberFormattedLikeAnInteger = Scanner.isNumberFormattedLikeAnInteger; | |
| 12 | 12 | |
| 13 | 13 | const Value = @import("./dynamic.zig").Value; |
| 14 | 14 | const Array = @import("./dynamic.zig").Array; |
lib/std/json/static_test.zig+14-16| ... | ... | @@ -12,9 +12,7 @@ const parseFromValue = @import("./static.zig").parseFromValue; |
| 12 | 12 | const parseFromValueLeaky = @import("./static.zig").parseFromValueLeaky; |
| 13 | 13 | const ParseOptions = @import("./static.zig").ParseOptions; |
| 14 | 14 | |
| 15 | const JsonScanner = @import("./scanner.zig").Scanner; | |
| 16 | const jsonReader = @import("./scanner.zig").reader; | |
| 17 | const Diagnostics = @import("./scanner.zig").Diagnostics; | |
| 15 | const Scanner = @import("Scanner.zig"); | |
| 18 | 16 | |
| 19 | 17 | const Value = @import("./dynamic.zig").Value; |
| 20 | 18 | |
| ... | ... | @@ -300,9 +298,9 @@ const subnamespaces_0_doc = |
| 300 | 298 | fn testAllParseFunctions(comptime T: type, expected: T, doc: []const u8) !void { |
| 301 | 299 | // First do the one with the debug info in case we get a SyntaxError or something. |
| 302 | 300 | { |
| 303 | var scanner = JsonScanner.initCompleteInput(testing.allocator, doc); | |
| 301 | var scanner = Scanner.initCompleteInput(testing.allocator, doc); | |
| 304 | 302 | defer scanner.deinit(); |
| 305 | var diagnostics = Diagnostics{}; | |
| 303 | var diagnostics = Scanner.Diagnostics{}; | |
| 306 | 304 | scanner.enableDiagnostics(&diagnostics); |
| 307 | 305 | var parsed = parseFromTokenSource(T, testing.allocator, &scanner, .{}) catch |e| { |
| 308 | 306 | std.debug.print("at line,col: {}:{}\n", .{ diagnostics.getLine(), diagnostics.getColumn() }); |
| ... | ... | @@ -317,8 +315,8 @@ fn testAllParseFunctions(comptime T: type, expected: T, doc: []const u8) !void { |
| 317 | 315 | try testing.expectEqualDeep(expected, parsed.value); |
| 318 | 316 | } |
| 319 | 317 | { |
| 320 | var stream: std.io.FixedBufferStream = .{ .buffer = doc }; | |
| 321 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | |
| 318 | var stream: std.Io.Reader = .fixed(doc); | |
| 319 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); | |
| 322 | 320 | defer json_reader.deinit(); |
| 323 | 321 | var parsed = try parseFromTokenSource(T, testing.allocator, &json_reader, .{}); |
| 324 | 322 | defer parsed.deinit(); |
| ... | ... | @@ -331,13 +329,13 @@ fn testAllParseFunctions(comptime T: type, expected: T, doc: []const u8) !void { |
| 331 | 329 | try testing.expectEqualDeep(expected, try parseFromSliceLeaky(T, arena.allocator(), doc, .{})); |
| 332 | 330 | } |
| 333 | 331 | { |
| 334 | var scanner = JsonScanner.initCompleteInput(testing.allocator, doc); | |
| 332 | var scanner = Scanner.initCompleteInput(testing.allocator, doc); | |
| 335 | 333 | defer scanner.deinit(); |
| 336 | 334 | try testing.expectEqualDeep(expected, try parseFromTokenSourceLeaky(T, arena.allocator(), &scanner, .{})); |
| 337 | 335 | } |
| 338 | 336 | { |
| 339 | var stream: std.io.FixedBufferStream = .{ .buffer = doc }; | |
| 340 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | |
| 337 | var stream: std.Io.Reader = .fixed(doc); | |
| 338 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); | |
| 341 | 339 | defer json_reader.deinit(); |
| 342 | 340 | try testing.expectEqualDeep(expected, try parseFromTokenSourceLeaky(T, arena.allocator(), &json_reader, .{})); |
| 343 | 341 | } |
| ... | ... | @@ -763,7 +761,7 @@ test "parse exponential into int" { |
| 763 | 761 | |
| 764 | 762 | test "parseFromTokenSource" { |
| 765 | 763 | { |
| 766 | var scanner = JsonScanner.initCompleteInput(testing.allocator, "123"); | |
| 764 | var scanner = Scanner.initCompleteInput(testing.allocator, "123"); | |
| 767 | 765 | defer scanner.deinit(); |
| 768 | 766 | var parsed = try parseFromTokenSource(u32, testing.allocator, &scanner, .{}); |
| 769 | 767 | defer parsed.deinit(); |
| ... | ... | @@ -771,8 +769,8 @@ test "parseFromTokenSource" { |
| 771 | 769 | } |
| 772 | 770 | |
| 773 | 771 | { |
| 774 | var stream: std.io.FixedBufferStream = .{ .buffer = "123" }; | |
| 775 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | |
| 772 | var stream: std.Io.Reader = .fixed("123"); | |
| 773 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); | |
| 776 | 774 | defer json_reader.deinit(); |
| 777 | 775 | var parsed = try parseFromTokenSource(u32, testing.allocator, &json_reader, .{}); |
| 778 | 776 | defer parsed.deinit(); |
| ... | ... | @@ -836,7 +834,7 @@ test "json parse partial" { |
| 836 | 834 | \\} |
| 837 | 835 | ; |
| 838 | 836 | const allocator = testing.allocator; |
| 839 | var scanner = JsonScanner.initCompleteInput(allocator, str); | |
| 837 | var scanner = Scanner.initCompleteInput(allocator, str); | |
| 840 | 838 | defer scanner.deinit(); |
| 841 | 839 | |
| 842 | 840 | var arena = ArenaAllocator.init(allocator); |
| ... | ... | @@ -886,8 +884,8 @@ test "json parse allocate when streaming" { |
| 886 | 884 | var arena = ArenaAllocator.init(allocator); |
| 887 | 885 | defer arena.deinit(); |
| 888 | 886 | |
| 889 | var stream: std.io.FixedBufferStream = .{ .buffer = str }; | |
| 890 | var json_reader = jsonReader(std.testing.allocator, stream.reader()); | |
| 887 | var stream: std.Io.Reader = .fixed(str); | |
| 888 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); | |
| 891 | 889 | |
| 892 | 890 | const parsed = parseFromTokenSourceLeaky(T, arena.allocator(), &json_reader, .{}) catch |err| { |
| 893 | 891 | json_reader.deinit(); |
lib/std/json/test.zig+3-4| ... | ... | @@ -2,8 +2,7 @@ const std = @import("std"); |
| 2 | 2 | const json = std.json; |
| 3 | 3 | const testing = std.testing; |
| 4 | 4 | const parseFromSlice = @import("./static.zig").parseFromSlice; |
| 5 | const validate = @import("./scanner.zig").validate; | |
| 6 | const JsonScanner = @import("./scanner.zig").Scanner; | |
| 5 | const Scanner = @import("./Scanner.zig"); | |
| 7 | 6 | const Value = @import("./dynamic.zig").Value; |
| 8 | 7 | |
| 9 | 8 | // Support for JSONTestSuite.zig |
| ... | ... | @@ -20,7 +19,7 @@ pub fn any(s: []const u8) !void { |
| 20 | 19 | testHighLevelDynamicParser(s) catch {}; |
| 21 | 20 | } |
| 22 | 21 | fn testLowLevelScanner(s: []const u8) !void { |
| 23 | var scanner = JsonScanner.initCompleteInput(testing.allocator, s); | |
| 22 | var scanner = Scanner.initCompleteInput(testing.allocator, s); | |
| 24 | 23 | defer scanner.deinit(); |
| 25 | 24 | while (true) { |
| 26 | 25 | const token = try scanner.next(); |
| ... | ... | @@ -47,7 +46,7 @@ test "n_object_closed_missing_value" { |
| 47 | 46 | } |
| 48 | 47 | |
| 49 | 48 | fn roundTrip(s: []const u8) !void { |
| 50 | try testing.expect(try validate(testing.allocator, s)); | |
| 49 | try testing.expect(try Scanner.validate(testing.allocator, s)); | |
| 51 | 50 | |
| 52 | 51 | var parsed = try parseFromSlice(Value, testing.allocator, s, .{}); |
| 53 | 52 | defer parsed.deinit(); |
lib/std/zig.zig+16-13| ... | ... | @@ -446,8 +446,8 @@ pub fn fmtString(bytes: []const u8) std.fmt.Formatter([]const u8, stringEscape) |
| 446 | 446 | } |
| 447 | 447 | |
| 448 | 448 | /// Return a formatter for escaping a single quoted Zig string. |
| 449 | pub fn fmtChar(bytes: []const u8) std.fmt.Formatter([]const u8, charEscape) { | |
| 450 | return .{ .data = bytes }; | |
| 449 | pub fn fmtChar(c: u21) std.fmt.Formatter(u21, charEscape) { | |
| 450 | return .{ .data = c }; | |
| 451 | 451 | } |
| 452 | 452 | |
| 453 | 453 | test fmtString { |
| ... | ... | @@ -458,9 +458,7 @@ test fmtString { |
| 458 | 458 | } |
| 459 | 459 | |
| 460 | 460 | test fmtChar { |
| 461 | try std.testing.expectFmt( | |
| 462 | \\" \\ hi \x07 \x11 " derp \'" | |
| 463 | , "\"{f}\"", .{fmtChar(" \\ hi \x07 \x11 \" derp '")}); | |
| 461 | try std.testing.expectFmt("c \\u{26a1}", "{f} {f}", .{ fmtChar('c'), fmtChar('⚡') }); | |
| 464 | 462 | } |
| 465 | 463 | |
| 466 | 464 | /// Print the string as escaped contents of a double quoted string. |
| ... | ... | @@ -480,21 +478,26 @@ pub fn stringEscape(bytes: []const u8, w: *Writer) Writer.Error!void { |
| 480 | 478 | }; |
| 481 | 479 | } |
| 482 | 480 | |
| 483 | /// Print the string as escaped contents of a single-quoted string. | |
| 484 | pub fn charEscape(bytes: []const u8, w: *Writer) Writer.Error!void { | |
| 485 | for (bytes) |byte| switch (byte) { | |
| 481 | /// Print as escaped contents of a single-quoted string. | |
| 482 | pub fn charEscape(codepoint: u21, w: *Writer) Writer.Error!void { | |
| 483 | switch (codepoint) { | |
| 486 | 484 | '\n' => try w.writeAll("\\n"), |
| 487 | 485 | '\r' => try w.writeAll("\\r"), |
| 488 | 486 | '\t' => try w.writeAll("\\t"), |
| 489 | 487 | '\\' => try w.writeAll("\\\\"), |
| 490 | '"' => try w.writeByte('"'), | |
| 491 | 488 | '\'' => try w.writeAll("\\'"), |
| 492 | ' ', '!', '#'...'&', '('...'[', ']'...'~' => try w.writeByte(byte), | |
| 489 | '"', ' ', '!', '#'...'&', '('...'[', ']'...'~' => try w.writeByte(@intCast(codepoint)), | |
| 493 | 490 | else => { |
| 494 | try w.writeAll("\\x"); | |
| 495 | try w.printInt(byte, 16, .lower, .{ .width = 2, .fill = '0' }); | |
| 491 | if (std.math.cast(u8, codepoint)) |byte| { | |
| 492 | try w.writeAll("\\x"); | |
| 493 | try w.printInt(byte, 16, .lower, .{ .width = 2, .fill = '0' }); | |
| 494 | } else { | |
| 495 | try w.writeAll("\\u{"); | |
| 496 | try w.printInt(codepoint, 16, .lower, .{}); | |
| 497 | try w.writeByte('}'); | |
| 498 | } | |
| 496 | 499 | }, |
| 497 | }; | |
| 500 | } | |
| 498 | 501 | } |
| 499 | 502 | |
| 500 | 503 | pub fn isValidId(bytes: []const u8) bool { |
lib/std/zig/Ast.zig+1-1| ... | ... | @@ -574,7 +574,7 @@ pub fn renderError(tree: Ast, parse_error: Error, w: *Writer) Writer.Error!void |
| 574 | 574 | '/' => "comment", |
| 575 | 575 | else => unreachable, |
| 576 | 576 | }, |
| 577 | std.zig.fmtChar(tok_slice[parse_error.extra.offset..][0..1]), | |
| 577 | std.zig.fmtChar(tok_slice[parse_error.extra.offset]), | |
| 578 | 578 | }); |
| 579 | 579 | }, |
| 580 | 580 |
lib/std/zig/Server.zig+2-2| ... | ... | @@ -203,8 +203,8 @@ pub const TestMetadata = struct { |
| 203 | 203 | |
| 204 | 204 | pub fn serveTestMetadata(s: *Server, test_metadata: TestMetadata) !void { |
| 205 | 205 | const header: OutMessage.TestMetadata = .{ |
| 206 | .tests_len = @as(u32, @intCast(test_metadata.names.len)), | |
| 207 | .string_bytes_len = @as(u32, @intCast(test_metadata.string_bytes.len)), | |
| 206 | .tests_len = @intCast(test_metadata.names.len), | |
| 207 | .string_bytes_len = @intCast(test_metadata.string_bytes.len), | |
| 208 | 208 | }; |
| 209 | 209 | const trailing = 2; |
| 210 | 210 | const bytes_len = @sizeOf(OutMessage.TestMetadata) + |
lib/std/zig/llvm/BitcodeReader.zig+1-1| ... | ... | @@ -177,7 +177,7 @@ pub fn next(bc: *BitcodeReader) !?Item { |
| 177 | 177 | |
| 178 | 178 | pub fn skipBlock(bc: *BitcodeReader, block: Block) !void { |
| 179 | 179 | assert(bc.bit_offset == 0); |
| 180 | try bc.reader.discardAll(4 * @as(u34, block.len)); | |
| 180 | try bc.reader.discardAll(4 * @as(usize, block.len)); | |
| 181 | 181 | try bc.endBlock(); |
| 182 | 182 | } |
| 183 | 183 |
lib/std/zon.zig+1| ... | ... | @@ -38,6 +38,7 @@ |
| 38 | 38 | |
| 39 | 39 | pub const parse = @import("zon/parse.zig"); |
| 40 | 40 | pub const stringify = @import("zon/stringify.zig"); |
| 41 | pub const Serializer = @import("zon/Serializer.zig"); | |
| 41 | 42 | |
| 42 | 43 | test { |
| 43 | 44 | _ = parse; |
lib/std/zon/Serializer.zig created+929| ... | ... | @@ -0,0 +1,929 @@ |
| 1 | //! Lower level control over serialization, you can create a new instance with `serializer`. | |
| 2 | //! | |
| 3 | //! Useful when you want control over which fields are serialized, how they're represented, | |
| 4 | //! or want to write a ZON object that does not exist in memory. | |
| 5 | //! | |
| 6 | //! You can serialize values with `value`. To serialize recursive types, the following are provided: | |
| 7 | //! * `valueMaxDepth` | |
| 8 | //! * `valueArbitraryDepth` | |
| 9 | //! | |
| 10 | //! You can also serialize values using specific notations: | |
| 11 | //! * `int` | |
| 12 | //! * `float` | |
| 13 | //! * `codePoint` | |
| 14 | //! * `tuple` | |
| 15 | //! * `tupleMaxDepth` | |
| 16 | //! * `tupleArbitraryDepth` | |
| 17 | //! * `string` | |
| 18 | //! * `multilineString` | |
| 19 | //! | |
| 20 | //! For manual serialization of containers, see: | |
| 21 | //! * `beginStruct` | |
| 22 | //! * `beginTuple` | |
| 23 | ||
| 24 | options: Options = .{}, | |
| 25 | indent_level: u8 = 0, | |
| 26 | writer: *Writer, | |
| 27 | ||
| 28 | const Serializer = @This(); | |
| 29 | const std = @import("std"); | |
| 30 | const assert = std.debug.assert; | |
| 31 | const Writer = std.Io.Writer; | |
| 32 | ||
| 33 | pub const Error = Writer.Error; | |
| 34 | pub const DepthError = Error || error{ExceededMaxDepth}; | |
| 35 | ||
| 36 | pub const Options = struct { | |
| 37 | /// If false, only syntactically necessary whitespace is emitted. | |
| 38 | whitespace: bool = true, | |
| 39 | }; | |
| 40 | ||
| 41 | /// Options for manual serialization of container types. | |
| 42 | pub const ContainerOptions = struct { | |
| 43 | /// The whitespace style that should be used for this container. Ignored if whitespace is off. | |
| 44 | whitespace_style: union(enum) { | |
| 45 | /// If true, wrap every field. If false do not. | |
| 46 | wrap: bool, | |
| 47 | /// Automatically decide whether to wrap or not based on the number of fields. Following | |
| 48 | /// the standard rule of thumb, containers with more than two fields are wrapped. | |
| 49 | fields: usize, | |
| 50 | } = .{ .wrap = true }, | |
| 51 | ||
| 52 | fn shouldWrap(self: ContainerOptions) bool { | |
| 53 | return switch (self.whitespace_style) { | |
| 54 | .wrap => |wrap| wrap, | |
| 55 | .fields => |fields| fields > 2, | |
| 56 | }; | |
| 57 | } | |
| 58 | }; | |
| 59 | ||
| 60 | /// Options for serialization of an individual value. | |
| 61 | /// | |
| 62 | /// See `SerializeOptions` for more information on these options. | |
| 63 | pub const ValueOptions = struct { | |
| 64 | emit_codepoint_literals: EmitCodepointLiterals = .never, | |
| 65 | emit_strings_as_containers: bool = false, | |
| 66 | emit_default_optional_fields: bool = true, | |
| 67 | }; | |
| 68 | ||
| 69 | /// Determines when to emit Unicode code point literals as opposed to integer literals. | |
| 70 | pub const EmitCodepointLiterals = enum { | |
| 71 | /// Never emit Unicode code point literals. | |
| 72 | never, | |
| 73 | /// Emit Unicode code point literals for any `u8` in the printable ASCII range. | |
| 74 | printable_ascii, | |
| 75 | /// Emit Unicode code point literals for any unsigned integer with 21 bits or fewer | |
| 76 | /// whose value is a valid non-surrogate code point. | |
| 77 | always, | |
| 78 | ||
| 79 | /// If the value should be emitted as a Unicode codepoint, return it as a u21. | |
| 80 | fn emitAsCodepoint(self: @This(), val: anytype) ?u21 { | |
| 81 | // Rule out incompatible integer types | |
| 82 | switch (@typeInfo(@TypeOf(val))) { | |
| 83 | .int => |int_info| if (int_info.signedness == .signed or int_info.bits > 21) { | |
| 84 | return null; | |
| 85 | }, | |
| 86 | .comptime_int => {}, | |
| 87 | else => comptime unreachable, | |
| 88 | } | |
| 89 | ||
| 90 | // Return null if the value shouldn't be printed as a Unicode codepoint, or the value casted | |
| 91 | // to a u21 if it should. | |
| 92 | switch (self) { | |
| 93 | .always => { | |
| 94 | const c = std.math.cast(u21, val) orelse return null; | |
| 95 | if (!std.unicode.utf8ValidCodepoint(c)) return null; | |
| 96 | return c; | |
| 97 | }, | |
| 98 | .printable_ascii => { | |
| 99 | const c = std.math.cast(u8, val) orelse return null; | |
| 100 | if (!std.ascii.isPrint(c)) return null; | |
| 101 | return c; | |
| 102 | }, | |
| 103 | .never => { | |
| 104 | return null; | |
| 105 | }, | |
| 106 | } | |
| 107 | } | |
| 108 | }; | |
| 109 | ||
| 110 | /// Serialize a value, similar to `serialize`. | |
| 111 | pub fn value(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 112 | comptime assert(!typeIsRecursive(@TypeOf(val))); | |
| 113 | return self.valueArbitraryDepth(val, options); | |
| 114 | } | |
| 115 | ||
| 116 | /// Serialize a value, similar to `serializeMaxDepth`. | |
| 117 | /// Can return `error.ExceededMaxDepth`. | |
| 118 | pub fn valueMaxDepth(self: *Serializer, val: anytype, options: ValueOptions, depth: usize) DepthError!void { | |
| 119 | try checkValueDepth(val, depth); | |
| 120 | return self.valueArbitraryDepth(val, options); | |
| 121 | } | |
| 122 | ||
| 123 | /// Serialize a value, similar to `serializeArbitraryDepth`. | |
| 124 | pub fn valueArbitraryDepth(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 125 | comptime assert(canSerializeType(@TypeOf(val))); | |
| 126 | switch (@typeInfo(@TypeOf(val))) { | |
| 127 | .int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| { | |
| 128 | self.codePoint(c) catch |err| switch (err) { | |
| 129 | error.InvalidCodepoint => unreachable, // Already validated | |
| 130 | else => |e| return e, | |
| 131 | }; | |
| 132 | } else { | |
| 133 | try self.int(val); | |
| 134 | }, | |
| 135 | .float, .comptime_float => try self.float(val), | |
| 136 | .bool, .null => try self.writer.print("{}", .{val}), | |
| 137 | .enum_literal => try self.ident(@tagName(val)), | |
| 138 | .@"enum" => try self.ident(@tagName(val)), | |
| 139 | .pointer => |pointer| { | |
| 140 | // Try to serialize as a string | |
| 141 | const item: ?type = switch (@typeInfo(pointer.child)) { | |
| 142 | .array => |array| array.child, | |
| 143 | else => if (pointer.size == .slice) pointer.child else null, | |
| 144 | }; | |
| 145 | if (item == u8 and | |
| 146 | (pointer.sentinel() == null or pointer.sentinel() == 0) and | |
| 147 | !options.emit_strings_as_containers) | |
| 148 | { | |
| 149 | return try self.string(val); | |
| 150 | } | |
| 151 | ||
| 152 | // Serialize as either a tuple or as the child type | |
| 153 | switch (pointer.size) { | |
| 154 | .slice => try self.tupleImpl(val, options), | |
| 155 | .one => try self.valueArbitraryDepth(val.*, options), | |
| 156 | else => comptime unreachable, | |
| 157 | } | |
| 158 | }, | |
| 159 | .array => { | |
| 160 | var container = try self.beginTuple( | |
| 161 | .{ .whitespace_style = .{ .fields = val.len } }, | |
| 162 | ); | |
| 163 | for (val) |item_val| { | |
| 164 | try container.fieldArbitraryDepth(item_val, options); | |
| 165 | } | |
| 166 | try container.end(); | |
| 167 | }, | |
| 168 | .@"struct" => |@"struct"| if (@"struct".is_tuple) { | |
| 169 | var container = try self.beginTuple( | |
| 170 | .{ .whitespace_style = .{ .fields = @"struct".fields.len } }, | |
| 171 | ); | |
| 172 | inline for (val) |field_value| { | |
| 173 | try container.fieldArbitraryDepth(field_value, options); | |
| 174 | } | |
| 175 | try container.end(); | |
| 176 | } else { | |
| 177 | // Decide which fields to emit | |
| 178 | const fields, const skipped: [@"struct".fields.len]bool = if (options.emit_default_optional_fields) b: { | |
| 179 | break :b .{ @"struct".fields.len, @splat(false) }; | |
| 180 | } else b: { | |
| 181 | var fields = @"struct".fields.len; | |
| 182 | var skipped: [@"struct".fields.len]bool = @splat(false); | |
| 183 | inline for (@"struct".fields, &skipped) |field_info, *skip| { | |
| 184 | if (field_info.default_value_ptr) |ptr| { | |
| 185 | const default: *const field_info.type = @ptrCast(@alignCast(ptr)); | |
| 186 | const field_value = @field(val, field_info.name); | |
| 187 | if (std.meta.eql(field_value, default.*)) { | |
| 188 | skip.* = true; | |
| 189 | fields -= 1; | |
| 190 | } | |
| 191 | } | |
| 192 | } | |
| 193 | break :b .{ fields, skipped }; | |
| 194 | }; | |
| 195 | ||
| 196 | // Emit those fields | |
| 197 | var container = try self.beginStruct( | |
| 198 | .{ .whitespace_style = .{ .fields = fields } }, | |
| 199 | ); | |
| 200 | inline for (@"struct".fields, skipped) |field_info, skip| { | |
| 201 | if (!skip) { | |
| 202 | try container.fieldArbitraryDepth( | |
| 203 | field_info.name, | |
| 204 | @field(val, field_info.name), | |
| 205 | options, | |
| 206 | ); | |
| 207 | } | |
| 208 | } | |
| 209 | try container.end(); | |
| 210 | }, | |
| 211 | .@"union" => |@"union"| { | |
| 212 | comptime assert(@"union".tag_type != null); | |
| 213 | switch (val) { | |
| 214 | inline else => |pl, tag| if (@TypeOf(pl) == void) | |
| 215 | try self.writer.print(".{s}", .{@tagName(tag)}) | |
| 216 | else { | |
| 217 | var container = try self.beginStruct(.{ .whitespace_style = .{ .fields = 1 } }); | |
| 218 | ||
| 219 | try container.fieldArbitraryDepth( | |
| 220 | @tagName(tag), | |
| 221 | pl, | |
| 222 | options, | |
| 223 | ); | |
| 224 | ||
| 225 | try container.end(); | |
| 226 | }, | |
| 227 | } | |
| 228 | }, | |
| 229 | .optional => if (val) |inner| { | |
| 230 | try self.valueArbitraryDepth(inner, options); | |
| 231 | } else { | |
| 232 | try self.writer.writeAll("null"); | |
| 233 | }, | |
| 234 | .vector => |vector| { | |
| 235 | var container = try self.beginTuple( | |
| 236 | .{ .whitespace_style = .{ .fields = vector.len } }, | |
| 237 | ); | |
| 238 | for (0..vector.len) |i| { | |
| 239 | try container.fieldArbitraryDepth(val[i], options); | |
| 240 | } | |
| 241 | try container.end(); | |
| 242 | }, | |
| 243 | ||
| 244 | else => comptime unreachable, | |
| 245 | } | |
| 246 | } | |
| 247 | ||
| 248 | /// Serialize an integer. | |
| 249 | pub fn int(self: *Serializer, val: anytype) Error!void { | |
| 250 | try self.writer.printInt(val, 10, .lower, .{}); | |
| 251 | } | |
| 252 | ||
| 253 | /// Serialize a float. | |
| 254 | pub fn float(self: *Serializer, val: anytype) Error!void { | |
| 255 | switch (@typeInfo(@TypeOf(val))) { | |
| 256 | .float => if (std.math.isNan(val)) { | |
| 257 | return self.writer.writeAll("nan"); | |
| 258 | } else if (std.math.isPositiveInf(val)) { | |
| 259 | return self.writer.writeAll("inf"); | |
| 260 | } else if (std.math.isNegativeInf(val)) { | |
| 261 | return self.writer.writeAll("-inf"); | |
| 262 | } else if (std.math.isNegativeZero(val)) { | |
| 263 | return self.writer.writeAll("-0.0"); | |
| 264 | } else { | |
| 265 | try self.writer.print("{d}", .{val}); | |
| 266 | }, | |
| 267 | .comptime_float => if (val == 0) { | |
| 268 | return self.writer.writeAll("0"); | |
| 269 | } else { | |
| 270 | try self.writer.print("{d}", .{val}); | |
| 271 | }, | |
| 272 | else => comptime unreachable, | |
| 273 | } | |
| 274 | } | |
| 275 | ||
| 276 | /// Serialize `name` as an identifier prefixed with `.`. | |
| 277 | /// | |
| 278 | /// Escapes the identifier if necessary. | |
| 279 | pub fn ident(self: *Serializer, name: []const u8) Error!void { | |
| 280 | try self.writer.print(".{f}", .{std.zig.fmtIdPU(name)}); | |
| 281 | } | |
| 282 | ||
| 283 | pub const CodePointError = Error || error{InvalidCodepoint}; | |
| 284 | ||
| 285 | /// Serialize `val` as a Unicode codepoint. | |
| 286 | /// | |
| 287 | /// Returns `error.InvalidCodepoint` if `val` is not a valid Unicode codepoint. | |
| 288 | pub fn codePoint(self: *Serializer, val: u21) CodePointError!void { | |
| 289 | try self.writer.print("'{f}'", .{std.zig.fmtChar(val)}); | |
| 290 | } | |
| 291 | ||
| 292 | /// Like `value`, but always serializes `val` as a tuple. | |
| 293 | /// | |
| 294 | /// Will fail at comptime if `val` is not a tuple, array, pointer to an array, or slice. | |
| 295 | pub fn tuple(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 296 | comptime assert(!typeIsRecursive(@TypeOf(val))); | |
| 297 | try self.tupleArbitraryDepth(val, options); | |
| 298 | } | |
| 299 | ||
| 300 | /// Like `tuple`, but recursive types are allowed. | |
| 301 | /// | |
| 302 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 303 | pub fn tupleMaxDepth( | |
| 304 | self: *Serializer, | |
| 305 | val: anytype, | |
| 306 | options: ValueOptions, | |
| 307 | depth: usize, | |
| 308 | ) DepthError!void { | |
| 309 | try checkValueDepth(val, depth); | |
| 310 | try self.tupleArbitraryDepth(val, options); | |
| 311 | } | |
| 312 | ||
| 313 | /// Like `tuple`, but recursive types are allowed. | |
| 314 | /// | |
| 315 | /// It is the caller's responsibility to ensure that `val` does not contain cycles. | |
| 316 | pub fn tupleArbitraryDepth( | |
| 317 | self: *Serializer, | |
| 318 | val: anytype, | |
| 319 | options: ValueOptions, | |
| 320 | ) Error!void { | |
| 321 | try self.tupleImpl(val, options); | |
| 322 | } | |
| 323 | ||
| 324 | fn tupleImpl(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 325 | comptime assert(canSerializeType(@TypeOf(val))); | |
| 326 | switch (@typeInfo(@TypeOf(val))) { | |
| 327 | .@"struct" => { | |
| 328 | var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } }); | |
| 329 | inline for (val) |item_val| { | |
| 330 | try container.fieldArbitraryDepth(item_val, options); | |
| 331 | } | |
| 332 | try container.end(); | |
| 333 | }, | |
| 334 | .pointer, .array => { | |
| 335 | var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } }); | |
| 336 | for (val) |item_val| { | |
| 337 | try container.fieldArbitraryDepth(item_val, options); | |
| 338 | } | |
| 339 | try container.end(); | |
| 340 | }, | |
| 341 | else => comptime unreachable, | |
| 342 | } | |
| 343 | } | |
| 344 | ||
| 345 | /// Like `value`, but always serializes `val` as a string. | |
| 346 | pub fn string(self: *Serializer, val: []const u8) Error!void { | |
| 347 | try self.writer.print("\"{f}\"", .{std.zig.fmtString(val)}); | |
| 348 | } | |
| 349 | ||
| 350 | /// Options for formatting multiline strings. | |
| 351 | pub const MultilineStringOptions = struct { | |
| 352 | /// If top level is true, whitespace before and after the multiline string is elided. | |
| 353 | /// If it is true, a newline is printed, then the value, followed by a newline, and if | |
| 354 | /// whitespace is true any necessary indentation follows. | |
| 355 | top_level: bool = false, | |
| 356 | }; | |
| 357 | ||
| 358 | pub const MultilineStringError = Error || error{InnerCarriageReturn}; | |
| 359 | ||
| 360 | /// Like `value`, but always serializes to a multiline string literal. | |
| 361 | /// | |
| 362 | /// Returns `error.InnerCarriageReturn` if `val` contains a CR not followed by a newline, | |
| 363 | /// since multiline strings cannot represent CR without a following newline. | |
| 364 | pub fn multilineString( | |
| 365 | self: *Serializer, | |
| 366 | val: []const u8, | |
| 367 | options: MultilineStringOptions, | |
| 368 | ) MultilineStringError!void { | |
| 369 | // Make sure the string does not contain any carriage returns not followed by a newline | |
| 370 | var i: usize = 0; | |
| 371 | while (i < val.len) : (i += 1) { | |
| 372 | if (val[i] == '\r') { | |
| 373 | if (i + 1 < val.len) { | |
| 374 | if (val[i + 1] == '\n') { | |
| 375 | i += 1; | |
| 376 | continue; | |
| 377 | } | |
| 378 | } | |
| 379 | return error.InnerCarriageReturn; | |
| 380 | } | |
| 381 | } | |
| 382 | ||
| 383 | if (!options.top_level) { | |
| 384 | try self.newline(); | |
| 385 | try self.indent(); | |
| 386 | } | |
| 387 | ||
| 388 | try self.writer.writeAll("\\\\"); | |
| 389 | for (val) |c| { | |
| 390 | if (c != '\r') { | |
| 391 | try self.writer.writeByte(c); // We write newlines here even if whitespace off | |
| 392 | if (c == '\n') { | |
| 393 | try self.indent(); | |
| 394 | try self.writer.writeAll("\\\\"); | |
| 395 | } | |
| 396 | } | |
| 397 | } | |
| 398 | ||
| 399 | if (!options.top_level) { | |
| 400 | try self.writer.writeByte('\n'); // Even if whitespace off | |
| 401 | try self.indent(); | |
| 402 | } | |
| 403 | } | |
| 404 | ||
| 405 | /// Create a `Struct` for writing ZON structs field by field. | |
| 406 | pub fn beginStruct(self: *Serializer, options: ContainerOptions) Error!Struct { | |
| 407 | return Struct.begin(self, options); | |
| 408 | } | |
| 409 | ||
| 410 | /// Creates a `Tuple` for writing ZON tuples field by field. | |
| 411 | pub fn beginTuple(self: *Serializer, options: ContainerOptions) Error!Tuple { | |
| 412 | return Tuple.begin(self, options); | |
| 413 | } | |
| 414 | ||
| 415 | fn indent(self: *Serializer) Error!void { | |
| 416 | if (self.options.whitespace) { | |
| 417 | try self.writer.splatByteAll(' ', 4 * self.indent_level); | |
| 418 | } | |
| 419 | } | |
| 420 | ||
| 421 | fn newline(self: *Serializer) Error!void { | |
| 422 | if (self.options.whitespace) { | |
| 423 | try self.writer.writeByte('\n'); | |
| 424 | } | |
| 425 | } | |
| 426 | ||
| 427 | fn newlineOrSpace(self: *Serializer, len: usize) Error!void { | |
| 428 | if (self.containerShouldWrap(len)) { | |
| 429 | try self.newline(); | |
| 430 | } else { | |
| 431 | try self.space(); | |
| 432 | } | |
| 433 | } | |
| 434 | ||
| 435 | fn space(self: *Serializer) Error!void { | |
| 436 | if (self.options.whitespace) { | |
| 437 | try self.writer.writeByte(' '); | |
| 438 | } | |
| 439 | } | |
| 440 | ||
| 441 | /// Writes ZON tuples field by field. | |
| 442 | pub const Tuple = struct { | |
| 443 | container: Container, | |
| 444 | ||
| 445 | fn begin(parent: *Serializer, options: ContainerOptions) Error!Tuple { | |
| 446 | return .{ | |
| 447 | .container = try Container.begin(parent, .anon, options), | |
| 448 | }; | |
| 449 | } | |
| 450 | ||
| 451 | /// Finishes serializing the tuple. | |
| 452 | /// | |
| 453 | /// Prints a trailing comma as configured when appropriate, and the closing bracket. | |
| 454 | pub fn end(self: *Tuple) Error!void { | |
| 455 | try self.container.end(); | |
| 456 | self.* = undefined; | |
| 457 | } | |
| 458 | ||
| 459 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`. | |
| 460 | pub fn field( | |
| 461 | self: *Tuple, | |
| 462 | val: anytype, | |
| 463 | options: ValueOptions, | |
| 464 | ) Error!void { | |
| 465 | try self.container.field(null, val, options); | |
| 466 | } | |
| 467 | ||
| 468 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`. | |
| 469 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 470 | pub fn fieldMaxDepth( | |
| 471 | self: *Tuple, | |
| 472 | val: anytype, | |
| 473 | options: ValueOptions, | |
| 474 | depth: usize, | |
| 475 | ) DepthError!void { | |
| 476 | try self.container.fieldMaxDepth(null, val, options, depth); | |
| 477 | } | |
| 478 | ||
| 479 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by | |
| 480 | /// `valueArbitraryDepth`. | |
| 481 | pub fn fieldArbitraryDepth( | |
| 482 | self: *Tuple, | |
| 483 | val: anytype, | |
| 484 | options: ValueOptions, | |
| 485 | ) Error!void { | |
| 486 | try self.container.fieldArbitraryDepth(null, val, options); | |
| 487 | } | |
| 488 | ||
| 489 | /// Starts a field with a struct as a value. Returns the struct. | |
| 490 | pub fn beginStructField( | |
| 491 | self: *Tuple, | |
| 492 | options: ContainerOptions, | |
| 493 | ) Error!Struct { | |
| 494 | try self.fieldPrefix(); | |
| 495 | return self.container.serializer.beginStruct(options); | |
| 496 | } | |
| 497 | ||
| 498 | /// Starts a field with a tuple as a value. Returns the tuple. | |
| 499 | pub fn beginTupleField( | |
| 500 | self: *Tuple, | |
| 501 | options: ContainerOptions, | |
| 502 | ) Error!Tuple { | |
| 503 | try self.fieldPrefix(); | |
| 504 | return self.container.serializer.beginTuple(options); | |
| 505 | } | |
| 506 | ||
| 507 | /// Print a field prefix. This prints any necessary commas, and whitespace as | |
| 508 | /// configured. Useful if you want to serialize the field value yourself. | |
| 509 | pub fn fieldPrefix(self: *Tuple) Error!void { | |
| 510 | try self.container.fieldPrefix(null); | |
| 511 | } | |
| 512 | }; | |
| 513 | ||
| 514 | /// Writes ZON structs field by field. | |
| 515 | pub const Struct = struct { | |
| 516 | container: Container, | |
| 517 | ||
| 518 | fn begin(parent: *Serializer, options: ContainerOptions) Error!Struct { | |
| 519 | return .{ | |
| 520 | .container = try Container.begin(parent, .named, options), | |
| 521 | }; | |
| 522 | } | |
| 523 | ||
| 524 | /// Finishes serializing the struct. | |
| 525 | /// | |
| 526 | /// Prints a trailing comma as configured when appropriate, and the closing bracket. | |
| 527 | pub fn end(self: *Struct) Error!void { | |
| 528 | try self.container.end(); | |
| 529 | self.* = undefined; | |
| 530 | } | |
| 531 | ||
| 532 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`. | |
| 533 | pub fn field( | |
| 534 | self: *Struct, | |
| 535 | name: []const u8, | |
| 536 | val: anytype, | |
| 537 | options: ValueOptions, | |
| 538 | ) Error!void { | |
| 539 | try self.container.field(name, val, options); | |
| 540 | } | |
| 541 | ||
| 542 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`. | |
| 543 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 544 | pub fn fieldMaxDepth( | |
| 545 | self: *Struct, | |
| 546 | name: []const u8, | |
| 547 | val: anytype, | |
| 548 | options: ValueOptions, | |
| 549 | depth: usize, | |
| 550 | ) DepthError!void { | |
| 551 | try self.container.fieldMaxDepth(name, val, options, depth); | |
| 552 | } | |
| 553 | ||
| 554 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by | |
| 555 | /// `valueArbitraryDepth`. | |
| 556 | pub fn fieldArbitraryDepth( | |
| 557 | self: *Struct, | |
| 558 | name: []const u8, | |
| 559 | val: anytype, | |
| 560 | options: ValueOptions, | |
| 561 | ) Error!void { | |
| 562 | try self.container.fieldArbitraryDepth(name, val, options); | |
| 563 | } | |
| 564 | ||
| 565 | /// Starts a field with a struct as a value. Returns the struct. | |
| 566 | pub fn beginStructField( | |
| 567 | self: *Struct, | |
| 568 | name: []const u8, | |
| 569 | options: ContainerOptions, | |
| 570 | ) Error!Struct { | |
| 571 | try self.fieldPrefix(name); | |
| 572 | return self.container.serializer.beginStruct(options); | |
| 573 | } | |
| 574 | ||
| 575 | /// Starts a field with a tuple as a value. Returns the tuple. | |
| 576 | pub fn beginTupleField( | |
| 577 | self: *Struct, | |
| 578 | name: []const u8, | |
| 579 | options: ContainerOptions, | |
| 580 | ) Error!Tuple { | |
| 581 | try self.fieldPrefix(name); | |
| 582 | return self.container.serializer.beginTuple(options); | |
| 583 | } | |
| 584 | ||
| 585 | /// Print a field prefix. This prints any necessary commas, the field name (escaped if | |
| 586 | /// necessary) and whitespace as configured. Useful if you want to serialize the field | |
| 587 | /// value yourself. | |
| 588 | pub fn fieldPrefix(self: *Struct, name: []const u8) Error!void { | |
| 589 | try self.container.fieldPrefix(name); | |
| 590 | } | |
| 591 | }; | |
| 592 | ||
| 593 | const Container = struct { | |
| 594 | const FieldStyle = enum { named, anon }; | |
| 595 | ||
| 596 | serializer: *Serializer, | |
| 597 | field_style: FieldStyle, | |
| 598 | options: ContainerOptions, | |
| 599 | empty: bool, | |
| 600 | ||
| 601 | fn begin( | |
| 602 | sz: *Serializer, | |
| 603 | field_style: FieldStyle, | |
| 604 | options: ContainerOptions, | |
| 605 | ) Error!Container { | |
| 606 | if (options.shouldWrap()) sz.indent_level +|= 1; | |
| 607 | try sz.writer.writeAll(".{"); | |
| 608 | return .{ | |
| 609 | .serializer = sz, | |
| 610 | .field_style = field_style, | |
| 611 | .options = options, | |
| 612 | .empty = true, | |
| 613 | }; | |
| 614 | } | |
| 615 | ||
| 616 | fn end(self: *Container) Error!void { | |
| 617 | if (self.options.shouldWrap()) self.serializer.indent_level -|= 1; | |
| 618 | if (!self.empty) { | |
| 619 | if (self.options.shouldWrap()) { | |
| 620 | if (self.serializer.options.whitespace) { | |
| 621 | try self.serializer.writer.writeByte(','); | |
| 622 | } | |
| 623 | try self.serializer.newline(); | |
| 624 | try self.serializer.indent(); | |
| 625 | } else if (!self.shouldElideSpaces()) { | |
| 626 | try self.serializer.space(); | |
| 627 | } | |
| 628 | } | |
| 629 | try self.serializer.writer.writeByte('}'); | |
| 630 | self.* = undefined; | |
| 631 | } | |
| 632 | ||
| 633 | fn fieldPrefix(self: *Container, name: ?[]const u8) Error!void { | |
| 634 | if (!self.empty) { | |
| 635 | try self.serializer.writer.writeByte(','); | |
| 636 | } | |
| 637 | self.empty = false; | |
| 638 | if (self.options.shouldWrap()) { | |
| 639 | try self.serializer.newline(); | |
| 640 | } else if (!self.shouldElideSpaces()) { | |
| 641 | try self.serializer.space(); | |
| 642 | } | |
| 643 | if (self.options.shouldWrap()) try self.serializer.indent(); | |
| 644 | if (name) |n| { | |
| 645 | try self.serializer.ident(n); | |
| 646 | try self.serializer.space(); | |
| 647 | try self.serializer.writer.writeByte('='); | |
| 648 | try self.serializer.space(); | |
| 649 | } | |
| 650 | } | |
| 651 | ||
| 652 | fn field( | |
| 653 | self: *Container, | |
| 654 | name: ?[]const u8, | |
| 655 | val: anytype, | |
| 656 | options: ValueOptions, | |
| 657 | ) Error!void { | |
| 658 | comptime assert(!typeIsRecursive(@TypeOf(val))); | |
| 659 | try self.fieldArbitraryDepth(name, val, options); | |
| 660 | } | |
| 661 | ||
| 662 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 663 | fn fieldMaxDepth( | |
| 664 | self: *Container, | |
| 665 | name: ?[]const u8, | |
| 666 | val: anytype, | |
| 667 | options: ValueOptions, | |
| 668 | depth: usize, | |
| 669 | ) DepthError!void { | |
| 670 | try checkValueDepth(val, depth); | |
| 671 | try self.fieldArbitraryDepth(name, val, options); | |
| 672 | } | |
| 673 | ||
| 674 | fn fieldArbitraryDepth( | |
| 675 | self: *Container, | |
| 676 | name: ?[]const u8, | |
| 677 | val: anytype, | |
| 678 | options: ValueOptions, | |
| 679 | ) Error!void { | |
| 680 | try self.fieldPrefix(name); | |
| 681 | try self.serializer.valueArbitraryDepth(val, options); | |
| 682 | } | |
| 683 | ||
| 684 | fn shouldElideSpaces(self: *const Container) bool { | |
| 685 | return switch (self.options.whitespace_style) { | |
| 686 | .fields => |fields| self.field_style != .named and fields == 1, | |
| 687 | else => false, | |
| 688 | }; | |
| 689 | } | |
| 690 | }; | |
| 691 | ||
| 692 | test Serializer { | |
| 693 | var discarding: Writer.Discarding = .init(&.{}); | |
| 694 | var s: Serializer = .{ .writer = &discarding.writer }; | |
| 695 | var vec2 = try s.beginStruct(.{}); | |
| 696 | try vec2.field("x", 1.5, .{}); | |
| 697 | try vec2.fieldPrefix("prefix"); | |
| 698 | try s.value(2.5, .{}); | |
| 699 | try vec2.end(); | |
| 700 | } | |
| 701 | ||
| 702 | inline fn typeIsRecursive(comptime T: type) bool { | |
| 703 | return comptime typeIsRecursiveInner(T, &.{}); | |
| 704 | } | |
| 705 | ||
| 706 | fn typeIsRecursiveInner(comptime T: type, comptime prev_visited: []const type) bool { | |
| 707 | for (prev_visited) |V| { | |
| 708 | if (V == T) return true; | |
| 709 | } | |
| 710 | const visited = prev_visited ++ .{T}; | |
| 711 | ||
| 712 | return switch (@typeInfo(T)) { | |
| 713 | .pointer => |pointer| typeIsRecursiveInner(pointer.child, visited), | |
| 714 | .optional => |optional| typeIsRecursiveInner(optional.child, visited), | |
| 715 | .array => |array| typeIsRecursiveInner(array.child, visited), | |
| 716 | .vector => |vector| typeIsRecursiveInner(vector.child, visited), | |
| 717 | .@"struct" => |@"struct"| for (@"struct".fields) |field| { | |
| 718 | if (typeIsRecursiveInner(field.type, visited)) break true; | |
| 719 | } else false, | |
| 720 | .@"union" => |@"union"| inline for (@"union".fields) |field| { | |
| 721 | if (typeIsRecursiveInner(field.type, visited)) break true; | |
| 722 | } else false, | |
| 723 | else => false, | |
| 724 | }; | |
| 725 | } | |
| 726 | ||
| 727 | test typeIsRecursive { | |
| 728 | try std.testing.expect(!typeIsRecursive(bool)); | |
| 729 | try std.testing.expect(!typeIsRecursive(struct { x: i32, y: i32 })); | |
| 730 | try std.testing.expect(!typeIsRecursive(struct { i32, i32 })); | |
| 731 | try std.testing.expect(typeIsRecursive(struct { x: i32, y: i32, z: *@This() })); | |
| 732 | try std.testing.expect(typeIsRecursive(struct { | |
| 733 | a: struct { | |
| 734 | const A = @This(); | |
| 735 | b: struct { | |
| 736 | c: *struct { | |
| 737 | a: ?A, | |
| 738 | }, | |
| 739 | }, | |
| 740 | }, | |
| 741 | })); | |
| 742 | try std.testing.expect(typeIsRecursive(struct { | |
| 743 | a: [3]*@This(), | |
| 744 | })); | |
| 745 | try std.testing.expect(typeIsRecursive(struct { | |
| 746 | a: union { a: i32, b: *@This() }, | |
| 747 | })); | |
| 748 | } | |
| 749 | ||
| 750 | fn checkValueDepth(val: anytype, depth: usize) error{ExceededMaxDepth}!void { | |
| 751 | if (depth == 0) return error.ExceededMaxDepth; | |
| 752 | const child_depth = depth - 1; | |
| 753 | ||
| 754 | switch (@typeInfo(@TypeOf(val))) { | |
| 755 | .pointer => |pointer| switch (pointer.size) { | |
| 756 | .one => try checkValueDepth(val.*, child_depth), | |
| 757 | .slice => for (val) |item| { | |
| 758 | try checkValueDepth(item, child_depth); | |
| 759 | }, | |
| 760 | .c, .many => {}, | |
| 761 | }, | |
| 762 | .array => for (val) |item| { | |
| 763 | try checkValueDepth(item, child_depth); | |
| 764 | }, | |
| 765 | .@"struct" => |@"struct"| inline for (@"struct".fields) |field_info| { | |
| 766 | try checkValueDepth(@field(val, field_info.name), child_depth); | |
| 767 | }, | |
| 768 | .@"union" => |@"union"| if (@"union".tag_type == null) { | |
| 769 | return; | |
| 770 | } else switch (val) { | |
| 771 | inline else => |payload| { | |
| 772 | return checkValueDepth(payload, child_depth); | |
| 773 | }, | |
| 774 | }, | |
| 775 | .optional => if (val) |inner| try checkValueDepth(inner, child_depth), | |
| 776 | else => {}, | |
| 777 | } | |
| 778 | } | |
| 779 | ||
| 780 | fn expectValueDepthEquals(expected: usize, v: anytype) !void { | |
| 781 | try checkValueDepth(v, expected); | |
| 782 | try std.testing.expectError(error.ExceededMaxDepth, checkValueDepth(v, expected - 1)); | |
| 783 | } | |
| 784 | ||
| 785 | test checkValueDepth { | |
| 786 | try expectValueDepthEquals(1, 10); | |
| 787 | try expectValueDepthEquals(2, .{ .x = 1, .y = 2 }); | |
| 788 | try expectValueDepthEquals(2, .{ 1, 2 }); | |
| 789 | try expectValueDepthEquals(3, .{ 1, .{ 2, 3 } }); | |
| 790 | try expectValueDepthEquals(3, .{ .{ 1, 2 }, 3 }); | |
| 791 | try expectValueDepthEquals(3, .{ .x = 0, .y = 1, .z = .{ .x = 3 } }); | |
| 792 | try expectValueDepthEquals(3, .{ .x = 0, .y = .{ .x = 1 }, .z = 2 }); | |
| 793 | try expectValueDepthEquals(3, .{ .x = .{ .x = 0 }, .y = 1, .z = 2 }); | |
| 794 | try expectValueDepthEquals(2, @as(?u32, 1)); | |
| 795 | try expectValueDepthEquals(1, @as(?u32, null)); | |
| 796 | try expectValueDepthEquals(1, null); | |
| 797 | try expectValueDepthEquals(2, &1); | |
| 798 | try expectValueDepthEquals(3, &@as(?u32, 1)); | |
| 799 | ||
| 800 | const Union = union(enum) { | |
| 801 | x: u32, | |
| 802 | y: struct { x: u32 }, | |
| 803 | }; | |
| 804 | try expectValueDepthEquals(2, Union{ .x = 1 }); | |
| 805 | try expectValueDepthEquals(3, Union{ .y = .{ .x = 1 } }); | |
| 806 | ||
| 807 | const Recurse = struct { r: ?*const @This() }; | |
| 808 | try expectValueDepthEquals(2, Recurse{ .r = null }); | |
| 809 | try expectValueDepthEquals(5, Recurse{ .r = &Recurse{ .r = null } }); | |
| 810 | try expectValueDepthEquals(8, Recurse{ .r = &Recurse{ .r = &Recurse{ .r = null } } }); | |
| 811 | ||
| 812 | try expectValueDepthEquals(2, @as([]const u8, &.{ 1, 2, 3 })); | |
| 813 | try expectValueDepthEquals(3, @as([]const []const u8, &.{&.{ 1, 2, 3 }})); | |
| 814 | } | |
| 815 | ||
| 816 | inline fn canSerializeType(T: type) bool { | |
| 817 | comptime return canSerializeTypeInner(T, &.{}, false); | |
| 818 | } | |
| 819 | ||
| 820 | fn canSerializeTypeInner( | |
| 821 | T: type, | |
| 822 | /// Visited structs and unions, to avoid infinite recursion. | |
| 823 | /// Tracking more types is unnecessary, and a little complex due to optional nesting. | |
| 824 | visited: []const type, | |
| 825 | parent_is_optional: bool, | |
| 826 | ) bool { | |
| 827 | return switch (@typeInfo(T)) { | |
| 828 | .bool, | |
| 829 | .int, | |
| 830 | .float, | |
| 831 | .comptime_float, | |
| 832 | .comptime_int, | |
| 833 | .null, | |
| 834 | .enum_literal, | |
| 835 | => true, | |
| 836 | ||
| 837 | .noreturn, | |
| 838 | .void, | |
| 839 | .type, | |
| 840 | .undefined, | |
| 841 | .error_union, | |
| 842 | .error_set, | |
| 843 | .@"fn", | |
| 844 | .frame, | |
| 845 | .@"anyframe", | |
| 846 | .@"opaque", | |
| 847 | => false, | |
| 848 | ||
| 849 | .@"enum" => |@"enum"| @"enum".is_exhaustive, | |
| 850 | ||
| 851 | .pointer => |pointer| switch (pointer.size) { | |
| 852 | .one => canSerializeTypeInner(pointer.child, visited, parent_is_optional), | |
| 853 | .slice => canSerializeTypeInner(pointer.child, visited, false), | |
| 854 | .many, .c => false, | |
| 855 | }, | |
| 856 | ||
| 857 | .optional => |optional| if (parent_is_optional) | |
| 858 | false | |
| 859 | else | |
| 860 | canSerializeTypeInner(optional.child, visited, true), | |
| 861 | ||
| 862 | .array => |array| canSerializeTypeInner(array.child, visited, false), | |
| 863 | .vector => |vector| canSerializeTypeInner(vector.child, visited, false), | |
| 864 | ||
| 865 | .@"struct" => |@"struct"| { | |
| 866 | for (visited) |V| if (T == V) return true; | |
| 867 | const new_visited = visited ++ .{T}; | |
| 868 | for (@"struct".fields) |field| { | |
| 869 | if (!canSerializeTypeInner(field.type, new_visited, false)) return false; | |
| 870 | } | |
| 871 | return true; | |
| 872 | }, | |
| 873 | .@"union" => |@"union"| { | |
| 874 | for (visited) |V| if (T == V) return true; | |
| 875 | const new_visited = visited ++ .{T}; | |
| 876 | if (@"union".tag_type == null) return false; | |
| 877 | for (@"union".fields) |field| { | |
| 878 | if (field.type != void and !canSerializeTypeInner(field.type, new_visited, false)) { | |
| 879 | return false; | |
| 880 | } | |
| 881 | } | |
| 882 | return true; | |
| 883 | }, | |
| 884 | }; | |
| 885 | } | |
| 886 | ||
| 887 | test canSerializeType { | |
| 888 | try std.testing.expect(!comptime canSerializeType(void)); | |
| 889 | try std.testing.expect(!comptime canSerializeType(struct { f: [*]u8 })); | |
| 890 | try std.testing.expect(!comptime canSerializeType(struct { error{foo} })); | |
| 891 | try std.testing.expect(!comptime canSerializeType(union(enum) { a: void, f: [*c]u8 })); | |
| 892 | try std.testing.expect(!comptime canSerializeType(@Vector(0, [*c]u8))); | |
| 893 | try std.testing.expect(!comptime canSerializeType(*?[*c]u8)); | |
| 894 | try std.testing.expect(!comptime canSerializeType(enum(u8) { _ })); | |
| 895 | try std.testing.expect(!comptime canSerializeType(union { foo: void })); | |
| 896 | try std.testing.expect(comptime canSerializeType(union(enum) { foo: void })); | |
| 897 | try std.testing.expect(comptime canSerializeType(comptime_float)); | |
| 898 | try std.testing.expect(comptime canSerializeType(comptime_int)); | |
| 899 | try std.testing.expect(!comptime canSerializeType(struct { comptime foo: ??u8 = null })); | |
| 900 | try std.testing.expect(comptime canSerializeType(@TypeOf(.foo))); | |
| 901 | try std.testing.expect(comptime canSerializeType(?u8)); | |
| 902 | try std.testing.expect(comptime canSerializeType(*?*u8)); | |
| 903 | try std.testing.expect(comptime canSerializeType(?struct { | |
| 904 | foo: ?struct { | |
| 905 | ?union(enum) { | |
| 906 | a: ?@Vector(0, ?*u8), | |
| 907 | }, | |
| 908 | ?struct { | |
| 909 | f: ?[]?u8, | |
| 910 | }, | |
| 911 | }, | |
| 912 | })); | |
| 913 | try std.testing.expect(!comptime canSerializeType(??u8)); | |
| 914 | try std.testing.expect(!comptime canSerializeType(?*?u8)); | |
| 915 | try std.testing.expect(!comptime canSerializeType(*?*?*u8)); | |
| 916 | try std.testing.expect(comptime canSerializeType(struct { x: comptime_int = 2 })); | |
| 917 | try std.testing.expect(comptime canSerializeType(struct { x: comptime_float = 2 })); | |
| 918 | try std.testing.expect(comptime canSerializeType(struct { comptime_int })); | |
| 919 | try std.testing.expect(comptime canSerializeType(struct { comptime x: @TypeOf(.foo) = .foo })); | |
| 920 | const Recursive = struct { foo: ?*@This() }; | |
| 921 | try std.testing.expect(comptime canSerializeType(Recursive)); | |
| 922 | ||
| 923 | // Make sure we validate nested optional before we early out due to already having seen | |
| 924 | // a type recursion! | |
| 925 | try std.testing.expect(!comptime canSerializeType(struct { | |
| 926 | add_to_visited: ?u8, | |
| 927 | retrieve_from_visited: ??u8, | |
| 928 | })); | |
| 929 | } |
lib/std/zon/parse.zig+5-5| ... | ... | @@ -64,14 +64,14 @@ pub const Error = union(enum) { |
| 64 | 64 | } |
| 65 | 65 | }; |
| 66 | 66 | |
| 67 | fn formatMessage(self: []const u8, w: *std.io.Writer) std.io.Writer.Error!void { | |
| 67 | fn formatMessage(self: []const u8, w: *std.Io.Writer) std.Io.Writer.Error!void { | |
| 68 | 68 | // Just writes the string for now, but we're keeping this behind a formatter so we have |
| 69 | 69 | // the option to extend it in the future to print more advanced messages (like `Error` |
| 70 | 70 | // does) without breaking the API. |
| 71 | 71 | try w.writeAll(self); |
| 72 | 72 | } |
| 73 | 73 | |
| 74 | pub fn fmtMessage(self: Note, diag: *const Diagnostics) std.fmt.Formatter([]const u8, Note.formatMessage) { | |
| 74 | pub fn fmtMessage(self: Note, diag: *const Diagnostics) std.fmt.Alt([]const u8, Note.formatMessage) { | |
| 75 | 75 | return .{ .data = switch (self) { |
| 76 | 76 | .zoir => |note| note.msg.get(diag.zoir), |
| 77 | 77 | .type_check => |note| note.msg, |
| ... | ... | @@ -147,14 +147,14 @@ pub const Error = union(enum) { |
| 147 | 147 | diag: *const Diagnostics, |
| 148 | 148 | }; |
| 149 | 149 | |
| 150 | fn formatMessage(self: FormatMessage, w: *std.io.Writer) std.io.Writer.Error!void { | |
| 150 | fn formatMessage(self: FormatMessage, w: *std.Io.Writer) std.Io.Writer.Error!void { | |
| 151 | 151 | switch (self.err) { |
| 152 | 152 | .zoir => |err| try w.writeAll(err.msg.get(self.diag.zoir)), |
| 153 | 153 | .type_check => |tc| try w.writeAll(tc.message), |
| 154 | 154 | } |
| 155 | 155 | } |
| 156 | 156 | |
| 157 | pub fn fmtMessage(self: @This(), diag: *const Diagnostics) std.fmt.Formatter(FormatMessage, formatMessage) { | |
| 157 | pub fn fmtMessage(self: @This(), diag: *const Diagnostics) std.fmt.Alt(FormatMessage, formatMessage) { | |
| 158 | 158 | return .{ .data = .{ |
| 159 | 159 | .err = self, |
| 160 | 160 | .diag = diag, |
| ... | ... | @@ -226,7 +226,7 @@ pub const Diagnostics = struct { |
| 226 | 226 | return .{ .diag = self }; |
| 227 | 227 | } |
| 228 | 228 | |
| 229 | pub fn format(self: *const @This(), w: *std.io.Writer) std.io.Writer.Error!void { | |
| 229 | pub fn format(self: *const @This(), w: *std.Io.Writer) std.Io.Writer.Error!void { | |
| 230 | 230 | var errors = self.iterateErrors(); |
| 231 | 231 | while (errors.next()) |err| { |
| 232 | 232 | const loc = err.getLocation(self); |
lib/std/zon/stringify.zig+29-962| ... | ... | @@ -22,14 +22,14 @@ |
| 22 | 22 | |
| 23 | 23 | const std = @import("std"); |
| 24 | 24 | const assert = std.debug.assert; |
| 25 | const Writer = std.io.Writer; | |
| 25 | const Writer = std.Io.Writer; | |
| 26 | const Serializer = std.zon.Serializer; | |
| 26 | 27 | |
| 27 | /// Options for `serialize`. | |
| 28 | 28 | pub const SerializeOptions = struct { |
| 29 | 29 | /// If false, whitespace is omitted. Otherwise whitespace is emitted in standard Zig style. |
| 30 | 30 | whitespace: bool = true, |
| 31 | 31 | /// Determines when to emit Unicode code point literals as opposed to integer literals. |
| 32 | emit_codepoint_literals: EmitCodepointLiterals = .never, | |
| 32 | emit_codepoint_literals: Serializer.EmitCodepointLiterals = .never, | |
| 33 | 33 | /// If true, slices of `u8`s, and pointers to arrays of `u8` are serialized as containers. |
| 34 | 34 | /// Otherwise they are serialized as string literals. |
| 35 | 35 | emit_strings_as_containers: bool = false, |
| ... | ... | @@ -93,102 +93,6 @@ pub fn serializeArbitraryDepth( |
| 93 | 93 | }); |
| 94 | 94 | } |
| 95 | 95 | |
| 96 | inline fn typeIsRecursive(comptime T: type) bool { | |
| 97 | return comptime typeIsRecursiveInner(T, &.{}); | |
| 98 | } | |
| 99 | ||
| 100 | fn typeIsRecursiveInner(comptime T: type, comptime prev_visited: []const type) bool { | |
| 101 | for (prev_visited) |V| { | |
| 102 | if (V == T) return true; | |
| 103 | } | |
| 104 | const visited = prev_visited ++ .{T}; | |
| 105 | ||
| 106 | return switch (@typeInfo(T)) { | |
| 107 | .pointer => |pointer| typeIsRecursiveInner(pointer.child, visited), | |
| 108 | .optional => |optional| typeIsRecursiveInner(optional.child, visited), | |
| 109 | .array => |array| typeIsRecursiveInner(array.child, visited), | |
| 110 | .vector => |vector| typeIsRecursiveInner(vector.child, visited), | |
| 111 | .@"struct" => |@"struct"| for (@"struct".fields) |field| { | |
| 112 | if (typeIsRecursiveInner(field.type, visited)) break true; | |
| 113 | } else false, | |
| 114 | .@"union" => |@"union"| inline for (@"union".fields) |field| { | |
| 115 | if (typeIsRecursiveInner(field.type, visited)) break true; | |
| 116 | } else false, | |
| 117 | else => false, | |
| 118 | }; | |
| 119 | } | |
| 120 | ||
| 121 | inline fn canSerializeType(T: type) bool { | |
| 122 | comptime return canSerializeTypeInner(T, &.{}, false); | |
| 123 | } | |
| 124 | ||
| 125 | fn canSerializeTypeInner( | |
| 126 | T: type, | |
| 127 | /// Visited structs and unions, to avoid infinite recursion. | |
| 128 | /// Tracking more types is unnecessary, and a little complex due to optional nesting. | |
| 129 | visited: []const type, | |
| 130 | parent_is_optional: bool, | |
| 131 | ) bool { | |
| 132 | return switch (@typeInfo(T)) { | |
| 133 | .bool, | |
| 134 | .int, | |
| 135 | .float, | |
| 136 | .comptime_float, | |
| 137 | .comptime_int, | |
| 138 | .null, | |
| 139 | .enum_literal, | |
| 140 | => true, | |
| 141 | ||
| 142 | .noreturn, | |
| 143 | .void, | |
| 144 | .type, | |
| 145 | .undefined, | |
| 146 | .error_union, | |
| 147 | .error_set, | |
| 148 | .@"fn", | |
| 149 | .frame, | |
| 150 | .@"anyframe", | |
| 151 | .@"opaque", | |
| 152 | => false, | |
| 153 | ||
| 154 | .@"enum" => |@"enum"| @"enum".is_exhaustive, | |
| 155 | ||
| 156 | .pointer => |pointer| switch (pointer.size) { | |
| 157 | .one => canSerializeTypeInner(pointer.child, visited, parent_is_optional), | |
| 158 | .slice => canSerializeTypeInner(pointer.child, visited, false), | |
| 159 | .many, .c => false, | |
| 160 | }, | |
| 161 | ||
| 162 | .optional => |optional| if (parent_is_optional) | |
| 163 | false | |
| 164 | else | |
| 165 | canSerializeTypeInner(optional.child, visited, true), | |
| 166 | ||
| 167 | .array => |array| canSerializeTypeInner(array.child, visited, false), | |
| 168 | .vector => |vector| canSerializeTypeInner(vector.child, visited, false), | |
| 169 | ||
| 170 | .@"struct" => |@"struct"| { | |
| 171 | for (visited) |V| if (T == V) return true; | |
| 172 | const new_visited = visited ++ .{T}; | |
| 173 | for (@"struct".fields) |field| { | |
| 174 | if (!canSerializeTypeInner(field.type, new_visited, false)) return false; | |
| 175 | } | |
| 176 | return true; | |
| 177 | }, | |
| 178 | .@"union" => |@"union"| { | |
| 179 | for (visited) |V| if (T == V) return true; | |
| 180 | const new_visited = visited ++ .{T}; | |
| 181 | if (@"union".tag_type == null) return false; | |
| 182 | for (@"union".fields) |field| { | |
| 183 | if (field.type != void and !canSerializeTypeInner(field.type, new_visited, false)) { | |
| 184 | return false; | |
| 185 | } | |
| 186 | } | |
| 187 | return true; | |
| 188 | }, | |
| 189 | }; | |
| 190 | } | |
| 191 | ||
| 192 | 96 | fn isNestedOptional(T: type) bool { |
| 193 | 97 | comptime switch (@typeInfo(T)) { |
| 194 | 98 | .optional => |optional| return isNestedOptionalInner(optional.child), |
| ... | ... | @@ -210,852 +114,13 @@ fn isNestedOptionalInner(T: type) bool { |
| 210 | 114 | } |
| 211 | 115 | } |
| 212 | 116 | |
| 213 | test "std.zon stringify canSerializeType" { | |
| 214 | try std.testing.expect(!comptime canSerializeType(void)); | |
| 215 | try std.testing.expect(!comptime canSerializeType(struct { f: [*]u8 })); | |
| 216 | try std.testing.expect(!comptime canSerializeType(struct { error{foo} })); | |
| 217 | try std.testing.expect(!comptime canSerializeType(union(enum) { a: void, f: [*c]u8 })); | |
| 218 | try std.testing.expect(!comptime canSerializeType(@Vector(0, [*c]u8))); | |
| 219 | try std.testing.expect(!comptime canSerializeType(*?[*c]u8)); | |
| 220 | try std.testing.expect(!comptime canSerializeType(enum(u8) { _ })); | |
| 221 | try std.testing.expect(!comptime canSerializeType(union { foo: void })); | |
| 222 | try std.testing.expect(comptime canSerializeType(union(enum) { foo: void })); | |
| 223 | try std.testing.expect(comptime canSerializeType(comptime_float)); | |
| 224 | try std.testing.expect(comptime canSerializeType(comptime_int)); | |
| 225 | try std.testing.expect(!comptime canSerializeType(struct { comptime foo: ??u8 = null })); | |
| 226 | try std.testing.expect(comptime canSerializeType(@TypeOf(.foo))); | |
| 227 | try std.testing.expect(comptime canSerializeType(?u8)); | |
| 228 | try std.testing.expect(comptime canSerializeType(*?*u8)); | |
| 229 | try std.testing.expect(comptime canSerializeType(?struct { | |
| 230 | foo: ?struct { | |
| 231 | ?union(enum) { | |
| 232 | a: ?@Vector(0, ?*u8), | |
| 233 | }, | |
| 234 | ?struct { | |
| 235 | f: ?[]?u8, | |
| 236 | }, | |
| 237 | }, | |
| 238 | })); | |
| 239 | try std.testing.expect(!comptime canSerializeType(??u8)); | |
| 240 | try std.testing.expect(!comptime canSerializeType(?*?u8)); | |
| 241 | try std.testing.expect(!comptime canSerializeType(*?*?*u8)); | |
| 242 | try std.testing.expect(comptime canSerializeType(struct { x: comptime_int = 2 })); | |
| 243 | try std.testing.expect(comptime canSerializeType(struct { x: comptime_float = 2 })); | |
| 244 | try std.testing.expect(comptime canSerializeType(struct { comptime_int })); | |
| 245 | try std.testing.expect(comptime canSerializeType(struct { comptime x: @TypeOf(.foo) = .foo })); | |
| 246 | const Recursive = struct { foo: ?*@This() }; | |
| 247 | try std.testing.expect(comptime canSerializeType(Recursive)); | |
| 248 | ||
| 249 | // Make sure we validate nested optional before we early out due to already having seen | |
| 250 | // a type recursion! | |
| 251 | try std.testing.expect(!comptime canSerializeType(struct { | |
| 252 | add_to_visited: ?u8, | |
| 253 | retrieve_from_visited: ??u8, | |
| 254 | })); | |
| 255 | } | |
| 256 | ||
| 257 | test "std.zon typeIsRecursive" { | |
| 258 | try std.testing.expect(!typeIsRecursive(bool)); | |
| 259 | try std.testing.expect(!typeIsRecursive(struct { x: i32, y: i32 })); | |
| 260 | try std.testing.expect(!typeIsRecursive(struct { i32, i32 })); | |
| 261 | try std.testing.expect(typeIsRecursive(struct { x: i32, y: i32, z: *@This() })); | |
| 262 | try std.testing.expect(typeIsRecursive(struct { | |
| 263 | a: struct { | |
| 264 | const A = @This(); | |
| 265 | b: struct { | |
| 266 | c: *struct { | |
| 267 | a: ?A, | |
| 268 | }, | |
| 269 | }, | |
| 270 | }, | |
| 271 | })); | |
| 272 | try std.testing.expect(typeIsRecursive(struct { | |
| 273 | a: [3]*@This(), | |
| 274 | })); | |
| 275 | try std.testing.expect(typeIsRecursive(struct { | |
| 276 | a: union { a: i32, b: *@This() }, | |
| 277 | })); | |
| 278 | } | |
| 279 | ||
| 280 | fn checkValueDepth(val: anytype, depth: usize) error{ExceededMaxDepth}!void { | |
| 281 | if (depth == 0) return error.ExceededMaxDepth; | |
| 282 | const child_depth = depth - 1; | |
| 283 | ||
| 284 | switch (@typeInfo(@TypeOf(val))) { | |
| 285 | .pointer => |pointer| switch (pointer.size) { | |
| 286 | .one => try checkValueDepth(val.*, child_depth), | |
| 287 | .slice => for (val) |item| { | |
| 288 | try checkValueDepth(item, child_depth); | |
| 289 | }, | |
| 290 | .c, .many => {}, | |
| 291 | }, | |
| 292 | .array => for (val) |item| { | |
| 293 | try checkValueDepth(item, child_depth); | |
| 294 | }, | |
| 295 | .@"struct" => |@"struct"| inline for (@"struct".fields) |field_info| { | |
| 296 | try checkValueDepth(@field(val, field_info.name), child_depth); | |
| 297 | }, | |
| 298 | .@"union" => |@"union"| if (@"union".tag_type == null) { | |
| 299 | return; | |
| 300 | } else switch (val) { | |
| 301 | inline else => |payload| { | |
| 302 | return checkValueDepth(payload, child_depth); | |
| 303 | }, | |
| 304 | }, | |
| 305 | .optional => if (val) |inner| try checkValueDepth(inner, child_depth), | |
| 306 | else => {}, | |
| 307 | } | |
| 308 | } | |
| 309 | ||
| 310 | fn expectValueDepthEquals(expected: usize, value: anytype) !void { | |
| 311 | try checkValueDepth(value, expected); | |
| 312 | try std.testing.expectError(error.ExceededMaxDepth, checkValueDepth(value, expected - 1)); | |
| 313 | } | |
| 314 | ||
| 315 | test "std.zon checkValueDepth" { | |
| 316 | try expectValueDepthEquals(1, 10); | |
| 317 | try expectValueDepthEquals(2, .{ .x = 1, .y = 2 }); | |
| 318 | try expectValueDepthEquals(2, .{ 1, 2 }); | |
| 319 | try expectValueDepthEquals(3, .{ 1, .{ 2, 3 } }); | |
| 320 | try expectValueDepthEquals(3, .{ .{ 1, 2 }, 3 }); | |
| 321 | try expectValueDepthEquals(3, .{ .x = 0, .y = 1, .z = .{ .x = 3 } }); | |
| 322 | try expectValueDepthEquals(3, .{ .x = 0, .y = .{ .x = 1 }, .z = 2 }); | |
| 323 | try expectValueDepthEquals(3, .{ .x = .{ .x = 0 }, .y = 1, .z = 2 }); | |
| 324 | try expectValueDepthEquals(2, @as(?u32, 1)); | |
| 325 | try expectValueDepthEquals(1, @as(?u32, null)); | |
| 326 | try expectValueDepthEquals(1, null); | |
| 327 | try expectValueDepthEquals(2, &1); | |
| 328 | try expectValueDepthEquals(3, &@as(?u32, 1)); | |
| 329 | ||
| 330 | const Union = union(enum) { | |
| 331 | x: u32, | |
| 332 | y: struct { x: u32 }, | |
| 333 | }; | |
| 334 | try expectValueDepthEquals(2, Union{ .x = 1 }); | |
| 335 | try expectValueDepthEquals(3, Union{ .y = .{ .x = 1 } }); | |
| 336 | ||
| 337 | const Recurse = struct { r: ?*const @This() }; | |
| 338 | try expectValueDepthEquals(2, Recurse{ .r = null }); | |
| 339 | try expectValueDepthEquals(5, Recurse{ .r = &Recurse{ .r = null } }); | |
| 340 | try expectValueDepthEquals(8, Recurse{ .r = &Recurse{ .r = &Recurse{ .r = null } } }); | |
| 341 | ||
| 342 | try expectValueDepthEquals(2, @as([]const u8, &.{ 1, 2, 3 })); | |
| 343 | try expectValueDepthEquals(3, @as([]const []const u8, &.{&.{ 1, 2, 3 }})); | |
| 344 | } | |
| 345 | ||
| 346 | /// Determines when to emit Unicode code point literals as opposed to integer literals. | |
| 347 | pub const EmitCodepointLiterals = enum { | |
| 348 | /// Never emit Unicode code point literals. | |
| 349 | never, | |
| 350 | /// Emit Unicode code point literals for any `u8` in the printable ASCII range. | |
| 351 | printable_ascii, | |
| 352 | /// Emit Unicode code point literals for any unsigned integer with 21 bits or fewer | |
| 353 | /// whose value is a valid non-surrogate code point. | |
| 354 | always, | |
| 355 | ||
| 356 | /// If the value should be emitted as a Unicode codepoint, return it as a u21. | |
| 357 | fn emitAsCodepoint(self: @This(), val: anytype) ?u21 { | |
| 358 | // Rule out incompatible integer types | |
| 359 | switch (@typeInfo(@TypeOf(val))) { | |
| 360 | .int => |int_info| if (int_info.signedness == .signed or int_info.bits > 21) { | |
| 361 | return null; | |
| 362 | }, | |
| 363 | .comptime_int => {}, | |
| 364 | else => comptime unreachable, | |
| 365 | } | |
| 366 | ||
| 367 | // Return null if the value shouldn't be printed as a Unicode codepoint, or the value casted | |
| 368 | // to a u21 if it should. | |
| 369 | switch (self) { | |
| 370 | .always => { | |
| 371 | const c = std.math.cast(u21, val) orelse return null; | |
| 372 | if (!std.unicode.utf8ValidCodepoint(c)) return null; | |
| 373 | return c; | |
| 374 | }, | |
| 375 | .printable_ascii => { | |
| 376 | const c = std.math.cast(u8, val) orelse return null; | |
| 377 | if (!std.ascii.isPrint(c)) return null; | |
| 378 | return c; | |
| 379 | }, | |
| 380 | .never => { | |
| 381 | return null; | |
| 382 | }, | |
| 383 | } | |
| 384 | } | |
| 385 | }; | |
| 386 | ||
| 387 | /// Options for serialization of an individual value. | |
| 388 | /// | |
| 389 | /// See `SerializeOptions` for more information on these options. | |
| 390 | pub const ValueOptions = struct { | |
| 391 | emit_codepoint_literals: EmitCodepointLiterals = .never, | |
| 392 | emit_strings_as_containers: bool = false, | |
| 393 | emit_default_optional_fields: bool = true, | |
| 394 | }; | |
| 395 | ||
| 396 | /// Options for manual serialization of container types. | |
| 397 | pub const SerializeContainerOptions = struct { | |
| 398 | /// The whitespace style that should be used for this container. Ignored if whitespace is off. | |
| 399 | whitespace_style: union(enum) { | |
| 400 | /// If true, wrap every field. If false do not. | |
| 401 | wrap: bool, | |
| 402 | /// Automatically decide whether to wrap or not based on the number of fields. Following | |
| 403 | /// the standard rule of thumb, containers with more than two fields are wrapped. | |
| 404 | fields: usize, | |
| 405 | } = .{ .wrap = true }, | |
| 406 | ||
| 407 | fn shouldWrap(self: SerializeContainerOptions) bool { | |
| 408 | return switch (self.whitespace_style) { | |
| 409 | .wrap => |wrap| wrap, | |
| 410 | .fields => |fields| fields > 2, | |
| 411 | }; | |
| 412 | } | |
| 413 | }; | |
| 414 | ||
| 415 | /// Lower level control over serialization, you can create a new instance with `serializer`. | |
| 416 | /// | |
| 417 | /// Useful when you want control over which fields are serialized, how they're represented, | |
| 418 | /// or want to write a ZON object that does not exist in memory. | |
| 419 | /// | |
| 420 | /// You can serialize values with `value`. To serialize recursive types, the following are provided: | |
| 421 | /// * `valueMaxDepth` | |
| 422 | /// * `valueArbitraryDepth` | |
| 423 | /// | |
| 424 | /// You can also serialize values using specific notations: | |
| 425 | /// * `int` | |
| 426 | /// * `float` | |
| 427 | /// * `codePoint` | |
| 428 | /// * `tuple` | |
| 429 | /// * `tupleMaxDepth` | |
| 430 | /// * `tupleArbitraryDepth` | |
| 431 | /// * `string` | |
| 432 | /// * `multilineString` | |
| 433 | /// | |
| 434 | /// For manual serialization of containers, see: | |
| 435 | /// * `beginStruct` | |
| 436 | /// * `beginTuple` | |
| 437 | pub const Serializer = struct { | |
| 438 | options: Options = .{}, | |
| 439 | indent_level: u8 = 0, | |
| 440 | writer: *Writer, | |
| 441 | ||
| 442 | pub const Error = Writer.Error; | |
| 443 | pub const DepthError = Error || error{ExceededMaxDepth}; | |
| 444 | ||
| 445 | pub const Options = struct { | |
| 446 | /// If false, only syntactically necessary whitespace is emitted. | |
| 447 | whitespace: bool = true, | |
| 448 | }; | |
| 449 | ||
| 450 | /// Serialize a value, similar to `serialize`. | |
| 451 | pub fn value(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 452 | comptime assert(!typeIsRecursive(@TypeOf(val))); | |
| 453 | return self.valueArbitraryDepth(val, options); | |
| 454 | } | |
| 455 | ||
| 456 | /// Serialize a value, similar to `serializeMaxDepth`. | |
| 457 | /// Can return `error.ExceededMaxDepth`. | |
| 458 | pub fn valueMaxDepth(self: *Serializer, val: anytype, options: ValueOptions, depth: usize) DepthError!void { | |
| 459 | try checkValueDepth(val, depth); | |
| 460 | return self.valueArbitraryDepth(val, options); | |
| 461 | } | |
| 462 | ||
| 463 | /// Serialize a value, similar to `serializeArbitraryDepth`. | |
| 464 | pub fn valueArbitraryDepth(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 465 | comptime assert(canSerializeType(@TypeOf(val))); | |
| 466 | switch (@typeInfo(@TypeOf(val))) { | |
| 467 | .int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| { | |
| 468 | self.codePoint(c) catch |err| switch (err) { | |
| 469 | error.InvalidCodepoint => unreachable, // Already validated | |
| 470 | else => |e| return e, | |
| 471 | }; | |
| 472 | } else { | |
| 473 | try self.int(val); | |
| 474 | }, | |
| 475 | .float, .comptime_float => try self.float(val), | |
| 476 | .bool, .null => try std.fmt.format(self.writer, "{}", .{val}), | |
| 477 | .enum_literal => try self.ident(@tagName(val)), | |
| 478 | .@"enum" => try self.ident(@tagName(val)), | |
| 479 | .pointer => |pointer| { | |
| 480 | // Try to serialize as a string | |
| 481 | const item: ?type = switch (@typeInfo(pointer.child)) { | |
| 482 | .array => |array| array.child, | |
| 483 | else => if (pointer.size == .slice) pointer.child else null, | |
| 484 | }; | |
| 485 | if (item == u8 and | |
| 486 | (pointer.sentinel() == null or pointer.sentinel() == 0) and | |
| 487 | !options.emit_strings_as_containers) | |
| 488 | { | |
| 489 | return try self.string(val); | |
| 490 | } | |
| 491 | ||
| 492 | // Serialize as either a tuple or as the child type | |
| 493 | switch (pointer.size) { | |
| 494 | .slice => try self.tupleImpl(val, options), | |
| 495 | .one => try self.valueArbitraryDepth(val.*, options), | |
| 496 | else => comptime unreachable, | |
| 497 | } | |
| 498 | }, | |
| 499 | .array => { | |
| 500 | var container = try self.beginTuple( | |
| 501 | .{ .whitespace_style = .{ .fields = val.len } }, | |
| 502 | ); | |
| 503 | for (val) |item_val| { | |
| 504 | try container.fieldArbitraryDepth(item_val, options); | |
| 505 | } | |
| 506 | try container.end(); | |
| 507 | }, | |
| 508 | .@"struct" => |@"struct"| if (@"struct".is_tuple) { | |
| 509 | var container = try self.beginTuple( | |
| 510 | .{ .whitespace_style = .{ .fields = @"struct".fields.len } }, | |
| 511 | ); | |
| 512 | inline for (val) |field_value| { | |
| 513 | try container.fieldArbitraryDepth(field_value, options); | |
| 514 | } | |
| 515 | try container.end(); | |
| 516 | } else { | |
| 517 | // Decide which fields to emit | |
| 518 | const fields, const skipped: [@"struct".fields.len]bool = if (options.emit_default_optional_fields) b: { | |
| 519 | break :b .{ @"struct".fields.len, @splat(false) }; | |
| 520 | } else b: { | |
| 521 | var fields = @"struct".fields.len; | |
| 522 | var skipped: [@"struct".fields.len]bool = @splat(false); | |
| 523 | inline for (@"struct".fields, &skipped) |field_info, *skip| { | |
| 524 | if (field_info.default_value_ptr) |ptr| { | |
| 525 | const default: *const field_info.type = @ptrCast(@alignCast(ptr)); | |
| 526 | const field_value = @field(val, field_info.name); | |
| 527 | if (std.meta.eql(field_value, default.*)) { | |
| 528 | skip.* = true; | |
| 529 | fields -= 1; | |
| 530 | } | |
| 531 | } | |
| 532 | } | |
| 533 | break :b .{ fields, skipped }; | |
| 534 | }; | |
| 535 | ||
| 536 | // Emit those fields | |
| 537 | var container = try self.beginStruct( | |
| 538 | .{ .whitespace_style = .{ .fields = fields } }, | |
| 539 | ); | |
| 540 | inline for (@"struct".fields, skipped) |field_info, skip| { | |
| 541 | if (!skip) { | |
| 542 | try container.fieldArbitraryDepth( | |
| 543 | field_info.name, | |
| 544 | @field(val, field_info.name), | |
| 545 | options, | |
| 546 | ); | |
| 547 | } | |
| 548 | } | |
| 549 | try container.end(); | |
| 550 | }, | |
| 551 | .@"union" => |@"union"| { | |
| 552 | comptime assert(@"union".tag_type != null); | |
| 553 | switch (val) { | |
| 554 | inline else => |pl, tag| if (@TypeOf(pl) == void) | |
| 555 | try self.writer.print(".{s}", .{@tagName(tag)}) | |
| 556 | else { | |
| 557 | var container = try self.beginStruct(.{ .whitespace_style = .{ .fields = 1 } }); | |
| 558 | ||
| 559 | try container.fieldArbitraryDepth( | |
| 560 | @tagName(tag), | |
| 561 | pl, | |
| 562 | options, | |
| 563 | ); | |
| 564 | ||
| 565 | try container.end(); | |
| 566 | }, | |
| 567 | } | |
| 568 | }, | |
| 569 | .optional => if (val) |inner| { | |
| 570 | try self.valueArbitraryDepth(inner, options); | |
| 571 | } else { | |
| 572 | try self.writer.writeAll("null"); | |
| 573 | }, | |
| 574 | .vector => |vector| { | |
| 575 | var container = try self.beginTuple( | |
| 576 | .{ .whitespace_style = .{ .fields = vector.len } }, | |
| 577 | ); | |
| 578 | for (0..vector.len) |i| { | |
| 579 | try container.fieldArbitraryDepth(val[i], options); | |
| 580 | } | |
| 581 | try container.end(); | |
| 582 | }, | |
| 583 | ||
| 584 | else => comptime unreachable, | |
| 585 | } | |
| 586 | } | |
| 587 | ||
| 588 | /// Serialize an integer. | |
| 589 | pub fn int(self: *Serializer, val: anytype) Error!void { | |
| 590 | try self.writer.printIntOptions(val, 10, .lower, .{}); | |
| 591 | } | |
| 592 | ||
| 593 | /// Serialize a float. | |
| 594 | pub fn float(self: *Serializer, val: anytype) Error!void { | |
| 595 | switch (@typeInfo(@TypeOf(val))) { | |
| 596 | .float => if (std.math.isNan(val)) { | |
| 597 | return self.writer.writeAll("nan"); | |
| 598 | } else if (std.math.isPositiveInf(val)) { | |
| 599 | return self.writer.writeAll("inf"); | |
| 600 | } else if (std.math.isNegativeInf(val)) { | |
| 601 | return self.writer.writeAll("-inf"); | |
| 602 | } else if (std.math.isNegativeZero(val)) { | |
| 603 | return self.writer.writeAll("-0.0"); | |
| 604 | } else { | |
| 605 | try std.fmt.format(self.writer, "{d}", .{val}); | |
| 606 | }, | |
| 607 | .comptime_float => if (val == 0) { | |
| 608 | return self.writer.writeAll("0"); | |
| 609 | } else { | |
| 610 | try std.fmt.format(self.writer, "{d}", .{val}); | |
| 611 | }, | |
| 612 | else => comptime unreachable, | |
| 613 | } | |
| 614 | } | |
| 615 | ||
| 616 | /// Serialize `name` as an identifier prefixed with `.`. | |
| 617 | /// | |
| 618 | /// Escapes the identifier if necessary. | |
| 619 | pub fn ident(self: *Serializer, name: []const u8) Error!void { | |
| 620 | try self.writer.print(".{fp_}", .{std.zig.fmtId(name)}); | |
| 621 | } | |
| 622 | ||
| 623 | pub const CodePointError = Error || error{InvalidCodepoint}; | |
| 624 | ||
| 625 | /// Serialize `val` as a Unicode codepoint. | |
| 626 | /// | |
| 627 | /// Returns `error.InvalidCodepoint` if `val` is not a valid Unicode codepoint. | |
| 628 | pub fn codePoint(self: *Serializer, val: u21) CodePointError!void { | |
| 629 | var buf: [8]u8 = undefined; | |
| 630 | const len = std.unicode.utf8Encode(val, &buf) catch return error.InvalidCodepoint; | |
| 631 | const str = buf[0..len]; | |
| 632 | try std.fmt.format(self.writer, "'{f'}'", .{std.zig.fmtEscapes(str)}); | |
| 633 | } | |
| 634 | ||
| 635 | /// Like `value`, but always serializes `val` as a tuple. | |
| 636 | /// | |
| 637 | /// Will fail at comptime if `val` is not a tuple, array, pointer to an array, or slice. | |
| 638 | pub fn tuple(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 639 | comptime assert(!typeIsRecursive(@TypeOf(val))); | |
| 640 | try self.tupleArbitraryDepth(val, options); | |
| 641 | } | |
| 642 | ||
| 643 | /// Like `tuple`, but recursive types are allowed. | |
| 644 | /// | |
| 645 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 646 | pub fn tupleMaxDepth( | |
| 647 | self: *Serializer, | |
| 648 | val: anytype, | |
| 649 | options: ValueOptions, | |
| 650 | depth: usize, | |
| 651 | ) DepthError!void { | |
| 652 | try checkValueDepth(val, depth); | |
| 653 | try self.tupleArbitraryDepth(val, options); | |
| 654 | } | |
| 655 | ||
| 656 | /// Like `tuple`, but recursive types are allowed. | |
| 657 | /// | |
| 658 | /// It is the caller's responsibility to ensure that `val` does not contain cycles. | |
| 659 | pub fn tupleArbitraryDepth( | |
| 660 | self: *Serializer, | |
| 661 | val: anytype, | |
| 662 | options: ValueOptions, | |
| 663 | ) Error!void { | |
| 664 | try self.tupleImpl(val, options); | |
| 665 | } | |
| 666 | ||
| 667 | fn tupleImpl(self: *Serializer, val: anytype, options: ValueOptions) Error!void { | |
| 668 | comptime assert(canSerializeType(@TypeOf(val))); | |
| 669 | switch (@typeInfo(@TypeOf(val))) { | |
| 670 | .@"struct" => { | |
| 671 | var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } }); | |
| 672 | inline for (val) |item_val| { | |
| 673 | try container.fieldArbitraryDepth(item_val, options); | |
| 674 | } | |
| 675 | try container.end(); | |
| 676 | }, | |
| 677 | .pointer, .array => { | |
| 678 | var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } }); | |
| 679 | for (val) |item_val| { | |
| 680 | try container.fieldArbitraryDepth(item_val, options); | |
| 681 | } | |
| 682 | try container.end(); | |
| 683 | }, | |
| 684 | else => comptime unreachable, | |
| 685 | } | |
| 686 | } | |
| 687 | ||
| 688 | /// Like `value`, but always serializes `val` as a string. | |
| 689 | pub fn string(self: *Serializer, val: []const u8) Error!void { | |
| 690 | try std.fmt.format(self.writer, "\"{f}\"", .{std.zig.fmtEscapes(val)}); | |
| 691 | } | |
| 692 | ||
| 693 | /// Options for formatting multiline strings. | |
| 694 | pub const MultilineStringOptions = struct { | |
| 695 | /// If top level is true, whitespace before and after the multiline string is elided. | |
| 696 | /// If it is true, a newline is printed, then the value, followed by a newline, and if | |
| 697 | /// whitespace is true any necessary indentation follows. | |
| 698 | top_level: bool = false, | |
| 699 | }; | |
| 700 | ||
| 701 | pub const MultilineStringError = Error || error{InnerCarriageReturn}; | |
| 702 | ||
| 703 | /// Like `value`, but always serializes to a multiline string literal. | |
| 704 | /// | |
| 705 | /// Returns `error.InnerCarriageReturn` if `val` contains a CR not followed by a newline, | |
| 706 | /// since multiline strings cannot represent CR without a following newline. | |
| 707 | pub fn multilineString( | |
| 708 | self: *Serializer, | |
| 709 | val: []const u8, | |
| 710 | options: MultilineStringOptions, | |
| 711 | ) MultilineStringError!void { | |
| 712 | // Make sure the string does not contain any carriage returns not followed by a newline | |
| 713 | var i: usize = 0; | |
| 714 | while (i < val.len) : (i += 1) { | |
| 715 | if (val[i] == '\r') { | |
| 716 | if (i + 1 < val.len) { | |
| 717 | if (val[i + 1] == '\n') { | |
| 718 | i += 1; | |
| 719 | continue; | |
| 720 | } | |
| 721 | } | |
| 722 | return error.InnerCarriageReturn; | |
| 723 | } | |
| 724 | } | |
| 725 | ||
| 726 | if (!options.top_level) { | |
| 727 | try self.newline(); | |
| 728 | try self.indent(); | |
| 729 | } | |
| 730 | ||
| 731 | try self.writer.writeAll("\\\\"); | |
| 732 | for (val) |c| { | |
| 733 | if (c != '\r') { | |
| 734 | try self.writer.writeByte(c); // We write newlines here even if whitespace off | |
| 735 | if (c == '\n') { | |
| 736 | try self.indent(); | |
| 737 | try self.writer.writeAll("\\\\"); | |
| 738 | } | |
| 739 | } | |
| 740 | } | |
| 741 | ||
| 742 | if (!options.top_level) { | |
| 743 | try self.writer.writeByte('\n'); // Even if whitespace off | |
| 744 | try self.indent(); | |
| 745 | } | |
| 746 | } | |
| 747 | ||
| 748 | /// Create a `Struct` for writing ZON structs field by field. | |
| 749 | pub fn beginStruct( | |
| 750 | self: *Serializer, | |
| 751 | options: SerializeContainerOptions, | |
| 752 | ) Error!Struct { | |
| 753 | return Struct.begin(self, options); | |
| 754 | } | |
| 755 | ||
| 756 | /// Creates a `Tuple` for writing ZON tuples field by field. | |
| 757 | pub fn beginTuple( | |
| 758 | self: *Serializer, | |
| 759 | options: SerializeContainerOptions, | |
| 760 | ) Error!Tuple { | |
| 761 | return Tuple.begin(self, options); | |
| 762 | } | |
| 763 | ||
| 764 | fn indent(self: *Serializer) Error!void { | |
| 765 | if (self.options.whitespace) { | |
| 766 | try self.writer.splatByteAll(' ', 4 * self.indent_level); | |
| 767 | } | |
| 768 | } | |
| 769 | ||
| 770 | fn newline(self: *Serializer) Error!void { | |
| 771 | if (self.options.whitespace) { | |
| 772 | try self.writer.writeByte('\n'); | |
| 773 | } | |
| 774 | } | |
| 775 | ||
| 776 | fn newlineOrSpace(self: *Serializer, len: usize) Error!void { | |
| 777 | if (self.containerShouldWrap(len)) { | |
| 778 | try self.newline(); | |
| 779 | } else { | |
| 780 | try self.space(); | |
| 781 | } | |
| 782 | } | |
| 783 | ||
| 784 | fn space(self: *Serializer) Error!void { | |
| 785 | if (self.options.whitespace) { | |
| 786 | try self.writer.writeByte(' '); | |
| 787 | } | |
| 788 | } | |
| 789 | ||
| 790 | /// Writes ZON tuples field by field. | |
| 791 | pub const Tuple = struct { | |
| 792 | container: Container, | |
| 793 | ||
| 794 | fn begin(parent: *Serializer, options: SerializeContainerOptions) Error!Tuple { | |
| 795 | return .{ | |
| 796 | .container = try Container.begin(parent, .anon, options), | |
| 797 | }; | |
| 798 | } | |
| 799 | ||
| 800 | /// Finishes serializing the tuple. | |
| 801 | /// | |
| 802 | /// Prints a trailing comma as configured when appropriate, and the closing bracket. | |
| 803 | pub fn end(self: *Tuple) Error!void { | |
| 804 | try self.container.end(); | |
| 805 | self.* = undefined; | |
| 806 | } | |
| 807 | ||
| 808 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`. | |
| 809 | pub fn field( | |
| 810 | self: *Tuple, | |
| 811 | val: anytype, | |
| 812 | options: ValueOptions, | |
| 813 | ) Error!void { | |
| 814 | try self.container.field(null, val, options); | |
| 815 | } | |
| 816 | ||
| 817 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`. | |
| 818 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 819 | pub fn fieldMaxDepth( | |
| 820 | self: *Tuple, | |
| 821 | val: anytype, | |
| 822 | options: ValueOptions, | |
| 823 | depth: usize, | |
| 824 | ) DepthError!void { | |
| 825 | try self.container.fieldMaxDepth(null, val, options, depth); | |
| 826 | } | |
| 827 | ||
| 828 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by | |
| 829 | /// `valueArbitraryDepth`. | |
| 830 | pub fn fieldArbitraryDepth( | |
| 831 | self: *Tuple, | |
| 832 | val: anytype, | |
| 833 | options: ValueOptions, | |
| 834 | ) Error!void { | |
| 835 | try self.container.fieldArbitraryDepth(null, val, options); | |
| 836 | } | |
| 837 | ||
| 838 | /// Starts a field with a struct as a value. Returns the struct. | |
| 839 | pub fn beginStructField( | |
| 840 | self: *Tuple, | |
| 841 | options: SerializeContainerOptions, | |
| 842 | ) Error!Struct { | |
| 843 | try self.fieldPrefix(); | |
| 844 | return self.container.serializer.beginStruct(options); | |
| 845 | } | |
| 846 | ||
| 847 | /// Starts a field with a tuple as a value. Returns the tuple. | |
| 848 | pub fn beginTupleField( | |
| 849 | self: *Tuple, | |
| 850 | options: SerializeContainerOptions, | |
| 851 | ) Error!Tuple { | |
| 852 | try self.fieldPrefix(); | |
| 853 | return self.container.serializer.beginTuple(options); | |
| 854 | } | |
| 855 | ||
| 856 | /// Print a field prefix. This prints any necessary commas, and whitespace as | |
| 857 | /// configured. Useful if you want to serialize the field value yourself. | |
| 858 | pub fn fieldPrefix(self: *Tuple) Error!void { | |
| 859 | try self.container.fieldPrefix(null); | |
| 860 | } | |
| 861 | }; | |
| 862 | ||
| 863 | /// Writes ZON structs field by field. | |
| 864 | pub const Struct = struct { | |
| 865 | container: Container, | |
| 866 | ||
| 867 | fn begin(parent: *Serializer, options: SerializeContainerOptions) Error!Struct { | |
| 868 | return .{ | |
| 869 | .container = try Container.begin(parent, .named, options), | |
| 870 | }; | |
| 871 | } | |
| 872 | ||
| 873 | /// Finishes serializing the struct. | |
| 874 | /// | |
| 875 | /// Prints a trailing comma as configured when appropriate, and the closing bracket. | |
| 876 | pub fn end(self: *Struct) Error!void { | |
| 877 | try self.container.end(); | |
| 878 | self.* = undefined; | |
| 879 | } | |
| 880 | ||
| 881 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`. | |
| 882 | pub fn field( | |
| 883 | self: *Struct, | |
| 884 | name: []const u8, | |
| 885 | val: anytype, | |
| 886 | options: ValueOptions, | |
| 887 | ) Error!void { | |
| 888 | try self.container.field(name, val, options); | |
| 889 | } | |
| 890 | ||
| 891 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`. | |
| 892 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 893 | pub fn fieldMaxDepth( | |
| 894 | self: *Struct, | |
| 895 | name: []const u8, | |
| 896 | val: anytype, | |
| 897 | options: ValueOptions, | |
| 898 | depth: usize, | |
| 899 | ) DepthError!void { | |
| 900 | try self.container.fieldMaxDepth(name, val, options, depth); | |
| 901 | } | |
| 902 | ||
| 903 | /// Serialize a field. Equivalent to calling `fieldPrefix` followed by | |
| 904 | /// `valueArbitraryDepth`. | |
| 905 | pub fn fieldArbitraryDepth( | |
| 906 | self: *Struct, | |
| 907 | name: []const u8, | |
| 908 | val: anytype, | |
| 909 | options: ValueOptions, | |
| 910 | ) Error!void { | |
| 911 | try self.container.fieldArbitraryDepth(name, val, options); | |
| 912 | } | |
| 913 | ||
| 914 | /// Starts a field with a struct as a value. Returns the struct. | |
| 915 | pub fn beginStructField( | |
| 916 | self: *Struct, | |
| 917 | name: []const u8, | |
| 918 | options: SerializeContainerOptions, | |
| 919 | ) Error!Struct { | |
| 920 | try self.fieldPrefix(name); | |
| 921 | return self.container.serializer.beginStruct(options); | |
| 922 | } | |
| 923 | ||
| 924 | /// Starts a field with a tuple as a value. Returns the tuple. | |
| 925 | pub fn beginTupleField( | |
| 926 | self: *Struct, | |
| 927 | name: []const u8, | |
| 928 | options: SerializeContainerOptions, | |
| 929 | ) Error!Tuple { | |
| 930 | try self.fieldPrefix(name); | |
| 931 | return self.container.serializer.beginTuple(options); | |
| 932 | } | |
| 933 | ||
| 934 | /// Print a field prefix. This prints any necessary commas, the field name (escaped if | |
| 935 | /// necessary) and whitespace as configured. Useful if you want to serialize the field | |
| 936 | /// value yourself. | |
| 937 | pub fn fieldPrefix(self: *Struct, name: []const u8) Error!void { | |
| 938 | try self.container.fieldPrefix(name); | |
| 939 | } | |
| 940 | }; | |
| 941 | ||
| 942 | const Container = struct { | |
| 943 | const FieldStyle = enum { named, anon }; | |
| 944 | ||
| 945 | serializer: *Serializer, | |
| 946 | field_style: FieldStyle, | |
| 947 | options: SerializeContainerOptions, | |
| 948 | empty: bool, | |
| 949 | ||
| 950 | fn begin( | |
| 951 | sz: *Serializer, | |
| 952 | field_style: FieldStyle, | |
| 953 | options: SerializeContainerOptions, | |
| 954 | ) Error!Container { | |
| 955 | if (options.shouldWrap()) sz.indent_level +|= 1; | |
| 956 | try sz.writer.writeAll(".{"); | |
| 957 | return .{ | |
| 958 | .serializer = sz, | |
| 959 | .field_style = field_style, | |
| 960 | .options = options, | |
| 961 | .empty = true, | |
| 962 | }; | |
| 963 | } | |
| 964 | ||
| 965 | fn end(self: *Container) Error!void { | |
| 966 | if (self.options.shouldWrap()) self.serializer.indent_level -|= 1; | |
| 967 | if (!self.empty) { | |
| 968 | if (self.options.shouldWrap()) { | |
| 969 | if (self.serializer.options.whitespace) { | |
| 970 | try self.serializer.writer.writeByte(','); | |
| 971 | } | |
| 972 | try self.serializer.newline(); | |
| 973 | try self.serializer.indent(); | |
| 974 | } else if (!self.shouldElideSpaces()) { | |
| 975 | try self.serializer.space(); | |
| 976 | } | |
| 977 | } | |
| 978 | try self.serializer.writer.writeByte('}'); | |
| 979 | self.* = undefined; | |
| 980 | } | |
| 981 | ||
| 982 | fn fieldPrefix(self: *Container, name: ?[]const u8) Error!void { | |
| 983 | if (!self.empty) { | |
| 984 | try self.serializer.writer.writeByte(','); | |
| 985 | } | |
| 986 | self.empty = false; | |
| 987 | if (self.options.shouldWrap()) { | |
| 988 | try self.serializer.newline(); | |
| 989 | } else if (!self.shouldElideSpaces()) { | |
| 990 | try self.serializer.space(); | |
| 991 | } | |
| 992 | if (self.options.shouldWrap()) try self.serializer.indent(); | |
| 993 | if (name) |n| { | |
| 994 | try self.serializer.ident(n); | |
| 995 | try self.serializer.space(); | |
| 996 | try self.serializer.writer.writeByte('='); | |
| 997 | try self.serializer.space(); | |
| 998 | } | |
| 999 | } | |
| 1000 | ||
| 1001 | fn field( | |
| 1002 | self: *Container, | |
| 1003 | name: ?[]const u8, | |
| 1004 | val: anytype, | |
| 1005 | options: ValueOptions, | |
| 1006 | ) Error!void { | |
| 1007 | comptime assert(!typeIsRecursive(@TypeOf(val))); | |
| 1008 | try self.fieldArbitraryDepth(name, val, options); | |
| 1009 | } | |
| 1010 | ||
| 1011 | /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. | |
| 1012 | fn fieldMaxDepth( | |
| 1013 | self: *Container, | |
| 1014 | name: ?[]const u8, | |
| 1015 | val: anytype, | |
| 1016 | options: ValueOptions, | |
| 1017 | depth: usize, | |
| 1018 | ) DepthError!void { | |
| 1019 | try checkValueDepth(val, depth); | |
| 1020 | try self.fieldArbitraryDepth(name, val, options); | |
| 1021 | } | |
| 1022 | ||
| 1023 | fn fieldArbitraryDepth( | |
| 1024 | self: *Container, | |
| 1025 | name: ?[]const u8, | |
| 1026 | val: anytype, | |
| 1027 | options: ValueOptions, | |
| 1028 | ) Error!void { | |
| 1029 | try self.fieldPrefix(name); | |
| 1030 | try self.serializer.valueArbitraryDepth(val, options); | |
| 1031 | } | |
| 1032 | ||
| 1033 | fn shouldElideSpaces(self: *const Container) bool { | |
| 1034 | return switch (self.options.whitespace_style) { | |
| 1035 | .fields => |fields| self.field_style != .named and fields == 1, | |
| 1036 | else => false, | |
| 1037 | }; | |
| 1038 | } | |
| 1039 | }; | |
| 1040 | }; | |
| 1041 | ||
| 1042 | test Serializer { | |
| 1043 | var w: Writer = .discarding(&.{}); | |
| 1044 | var s: Serializer = .{ .writer = &w }; | |
| 1045 | var vec2 = try s.beginStruct(.{}); | |
| 1046 | try vec2.field("x", 1.5, .{}); | |
| 1047 | try vec2.fieldPrefix("prefix"); | |
| 1048 | try s.value(2.5, .{}); | |
| 1049 | try vec2.end(); | |
| 1050 | } | |
| 1051 | ||
| 1052 | 117 | fn expectSerializeEqual( |
| 1053 | 118 | expected: []const u8, |
| 1054 | 119 | value: anytype, |
| 1055 | 120 | options: SerializeOptions, |
| 1056 | 121 | ) !void { |
| 1057 | var aw: std.io.Writer.Allocating = .init(std.testing.allocator); | |
| 1058 | const bw = &aw.interface; | |
| 122 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 123 | const bw = &aw.writer; | |
| 1059 | 124 | defer aw.deinit(); |
| 1060 | 125 | |
| 1061 | 126 | try serialize(value, options, bw); |
| ... | ... | @@ -1156,8 +221,8 @@ test "std.zon stringify whitespace, high level API" { |
| 1156 | 221 | } |
| 1157 | 222 | |
| 1158 | 223 | test "std.zon stringify whitespace, low level API" { |
| 1159 | var aw: std.io.Writer.Allocating = .init(std.testing.allocator); | |
| 1160 | var s: Serializer = .{ .writer = &aw.interface }; | |
| 224 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 225 | var s: Serializer = .{ .writer = &aw.writer }; | |
| 1161 | 226 | defer aw.deinit(); |
| 1162 | 227 | |
| 1163 | 228 | for ([2]bool{ true, false }) |whitespace| { |
| ... | ... | @@ -1513,8 +578,8 @@ test "std.zon stringify whitespace, low level API" { |
| 1513 | 578 | } |
| 1514 | 579 | |
| 1515 | 580 | test "std.zon stringify utf8 codepoints" { |
| 1516 | var aw: std.io.Writer.Allocating = .init(std.testing.allocator); | |
| 1517 | var s: Serializer = .{ .writer = &aw.interface }; | |
| 581 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 582 | var s: Serializer = .{ .writer = &aw.writer }; | |
| 1518 | 583 | defer aw.deinit(); |
| 1519 | 584 | |
| 1520 | 585 | // Printable ASCII |
| ... | ... | @@ -1565,11 +630,11 @@ test "std.zon stringify utf8 codepoints" { |
| 1565 | 630 | aw.clearRetainingCapacity(); |
| 1566 | 631 | |
| 1567 | 632 | try s.codePoint('⚡'); |
| 1568 | try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", aw.getWritten()); | |
| 633 | try std.testing.expectEqualStrings("'\\u{26a1}'", aw.getWritten()); | |
| 1569 | 634 | aw.clearRetainingCapacity(); |
| 1570 | 635 | |
| 1571 | 636 | try s.value('⚡', .{ .emit_codepoint_literals = .always }); |
| 1572 | try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", aw.getWritten()); | |
| 637 | try std.testing.expectEqualStrings("'\\u{26a1}'", aw.getWritten()); | |
| 1573 | 638 | aw.clearRetainingCapacity(); |
| 1574 | 639 | |
| 1575 | 640 | try s.value('⚡', .{ .emit_codepoint_literals = .printable_ascii }); |
| ... | ... | @@ -1581,7 +646,9 @@ test "std.zon stringify utf8 codepoints" { |
| 1581 | 646 | aw.clearRetainingCapacity(); |
| 1582 | 647 | |
| 1583 | 648 | // Invalid codepoint |
| 1584 | try std.testing.expectError(error.InvalidCodepoint, s.codePoint(0x110000 + 1)); | |
| 649 | try s.codePoint(0x110000 + 1); | |
| 650 | try std.testing.expectEqualStrings("'\\u{110001}'", aw.getWritten()); | |
| 651 | aw.clearRetainingCapacity(); | |
| 1585 | 652 | |
| 1586 | 653 | try s.int(0x110000 + 1); |
| 1587 | 654 | try std.testing.expectEqualStrings("1114113", aw.getWritten()); |
| ... | ... | @@ -1614,7 +681,7 @@ test "std.zon stringify utf8 codepoints" { |
| 1614 | 681 | |
| 1615 | 682 | // Make sure value options are passed to children |
| 1616 | 683 | try s.value(.{ .c = '⚡' }, .{ .emit_codepoint_literals = .always }); |
| 1617 | try std.testing.expectEqualStrings(".{ .c = '\\xe2\\x9a\\xa1' }", aw.getWritten()); | |
| 684 | try std.testing.expectEqualStrings(".{ .c = '\\u{26a1}' }", aw.getWritten()); | |
| 1618 | 685 | aw.clearRetainingCapacity(); |
| 1619 | 686 | |
| 1620 | 687 | try s.value(.{ .c = '⚡' }, .{ .emit_codepoint_literals = .never }); |
| ... | ... | @@ -1623,8 +690,8 @@ test "std.zon stringify utf8 codepoints" { |
| 1623 | 690 | } |
| 1624 | 691 | |
| 1625 | 692 | test "std.zon stringify strings" { |
| 1626 | var aw: std.io.Writer.Allocating = .init(std.testing.allocator); | |
| 1627 | var s: Serializer = .{ .writer = &aw.interface }; | |
| 693 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 694 | var s: Serializer = .{ .writer = &aw.writer }; | |
| 1628 | 695 | defer aw.deinit(); |
| 1629 | 696 | |
| 1630 | 697 | // Minimal case |
| ... | ... | @@ -1693,8 +760,8 @@ test "std.zon stringify strings" { |
| 1693 | 760 | } |
| 1694 | 761 | |
| 1695 | 762 | test "std.zon stringify multiline strings" { |
| 1696 | var aw: std.io.Writer.Allocating = .init(std.testing.allocator); | |
| 1697 | var s: Serializer = .{ .writer = &aw.interface }; | |
| 763 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 764 | var s: Serializer = .{ .writer = &aw.writer }; | |
| 1698 | 765 | defer aw.deinit(); |
| 1699 | 766 | |
| 1700 | 767 | inline for (.{ true, false }) |whitespace| { |
| ... | ... | @@ -1913,8 +980,8 @@ test "std.zon stringify skip default fields" { |
| 1913 | 980 | } |
| 1914 | 981 | |
| 1915 | 982 | test "std.zon depth limits" { |
| 1916 | var aw: std.io.Writer.Allocating = .init(std.testing.allocator); | |
| 1917 | const bw = &aw.interface; | |
| 983 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 984 | const bw = &aw.writer; | |
| 1918 | 985 | defer aw.deinit(); |
| 1919 | 986 | |
| 1920 | 987 | const Recurse = struct { r: []const @This() }; |
| ... | ... | @@ -2174,8 +1241,8 @@ test "std.zon stringify primitives" { |
| 2174 | 1241 | } |
| 2175 | 1242 | |
| 2176 | 1243 | test "std.zon stringify ident" { |
| 2177 | var aw: std.io.Writer.Allocating = .init(std.testing.allocator); | |
| 2178 | var s: Serializer = .{ .writer = &aw.interface }; | |
| 1244 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 1245 | var s: Serializer = .{ .writer = &aw.writer }; | |
| 2179 | 1246 | defer aw.deinit(); |
| 2180 | 1247 | |
| 2181 | 1248 | try expectSerializeEqual(".{ .a = 0 }", .{ .a = 0 }, .{}); |
| ... | ... | @@ -2221,8 +1288,8 @@ test "std.zon stringify ident" { |
| 2221 | 1288 | } |
| 2222 | 1289 | |
| 2223 | 1290 | test "std.zon stringify as tuple" { |
| 2224 | var aw: std.io.Writer.Allocating = .init(std.testing.allocator); | |
| 2225 | var s: Serializer = .{ .writer = &aw.interface }; | |
| 1291 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 1292 | var s: Serializer = .{ .writer = &aw.writer }; | |
| 2226 | 1293 | defer aw.deinit(); |
| 2227 | 1294 | |
| 2228 | 1295 | // Tuples |
| ... | ... | @@ -2242,8 +1309,8 @@ test "std.zon stringify as tuple" { |
| 2242 | 1309 | } |
| 2243 | 1310 | |
| 2244 | 1311 | test "std.zon stringify as float" { |
| 2245 | var aw: std.io.Writer.Allocating = .init(std.testing.allocator); | |
| 2246 | var s: Serializer = .{ .writer = &aw.interface }; | |
| 1312 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 1313 | var s: Serializer = .{ .writer = &aw.writer }; | |
| 2247 | 1314 | defer aw.deinit(); |
| 2248 | 1315 | |
| 2249 | 1316 | // Comptime float |
| ... | ... | @@ -2346,8 +1413,8 @@ test "std.zon pointers" { |
| 2346 | 1413 | } |
| 2347 | 1414 | |
| 2348 | 1415 | test "std.zon tuple/struct field" { |
| 2349 | var aw: std.io.Writer.Allocating = .init(std.testing.allocator); | |
| 2350 | var s: Serializer = .{ .writer = &aw.interface }; | |
| 1416 | var aw: Writer.Allocating = .init(std.testing.allocator); | |
| 1417 | var s: Serializer = .{ .writer = &aw.writer }; | |
| 2351 | 1418 | defer aw.deinit(); |
| 2352 | 1419 | |
| 2353 | 1420 | // Test on structs |
src/Zcu.zig+1-1| ... | ... | @@ -2821,7 +2821,7 @@ pub fn loadZirCache(gpa: Allocator, cache_file: std.fs.File) !Zir { |
| 2821 | 2821 | var buffer: [2000]u8 = undefined; |
| 2822 | 2822 | var file_reader = cache_file.reader(&buffer); |
| 2823 | 2823 | return result: { |
| 2824 | const header = file_reader.interface.takeStructReference(Zir.Header) catch |err| break :result err; | |
| 2824 | const header = file_reader.interface.takeStructPointer(Zir.Header) catch |err| break :result err; | |
| 2825 | 2825 | break :result loadZirCacheBody(gpa, header.*, &file_reader.interface); |
| 2826 | 2826 | } catch |err| switch (err) { |
| 2827 | 2827 | error.ReadFailed => return file_reader.err.?, |
src/Zcu/PerThread.zig+1-1| ... | ... | @@ -349,7 +349,7 @@ fn loadZirZoirCache( |
| 349 | 349 | const cache_br = &cache_fr.interface; |
| 350 | 350 | |
| 351 | 351 | // First we read the header to determine the lengths of arrays. |
| 352 | const header = (cache_br.takeStructReference(Header) catch |err| switch (err) { | |
| 352 | const header = (cache_br.takeStructPointer(Header) catch |err| switch (err) { | |
| 353 | 353 | error.ReadFailed => return cache_fr.err.?, |
| 354 | 354 | // This can happen if Zig bails out of this function between creating |
| 355 | 355 | // the cached file and writing it. |
src/codegen/c.zig+4-1| ... | ... | @@ -2438,7 +2438,10 @@ pub const DeclGen = struct { |
| 2438 | 2438 | const ty = val.typeOf(zcu); |
| 2439 | 2439 | return .{ .data = .{ |
| 2440 | 2440 | .dg = dg, |
| 2441 | .int_info = ty.intInfo(zcu), | |
| 2441 | .int_info = if (ty.zigTypeTag(zcu) == .@"union" and ty.containerLayout(zcu) == .@"packed") | |
| 2442 | .{ .signedness = .unsigned, .bits = @intCast(ty.bitSize(zcu)) } | |
| 2443 | else | |
| 2444 | ty.intInfo(zcu), | |
| 2442 | 2445 | .kind = kind, |
| 2443 | 2446 | .ctype = try dg.ctypeFromType(ty, kind), |
| 2444 | 2447 | .val = val, |
src/codegen/llvm.zig+3| ... | ... | @@ -6385,6 +6385,9 @@ pub const FuncGen = struct { |
| 6385 | 6385 | // * https://github.com/llvm/llvm-project/blob/56905dab7da50bccfcceaeb496b206ff476127e1/llvm/test/MC/WebAssembly/blockaddress.ll |
| 6386 | 6386 | if (zcu.comp.getTarget().cpu.arch.isWasm()) break :jmp_table null; |
| 6387 | 6387 | |
| 6388 | // Workaround for https://github.com/ziglang/zig/issues/24383: | |
| 6389 | if (self.ng.ownerModule().optimize_mode == .ReleaseSafe) break :jmp_table null; | |
| 6390 | ||
| 6388 | 6391 | // On a 64-bit target, 1024 pointers in our jump table is about 8K of pointers. This seems just |
| 6389 | 6392 | // about acceptable - it won't fill L1d cache on most CPUs. |
| 6390 | 6393 | const max_table_len = 1024; |
src/main.zig+6-3| ... | ... | @@ -346,8 +346,9 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 346 | 346 | } else if (mem.eql(u8, cmd, "targets")) { |
| 347 | 347 | dev.check(.targets_command); |
| 348 | 348 | const host = std.zig.resolveTargetQueryOrFatal(.{}); |
| 349 | const stdout = fs.File.stdout().deprecatedWriter(); | |
| 350 | return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, &host); | |
| 349 | var stdout_writer = fs.File.stdout().writer(&stdout_buffer); | |
| 350 | try @import("print_targets.zig").cmdTargets(arena, cmd_args, &stdout_writer.interface, &host); | |
| 351 | return stdout_writer.interface.flush(); | |
| 351 | 352 | } else if (mem.eql(u8, cmd, "version")) { |
| 352 | 353 | dev.check(.version_command); |
| 353 | 354 | try fs.File.stdout().writeAll(build_options.version ++ "\n"); |
| ... | ... | @@ -358,7 +359,9 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 358 | 359 | } else if (mem.eql(u8, cmd, "env")) { |
| 359 | 360 | dev.check(.env_command); |
| 360 | 361 | verifyLibcxxCorrectlyLinked(); |
| 361 | return @import("print_env.zig").cmdEnv(arena, cmd_args); | |
| 362 | var stdout_writer = fs.File.stdout().writer(&stdout_buffer); | |
| 363 | try @import("print_env.zig").cmdEnv(arena, &stdout_writer.interface); | |
| 364 | return stdout_writer.interface.flush(); | |
| 362 | 365 | } else if (mem.eql(u8, cmd, "reduce")) { |
| 363 | 366 | return jitCmd(gpa, arena, cmd_args, .{ |
| 364 | 367 | .cmd_name = "reduce", |
src/print_env.zig+14-35| ... | ... | @@ -4,8 +4,7 @@ const introspect = @import("introspect.zig"); |
| 4 | 4 | const Allocator = std.mem.Allocator; |
| 5 | 5 | const fatal = std.process.fatal; |
| 6 | 6 | |
| 7 | pub fn cmdEnv(arena: Allocator, args: []const []const u8) !void { | |
| 8 | _ = args; | |
| 7 | pub fn cmdEnv(arena: Allocator, out: *std.Io.Writer) !void { | |
| 9 | 8 | const cwd_path = try introspect.getResolvedCwd(arena); |
| 10 | 9 | const self_exe_path = try std.fs.selfExePathAlloc(arena); |
| 11 | 10 | |
| ... | ... | @@ -21,41 +20,21 @@ pub fn cmdEnv(arena: Allocator, args: []const []const u8) !void { |
| 21 | 20 | const host = try std.zig.system.resolveTargetQuery(.{}); |
| 22 | 21 | const triple = try host.zigTriple(arena); |
| 23 | 22 | |
| 24 | var buffer: [1024]u8 = undefined; | |
| 25 | var stdout_writer = std.fs.File.stdout().writer(&buffer); | |
| 26 | const w = &stdout_writer.interface(); | |
| 27 | var jws: std.json.Stringify = .{ .writer = w, .options = .{ .whitespace = .indent_1 } }; | |
| 23 | var serializer: std.zon.Serializer = .{ .writer = out }; | |
| 24 | var root = try serializer.beginStruct(.{}); | |
| 28 | 25 | |
| 29 | try jws.beginObject(); | |
| 30 | ||
| 31 | try jws.objectField("zig_exe"); | |
| 32 | try jws.write(self_exe_path); | |
| 33 | ||
| 34 | try jws.objectField("lib_dir"); | |
| 35 | try jws.write(zig_lib_directory.path.?); | |
| 36 | ||
| 37 | try jws.objectField("std_dir"); | |
| 38 | try jws.write(zig_std_dir); | |
| 39 | ||
| 40 | try jws.objectField("global_cache_dir"); | |
| 41 | try jws.write(global_cache_dir); | |
| 42 | ||
| 43 | try jws.objectField("version"); | |
| 44 | try jws.write(build_options.version); | |
| 45 | ||
| 46 | try jws.objectField("target"); | |
| 47 | try jws.write(triple); | |
| 48 | ||
| 49 | try jws.objectField("env"); | |
| 50 | try jws.beginObject(); | |
| 26 | try root.field("zig_exe", self_exe_path, .{}); | |
| 27 | try root.field("lib_dir", zig_lib_directory.path.?, .{}); | |
| 28 | try root.field("std_dir", zig_std_dir, .{}); | |
| 29 | try root.field("global_cache_dir", global_cache_dir, .{}); | |
| 30 | try root.field("version", build_options.version, .{}); | |
| 31 | try root.field("target", triple, .{}); | |
| 32 | var env = try root.beginStructField("env", .{}); | |
| 51 | 33 | inline for (@typeInfo(std.zig.EnvVar).@"enum".fields) |field| { |
| 52 | try jws.objectField(field.name); | |
| 53 | try jws.write(try @field(std.zig.EnvVar, field.name).get(arena)); | |
| 34 | try env.field(field.name, try @field(std.zig.EnvVar, field.name).get(arena), .{}); | |
| 54 | 35 | } |
| 55 | try jws.endObject(); | |
| 56 | ||
| 57 | try jws.endObject(); | |
| 58 | try w.writeByte('\n'); | |
| 36 | try env.end(); | |
| 37 | try root.end(); | |
| 59 | 38 | |
| 60 | try w.flush(); | |
| 39 | try out.writeByte('\n'); | |
| 61 | 40 | } |
src/print_targets.zig+28-26| ... | ... | @@ -10,38 +10,37 @@ const target = @import("target.zig"); |
| 10 | 10 | const assert = std.debug.assert; |
| 11 | 11 | const glibc = @import("libs/glibc.zig"); |
| 12 | 12 | const introspect = @import("introspect.zig"); |
| 13 | const Writer = std.io.Writer; | |
| 14 | 13 | |
| 15 | pub fn cmdTargets(arena: Allocator, args: []const []const u8) !void { | |
| 14 | pub fn cmdTargets( | |
| 15 | allocator: Allocator, | |
| 16 | args: []const []const u8, | |
| 17 | out: *std.Io.Writer, | |
| 18 | native_target: *const Target, | |
| 19 | ) !void { | |
| 16 | 20 | _ = args; |
| 17 | const host = std.zig.resolveTargetQueryOrFatal(.{}); | |
| 18 | var buffer: [1024]u8 = undefined; | |
| 19 | var bw = fs.File.stdout().writer().buffered(&buffer); | |
| 20 | try print(arena, &bw, host); | |
| 21 | try bw.flush(); | |
| 22 | } | |
| 23 | ||
| 24 | fn print(arena: Allocator, output: *Writer, host: *const Target) Writer.Error!void { | |
| 25 | var zig_lib_directory = introspect.findZigLibDir(arena) catch |err| { | |
| 21 | var zig_lib_directory = introspect.findZigLibDir(allocator) catch |err| { | |
| 26 | 22 | fatal("unable to find zig installation directory: {s}\n", .{@errorName(err)}); |
| 27 | 23 | }; |
| 28 | 24 | defer zig_lib_directory.handle.close(); |
| 25 | defer allocator.free(zig_lib_directory.path.?); | |
| 29 | 26 | |
| 30 | 27 | const abilists_contents = zig_lib_directory.handle.readFileAlloc( |
| 28 | allocator, | |
| 31 | 29 | glibc.abilists_path, |
| 32 | arena, | |
| 33 | .limited(glibc.abilists_max_size), | |
| 30 | glibc.abilists_max_size, | |
| 34 | 31 | ) catch |err| switch (err) { |
| 35 | 32 | error.OutOfMemory => return error.OutOfMemory, |
| 36 | 33 | else => fatal("unable to read " ++ glibc.abilists_path ++ ": {s}", .{@errorName(err)}), |
| 37 | 34 | }; |
| 35 | defer allocator.free(abilists_contents); | |
| 38 | 36 | |
| 39 | const glibc_abi = try glibc.loadMetaData(arena, abilists_contents); | |
| 37 | const glibc_abi = try glibc.loadMetaData(allocator, abilists_contents); | |
| 38 | defer glibc_abi.destroy(allocator); | |
| 40 | 39 | |
| 41 | var sz: std.zon.stringify.Serializer = .{ .writer = output }; | |
| 40 | var serializer: std.zon.Serializer = .{ .writer = out }; | |
| 42 | 41 | |
| 43 | 42 | { |
| 44 | var root_obj = try sz.beginStruct(.{}); | |
| 43 | var root_obj = try serializer.beginStruct(.{}); | |
| 45 | 44 | |
| 46 | 45 | try root_obj.field("arch", meta.fieldNames(Target.Cpu.Arch), .{}); |
| 47 | 46 | try root_obj.field("os", meta.fieldNames(Target.Os.Tag), .{}); |
| ... | ... | @@ -50,9 +49,10 @@ fn print(arena: Allocator, output: *Writer, host: *const Target) Writer.Error!vo |
| 50 | 49 | { |
| 51 | 50 | var libc_obj = try root_obj.beginTupleField("libc", .{}); |
| 52 | 51 | for (std.zig.target.available_libcs) |libc| { |
| 53 | const tmp = try std.fmt.allocPrint(arena, "{s}-{s}-{s}", .{ | |
| 52 | const tmp = try std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ | |
| 54 | 53 | @tagName(libc.arch), @tagName(libc.os), @tagName(libc.abi), |
| 55 | 54 | }); |
| 55 | defer allocator.free(tmp); | |
| 56 | 56 | try libc_obj.field(tmp, .{}); |
| 57 | 57 | } |
| 58 | 58 | try libc_obj.end(); |
| ... | ... | @@ -61,7 +61,8 @@ fn print(arena: Allocator, output: *Writer, host: *const Target) Writer.Error!vo |
| 61 | 61 | { |
| 62 | 62 | var glibc_obj = try root_obj.beginTupleField("glibc", .{}); |
| 63 | 63 | for (glibc_abi.all_versions) |ver| { |
| 64 | const tmp = try std.fmt.allocPrint(arena, "{f}", .{ver}); | |
| 64 | const tmp = try std.fmt.allocPrint(allocator, "{f}", .{ver}); | |
| 65 | defer allocator.free(tmp); | |
| 65 | 66 | try glibc_obj.field(tmp, .{}); |
| 66 | 67 | } |
| 67 | 68 | try glibc_obj.end(); |
| ... | ... | @@ -101,20 +102,21 @@ fn print(arena: Allocator, output: *Writer, host: *const Target) Writer.Error!vo |
| 101 | 102 | { |
| 102 | 103 | var native_obj = try root_obj.beginStructField("native", .{}); |
| 103 | 104 | { |
| 104 | const triple = try host.zigTriple(arena); | |
| 105 | const triple = try native_target.zigTriple(allocator); | |
| 106 | defer allocator.free(triple); | |
| 105 | 107 | try native_obj.field("triple", triple, .{}); |
| 106 | 108 | } |
| 107 | 109 | { |
| 108 | 110 | var cpu_obj = try native_obj.beginStructField("cpu", .{}); |
| 109 | try cpu_obj.field("arch", @tagName(host.cpu.arch), .{}); | |
| 111 | try cpu_obj.field("arch", @tagName(native_target.cpu.arch), .{}); | |
| 110 | 112 | |
| 111 | try cpu_obj.field("name", host.cpu.model.name, .{}); | |
| 113 | try cpu_obj.field("name", native_target.cpu.model.name, .{}); | |
| 112 | 114 | |
| 113 | 115 | { |
| 114 | 116 | var features = try native_obj.beginTupleField("features", .{}); |
| 115 | for (host.cpu.arch.allFeaturesList(), 0..) |feature, i_usize| { | |
| 117 | for (native_target.cpu.arch.allFeaturesList(), 0..) |feature, i_usize| { | |
| 116 | 118 | const index = @as(Target.Cpu.Feature.Set.Index, @intCast(i_usize)); |
| 117 | if (host.cpu.features.isEnabled(index)) { | |
| 119 | if (native_target.cpu.features.isEnabled(index)) { | |
| 118 | 120 | try features.field(feature.name, .{}); |
| 119 | 121 | } |
| 120 | 122 | } |
| ... | ... | @@ -123,13 +125,13 @@ fn print(arena: Allocator, output: *Writer, host: *const Target) Writer.Error!vo |
| 123 | 125 | try cpu_obj.end(); |
| 124 | 126 | } |
| 125 | 127 | |
| 126 | try native_obj.field("os", @tagName(host.os.tag), .{}); | |
| 127 | try native_obj.field("abi", @tagName(host.abi), .{}); | |
| 128 | try native_obj.field("os", @tagName(native_target.os.tag), .{}); | |
| 129 | try native_obj.field("abi", @tagName(native_target.abi), .{}); | |
| 128 | 130 | try native_obj.end(); |
| 129 | 131 | } |
| 130 | 132 | |
| 131 | 133 | try root_obj.end(); |
| 132 | 134 | } |
| 133 | 135 | |
| 134 | try output.writeByte('\n'); | |
| 136 | try out.writeByte('\n'); | |
| 135 | 137 | } |
src/translate_c.zig+1-1| ... | ... | @@ -3338,7 +3338,7 @@ fn transPredefinedExpr(c: *Context, scope: *Scope, expr: *const clang.Predefined |
| 3338 | 3338 | |
| 3339 | 3339 | fn transCreateCharLitNode(c: *Context, narrow: bool, val: u32) TransError!Node { |
| 3340 | 3340 | return Tag.char_literal.create(c.arena, if (narrow) |
| 3341 | try std.fmt.allocPrint(c.arena, "'{f}'", .{std.zig.fmtChar(&.{@as(u8, @intCast(val))})}) | |
| 3341 | try std.fmt.allocPrint(c.arena, "'{f}'", .{std.zig.fmtChar(@intCast(val))}) | |
| 3342 | 3342 | else |
| 3343 | 3343 | try std.fmt.allocPrint(c.arena, "'\\u{{{x}}}'", .{val})); |
| 3344 | 3344 | } |
test/behavior/type_info.zig-14| ... | ... | @@ -539,20 +539,6 @@ fn add(a: i32, b: i32) i32 { |
| 539 | 539 | return a + b; |
| 540 | 540 | } |
| 541 | 541 | |
| 542 | test "type info for async frames" { | |
| 543 | if (true) { | |
| 544 | // https://github.com/ziglang/zig/issues/6025 | |
| 545 | return error.SkipZigTest; | |
| 546 | } | |
| 547 | ||
| 548 | switch (@typeInfo(@Frame(add))) { | |
| 549 | .frame => |frame| { | |
| 550 | try expect(@as(@TypeOf(add), @ptrCast(frame.function)) == add); | |
| 551 | }, | |
| 552 | else => unreachable, | |
| 553 | } | |
| 554 | } | |
| 555 | ||
| 556 | 542 | test "Declarations are returned in declaration order" { |
| 557 | 543 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 558 | 544 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; |
test/cases/compile_errors/async/Frame_of_generic_function.zig deleted-14| ... | ... | @@ -1,14 +0,0 @@ |
| 1 | export fn entry() void { | |
| 2 | var frame: @Frame(func) = undefined; | |
| 3 | _ = &frame; | |
| 4 | } | |
| 5 | fn func(comptime T: type) void { | |
| 6 | var x: T = undefined; | |
| 7 | _ = &x; | |
| 8 | } | |
| 9 | ||
| 10 | // error | |
| 11 | // backend=stage1 | |
| 12 | // target=native | |
| 13 | // | |
| 14 | // tmp.zig:2:16: error: @Frame() of generic function |
test/cases/compile_errors/async/bad_alignment_in_asynccall.zig deleted-13| ... | ... | @@ -1,13 +0,0 @@ |
| 1 | export fn entry() void { | |
| 2 | var ptr: fn () callconv(.@"async") void = func; | |
| 3 | var bytes: [64]u8 = undefined; | |
| 4 | _ = @asyncCall(&bytes, {}, ptr, .{}); | |
| 5 | _ = &ptr; | |
| 6 | } | |
| 7 | fn func() callconv(.@"async") void {} | |
| 8 | ||
| 9 | // error | |
| 10 | // backend=stage1 | |
| 11 | // target=aarch64-linux-none | |
| 12 | // | |
| 13 | // tmp.zig:4:21: error: expected type '[]align(8) u8', found '*[64]u8' |
test/cases/compile_errors/async/exported_async_function.zig deleted-7| ... | ... | @@ -1,7 +0,0 @@ |
| 1 | export fn foo() callconv(.@"async") void {} | |
| 2 | ||
| 3 | // error | |
| 4 | // backend=stage1 | |
| 5 | // target=native | |
| 6 | // | |
| 7 | // tmp.zig:1:1: error: exported function cannot be async |
test/cases/compile_errors/async/frame_called_outside_of_function_definition.zig deleted-11| ... | ... | @@ -1,11 +0,0 @@ |
| 1 | var handle_undef: anyframe = undefined; | |
| 2 | var handle_dummy: anyframe = @frame(); | |
| 3 | export fn entry() bool { | |
| 4 | return handle_undef == handle_dummy; | |
| 5 | } | |
| 6 | ||
| 7 | // error | |
| 8 | // backend=stage1 | |
| 9 | // target=native | |
| 10 | // | |
| 11 | // tmp.zig:2:30: error: @frame() called outside of function definition |
test/cases/compile_errors/async/frame_causes_function_to_be_async.zig deleted-13| ... | ... | @@ -1,13 +0,0 @@ |
| 1 | export fn entry() void { | |
| 2 | func(); | |
| 3 | } | |
| 4 | fn func() void { | |
| 5 | _ = @frame(); | |
| 6 | } | |
| 7 | ||
| 8 | // error | |
| 9 | // backend=stage1 | |
| 10 | // target=native | |
| 11 | // | |
| 12 | // tmp.zig:1:1: error: function with calling convention 'C' cannot be async | |
| 13 | // tmp.zig:5:9: note: @frame() causes function to be async |
test/cases/compile_errors/async/non-async_function_pointer_eventually_is_inferred_to_become_async.zig deleted-15| ... | ... | @@ -1,15 +0,0 @@ |
| 1 | export fn a() void { | |
| 2 | var non_async_fn: fn () void = undefined; | |
| 3 | non_async_fn = func; | |
| 4 | } | |
| 5 | fn func() void { | |
| 6 | suspend {} | |
| 7 | } | |
| 8 | ||
| 9 | // error | |
| 10 | // backend=stage1 | |
| 11 | // target=native | |
| 12 | // | |
| 13 | // tmp.zig:5:1: error: 'func' cannot be async | |
| 14 | // tmp.zig:3:20: note: required to be non-async here | |
| 15 | // tmp.zig:6:5: note: suspends here |
test/cases/compile_errors/async/non_async_function_pointer_passed_to_asyncCall.zig deleted-13| ... | ... | @@ -1,13 +0,0 @@ |
| 1 | export fn entry() void { | |
| 2 | var ptr = afunc; | |
| 3 | var bytes: [100]u8 align(16) = undefined; | |
| 4 | _ = @asyncCall(&bytes, {}, ptr, .{}); | |
| 5 | _ = &ptr; | |
| 6 | } | |
| 7 | fn afunc() void {} | |
| 8 | ||
| 9 | // error | |
| 10 | // backend=stage1 | |
| 11 | // target=native | |
| 12 | // | |
| 13 | // tmp.zig:4:32: error: expected async function, found 'fn () void' |
test/cases/compile_errors/async/prevent_bad_implicit_casting_of_anyframe_types.zig deleted-24| ... | ... | @@ -1,24 +0,0 @@ |
| 1 | export fn a() void { | |
| 2 | var x: anyframe = undefined; | |
| 3 | var y: anyframe->i32 = x; | |
| 4 | _ = .{ &x, &y }; | |
| 5 | } | |
| 6 | export fn b() void { | |
| 7 | var x: i32 = undefined; | |
| 8 | var y: anyframe->i32 = x; | |
| 9 | _ = .{ &x, &y }; | |
| 10 | } | |
| 11 | export fn c() void { | |
| 12 | var x: @Frame(func) = undefined; | |
| 13 | var y: anyframe->i32 = &x; | |
| 14 | _ = .{ &x, &y }; | |
| 15 | } | |
| 16 | fn func() void {} | |
| 17 | ||
| 18 | // error | |
| 19 | // backend=stage1 | |
| 20 | // target=native | |
| 21 | // | |
| 22 | // :3:28: error: expected type 'anyframe->i32', found 'anyframe' | |
| 23 | // :8:28: error: expected type 'anyframe->i32', found 'i32' | |
| 24 | // tmp.zig:13:29: error: expected type 'anyframe->i32', found '*@Frame(func)' |
test/cases/compile_errors/async/wrong_type_for_argument_tuple_to_asyncCall.zig deleted-14| ... | ... | @@ -1,14 +0,0 @@ |
| 1 | export fn entry1() void { | |
| 2 | var frame: @Frame(foo) = undefined; | |
| 3 | @asyncCall(&frame, {}, foo, {}); | |
| 4 | } | |
| 5 | ||
| 6 | fn foo() i32 { | |
| 7 | return 0; | |
| 8 | } | |
| 9 | ||
| 10 | // error | |
| 11 | // backend=stage1 | |
| 12 | // target=native | |
| 13 | // | |
| 14 | // tmp.zig:3:33: error: expected tuple or struct, found 'void' |
test/cases/safety/nosuspend function call, callee suspends.zig	 deleted-20| ... | ... | @@ -1,20 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | ||
| 3 | pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn { | |
| 4 | _ = message; | |
| 5 | _ = stack_trace; | |
| 6 | std.process.exit(0); | |
| 7 | } | |
| 8 | pub fn main() !void { | |
| 9 | _ = nosuspend add(101, 100); | |
| 10 | return error.TestFailed; | |
| 11 | } | |
| 12 | fn add(a: i32, b: i32) i32 { | |
| 13 | if (a > 100) { | |
| 14 | suspend {} | |
| 15 | } | |
| 16 | return a + b; | |
| 17 | } | |
| 18 | // run | |
| 19 | // backend=stage1 | |
| 20 | // target=native |