| ... | ... | @@ -6,6 +6,7 @@ const ArrayList = std.ArrayList; |
| 6 | 6 | |
| 7 | 7 | const Scanner = @import("./scanner.zig").Scanner; |
| 8 | 8 | const Token = @import("./scanner.zig").Token; |
| 9 | const TokenType = @import("./scanner.zig").TokenType; |
| 9 | 10 | const AllocWhen = @import("./scanner.zig").AllocWhen; |
| 10 | 11 | const Diagnostics = @import("./scanner.zig").Diagnostics; |
| 11 | 12 | const default_max_value_len = @import("./scanner.zig").default_max_value_len; |
| ... | ... | @@ -219,13 +220,13 @@ pub fn innerParse( |
| 219 | 220 | options: ParseOptions, |
| 220 | 221 | ) ParseError(@TypeOf(source.*))!T { |
| 221 | 222 | errdefer source.saveDiagnostics(); |
| 222 | | errdefer maybeRecordDiagnosticContext(allocator, options.diagnostics, @typeName(T)) catch {}; |
| 223 | errdefer maybeRecordDiagnosticContext(allocator, options.diagnostics, @typeName(T)); |
| 223 | 224 | switch (@typeInfo(T)) { |
| 224 | 225 | .Bool => { |
| 225 | 226 | return switch (try source.next()) { |
| 226 | 227 | .true => true, |
| 227 | 228 | .false => false, |
| 228 | | else => error.UnexpectedToken, |
| 229 | else => |t| return typeError(allocator, options.diagnostics, t, "bool"), |
| 229 | 230 | }; |
| 230 | 231 | }, |
| 231 | 232 | .Float, .ComptimeFloat => { |
| ... | ... | @@ -233,7 +234,7 @@ pub fn innerParse( |
| 233 | 234 | defer freeAllocated(allocator, token); |
| 234 | 235 | const slice = switch (token) { |
| 235 | 236 | inline .number, .allocated_number, .string, .allocated_string => |slice| slice, |
| 236 | | else => return error.UnexpectedToken, |
| 237 | else => |t| return typeError(allocator, options.diagnostics, t, "float"), |
| 237 | 238 | }; |
| 238 | 239 | return try std.fmt.parseFloat(T, slice); |
| 239 | 240 | }, |
| ... | ... | @@ -242,7 +243,7 @@ pub fn innerParse( |
| 242 | 243 | defer freeAllocated(allocator, token); |
| 243 | 244 | const slice = switch (token) { |
| 244 | 245 | inline .number, .allocated_number, .string, .allocated_string => |slice| slice, |
| 245 | | else => return error.UnexpectedToken, |
| 246 | else => |t| return typeError(allocator, options.diagnostics, t, "int"), |
| 246 | 247 | }; |
| 247 | 248 | return sliceToInt(T, slice); |
| 248 | 249 | }, |
| ... | ... | @@ -266,7 +267,7 @@ pub fn innerParse( |
| 266 | 267 | defer freeAllocated(allocator, token); |
| 267 | 268 | const slice = switch (token) { |
| 268 | 269 | inline .number, .allocated_number, .string, .allocated_string => |slice| slice, |
| 269 | | else => return error.UnexpectedToken, |
| 270 | else => |t| return typeError(allocator, options.diagnostics, t, "enum (number or string)"), |
| 270 | 271 | }; |
| 271 | 272 | return sliceToEnum(T, slice); |
| 272 | 273 | }, |
| ... | ... | @@ -277,15 +278,16 @@ pub fn innerParse( |
| 277 | 278 | |
| 278 | 279 | if (unionInfo.tag_type == null) @compileError("Unable to parse into untagged union '" ++ @typeName(T) ++ "'"); |
| 279 | 280 | |
| 280 | | if (.object_begin != try source.next()) return error.UnexpectedToken; |
| 281 | switch (try source.next()) { |
| 282 | .object_begin => {}, |
| 283 | else => |t| return typeError(allocator, options.diagnostics, t, "union (object with one field)"), |
| 284 | } |
| 281 | 285 | |
| 282 | 286 | var result: ?T = null; |
| 283 | 287 | var name_token: ?Token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?); |
| 284 | 288 | const field_name = switch (name_token.?) { |
| 285 | 289 | inline .string, .allocated_string => |slice| slice, |
| 286 | | else => { |
| 287 | | return error.UnexpectedToken; |
| 288 | | }, |
| 290 | else => return error.MissingField, |
| 289 | 291 | }; |
| 290 | 292 | |
| 291 | 293 | inline for (unionInfo.fields) |u_field| { |
| ... | ... | @@ -296,7 +298,10 @@ pub fn innerParse( |
| 296 | 298 | name_token = null; |
| 297 | 299 | if (u_field.type == void) { |
| 298 | 300 | // void isn't really a json type, but we can support void payload union tags with {} as a value. |
| 299 | | if (.object_begin != try source.next()) return error.UnexpectedToken; |
| 301 | switch (try source.next()) { |
| 302 | .object_begin => {}, |
| 303 | else => |t| return typeError(allocator, options.diagnostics, t, "void payload ('{}')"), |
| 304 | } |
| 300 | 305 | if (.object_end != try source.next()) return error.UnknownField; |
| 301 | 306 | result = @unionInit(T, u_field.name, {}); |
| 302 | 307 | } else { |
| ... | ... | @@ -310,21 +315,25 @@ pub fn innerParse( |
| 310 | 315 | return error.UnknownField; |
| 311 | 316 | } |
| 312 | 317 | |
| 313 | | if (.object_end != try source.next()) return error.UnexpectedToken; |
| 318 | if (.object_end != try source.next()) return error.UnknownField; |
| 314 | 319 | |
| 315 | 320 | return result.?; |
| 316 | 321 | }, |
| 317 | 322 | |
| 318 | 323 | .Struct => |structInfo| { |
| 319 | 324 | if (structInfo.is_tuple) { |
| 320 | | if (.array_begin != try source.next()) return error.UnexpectedToken; |
| 325 | switch (try source.next()) { |
| 326 | .array_begin => {}, |
| 327 | else => |t| return typeError(allocator, options.diagnostics, t, "tuple (array of values)"), |
| 328 | } |
| 321 | 329 | |
| 322 | 330 | var r: T = undefined; |
| 323 | 331 | inline for (0..structInfo.fields.len) |i| { |
| 332 | if (.array_end == try source.peekNextTokenType()) return error.LengthMismatch; |
| 324 | 333 | r[i] = try innerParse(structInfo.fields[i].type, allocator, source, options); |
| 325 | 334 | } |
| 326 | 335 | |
| 327 | | if (.array_end != try source.next()) return error.UnexpectedToken; |
| 336 | if (.array_end != try source.next()) return error.LengthMismatch; |
| 328 | 337 | |
| 329 | 338 | return r; |
| 330 | 339 | } |
| ... | ... | @@ -333,7 +342,10 @@ pub fn innerParse( |
| 333 | 342 | return T.jsonParse(allocator, source, options); |
| 334 | 343 | } |
| 335 | 344 | |
| 336 | | if (.object_begin != try source.next()) return error.UnexpectedToken; |
| 345 | switch (try source.next()) { |
| 346 | .object_begin => {}, |
| 347 | else => |t| return typeError(allocator, options.diagnostics, t, "struct ('{...}')"), |
| 348 | } |
| 337 | 349 | |
| 338 | 350 | var r: T = undefined; |
| 339 | 351 | var fields_seen = [_]bool{false} ** structInfo.fields.len; |
| ... | ... | @@ -354,7 +366,7 @@ pub fn innerParse( |
| 354 | 366 | // Free the name token now in case we're using an allocator that optimizes freeing the last allocated object. |
| 355 | 367 | // (Recursing into innerParse() might trigger more allocations.) |
| 356 | 368 | freeAllocated(allocator, name_token.?); |
| 357 | | errdefer maybeRecordDiagnosticContext(allocator, options.diagnostics, @typeName(T) ++ "." ++ field.name) catch {}; |
| 369 | errdefer maybeRecordDiagnosticContext(allocator, options.diagnostics, @typeName(T) ++ "." ++ field.name); |
| 358 | 370 | name_token = null; |
| 359 | 371 | if (fields_seen[i]) { |
| 360 | 372 | switch (options.duplicate_field_behavior) { |
| ... | ... | @@ -393,7 +405,7 @@ pub fn innerParse( |
| 393 | 405 | return internalParseArray(T, arrayInfo.child, arrayInfo.len, allocator, source, options); |
| 394 | 406 | }, |
| 395 | 407 | .string => { |
| 396 | | if (arrayInfo.child != u8) return error.UnexpectedToken; |
| 408 | if (arrayInfo.child != u8) return typeError(allocator, options.diagnostics, .string, "array"); |
| 397 | 409 | // Fixed-length string. |
| 398 | 410 | |
| 399 | 411 | var r: T = undefined; |
| ... | ... | @@ -437,7 +449,7 @@ pub fn innerParse( |
| 437 | 449 | return r; |
| 438 | 450 | }, |
| 439 | 451 | |
| 440 | | else => return error.UnexpectedToken, |
| 452 | else => |t| return typeError(allocator, options.diagnostics, t, "array"), |
| 441 | 453 | } |
| 442 | 454 | }, |
| 443 | 455 | |
| ... | ... | @@ -446,7 +458,7 @@ pub fn innerParse( |
| 446 | 458 | .array_begin => { |
| 447 | 459 | return internalParseArray(T, vecInfo.child, vecInfo.len, allocator, source, options); |
| 448 | 460 | }, |
| 449 | | else => return error.UnexpectedToken, |
| 461 | else => |t| return typeError(allocator, options.diagnostics, t, "array"), |
| 450 | 462 | } |
| 451 | 463 | }, |
| 452 | 464 | |
| ... | ... | @@ -485,7 +497,7 @@ pub fn innerParse( |
| 485 | 497 | return try arraylist.toOwnedSlice(); |
| 486 | 498 | }, |
| 487 | 499 | .string => { |
| 488 | | if (ptrInfo.child != u8) return error.UnexpectedToken; |
| 500 | if (ptrInfo.child != u8) return typeError(allocator, options.diagnostics, .string, "array"); |
| 489 | 501 | |
| 490 | 502 | // Dynamic length string. |
| 491 | 503 | if (ptrInfo.sentinel) |sentinel_ptr| { |
| ... | ... | @@ -507,7 +519,7 @@ pub fn innerParse( |
| 507 | 519 | } |
| 508 | 520 | } |
| 509 | 521 | }, |
| 510 | | else => return error.UnexpectedToken, |
| 522 | else => |t| return typeError(allocator, options.diagnostics, t, "array"), |
| 511 | 523 | } |
| 512 | 524 | }, |
| 513 | 525 | else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"), |
| ... | ... | @@ -531,14 +543,67 @@ fn internalParseArray( |
| 531 | 543 | var r: T = undefined; |
| 532 | 544 | var i: usize = 0; |
| 533 | 545 | while (i < len) : (i += 1) { |
| 546 | if (.array_end == try source.peekNextTokenType()) return error.LengthMismatch; |
| 534 | 547 | r[i] = try innerParse(Child, allocator, source, options); |
| 535 | 548 | } |
| 536 | 549 | |
| 537 | | if (.array_end != try source.next()) return error.UnexpectedToken; |
| 550 | if (.array_end != try source.next()) return error.LengthMismatch; |
| 538 | 551 | |
| 539 | 552 | return r; |
| 540 | 553 | } |
| 541 | 554 | |
| 555 | fn coerceToTokenType(token: anytype) TokenType { |
| 556 | if (@TypeOf(token) == TokenType) return token; |
| 557 | return switch (@as(std.meta.Tag(Token), token)) { |
| 558 | // Coerce Token tag into TokenType |
| 559 | .object_begin => .object_begin, |
| 560 | .array_begin => .array_begin, |
| 561 | |
| 562 | .true => .true, |
| 563 | .false => .false, |
| 564 | .null => .null, |
| 565 | |
| 566 | .number, |
| 567 | .partial_number, |
| 568 | .allocated_number, |
| 569 | => .number, |
| 570 | |
| 571 | .string, |
| 572 | .partial_string, |
| 573 | .partial_string_escaped_1, |
| 574 | .partial_string_escaped_2, |
| 575 | .partial_string_escaped_3, |
| 576 | .partial_string_escaped_4, |
| 577 | .allocated_string, |
| 578 | => .string, |
| 579 | |
| 580 | .object_end => .object_end, |
| 581 | .array_end => .array_end, |
| 582 | .end_of_document => .end_of_document, |
| 583 | }; |
| 584 | } |
| 585 | fn typeError(allocator: Allocator, diagnostics: ?*Diagnostics, token: anytype, expected: []const u8) error{UnexpectedToken} { |
| 586 | if (diagnostics) |diag| { |
| 587 | if (std.fmt.allocPrint(allocator, "expected: {s}, found: {s}", .{ |
| 588 | expected, |
| 589 | switch (coerceToTokenType(token)) { |
| 590 | .object_begin => "'{'", |
| 591 | .array_begin => "'['", |
| 592 | .true, .false => "bool", |
| 593 | .null => "null", |
| 594 | .number => "number", |
| 595 | .string => "string", |
| 596 | .object_end => unreachable, // type errors happen at the start of a value. |
| 597 | .array_end => unreachable, // type errors happen at the start of a value. |
| 598 | .end_of_document => unreachable, // type errors happen at the start of a value. |
| 599 | }, |
| 600 | })) |s| { |
| 601 | diag.recordContext(allocator, s) catch {}; |
| 602 | } else |_| {} |
| 603 | } |
| 604 | return error.UnexpectedToken; |
| 605 | } |
| 606 | |
| 542 | 607 | /// This is an internal function called recursively |
| 543 | 608 | /// during the implementation of `parseFromValueLeaky`. |
| 544 | 609 | /// It is exposed primarily to enable custom `jsonParseFromValue()` methods to call back into the `parseFromValue*` system, |