| 1 | const std = @import("std"); |
| 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; |
| 9 | |
| 10 | const example_document_str = |
| 11 | \\{ |
| 12 | \\ "Image": { |
| 13 | \\ "Width": 800, |
| 14 | \\ "Height": 600, |
| 15 | \\ "Title": "View from 15th Floor", |
| 16 | \\ "Thumbnail": { |
| 17 | \\ "Url": "http://www.example.com/image/481989943", |
| 18 | \\ "Height": 125, |
| 19 | \\ "Width": 100 |
| 20 | \\ }, |
| 21 | \\ "Animated" : false, |
| 22 | \\ "IDs": [116, 943, 234, 38793] |
| 23 | \\ } |
| 24 | \\} |
| 25 | ; |
| 26 | |
| 27 | fn expectNext(scanner_or_reader: anytype, expected_token: Token) !void { |
| 28 | return expectEqualTokens(expected_token, try scanner_or_reader.next()); |
| 29 | } |
| 30 | |
| 31 | fn expectPeekNext(scanner_or_reader: anytype, expected_token_type: TokenType, expected_token: Token) !void { |
| 32 | try std.testing.expectEqual(expected_token_type, try scanner_or_reader.peekNextTokenType()); |
| 33 | try expectEqualTokens(expected_token, try scanner_or_reader.next()); |
| 34 | } |
| 35 | |
| 36 | test "token" { |
| 37 | var scanner = Scanner.initCompleteInput(std.testing.allocator, example_document_str); |
| 38 | defer scanner.deinit(); |
| 39 | |
| 40 | try expectNext(&scanner, .object_begin); |
| 41 | try expectNext(&scanner, Token{ .string = "Image" }); |
| 42 | try expectNext(&scanner, .object_begin); |
| 43 | try expectNext(&scanner, Token{ .string = "Width" }); |
| 44 | try expectNext(&scanner, Token{ .number = "800" }); |
| 45 | try expectNext(&scanner, Token{ .string = "Height" }); |
| 46 | try expectNext(&scanner, Token{ .number = "600" }); |
| 47 | try expectNext(&scanner, Token{ .string = "Title" }); |
| 48 | try expectNext(&scanner, Token{ .string = "View from 15th Floor" }); |
| 49 | try expectNext(&scanner, Token{ .string = "Thumbnail" }); |
| 50 | try expectNext(&scanner, .object_begin); |
| 51 | try expectNext(&scanner, Token{ .string = "Url" }); |
| 52 | try expectNext(&scanner, Token{ .string = "http://www.example.com/image/481989943" }); |
| 53 | try expectNext(&scanner, Token{ .string = "Height" }); |
| 54 | try expectNext(&scanner, Token{ .number = "125" }); |
| 55 | try expectNext(&scanner, Token{ .string = "Width" }); |
| 56 | try expectNext(&scanner, Token{ .number = "100" }); |
| 57 | try expectNext(&scanner, .object_end); |
| 58 | try expectNext(&scanner, Token{ .string = "Animated" }); |
| 59 | try expectNext(&scanner, .false); |
| 60 | try expectNext(&scanner, Token{ .string = "IDs" }); |
| 61 | try expectNext(&scanner, .array_begin); |
| 62 | try expectNext(&scanner, Token{ .number = "116" }); |
| 63 | try expectNext(&scanner, Token{ .number = "943" }); |
| 64 | try expectNext(&scanner, Token{ .number = "234" }); |
| 65 | try expectNext(&scanner, Token{ .number = "38793" }); |
| 66 | try expectNext(&scanner, .array_end); |
| 67 | try expectNext(&scanner, .object_end); |
| 68 | try expectNext(&scanner, .object_end); |
| 69 | try expectNext(&scanner, .end_of_document); |
| 70 | } |
| 71 | |
| 72 | const all_types_test_case = |
| 73 | \\[ |
| 74 | \\ "", "a\nb", |
| 75 | \\ 0, 0.0, -1.1e-1, |
| 76 | \\ true, false, null, |
| 77 | \\ {"a": {}}, |
| 78 | \\ [] |
| 79 | \\] |
| 80 | ; |
| 81 | |
| 82 | fn testAllTypes(source: anytype, large_buffer: bool) !void { |
| 83 | try expectPeekNext(source, .array_begin, .array_begin); |
| 84 | try expectPeekNext(source, .string, Token{ .string = "" }); |
| 85 | try expectPeekNext(source, .string, Token{ .partial_string = "a" }); |
| 86 | try expectPeekNext(source, .string, Token{ .partial_string_escaped_1 = "\n".* }); |
| 87 | if (large_buffer) { |
| 88 | try expectPeekNext(source, .string, Token{ .string = "b" }); |
| 89 | } else { |
| 90 | try expectPeekNext(source, .string, Token{ .partial_string = "b" }); |
| 91 | try expectPeekNext(source, .string, Token{ .string = "" }); |
| 92 | } |
| 93 | if (large_buffer) { |
| 94 | try expectPeekNext(source, .number, Token{ .number = "0" }); |
| 95 | } else { |
| 96 | try expectPeekNext(source, .number, Token{ .partial_number = "0" }); |
| 97 | try expectPeekNext(source, .number, Token{ .number = "" }); |
| 98 | } |
| 99 | if (large_buffer) { |
| 100 | try expectPeekNext(source, .number, Token{ .number = "0.0" }); |
| 101 | } else { |
| 102 | try expectPeekNext(source, .number, Token{ .partial_number = "0" }); |
| 103 | try expectPeekNext(source, .number, Token{ .partial_number = "." }); |
| 104 | try expectPeekNext(source, .number, Token{ .partial_number = "0" }); |
| 105 | try expectPeekNext(source, .number, Token{ .number = "" }); |
| 106 | } |
| 107 | if (large_buffer) { |
| 108 | try expectPeekNext(source, .number, Token{ .number = "-1.1e-1" }); |
| 109 | } else { |
| 110 | try expectPeekNext(source, .number, Token{ .partial_number = "-" }); |
| 111 | try expectPeekNext(source, .number, Token{ .partial_number = "1" }); |
| 112 | try expectPeekNext(source, .number, Token{ .partial_number = "." }); |
| 113 | try expectPeekNext(source, .number, Token{ .partial_number = "1" }); |
| 114 | try expectPeekNext(source, .number, Token{ .partial_number = "e" }); |
| 115 | try expectPeekNext(source, .number, Token{ .partial_number = "-" }); |
| 116 | try expectPeekNext(source, .number, Token{ .partial_number = "1" }); |
| 117 | try expectPeekNext(source, .number, Token{ .number = "" }); |
| 118 | } |
| 119 | try expectPeekNext(source, .true, .true); |
| 120 | try expectPeekNext(source, .false, .false); |
| 121 | try expectPeekNext(source, .null, .null); |
| 122 | try expectPeekNext(source, .object_begin, .object_begin); |
| 123 | if (large_buffer) { |
| 124 | try expectPeekNext(source, .string, Token{ .string = "a" }); |
| 125 | } else { |
| 126 | try expectPeekNext(source, .string, Token{ .partial_string = "a" }); |
| 127 | try expectPeekNext(source, .string, Token{ .string = "" }); |
| 128 | } |
| 129 | try expectPeekNext(source, .object_begin, .object_begin); |
| 130 | try expectPeekNext(source, .object_end, .object_end); |
| 131 | try expectPeekNext(source, .object_end, .object_end); |
| 132 | try expectPeekNext(source, .array_begin, .array_begin); |
| 133 | try expectPeekNext(source, .array_end, .array_end); |
| 134 | try expectPeekNext(source, .array_end, .array_end); |
| 135 | try expectPeekNext(source, .end_of_document, .end_of_document); |
| 136 | } |
| 137 | |
| 138 | test "peek all types" { |
| 139 | var scanner = Scanner.initCompleteInput(std.testing.allocator, all_types_test_case); |
| 140 | defer scanner.deinit(); |
| 141 | try testAllTypes(&scanner, true); |
| 142 | |
| 143 | var stream: std.Io.Reader = .fixed(all_types_test_case); |
| 144 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); |
| 145 | defer json_reader.deinit(); |
| 146 | try testAllTypes(&json_reader, true); |
| 147 | |
| 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 | defer tiny_json_reader.deinit(); |
| 153 | try testAllTypes(&tiny_json_reader, false); |
| 154 | } |
| 155 | |
| 156 | test "token mismatched close" { |
| 157 | var scanner = Scanner.initCompleteInput(std.testing.allocator, "[102, 111, 111 }"); |
| 158 | defer scanner.deinit(); |
| 159 | try expectNext(&scanner, .array_begin); |
| 160 | try expectNext(&scanner, Token{ .number = "102" }); |
| 161 | try expectNext(&scanner, Token{ .number = "111" }); |
| 162 | try expectNext(&scanner, Token{ .number = "111" }); |
| 163 | try std.testing.expectError(error.SyntaxError, scanner.next()); |
| 164 | } |
| 165 | |
| 166 | test "token premature object close" { |
| 167 | var scanner = Scanner.initCompleteInput(std.testing.allocator, "{ \"key\": }"); |
| 168 | defer scanner.deinit(); |
| 169 | try expectNext(&scanner, .object_begin); |
| 170 | try expectNext(&scanner, Token{ .string = "key" }); |
| 171 | try std.testing.expectError(error.SyntaxError, scanner.next()); |
| 172 | } |
| 173 | |
| 174 | test "Scanner basic" { |
| 175 | var scanner = Scanner.initCompleteInput(std.testing.allocator, example_document_str); |
| 176 | defer scanner.deinit(); |
| 177 | |
| 178 | while (true) { |
| 179 | const token = try scanner.next(); |
| 180 | if (token == .end_of_document) break; |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | test "Scanner.Reader basic" { |
| 185 | var stream: std.Io.Reader = .fixed(example_document_str); |
| 186 | |
| 187 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); |
| 188 | defer json_reader.deinit(); |
| 189 | |
| 190 | while (true) { |
| 191 | const token = try json_reader.next(); |
| 192 | if (token == .end_of_document) break; |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | const number_test_stems = .{ |
| 197 | .{ "", "-" }, |
| 198 | .{ "0", "1", "10", "9999999999999999999999999" }, |
| 199 | .{ "", ".0", ".999999999999999999999999" }, |
| 200 | .{ "", "e0", "E0", "e+0", "e-0", "e9999999999999999999999999999" }, |
| 201 | }; |
| 202 | const number_test_items = blk: { |
| 203 | var ret: []const []const u8 = &[_][]const u8{}; |
| 204 | for (number_test_stems[0]) |s0| { |
| 205 | for (number_test_stems[1]) |s1| { |
| 206 | for (number_test_stems[2]) |s2| { |
| 207 | for (number_test_stems[3]) |s3| { |
| 208 | ret = ret ++ &[_][]const u8{s0 ++ s1 ++ s2 ++ s3}; |
| 209 | } |
| 210 | } |
| 211 | } |
| 212 | } |
| 213 | break :blk ret; |
| 214 | }; |
| 215 | |
| 216 | test "numbers" { |
| 217 | for (number_test_items) |number_str| { |
| 218 | var scanner = Scanner.initCompleteInput(std.testing.allocator, number_str); |
| 219 | defer scanner.deinit(); |
| 220 | |
| 221 | const token = try scanner.next(); |
| 222 | const value = token.number; // assert this is a number |
| 223 | try std.testing.expectEqualStrings(number_str, value); |
| 224 | |
| 225 | try std.testing.expectEqual(Token.end_of_document, try scanner.next()); |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | const string_test_cases = .{ |
| 230 | // The left is JSON without the "quotes". |
| 231 | // The right is the expected unescaped content. |
| 232 | .{ "", "" }, |
| 233 | .{ "\\\\", "\\" }, |
| 234 | .{ "a\\\\b", "a\\b" }, |
| 235 | .{ "a\\\"b", "a\"b" }, |
| 236 | .{ "\\n", "\n" }, |
| 237 | .{ "\\u000a", "\n" }, |
| 238 | .{ "𝄞", "\u{1D11E}" }, |
| 239 | .{ "\\uD834\\uDD1E", "\u{1D11E}" }, |
| 240 | .{ "\\uD87F\\uDFFE", "\u{2FFFE}" }, |
| 241 | .{ "\\uff20", "@" }, |
| 242 | }; |
| 243 | |
| 244 | test "strings" { |
| 245 | inline for (string_test_cases) |tuple| { |
| 246 | var stream: std.Io.Reader = .fixed("\"" ++ tuple[0] ++ "\""); |
| 247 | var arena = std.heap.ArenaAllocator.init(std.testing.allocator); |
| 248 | defer arena.deinit(); |
| 249 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); |
| 250 | defer json_reader.deinit(); |
| 251 | |
| 252 | const token = try json_reader.nextAlloc(arena.allocator(), .alloc_if_needed); |
| 253 | const value = switch (token) { |
| 254 | .string => |value| value, |
| 255 | .allocated_string => |value| value, |
| 256 | else => return error.ExpectedString, |
| 257 | }; |
| 258 | try std.testing.expectEqualStrings(tuple[1], value); |
| 259 | |
| 260 | try std.testing.expectEqual(Token.end_of_document, try json_reader.next()); |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | const nesting_test_cases = cases: { |
| 265 | const open_arrays: *const [1000]u8 = &@splat('['); |
| 266 | const close_arrays: *const [1000]u8 = &@splat(']'); |
| 267 | |
| 268 | const open_objects_buf: [1000][4]u8 = @splat("{\"\":".*); |
| 269 | const open_objects: []const u8 = @ptrCast(&open_objects_buf); |
| 270 | const close_objects: *const [1000]u8 = &@splat('}'); |
| 271 | |
| 272 | break :cases .{ |
| 273 | .{ null, "[]" }, |
| 274 | .{ null, "{}" }, |
| 275 | .{ error.SyntaxError, "[}" }, |
| 276 | .{ error.SyntaxError, "{]" }, |
| 277 | .{ null, open_arrays ++ close_arrays }, |
| 278 | .{ null, open_objects ++ "0" ++ close_objects }, |
| 279 | .{ error.SyntaxError, open_arrays ++ close_arrays[0..999] ++ "}" }, |
| 280 | .{ error.SyntaxError, open_objects ++ "0" ++ close_objects[0..999] ++ "]" }, |
| 281 | .{ error.SyntaxError, open_arrays ++ close_arrays ++ "]" }, |
| 282 | .{ error.SyntaxError, open_objects ++ "0" ++ close_objects ++ "}" }, |
| 283 | .{ error.UnexpectedEndOfInput, open_arrays ++ close_arrays[0..999] }, |
| 284 | .{ error.UnexpectedEndOfInput, open_objects ++ "0" ++ close_objects[0..999] }, |
| 285 | }; |
| 286 | }; |
| 287 | |
| 288 | test "nesting" { |
| 289 | inline for (nesting_test_cases) |tuple| { |
| 290 | const maybe_error = tuple[0]; |
| 291 | const document_str = tuple[1]; |
| 292 | |
| 293 | expectMaybeError(document_str, maybe_error) catch |err| { |
| 294 | std.debug.print("in json document: {s}\n", .{document_str}); |
| 295 | return err; |
| 296 | }; |
| 297 | } |
| 298 | } |
| 299 | |
| 300 | fn expectMaybeError(document_str: []const u8, maybe_error: ?Error) !void { |
| 301 | var scanner = Scanner.initCompleteInput(std.testing.allocator, document_str); |
| 302 | defer scanner.deinit(); |
| 303 | |
| 304 | while (true) { |
| 305 | const token = scanner.next() catch |err| { |
| 306 | if (maybe_error) |expected_err| { |
| 307 | if (err == expected_err) return; |
| 308 | } |
| 309 | return err; |
| 310 | }; |
| 311 | if (token == .end_of_document) break; |
| 312 | } |
| 313 | if (maybe_error != null) return error.ExpectedError; |
| 314 | } |
| 315 | |
| 316 | fn expectEqualTokens(expected_token: Token, actual_token: Token) !void { |
| 317 | try std.testing.expectEqual(std.meta.activeTag(expected_token), std.meta.activeTag(actual_token)); |
| 318 | switch (expected_token) { |
| 319 | .number => |expected_value| { |
| 320 | try std.testing.expectEqualStrings(expected_value, actual_token.number); |
| 321 | }, |
| 322 | .allocated_number => |expected_value| { |
| 323 | try std.testing.expectEqualStrings(expected_value, actual_token.allocated_number); |
| 324 | }, |
| 325 | .partial_number => |expected_value| { |
| 326 | try std.testing.expectEqualStrings(expected_value, actual_token.partial_number); |
| 327 | }, |
| 328 | |
| 329 | .string => |expected_value| { |
| 330 | try std.testing.expectEqualStrings(expected_value, actual_token.string); |
| 331 | }, |
| 332 | .allocated_string => |expected_value| { |
| 333 | try std.testing.expectEqualStrings(expected_value, actual_token.allocated_string); |
| 334 | }, |
| 335 | .partial_string => |expected_value| { |
| 336 | try std.testing.expectEqualStrings(expected_value, actual_token.partial_string); |
| 337 | }, |
| 338 | .partial_string_escaped_1 => |expected_value| { |
| 339 | try std.testing.expectEqualStrings(&expected_value, &actual_token.partial_string_escaped_1); |
| 340 | }, |
| 341 | .partial_string_escaped_2 => |expected_value| { |
| 342 | try std.testing.expectEqualStrings(&expected_value, &actual_token.partial_string_escaped_2); |
| 343 | }, |
| 344 | .partial_string_escaped_3 => |expected_value| { |
| 345 | try std.testing.expectEqualStrings(&expected_value, &actual_token.partial_string_escaped_3); |
| 346 | }, |
| 347 | .partial_string_escaped_4 => |expected_value| { |
| 348 | try std.testing.expectEqualStrings(&expected_value, &actual_token.partial_string_escaped_4); |
| 349 | }, |
| 350 | |
| 351 | .object_begin, |
| 352 | .object_end, |
| 353 | .array_begin, |
| 354 | .array_end, |
| 355 | .true, |
| 356 | .false, |
| 357 | .null, |
| 358 | .end_of_document, |
| 359 | => {}, |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | fn testTinyBufferSize(document_str: []const u8) !void { |
| 364 | var tiny_stream: std.Io.Reader = .fixed(document_str); |
| 365 | var normal_stream: std.Io.Reader = .fixed(document_str); |
| 366 | |
| 367 | var tiny_json_reader: Scanner.Reader = .init(std.testing.allocator, &tiny_stream); |
| 368 | defer tiny_json_reader.deinit(); |
| 369 | var normal_json_reader: Scanner.Reader = .init(std.testing.allocator, &normal_stream); |
| 370 | defer normal_json_reader.deinit(); |
| 371 | |
| 372 | expectEqualStreamOfTokens(&normal_json_reader, &tiny_json_reader) catch |err| { |
| 373 | std.debug.print("in json document: {s}\n", .{document_str}); |
| 374 | return err; |
| 375 | }; |
| 376 | } |
| 377 | fn expectEqualStreamOfTokens(control_json_reader: anytype, test_json_reader: anytype) !void { |
| 378 | var arena = std.heap.ArenaAllocator.init(std.testing.allocator); |
| 379 | defer arena.deinit(); |
| 380 | while (true) { |
| 381 | const control_token = try control_json_reader.nextAlloc(arena.allocator(), .alloc_always); |
| 382 | const test_token = try test_json_reader.nextAlloc(arena.allocator(), .alloc_always); |
| 383 | try expectEqualTokens(control_token, test_token); |
| 384 | if (control_token == .end_of_document) break; |
| 385 | _ = arena.reset(.retain_capacity); |
| 386 | } |
| 387 | } |
| 388 | |
| 389 | test "BufferUnderrun" { |
| 390 | try testTinyBufferSize(example_document_str); |
| 391 | for (number_test_items) |number_str| { |
| 392 | try testTinyBufferSize(number_str); |
| 393 | } |
| 394 | inline for (string_test_cases) |tuple| { |
| 395 | try testTinyBufferSize("\"" ++ tuple[0] ++ "\""); |
| 396 | } |
| 397 | } |
| 398 | |
| 399 | test "validate" { |
| 400 | try std.testing.expectEqual(true, try validate(std.testing.allocator, "{}")); |
| 401 | try std.testing.expectEqual(true, try validate(std.testing.allocator, "[]")); |
| 402 | try std.testing.expectEqual(false, try validate(std.testing.allocator, "[{[[[[{}]]]]}]")); |
| 403 | try std.testing.expectEqual(false, try validate(std.testing.allocator, "{]")); |
| 404 | try std.testing.expectEqual(false, try validate(std.testing.allocator, "[}")); |
| 405 | try std.testing.expectEqual(false, try validate(std.testing.allocator, "{{{{[]}}}]")); |
| 406 | } |
| 407 | |
| 408 | fn testSkipValue(s: []const u8) !void { |
| 409 | var scanner = Scanner.initCompleteInput(std.testing.allocator, s); |
| 410 | defer scanner.deinit(); |
| 411 | try scanner.skipValue(); |
| 412 | try expectEqualTokens(.end_of_document, try scanner.next()); |
| 413 | |
| 414 | var stream: std.Io.Reader = .fixed(s); |
| 415 | var json_reader: Scanner.Reader = .init(std.testing.allocator, &stream); |
| 416 | defer json_reader.deinit(); |
| 417 | try json_reader.skipValue(); |
| 418 | try expectEqualTokens(.end_of_document, try json_reader.next()); |
| 419 | } |
| 420 | |
| 421 | test "skipValue" { |
| 422 | try testSkipValue("false"); |
| 423 | try testSkipValue("true"); |
| 424 | try testSkipValue("null"); |
| 425 | try testSkipValue("42"); |
| 426 | try testSkipValue("42.0"); |
| 427 | try testSkipValue("\"foo\""); |
| 428 | try testSkipValue("[101, 111, 121]"); |
| 429 | try testSkipValue("{}"); |
| 430 | try testSkipValue("{\"foo\": \"bar\\nbaz\"}"); |
| 431 | |
| 432 | // An absurd number of nestings |
| 433 | const open_all: [1000]u8 = @splat('['); |
| 434 | const close_all: [1000]u8 = @splat(']'); |
| 435 | try testSkipValue(&(open_all ++ close_all)); |
| 436 | |
| 437 | // Would a number token cause problems in a deeply-nested array? |
| 438 | try testSkipValue(&open_all ++ "0.118, 999, 881.99, 911.9, 725, 3" ++ &close_all); |
| 439 | |
| 440 | // Mismatched brace/square bracket |
| 441 | try std.testing.expectError(error.SyntaxError, testSkipValue("[102, 111, 111}")); |
| 442 | } |
| 443 | |
| 444 | fn testDiagnosticsFromSource(expected_error: ?anyerror, line: u64, col: u64, byte_offset: u64, source: anytype) !void { |
| 445 | var diagnostics = Diagnostics{}; |
| 446 | source.enableDiagnostics(&diagnostics); |
| 447 | |
| 448 | if (expected_error) |expected_err| { |
| 449 | try std.testing.expectError(expected_err, source.skipValue()); |
| 450 | } else { |
| 451 | try source.skipValue(); |
| 452 | try std.testing.expectEqual(Token.end_of_document, try source.next()); |
| 453 | } |
| 454 | try std.testing.expectEqual(line, diagnostics.getLine()); |
| 455 | try std.testing.expectEqual(col, diagnostics.getColumn()); |
| 456 | try std.testing.expectEqual(byte_offset, diagnostics.getByteOffset()); |
| 457 | } |
| 458 | fn testDiagnostics(expected_error: ?anyerror, line: u64, col: u64, byte_offset: u64, s: []const u8) !void { |
| 459 | var scanner = Scanner.initCompleteInput(std.testing.allocator, s); |
| 460 | defer scanner.deinit(); |
| 461 | try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &scanner); |
| 462 | |
| 463 | var tiny_stream: std.Io.Reader = .fixed(s); |
| 464 | var tiny_json_reader: Scanner.Reader = .init(std.testing.allocator, &tiny_stream); |
| 465 | defer tiny_json_reader.deinit(); |
| 466 | try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &tiny_json_reader); |
| 467 | |
| 468 | var medium_stream: std.Io.Reader = .fixed(s); |
| 469 | var medium_json_reader: Scanner.Reader = .init(std.testing.allocator, &medium_stream); |
| 470 | defer medium_json_reader.deinit(); |
| 471 | try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &medium_json_reader); |
| 472 | } |
| 473 | test "enableDiagnostics" { |
| 474 | try testDiagnostics(error.UnexpectedEndOfInput, 1, 1, 0, ""); |
| 475 | try testDiagnostics(null, 1, 3, 2, "[]"); |
| 476 | try testDiagnostics(null, 2, 2, 3, "[\n]"); |
| 477 | try testDiagnostics(null, 14, 2, example_document_str.len, example_document_str); |
| 478 | |
| 479 | try testDiagnostics(error.SyntaxError, 3, 1, 25, |
| 480 | \\{ |
| 481 | \\ "common": "mistake", |
| 482 | \\} |
| 483 | ); |
| 484 | |
| 485 | inline for ([_]comptime_int{ 5, 6, 7, 99 }) |reps| { |
| 486 | // The error happens 1 byte before the end. |
| 487 | const open_all: [reps]u8 = @splat('['); |
| 488 | const s = &open_all ++ "}"; |
| 489 | try testDiagnostics(error.SyntaxError, 1, s.len, s.len - 1, s); |
| 490 | } |
| 491 | } |
| 492 | |
| 493 | test isNumberFormattedLikeAnInteger { |
| 494 | try std.testing.expect(isNumberFormattedLikeAnInteger("0")); |
| 495 | try std.testing.expect(isNumberFormattedLikeAnInteger("1")); |
| 496 | try std.testing.expect(isNumberFormattedLikeAnInteger("123")); |
| 497 | try std.testing.expect(!isNumberFormattedLikeAnInteger("-0")); |
| 498 | try std.testing.expect(!isNumberFormattedLikeAnInteger("0.0")); |
| 499 | try std.testing.expect(!isNumberFormattedLikeAnInteger("1.0")); |
| 500 | try std.testing.expect(!isNumberFormattedLikeAnInteger("1.23")); |
| 501 | try std.testing.expect(!isNumberFormattedLikeAnInteger("1e10")); |
| 502 | try std.testing.expect(!isNumberFormattedLikeAnInteger("1E10")); |
| 503 | } |