| ... | ... | @@ -1374,6 +1374,65 @@ test "Value.jsonStringify" { |
| 1374 | 1374 | } |
| 1375 | 1375 | } |
| 1376 | 1376 | |
| 1377 | /// parse tokens from a stream, returning `false` if they do not decode to `value` |
| 1378 | fn parsesTo(comptime T: type, value: T, tokens: *TokenStream, options: ParseOptions) !bool { |
| 1379 | // TODO: should be able to write this function to not require an allocator |
| 1380 | const tmp = try parse(T, tokens, options); |
| 1381 | defer parseFree(T, tmp, options); |
| 1382 | |
| 1383 | return parsedEqual(tmp, value); |
| 1384 | } |
| 1385 | |
| 1386 | /// Returns if a value returned by `parse` is deep-equal to another value |
| 1387 | fn parsedEqual(a: anytype, b: @TypeOf(a)) bool { |
| 1388 | switch (@typeInfo(@TypeOf(a))) { |
| 1389 | .Optional => { |
| 1390 | if (a == null and b == null) return true; |
| 1391 | if (a == null or b == null) return false; |
| 1392 | return parsedEqual(a.?, b.?); |
| 1393 | }, |
| 1394 | .Union => |unionInfo| { |
| 1395 | if (info.tag_type) |UnionTag| { |
| 1396 | const tag_a = std.meta.activeTag(a); |
| 1397 | const tag_b = std.meta.activeTag(b); |
| 1398 | if (tag_a != tag_b) return false; |
| 1399 | |
| 1400 | inline for (info.fields) |field_info| { |
| 1401 | if (@field(UnionTag, field_info.name) == tag_a) { |
| 1402 | return parsedEqual(@field(a, field_info.name), @field(b, field_info.name)); |
| 1403 | } |
| 1404 | } |
| 1405 | return false; |
| 1406 | } else { |
| 1407 | unreachable; |
| 1408 | } |
| 1409 | }, |
| 1410 | .Array => { |
| 1411 | for (a) |e, i| |
| 1412 | if (!parsedEqual(e, b[i])) return false; |
| 1413 | return true; |
| 1414 | }, |
| 1415 | .Struct => |info| { |
| 1416 | inline for (info.fields) |field_info| { |
| 1417 | if (!parsedEqual(@field(a, field_info.name), @field(b, field_info.name))) return false; |
| 1418 | } |
| 1419 | return true; |
| 1420 | }, |
| 1421 | .Pointer => |ptrInfo| switch (ptrInfo.size) { |
| 1422 | .One => return parsedEqual(a.*, b.*), |
| 1423 | .Slice => { |
| 1424 | if (a.len != b.len) return false; |
| 1425 | for (a) |e, i| |
| 1426 | if (!parsedEqual(e, b[i])) return false; |
| 1427 | return true; |
| 1428 | }, |
| 1429 | .Many, .C => unreachable, |
| 1430 | }, |
| 1431 | else => return a == b, |
| 1432 | } |
| 1433 | unreachable; |
| 1434 | } |
| 1435 | |
| 1377 | 1436 | pub const ParseOptions = struct { |
| 1378 | 1437 | allocator: ?*Allocator = null, |
| 1379 | 1438 | |
| ... | ... | @@ -1507,9 +1566,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: |
| 1507 | 1566 | } |
| 1508 | 1567 | } |
| 1509 | 1568 | if (field.is_comptime) { |
| 1510 | | const value = try parse(field.field_type, tokens, options); |
| 1511 | | defer parseFree(field.field_type, value, options); |
| 1512 | | if (value != @field(r, field.name)) { |
| 1569 | if (!try parsesTo(field.field_type, field.default_value.?, tokens, options)) { |
| 1513 | 1570 | return error.UnexpectedValue; |
| 1514 | 1571 | } |
| 1515 | 1572 | } else { |
| ... | ... | @@ -1809,16 +1866,40 @@ test "parseFree descends into tagged union" { |
| 1809 | 1866 | } |
| 1810 | 1867 | |
| 1811 | 1868 | test "parse with comptime field" { |
| 1812 | | const T = struct { |
| 1813 | | comptime a: i32 = 0, |
| 1814 | | b: bool, |
| 1815 | | }; |
| 1816 | | testing.expectEqual(T{ .a = 0, .b = true }, try parse(T, &TokenStream.init( |
| 1817 | | \\{ |
| 1818 | | \\ "a": 0, |
| 1819 | | \\ "b": true |
| 1820 | | \\} |
| 1821 | | ), ParseOptions{})); |
| 1869 | { |
| 1870 | const T = struct { |
| 1871 | comptime a: i32 = 0, |
| 1872 | b: bool, |
| 1873 | }; |
| 1874 | testing.expectEqual(T{ .a = 0, .b = true }, try parse(T, &TokenStream.init( |
| 1875 | \\{ |
| 1876 | \\ "a": 0, |
| 1877 | \\ "b": true |
| 1878 | \\} |
| 1879 | ), ParseOptions{})); |
| 1880 | } |
| 1881 | |
| 1882 | { // string comptime values currently require an allocator |
| 1883 | const T = union(enum) { |
| 1884 | foo: struct { |
| 1885 | comptime kind: []const u8 = "boolean", |
| 1886 | b: bool, |
| 1887 | }, |
| 1888 | bar: struct { |
| 1889 | comptime kind: []const u8 = "float", |
| 1890 | b: f64, |
| 1891 | }, |
| 1892 | }; |
| 1893 | |
| 1894 | const r = try std.json.parse(T, &std.json.TokenStream.init( |
| 1895 | \\{ |
| 1896 | \\ "kind": "float", |
| 1897 | \\ "b": 1.0 |
| 1898 | \\} |
| 1899 | ), .{ |
| 1900 | .allocator = std.testing.allocator, |
| 1901 | }); |
| 1902 | } |
| 1822 | 1903 | } |
| 1823 | 1904 | |
| 1824 | 1905 | test "parse into struct with no fields" { |