| ... | ... | @@ -10,18 +10,18 @@ const maxInt = std.math.maxInt; |
| 10 | 10 | |
| 11 | 11 | pub const WriteStream = @import("json/write_stream.zig").WriteStream; |
| 12 | 12 | |
| 13 | | // A single token slice into the parent string. |
| 14 | | // |
| 15 | | // Use `token.slice()` on the input at the current position to get the current slice. |
| 13 | /// A single token slice into the parent string. |
| 14 | /// |
| 15 | /// Use `token.slice()` on the input at the current position to get the current slice. |
| 16 | 16 | pub const Token = struct { |
| 17 | 17 | id: Id, |
| 18 | | // How many bytes do we skip before counting |
| 18 | /// How many bytes do we skip before counting |
| 19 | 19 | offset: u1, |
| 20 | | // Whether string contains a \uXXXX sequence and cannot be zero-copied |
| 20 | /// Whether string contains an escape sequence and cannot be zero-copied |
| 21 | 21 | string_has_escape: bool, |
| 22 | | // Whether number is simple and can be represented by an integer (i.e. no `.` or `e`) |
| 22 | /// Whether number is simple and can be represented by an integer (i.e. no `.` or `e`) |
| 23 | 23 | number_is_integer: bool, |
| 24 | | // How many bytes from the current position behind the start of this token is. |
| 24 | /// How many bytes from the current position behind the start of this token is. |
| 25 | 25 | count: usize, |
| 26 | 26 | |
| 27 | 27 | pub const Id = enum { |
| ... | ... | @@ -66,7 +66,7 @@ pub const Token = struct { |
| 66 | 66 | }; |
| 67 | 67 | } |
| 68 | 68 | |
| 69 | | // A marker token is a zero-length |
| 69 | /// A marker token is a zero-length |
| 70 | 70 | pub fn initMarker(id: Id) Token { |
| 71 | 71 | return Token{ |
| 72 | 72 | .id = id, |
| ... | ... | @@ -77,19 +77,19 @@ pub const Token = struct { |
| 77 | 77 | }; |
| 78 | 78 | } |
| 79 | 79 | |
| 80 | | // Slice into the underlying input string. |
| 80 | /// Slice into the underlying input string. |
| 81 | 81 | pub fn slice(self: Token, input: []const u8, i: usize) []const u8 { |
| 82 | 82 | return input[i + self.offset - self.count .. i + self.offset]; |
| 83 | 83 | } |
| 84 | 84 | }; |
| 85 | 85 | |
| 86 | | // A small streaming JSON parser. This accepts input one byte at a time and returns tokens as |
| 87 | | // they are encountered. No copies or allocations are performed during parsing and the entire |
| 88 | | // parsing state requires ~40-50 bytes of stack space. |
| 89 | | // |
| 90 | | // Conforms strictly to RFC8529. |
| 91 | | // |
| 92 | | // For a non-byte based wrapper, consider using TokenStream instead. |
| 86 | /// A small streaming JSON parser. This accepts input one byte at a time and returns tokens as |
| 87 | /// they are encountered. No copies or allocations are performed during parsing and the entire |
| 88 | /// parsing state requires ~40-50 bytes of stack space. |
| 89 | /// |
| 90 | /// Conforms strictly to RFC8529. |
| 91 | /// |
| 92 | /// For a non-byte based wrapper, consider using TokenStream instead. |
| 93 | 93 | pub const StreamingParser = struct { |
| 94 | 94 | // Current state |
| 95 | 95 | state: State, |
| ... | ... | @@ -205,10 +205,10 @@ pub const StreamingParser = struct { |
| 205 | 205 | InvalidControlCharacter, |
| 206 | 206 | }; |
| 207 | 207 | |
| 208 | | // Give another byte to the parser and obtain any new tokens. This may (rarely) return two |
| 209 | | // tokens. token2 is always null if token1 is null. |
| 210 | | // |
| 211 | | // There is currently no error recovery on a bad stream. |
| 208 | /// Give another byte to the parser and obtain any new tokens. This may (rarely) return two |
| 209 | /// tokens. token2 is always null if token1 is null. |
| 210 | /// |
| 211 | /// There is currently no error recovery on a bad stream. |
| 212 | 212 | pub fn feed(p: *StreamingParser, c: u8, token1: *?Token, token2: *?Token) Error!void { |
| 213 | 213 | token1.* = null; |
| 214 | 214 | token2.* = null; |
| ... | ... | @@ -866,7 +866,7 @@ pub const StreamingParser = struct { |
| 866 | 866 | } |
| 867 | 867 | }; |
| 868 | 868 | |
| 869 | | // A small wrapper over a StreamingParser for full slices. Returns a stream of json Tokens. |
| 869 | /// A small wrapper over a StreamingParser for full slices. Returns a stream of json Tokens. |
| 870 | 870 | pub const TokenStream = struct { |
| 871 | 871 | i: usize, |
| 872 | 872 | slice: []const u8, |
| ... | ... | @@ -905,7 +905,13 @@ pub const TokenStream = struct { |
| 905 | 905 | } |
| 906 | 906 | } |
| 907 | 907 | |
| 908 | | if (self.parser.complete) { |
| 908 | // Without this a bare number fails, becasue the streaming parser doesn't know it ended |
| 909 | try self.parser.feed(' ', &t1, &t2); |
| 910 | self.i += 1; |
| 911 | |
| 912 | if (t1) |token| { |
| 913 | return token; |
| 914 | } else if (self.parser.complete) { |
| 909 | 915 | return null; |
| 910 | 916 | } else { |
| 911 | 917 | return error.UnexpectedEndOfJson; |
| ... | ... | @@ -971,8 +977,8 @@ test "json.token" { |
| 971 | 977 | testing.expect((try p.next()) == null); |
| 972 | 978 | } |
| 973 | 979 | |
| 974 | | // Validate a JSON string. This does not limit number precision so a decoder may not necessarily |
| 975 | | // be able to decode the string even if this returns true. |
| 980 | /// Validate a JSON string. This does not limit number precision so a decoder may not necessarily |
| 981 | /// be able to decode the string even if this returns true. |
| 976 | 982 | pub fn validate(s: []const u8) bool { |
| 977 | 983 | var p = StreamingParser.init(); |
| 978 | 984 | |
| ... | ... | @@ -1009,6 +1015,8 @@ pub const ValueTree = struct { |
| 1009 | 1015 | pub const ObjectMap = StringHashMap(Value); |
| 1010 | 1016 | pub const Array = ArrayList(Value); |
| 1011 | 1017 | |
| 1018 | /// Represents a JSON value |
| 1019 | /// Currently only supports numbers that fit into i64 or f64. |
| 1012 | 1020 | pub const Value = union(enum) { |
| 1013 | 1021 | Null, |
| 1014 | 1022 | Bool: bool, |
| ... | ... | @@ -1055,7 +1063,7 @@ pub const Value = union(enum) { |
| 1055 | 1063 | } |
| 1056 | 1064 | }; |
| 1057 | 1065 | |
| 1058 | | // A non-stream JSON parser which constructs a tree of Value's. |
| 1066 | /// A non-stream JSON parser which constructs a tree of Value's. |
| 1059 | 1067 | pub const Parser = struct { |
| 1060 | 1068 | allocator: *Allocator, |
| 1061 | 1069 | state: State, |
| ... | ... | @@ -1124,7 +1132,10 @@ pub const Parser = struct { |
| 1124 | 1132 | p.state = State.ObjectValue; |
| 1125 | 1133 | }, |
| 1126 | 1134 | else => { |
| 1127 | | unreachable; |
| 1135 | // The streaming parser would return an error eventually. |
| 1136 | // To prevent invalid state we return an error now. |
| 1137 | // TODO make the streaming parser return an error as soon as it encounters an invalid object key |
| 1138 | return error.InvalidLiteral; |
| 1128 | 1139 | }, |
| 1129 | 1140 | }, |
| 1130 | 1141 | State.ObjectValue => { |
| ... | ... | @@ -1266,7 +1277,7 @@ pub const Parser = struct { |
| 1266 | 1277 | // TODO: We don't strictly have to copy values which do not contain any escape |
| 1267 | 1278 | // characters if flagged with the option. |
| 1268 | 1279 | const slice = token.slice(input, i); |
| 1269 | | return Value{ .String = try mem.dupe(allocator, u8, slice) }; |
| 1280 | return Value{ .String = try unescapeStringAlloc(allocator, slice) }; |
| 1270 | 1281 | } |
| 1271 | 1282 | |
| 1272 | 1283 | fn parseNumber(p: *Parser, token: Token, input: []const u8, i: usize) !Value { |
| ... | ... | @@ -1277,6 +1288,77 @@ pub const Parser = struct { |
| 1277 | 1288 | } |
| 1278 | 1289 | }; |
| 1279 | 1290 | |
| 1291 | // Unescape a JSON string |
| 1292 | // Only to be used on strings already validated by the parser |
| 1293 | // (note the unreachable statements and lack of bounds checking) |
| 1294 | // Optimized for arena allocators, uses Allocator.shrink |
| 1295 | // |
| 1296 | // Idea: count how many bytes we will need to allocate in the streaming parser and store it |
| 1297 | // in the token to avoid allocating too much memory or iterating through the string again |
| 1298 | // Downside: need to find how many bytes a unicode escape sequence will produce twice |
| 1299 | fn unescapeStringAlloc(alloc: *Allocator, input: []const u8) ![]u8 { |
| 1300 | const output = try alloc.alloc(u8, input.len); |
| 1301 | errdefer alloc.free(output); |
| 1302 | |
| 1303 | var inIndex: usize = 0; |
| 1304 | var outIndex: usize = 0; |
| 1305 | |
| 1306 | while(inIndex < input.len) { |
| 1307 | if(input[inIndex] != '\\'){ |
| 1308 | // not an escape sequence |
| 1309 | output[outIndex] = input[inIndex]; |
| 1310 | inIndex += 1; |
| 1311 | outIndex += 1; |
| 1312 | } else if(input[inIndex + 1] != 'u'){ |
| 1313 | // a simple escape sequence |
| 1314 | output[outIndex] = @as(u8, |
| 1315 | switch(input[inIndex + 1]){ |
| 1316 | '\\' => '\\', |
| 1317 | '/' => '/', |
| 1318 | 'n' => '\n', |
| 1319 | 'r' => '\r', |
| 1320 | 't' => '\t', |
| 1321 | 'f' => 12, |
| 1322 | 'b' => 8, |
| 1323 | '"' => '"', |
| 1324 | else => unreachable |
| 1325 | } |
| 1326 | ); |
| 1327 | inIndex += 2; |
| 1328 | outIndex += 1; |
| 1329 | } else { |
| 1330 | // a unicode escape sequence |
| 1331 | const firstCodeUnit = std.fmt.parseInt(u16, input[inIndex+2 .. inIndex+6], 16) catch unreachable; |
| 1332 | |
| 1333 | // guess optimistically that it's not a surrogate pair |
| 1334 | if(std.unicode.utf8Encode(firstCodeUnit, output[outIndex..])) |byteCount| { |
| 1335 | outIndex += byteCount; |
| 1336 | inIndex += 6; |
| 1337 | } else |err| { |
| 1338 | // it might be a surrogate pair |
| 1339 | if(err != error.Utf8CannotEncodeSurrogateHalf) { |
| 1340 | return error.InvalidUnicodeHexSymbol; |
| 1341 | } |
| 1342 | // check if a second code unit is present |
| 1343 | if(inIndex + 7 >= input.len or input[inIndex + 6] != '\\' or input[inIndex + 7] != 'u'){ |
| 1344 | return error.InvalidUnicodeHexSymbol; |
| 1345 | } |
| 1346 | |
| 1347 | const secondCodeUnit = std.fmt.parseInt(u16, input[inIndex+8 .. inIndex+12], 16) catch unreachable; |
| 1348 | |
| 1349 | if(std.unicode.utf16leToUtf8(output[outIndex..], [2]u16{ firstCodeUnit, secondCodeUnit })) |byteCount| { |
| 1350 | outIndex += byteCount; |
| 1351 | inIndex += 12; |
| 1352 | } else |_| { |
| 1353 | return error.InvalidUnicodeHexSymbol; |
| 1354 | } |
| 1355 | } |
| 1356 | } |
| 1357 | } |
| 1358 | |
| 1359 | return alloc.shrink(output, outIndex); |
| 1360 | } |
| 1361 | |
| 1280 | 1362 | test "json.parser.dynamic" { |
| 1281 | 1363 | var p = Parser.init(debug.global_allocator, false); |
| 1282 | 1364 | defer p.deinit(); |
| ... | ... | @@ -1399,3 +1481,36 @@ test "integer after float has proper type" { |
| 1399 | 1481 | ); |
| 1400 | 1482 | std.testing.expect(json.Object.getValue("ints").?.Array.at(0) == .Integer); |
| 1401 | 1483 | } |
| 1484 | |
| 1485 | test "escaped characters" { |
| 1486 | const input = |
| 1487 | \\{ |
| 1488 | \\ "backslash": "\\", |
| 1489 | \\ "forwardslash": "\/", |
| 1490 | \\ "newline": "\n", |
| 1491 | \\ "carriagereturn": "\r", |
| 1492 | \\ "tab": "\t", |
| 1493 | \\ "formfeed": "\f", |
| 1494 | \\ "backspace": "\b", |
| 1495 | \\ "doublequote": "\"", |
| 1496 | \\ "unicode": "\u0105", |
| 1497 | \\ "surrogatepair": "\ud83d\ude02" |
| 1498 | \\} |
| 1499 | ; |
| 1500 | |
| 1501 | var p = Parser.init(debug.global_allocator, false); |
| 1502 | const tree = try p.parse(input); |
| 1503 | |
| 1504 | const obj = tree.root.Object; |
| 1505 | |
| 1506 | testing.expectEqualSlices(u8, obj.get("backslash").?.value.String, "\\"); |
| 1507 | testing.expectEqualSlices(u8, obj.get("forwardslash").?.value.String, "/"); |
| 1508 | testing.expectEqualSlices(u8, obj.get("newline").?.value.String, "\n"); |
| 1509 | testing.expectEqualSlices(u8, obj.get("carriagereturn").?.value.String, "\r"); |
| 1510 | testing.expectEqualSlices(u8, obj.get("tab").?.value.String, "\t"); |
| 1511 | testing.expectEqualSlices(u8, obj.get("formfeed").?.value.String, "\x0C"); |
| 1512 | testing.expectEqualSlices(u8, obj.get("backspace").?.value.String, "\x08"); |
| 1513 | testing.expectEqualSlices(u8, obj.get("doublequote").?.value.String, "\""); |
| 1514 | testing.expectEqualSlices(u8, obj.get("unicode").?.value.String, "ą"); |
| 1515 | testing.expectEqualSlices(u8, obj.get("surrogatepair").?.value.String, "😂"); |
| 1516 | } |