mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
std: Rewrite low-level json api to support streaming (#15602)
This commit is contained in:
parent
c7bf8bab38
commit
018b743c7a
20 changed files with 5794 additions and 5846 deletions
2857
lib/std/json.zig
2857
lib/std/json.zig
File diff suppressed because it is too large
Load diff
960
lib/std/json/JSONTestSuite_test.zig
Normal file
960
lib/std/json/JSONTestSuite_test.zig
Normal file
|
|
@ -0,0 +1,960 @@
|
||||||
|
// This file was generated by _generate_JSONTestSuite.zig
|
||||||
|
// These test cases are sourced from: https://github.com/nst/JSONTestSuite
|
||||||
|
const ok = @import("./test.zig").ok;
|
||||||
|
const err = @import("./test.zig").err;
|
||||||
|
const any = @import("./test.zig").any;
|
||||||
|
|
||||||
|
test "i_number_double_huge_neg_exp.json" {
|
||||||
|
try any("[123.456e-789]");
|
||||||
|
}
|
||||||
|
test "i_number_huge_exp.json" {
|
||||||
|
try any("[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006]");
|
||||||
|
}
|
||||||
|
test "i_number_neg_int_huge_exp.json" {
|
||||||
|
try any("[-1e+9999]");
|
||||||
|
}
|
||||||
|
test "i_number_pos_double_huge_exp.json" {
|
||||||
|
try any("[1.5e+9999]");
|
||||||
|
}
|
||||||
|
test "i_number_real_neg_overflow.json" {
|
||||||
|
try any("[-123123e100000]");
|
||||||
|
}
|
||||||
|
test "i_number_real_pos_overflow.json" {
|
||||||
|
try any("[123123e100000]");
|
||||||
|
}
|
||||||
|
test "i_number_real_underflow.json" {
|
||||||
|
try any("[123e-10000000]");
|
||||||
|
}
|
||||||
|
test "i_number_too_big_neg_int.json" {
|
||||||
|
try any("[-123123123123123123123123123123]");
|
||||||
|
}
|
||||||
|
test "i_number_too_big_pos_int.json" {
|
||||||
|
try any("[100000000000000000000]");
|
||||||
|
}
|
||||||
|
test "i_number_very_big_negative_int.json" {
|
||||||
|
try any("[-237462374673276894279832749832423479823246327846]");
|
||||||
|
}
|
||||||
|
test "i_object_key_lone_2nd_surrogate.json" {
|
||||||
|
try any("{\"\\uDFAA\":0}");
|
||||||
|
}
|
||||||
|
test "i_string_1st_surrogate_but_2nd_missing.json" {
|
||||||
|
try any("[\"\\uDADA\"]");
|
||||||
|
}
|
||||||
|
test "i_string_1st_valid_surrogate_2nd_invalid.json" {
|
||||||
|
try any("[\"\\uD888\\u1234\"]");
|
||||||
|
}
|
||||||
|
test "i_string_UTF-16LE_with_BOM.json" {
|
||||||
|
try any("\xff\xfe[\x00\"\x00\xe9\x00\"\x00]\x00");
|
||||||
|
}
|
||||||
|
test "i_string_UTF-8_invalid_sequence.json" {
|
||||||
|
try any("[\"\xe6\x97\xa5\xd1\x88\xfa\"]");
|
||||||
|
}
|
||||||
|
test "i_string_UTF8_surrogate_U+D800.json" {
|
||||||
|
try any("[\"\xed\xa0\x80\"]");
|
||||||
|
}
|
||||||
|
test "i_string_incomplete_surrogate_and_escape_valid.json" {
|
||||||
|
try any("[\"\\uD800\\n\"]");
|
||||||
|
}
|
||||||
|
test "i_string_incomplete_surrogate_pair.json" {
|
||||||
|
try any("[\"\\uDd1ea\"]");
|
||||||
|
}
|
||||||
|
test "i_string_incomplete_surrogates_escape_valid.json" {
|
||||||
|
try any("[\"\\uD800\\uD800\\n\"]");
|
||||||
|
}
|
||||||
|
test "i_string_invalid_lonely_surrogate.json" {
|
||||||
|
try any("[\"\\ud800\"]");
|
||||||
|
}
|
||||||
|
test "i_string_invalid_surrogate.json" {
|
||||||
|
try any("[\"\\ud800abc\"]");
|
||||||
|
}
|
||||||
|
test "i_string_invalid_utf-8.json" {
|
||||||
|
try any("[\"\xff\"]");
|
||||||
|
}
|
||||||
|
test "i_string_inverted_surrogates_U+1D11E.json" {
|
||||||
|
try any("[\"\\uDd1e\\uD834\"]");
|
||||||
|
}
|
||||||
|
test "i_string_iso_latin_1.json" {
|
||||||
|
try any("[\"\xe9\"]");
|
||||||
|
}
|
||||||
|
test "i_string_lone_second_surrogate.json" {
|
||||||
|
try any("[\"\\uDFAA\"]");
|
||||||
|
}
|
||||||
|
test "i_string_lone_utf8_continuation_byte.json" {
|
||||||
|
try any("[\"\x81\"]");
|
||||||
|
}
|
||||||
|
test "i_string_not_in_unicode_range.json" {
|
||||||
|
try any("[\"\xf4\xbf\xbf\xbf\"]");
|
||||||
|
}
|
||||||
|
test "i_string_overlong_sequence_2_bytes.json" {
|
||||||
|
try any("[\"\xc0\xaf\"]");
|
||||||
|
}
|
||||||
|
test "i_string_overlong_sequence_6_bytes.json" {
|
||||||
|
try any("[\"\xfc\x83\xbf\xbf\xbf\xbf\"]");
|
||||||
|
}
|
||||||
|
test "i_string_overlong_sequence_6_bytes_null.json" {
|
||||||
|
try any("[\"\xfc\x80\x80\x80\x80\x80\"]");
|
||||||
|
}
|
||||||
|
test "i_string_truncated-utf-8.json" {
|
||||||
|
try any("[\"\xe0\xff\"]");
|
||||||
|
}
|
||||||
|
test "i_string_utf16BE_no_BOM.json" {
|
||||||
|
try any("\x00[\x00\"\x00\xe9\x00\"\x00]");
|
||||||
|
}
|
||||||
|
test "i_string_utf16LE_no_BOM.json" {
|
||||||
|
try any("[\x00\"\x00\xe9\x00\"\x00]\x00");
|
||||||
|
}
|
||||||
|
test "i_structure_500_nested_arrays.json" {
|
||||||
|
try any("[" ** 500 ++ "]" ** 500);
|
||||||
|
}
|
||||||
|
test "i_structure_UTF-8_BOM_empty_object.json" {
|
||||||
|
try any("\xef\xbb\xbf{}");
|
||||||
|
}
|
||||||
|
test "n_array_1_true_without_comma.json" {
|
||||||
|
try err("[1 true]");
|
||||||
|
}
|
||||||
|
test "n_array_a_invalid_utf8.json" {
|
||||||
|
try err("[a\xe5]");
|
||||||
|
}
|
||||||
|
test "n_array_colon_instead_of_comma.json" {
|
||||||
|
try err("[\"\": 1]");
|
||||||
|
}
|
||||||
|
test "n_array_comma_after_close.json" {
|
||||||
|
try err("[\"\"],");
|
||||||
|
}
|
||||||
|
test "n_array_comma_and_number.json" {
|
||||||
|
try err("[,1]");
|
||||||
|
}
|
||||||
|
test "n_array_double_comma.json" {
|
||||||
|
try err("[1,,2]");
|
||||||
|
}
|
||||||
|
test "n_array_double_extra_comma.json" {
|
||||||
|
try err("[\"x\",,]");
|
||||||
|
}
|
||||||
|
test "n_array_extra_close.json" {
|
||||||
|
try err("[\"x\"]]");
|
||||||
|
}
|
||||||
|
test "n_array_extra_comma.json" {
|
||||||
|
try err("[\"\",]");
|
||||||
|
}
|
||||||
|
test "n_array_incomplete.json" {
|
||||||
|
try err("[\"x\"");
|
||||||
|
}
|
||||||
|
test "n_array_incomplete_invalid_value.json" {
|
||||||
|
try err("[x");
|
||||||
|
}
|
||||||
|
test "n_array_inner_array_no_comma.json" {
|
||||||
|
try err("[3[4]]");
|
||||||
|
}
|
||||||
|
test "n_array_invalid_utf8.json" {
|
||||||
|
try err("[\xff]");
|
||||||
|
}
|
||||||
|
test "n_array_items_separated_by_semicolon.json" {
|
||||||
|
try err("[1:2]");
|
||||||
|
}
|
||||||
|
test "n_array_just_comma.json" {
|
||||||
|
try err("[,]");
|
||||||
|
}
|
||||||
|
test "n_array_just_minus.json" {
|
||||||
|
try err("[-]");
|
||||||
|
}
|
||||||
|
test "n_array_missing_value.json" {
|
||||||
|
try err("[ , \"\"]");
|
||||||
|
}
|
||||||
|
test "n_array_newlines_unclosed.json" {
|
||||||
|
try err("[\"a\",\n4\n,1,");
|
||||||
|
}
|
||||||
|
test "n_array_number_and_comma.json" {
|
||||||
|
try err("[1,]");
|
||||||
|
}
|
||||||
|
test "n_array_number_and_several_commas.json" {
|
||||||
|
try err("[1,,]");
|
||||||
|
}
|
||||||
|
test "n_array_spaces_vertical_tab_formfeed.json" {
|
||||||
|
try err("[\"\x0ba\"\\f]");
|
||||||
|
}
|
||||||
|
test "n_array_star_inside.json" {
|
||||||
|
try err("[*]");
|
||||||
|
}
|
||||||
|
test "n_array_unclosed.json" {
|
||||||
|
try err("[\"\"");
|
||||||
|
}
|
||||||
|
test "n_array_unclosed_trailing_comma.json" {
|
||||||
|
try err("[1,");
|
||||||
|
}
|
||||||
|
test "n_array_unclosed_with_new_lines.json" {
|
||||||
|
try err("[1,\n1\n,1");
|
||||||
|
}
|
||||||
|
test "n_array_unclosed_with_object_inside.json" {
|
||||||
|
try err("[{}");
|
||||||
|
}
|
||||||
|
test "n_incomplete_false.json" {
|
||||||
|
try err("[fals]");
|
||||||
|
}
|
||||||
|
test "n_incomplete_null.json" {
|
||||||
|
try err("[nul]");
|
||||||
|
}
|
||||||
|
test "n_incomplete_true.json" {
|
||||||
|
try err("[tru]");
|
||||||
|
}
|
||||||
|
test "n_multidigit_number_then_00.json" {
|
||||||
|
try err("123\x00");
|
||||||
|
}
|
||||||
|
test "n_number_++.json" {
|
||||||
|
try err("[++1234]");
|
||||||
|
}
|
||||||
|
test "n_number_+1.json" {
|
||||||
|
try err("[+1]");
|
||||||
|
}
|
||||||
|
test "n_number_+Inf.json" {
|
||||||
|
try err("[+Inf]");
|
||||||
|
}
|
||||||
|
test "n_number_-01.json" {
|
||||||
|
try err("[-01]");
|
||||||
|
}
|
||||||
|
test "n_number_-1.0..json" {
|
||||||
|
try err("[-1.0.]");
|
||||||
|
}
|
||||||
|
test "n_number_-2..json" {
|
||||||
|
try err("[-2.]");
|
||||||
|
}
|
||||||
|
test "n_number_-NaN.json" {
|
||||||
|
try err("[-NaN]");
|
||||||
|
}
|
||||||
|
test "n_number_.-1.json" {
|
||||||
|
try err("[.-1]");
|
||||||
|
}
|
||||||
|
test "n_number_.2e-3.json" {
|
||||||
|
try err("[.2e-3]");
|
||||||
|
}
|
||||||
|
test "n_number_0.1.2.json" {
|
||||||
|
try err("[0.1.2]");
|
||||||
|
}
|
||||||
|
test "n_number_0.3e+.json" {
|
||||||
|
try err("[0.3e+]");
|
||||||
|
}
|
||||||
|
test "n_number_0.3e.json" {
|
||||||
|
try err("[0.3e]");
|
||||||
|
}
|
||||||
|
test "n_number_0.e1.json" {
|
||||||
|
try err("[0.e1]");
|
||||||
|
}
|
||||||
|
test "n_number_0_capital_E+.json" {
|
||||||
|
try err("[0E+]");
|
||||||
|
}
|
||||||
|
test "n_number_0_capital_E.json" {
|
||||||
|
try err("[0E]");
|
||||||
|
}
|
||||||
|
test "n_number_0e+.json" {
|
||||||
|
try err("[0e+]");
|
||||||
|
}
|
||||||
|
test "n_number_0e.json" {
|
||||||
|
try err("[0e]");
|
||||||
|
}
|
||||||
|
test "n_number_1.0e+.json" {
|
||||||
|
try err("[1.0e+]");
|
||||||
|
}
|
||||||
|
test "n_number_1.0e-.json" {
|
||||||
|
try err("[1.0e-]");
|
||||||
|
}
|
||||||
|
test "n_number_1.0e.json" {
|
||||||
|
try err("[1.0e]");
|
||||||
|
}
|
||||||
|
test "n_number_1_000.json" {
|
||||||
|
try err("[1 000.0]");
|
||||||
|
}
|
||||||
|
test "n_number_1eE2.json" {
|
||||||
|
try err("[1eE2]");
|
||||||
|
}
|
||||||
|
test "n_number_2.e+3.json" {
|
||||||
|
try err("[2.e+3]");
|
||||||
|
}
|
||||||
|
test "n_number_2.e-3.json" {
|
||||||
|
try err("[2.e-3]");
|
||||||
|
}
|
||||||
|
test "n_number_2.e3.json" {
|
||||||
|
try err("[2.e3]");
|
||||||
|
}
|
||||||
|
test "n_number_9.e+.json" {
|
||||||
|
try err("[9.e+]");
|
||||||
|
}
|
||||||
|
test "n_number_Inf.json" {
|
||||||
|
try err("[Inf]");
|
||||||
|
}
|
||||||
|
test "n_number_NaN.json" {
|
||||||
|
try err("[NaN]");
|
||||||
|
}
|
||||||
|
test "n_number_U+FF11_fullwidth_digit_one.json" {
|
||||||
|
try err("[\xef\xbc\x91]");
|
||||||
|
}
|
||||||
|
test "n_number_expression.json" {
|
||||||
|
try err("[1+2]");
|
||||||
|
}
|
||||||
|
test "n_number_hex_1_digit.json" {
|
||||||
|
try err("[0x1]");
|
||||||
|
}
|
||||||
|
test "n_number_hex_2_digits.json" {
|
||||||
|
try err("[0x42]");
|
||||||
|
}
|
||||||
|
test "n_number_infinity.json" {
|
||||||
|
try err("[Infinity]");
|
||||||
|
}
|
||||||
|
test "n_number_invalid+-.json" {
|
||||||
|
try err("[0e+-1]");
|
||||||
|
}
|
||||||
|
test "n_number_invalid-negative-real.json" {
|
||||||
|
try err("[-123.123foo]");
|
||||||
|
}
|
||||||
|
test "n_number_invalid-utf-8-in-bigger-int.json" {
|
||||||
|
try err("[123\xe5]");
|
||||||
|
}
|
||||||
|
test "n_number_invalid-utf-8-in-exponent.json" {
|
||||||
|
try err("[1e1\xe5]");
|
||||||
|
}
|
||||||
|
test "n_number_invalid-utf-8-in-int.json" {
|
||||||
|
try err("[0\xe5]\n");
|
||||||
|
}
|
||||||
|
test "n_number_minus_infinity.json" {
|
||||||
|
try err("[-Infinity]");
|
||||||
|
}
|
||||||
|
test "n_number_minus_sign_with_trailing_garbage.json" {
|
||||||
|
try err("[-foo]");
|
||||||
|
}
|
||||||
|
test "n_number_minus_space_1.json" {
|
||||||
|
try err("[- 1]");
|
||||||
|
}
|
||||||
|
test "n_number_neg_int_starting_with_zero.json" {
|
||||||
|
try err("[-012]");
|
||||||
|
}
|
||||||
|
test "n_number_neg_real_without_int_part.json" {
|
||||||
|
try err("[-.123]");
|
||||||
|
}
|
||||||
|
test "n_number_neg_with_garbage_at_end.json" {
|
||||||
|
try err("[-1x]");
|
||||||
|
}
|
||||||
|
test "n_number_real_garbage_after_e.json" {
|
||||||
|
try err("[1ea]");
|
||||||
|
}
|
||||||
|
test "n_number_real_with_invalid_utf8_after_e.json" {
|
||||||
|
try err("[1e\xe5]");
|
||||||
|
}
|
||||||
|
test "n_number_real_without_fractional_part.json" {
|
||||||
|
try err("[1.]");
|
||||||
|
}
|
||||||
|
test "n_number_starting_with_dot.json" {
|
||||||
|
try err("[.123]");
|
||||||
|
}
|
||||||
|
test "n_number_with_alpha.json" {
|
||||||
|
try err("[1.2a-3]");
|
||||||
|
}
|
||||||
|
test "n_number_with_alpha_char.json" {
|
||||||
|
try err("[1.8011670033376514H-308]");
|
||||||
|
}
|
||||||
|
test "n_number_with_leading_zero.json" {
|
||||||
|
try err("[012]");
|
||||||
|
}
|
||||||
|
test "n_object_bad_value.json" {
|
||||||
|
try err("[\"x\", truth]");
|
||||||
|
}
|
||||||
|
test "n_object_bracket_key.json" {
|
||||||
|
try err("{[: \"x\"}\n");
|
||||||
|
}
|
||||||
|
test "n_object_comma_instead_of_colon.json" {
|
||||||
|
try err("{\"x\", null}");
|
||||||
|
}
|
||||||
|
test "n_object_double_colon.json" {
|
||||||
|
try err("{\"x\"::\"b\"}");
|
||||||
|
}
|
||||||
|
test "n_object_emoji.json" {
|
||||||
|
try err("{\xf0\x9f\x87\xa8\xf0\x9f\x87\xad}");
|
||||||
|
}
|
||||||
|
test "n_object_garbage_at_end.json" {
|
||||||
|
try err("{\"a\":\"a\" 123}");
|
||||||
|
}
|
||||||
|
test "n_object_key_with_single_quotes.json" {
|
||||||
|
try err("{key: 'value'}");
|
||||||
|
}
|
||||||
|
test "n_object_lone_continuation_byte_in_key_and_trailing_comma.json" {
|
||||||
|
try err("{\"\xb9\":\"0\",}");
|
||||||
|
}
|
||||||
|
test "n_object_missing_colon.json" {
|
||||||
|
try err("{\"a\" b}");
|
||||||
|
}
|
||||||
|
test "n_object_missing_key.json" {
|
||||||
|
try err("{:\"b\"}");
|
||||||
|
}
|
||||||
|
test "n_object_missing_semicolon.json" {
|
||||||
|
try err("{\"a\" \"b\"}");
|
||||||
|
}
|
||||||
|
test "n_object_missing_value.json" {
|
||||||
|
try err("{\"a\":");
|
||||||
|
}
|
||||||
|
test "n_object_no-colon.json" {
|
||||||
|
try err("{\"a\"");
|
||||||
|
}
|
||||||
|
test "n_object_non_string_key.json" {
|
||||||
|
try err("{1:1}");
|
||||||
|
}
|
||||||
|
test "n_object_non_string_key_but_huge_number_instead.json" {
|
||||||
|
try err("{9999E9999:1}");
|
||||||
|
}
|
||||||
|
test "n_object_repeated_null_null.json" {
|
||||||
|
try err("{null:null,null:null}");
|
||||||
|
}
|
||||||
|
test "n_object_several_trailing_commas.json" {
|
||||||
|
try err("{\"id\":0,,,,,}");
|
||||||
|
}
|
||||||
|
test "n_object_single_quote.json" {
|
||||||
|
try err("{'a':0}");
|
||||||
|
}
|
||||||
|
test "n_object_trailing_comma.json" {
|
||||||
|
try err("{\"id\":0,}");
|
||||||
|
}
|
||||||
|
test "n_object_trailing_comment.json" {
|
||||||
|
try err("{\"a\":\"b\"}/**/");
|
||||||
|
}
|
||||||
|
test "n_object_trailing_comment_open.json" {
|
||||||
|
try err("{\"a\":\"b\"}/**//");
|
||||||
|
}
|
||||||
|
test "n_object_trailing_comment_slash_open.json" {
|
||||||
|
try err("{\"a\":\"b\"}//");
|
||||||
|
}
|
||||||
|
test "n_object_trailing_comment_slash_open_incomplete.json" {
|
||||||
|
try err("{\"a\":\"b\"}/");
|
||||||
|
}
|
||||||
|
test "n_object_two_commas_in_a_row.json" {
|
||||||
|
try err("{\"a\":\"b\",,\"c\":\"d\"}");
|
||||||
|
}
|
||||||
|
test "n_object_unquoted_key.json" {
|
||||||
|
try err("{a: \"b\"}");
|
||||||
|
}
|
||||||
|
test "n_object_unterminated-value.json" {
|
||||||
|
try err("{\"a\":\"a");
|
||||||
|
}
|
||||||
|
test "n_object_with_single_string.json" {
|
||||||
|
try err("{ \"foo\" : \"bar\", \"a\" }");
|
||||||
|
}
|
||||||
|
test "n_object_with_trailing_garbage.json" {
|
||||||
|
try err("{\"a\":\"b\"}#");
|
||||||
|
}
|
||||||
|
test "n_single_space.json" {
|
||||||
|
try err(" ");
|
||||||
|
}
|
||||||
|
test "n_string_1_surrogate_then_escape.json" {
|
||||||
|
try err("[\"\\uD800\\\"]");
|
||||||
|
}
|
||||||
|
test "n_string_1_surrogate_then_escape_u.json" {
|
||||||
|
try err("[\"\\uD800\\u\"]");
|
||||||
|
}
|
||||||
|
test "n_string_1_surrogate_then_escape_u1.json" {
|
||||||
|
try err("[\"\\uD800\\u1\"]");
|
||||||
|
}
|
||||||
|
test "n_string_1_surrogate_then_escape_u1x.json" {
|
||||||
|
try err("[\"\\uD800\\u1x\"]");
|
||||||
|
}
|
||||||
|
test "n_string_accentuated_char_no_quotes.json" {
|
||||||
|
try err("[\xc3\xa9]");
|
||||||
|
}
|
||||||
|
test "n_string_backslash_00.json" {
|
||||||
|
try err("[\"\\\x00\"]");
|
||||||
|
}
|
||||||
|
test "n_string_escape_x.json" {
|
||||||
|
try err("[\"\\x00\"]");
|
||||||
|
}
|
||||||
|
test "n_string_escaped_backslash_bad.json" {
|
||||||
|
try err("[\"\\\\\\\"]");
|
||||||
|
}
|
||||||
|
test "n_string_escaped_ctrl_char_tab.json" {
|
||||||
|
try err("[\"\\\x09\"]");
|
||||||
|
}
|
||||||
|
test "n_string_escaped_emoji.json" {
|
||||||
|
try err("[\"\\\xf0\x9f\x8c\x80\"]");
|
||||||
|
}
|
||||||
|
test "n_string_incomplete_escape.json" {
|
||||||
|
try err("[\"\\\"]");
|
||||||
|
}
|
||||||
|
test "n_string_incomplete_escaped_character.json" {
|
||||||
|
try err("[\"\\u00A\"]");
|
||||||
|
}
|
||||||
|
test "n_string_incomplete_surrogate.json" {
|
||||||
|
try err("[\"\\uD834\\uDd\"]");
|
||||||
|
}
|
||||||
|
test "n_string_incomplete_surrogate_escape_invalid.json" {
|
||||||
|
try err("[\"\\uD800\\uD800\\x\"]");
|
||||||
|
}
|
||||||
|
test "n_string_invalid-utf-8-in-escape.json" {
|
||||||
|
try err("[\"\\u\xe5\"]");
|
||||||
|
}
|
||||||
|
test "n_string_invalid_backslash_esc.json" {
|
||||||
|
try err("[\"\\a\"]");
|
||||||
|
}
|
||||||
|
test "n_string_invalid_unicode_escape.json" {
|
||||||
|
try err("[\"\\uqqqq\"]");
|
||||||
|
}
|
||||||
|
test "n_string_invalid_utf8_after_escape.json" {
|
||||||
|
try err("[\"\\\xe5\"]");
|
||||||
|
}
|
||||||
|
test "n_string_leading_uescaped_thinspace.json" {
|
||||||
|
try err("[\\u0020\"asd\"]");
|
||||||
|
}
|
||||||
|
test "n_string_no_quotes_with_bad_escape.json" {
|
||||||
|
try err("[\\n]");
|
||||||
|
}
|
||||||
|
test "n_string_single_doublequote.json" {
|
||||||
|
try err("\"");
|
||||||
|
}
|
||||||
|
test "n_string_single_quote.json" {
|
||||||
|
try err("['single quote']");
|
||||||
|
}
|
||||||
|
test "n_string_single_string_no_double_quotes.json" {
|
||||||
|
try err("abc");
|
||||||
|
}
|
||||||
|
test "n_string_start_escape_unclosed.json" {
|
||||||
|
try err("[\"\\");
|
||||||
|
}
|
||||||
|
test "n_string_unescaped_ctrl_char.json" {
|
||||||
|
try err("[\"a\x00a\"]");
|
||||||
|
}
|
||||||
|
test "n_string_unescaped_newline.json" {
|
||||||
|
try err("[\"new\nline\"]");
|
||||||
|
}
|
||||||
|
test "n_string_unescaped_tab.json" {
|
||||||
|
try err("[\"\x09\"]");
|
||||||
|
}
|
||||||
|
test "n_string_unicode_CapitalU.json" {
|
||||||
|
try err("\"\\UA66D\"");
|
||||||
|
}
|
||||||
|
test "n_string_with_trailing_garbage.json" {
|
||||||
|
try err("\"\"x");
|
||||||
|
}
|
||||||
|
test "n_structure_100000_opening_arrays.json" {
|
||||||
|
try err("[" ** 100000);
|
||||||
|
}
|
||||||
|
test "n_structure_U+2060_word_joined.json" {
|
||||||
|
try err("[\xe2\x81\xa0]");
|
||||||
|
}
|
||||||
|
test "n_structure_UTF8_BOM_no_data.json" {
|
||||||
|
try err("\xef\xbb\xbf");
|
||||||
|
}
|
||||||
|
test "n_structure_angle_bracket_..json" {
|
||||||
|
try err("<.>");
|
||||||
|
}
|
||||||
|
test "n_structure_angle_bracket_null.json" {
|
||||||
|
try err("[<null>]");
|
||||||
|
}
|
||||||
|
test "n_structure_array_trailing_garbage.json" {
|
||||||
|
try err("[1]x");
|
||||||
|
}
|
||||||
|
test "n_structure_array_with_extra_array_close.json" {
|
||||||
|
try err("[1]]");
|
||||||
|
}
|
||||||
|
test "n_structure_array_with_unclosed_string.json" {
|
||||||
|
try err("[\"asd]");
|
||||||
|
}
|
||||||
|
test "n_structure_ascii-unicode-identifier.json" {
|
||||||
|
try err("a\xc3\xa5");
|
||||||
|
}
|
||||||
|
test "n_structure_capitalized_True.json" {
|
||||||
|
try err("[True]");
|
||||||
|
}
|
||||||
|
test "n_structure_close_unopened_array.json" {
|
||||||
|
try err("1]");
|
||||||
|
}
|
||||||
|
test "n_structure_comma_instead_of_closing_brace.json" {
|
||||||
|
try err("{\"x\": true,");
|
||||||
|
}
|
||||||
|
test "n_structure_double_array.json" {
|
||||||
|
try err("[][]");
|
||||||
|
}
|
||||||
|
test "n_structure_end_array.json" {
|
||||||
|
try err("]");
|
||||||
|
}
|
||||||
|
test "n_structure_incomplete_UTF8_BOM.json" {
|
||||||
|
try err("\xef\xbb{}");
|
||||||
|
}
|
||||||
|
test "n_structure_lone-invalid-utf-8.json" {
|
||||||
|
try err("\xe5");
|
||||||
|
}
|
||||||
|
test "n_structure_lone-open-bracket.json" {
|
||||||
|
try err("[");
|
||||||
|
}
|
||||||
|
test "n_structure_no_data.json" {
|
||||||
|
try err("");
|
||||||
|
}
|
||||||
|
test "n_structure_null-byte-outside-string.json" {
|
||||||
|
try err("[\x00]");
|
||||||
|
}
|
||||||
|
test "n_structure_number_with_trailing_garbage.json" {
|
||||||
|
try err("2@");
|
||||||
|
}
|
||||||
|
test "n_structure_object_followed_by_closing_object.json" {
|
||||||
|
try err("{}}");
|
||||||
|
}
|
||||||
|
test "n_structure_object_unclosed_no_value.json" {
|
||||||
|
try err("{\"\":");
|
||||||
|
}
|
||||||
|
test "n_structure_object_with_comment.json" {
|
||||||
|
try err("{\"a\":/*comment*/\"b\"}");
|
||||||
|
}
|
||||||
|
test "n_structure_object_with_trailing_garbage.json" {
|
||||||
|
try err("{\"a\": true} \"x\"");
|
||||||
|
}
|
||||||
|
test "n_structure_open_array_apostrophe.json" {
|
||||||
|
try err("['");
|
||||||
|
}
|
||||||
|
test "n_structure_open_array_comma.json" {
|
||||||
|
try err("[,");
|
||||||
|
}
|
||||||
|
test "n_structure_open_array_object.json" {
|
||||||
|
try err("[{\"\":" ** 50000 ++ "\n");
|
||||||
|
}
|
||||||
|
test "n_structure_open_array_open_object.json" {
|
||||||
|
try err("[{");
|
||||||
|
}
|
||||||
|
test "n_structure_open_array_open_string.json" {
|
||||||
|
try err("[\"a");
|
||||||
|
}
|
||||||
|
test "n_structure_open_array_string.json" {
|
||||||
|
try err("[\"a\"");
|
||||||
|
}
|
||||||
|
test "n_structure_open_object.json" {
|
||||||
|
try err("{");
|
||||||
|
}
|
||||||
|
test "n_structure_open_object_close_array.json" {
|
||||||
|
try err("{]");
|
||||||
|
}
|
||||||
|
test "n_structure_open_object_comma.json" {
|
||||||
|
try err("{,");
|
||||||
|
}
|
||||||
|
test "n_structure_open_object_open_array.json" {
|
||||||
|
try err("{[");
|
||||||
|
}
|
||||||
|
test "n_structure_open_object_open_string.json" {
|
||||||
|
try err("{\"a");
|
||||||
|
}
|
||||||
|
test "n_structure_open_object_string_with_apostrophes.json" {
|
||||||
|
try err("{'a'");
|
||||||
|
}
|
||||||
|
test "n_structure_open_open.json" {
|
||||||
|
try err("[\"\\{[\"\\{[\"\\{[\"\\{");
|
||||||
|
}
|
||||||
|
test "n_structure_single_eacute.json" {
|
||||||
|
try err("\xe9");
|
||||||
|
}
|
||||||
|
test "n_structure_single_star.json" {
|
||||||
|
try err("*");
|
||||||
|
}
|
||||||
|
test "n_structure_trailing_#.json" {
|
||||||
|
try err("{\"a\":\"b\"}#{}");
|
||||||
|
}
|
||||||
|
test "n_structure_uescaped_LF_before_string.json" {
|
||||||
|
try err("[\\u000A\"\"]");
|
||||||
|
}
|
||||||
|
test "n_structure_unclosed_array.json" {
|
||||||
|
try err("[1");
|
||||||
|
}
|
||||||
|
test "n_structure_unclosed_array_partial_null.json" {
|
||||||
|
try err("[ false, nul");
|
||||||
|
}
|
||||||
|
test "n_structure_unclosed_array_unfinished_false.json" {
|
||||||
|
try err("[ true, fals");
|
||||||
|
}
|
||||||
|
test "n_structure_unclosed_array_unfinished_true.json" {
|
||||||
|
try err("[ false, tru");
|
||||||
|
}
|
||||||
|
test "n_structure_unclosed_object.json" {
|
||||||
|
try err("{\"asd\":\"asd\"");
|
||||||
|
}
|
||||||
|
test "n_structure_unicode-identifier.json" {
|
||||||
|
try err("\xc3\xa5");
|
||||||
|
}
|
||||||
|
test "n_structure_whitespace_U+2060_word_joiner.json" {
|
||||||
|
try err("[\xe2\x81\xa0]");
|
||||||
|
}
|
||||||
|
test "n_structure_whitespace_formfeed.json" {
|
||||||
|
try err("[\x0c]");
|
||||||
|
}
|
||||||
|
test "y_array_arraysWithSpaces.json" {
|
||||||
|
try ok("[[] ]");
|
||||||
|
}
|
||||||
|
test "y_array_empty-string.json" {
|
||||||
|
try ok("[\"\"]");
|
||||||
|
}
|
||||||
|
test "y_array_empty.json" {
|
||||||
|
try ok("[]");
|
||||||
|
}
|
||||||
|
test "y_array_ending_with_newline.json" {
|
||||||
|
try ok("[\"a\"]");
|
||||||
|
}
|
||||||
|
test "y_array_false.json" {
|
||||||
|
try ok("[false]");
|
||||||
|
}
|
||||||
|
test "y_array_heterogeneous.json" {
|
||||||
|
try ok("[null, 1, \"1\", {}]");
|
||||||
|
}
|
||||||
|
test "y_array_null.json" {
|
||||||
|
try ok("[null]");
|
||||||
|
}
|
||||||
|
test "y_array_with_1_and_newline.json" {
|
||||||
|
try ok("[1\n]");
|
||||||
|
}
|
||||||
|
test "y_array_with_leading_space.json" {
|
||||||
|
try ok(" [1]");
|
||||||
|
}
|
||||||
|
test "y_array_with_several_null.json" {
|
||||||
|
try ok("[1,null,null,null,2]");
|
||||||
|
}
|
||||||
|
test "y_array_with_trailing_space.json" {
|
||||||
|
try ok("[2] ");
|
||||||
|
}
|
||||||
|
test "y_number.json" {
|
||||||
|
try ok("[123e65]");
|
||||||
|
}
|
||||||
|
test "y_number_0e+1.json" {
|
||||||
|
try ok("[0e+1]");
|
||||||
|
}
|
||||||
|
test "y_number_0e1.json" {
|
||||||
|
try ok("[0e1]");
|
||||||
|
}
|
||||||
|
test "y_number_after_space.json" {
|
||||||
|
try ok("[ 4]");
|
||||||
|
}
|
||||||
|
test "y_number_double_close_to_zero.json" {
|
||||||
|
try ok("[-0.000000000000000000000000000000000000000000000000000000000000000000000000000001]\n");
|
||||||
|
}
|
||||||
|
test "y_number_int_with_exp.json" {
|
||||||
|
try ok("[20e1]");
|
||||||
|
}
|
||||||
|
test "y_number_minus_zero.json" {
|
||||||
|
try ok("[-0]");
|
||||||
|
}
|
||||||
|
test "y_number_negative_int.json" {
|
||||||
|
try ok("[-123]");
|
||||||
|
}
|
||||||
|
test "y_number_negative_one.json" {
|
||||||
|
try ok("[-1]");
|
||||||
|
}
|
||||||
|
test "y_number_negative_zero.json" {
|
||||||
|
try ok("[-0]");
|
||||||
|
}
|
||||||
|
test "y_number_real_capital_e.json" {
|
||||||
|
try ok("[1E22]");
|
||||||
|
}
|
||||||
|
test "y_number_real_capital_e_neg_exp.json" {
|
||||||
|
try ok("[1E-2]");
|
||||||
|
}
|
||||||
|
test "y_number_real_capital_e_pos_exp.json" {
|
||||||
|
try ok("[1E+2]");
|
||||||
|
}
|
||||||
|
test "y_number_real_exponent.json" {
|
||||||
|
try ok("[123e45]");
|
||||||
|
}
|
||||||
|
test "y_number_real_fraction_exponent.json" {
|
||||||
|
try ok("[123.456e78]");
|
||||||
|
}
|
||||||
|
test "y_number_real_neg_exp.json" {
|
||||||
|
try ok("[1e-2]");
|
||||||
|
}
|
||||||
|
test "y_number_real_pos_exponent.json" {
|
||||||
|
try ok("[1e+2]");
|
||||||
|
}
|
||||||
|
test "y_number_simple_int.json" {
|
||||||
|
try ok("[123]");
|
||||||
|
}
|
||||||
|
test "y_number_simple_real.json" {
|
||||||
|
try ok("[123.456789]");
|
||||||
|
}
|
||||||
|
test "y_object.json" {
|
||||||
|
try ok("{\"asd\":\"sdf\", \"dfg\":\"fgh\"}");
|
||||||
|
}
|
||||||
|
test "y_object_basic.json" {
|
||||||
|
try ok("{\"asd\":\"sdf\"}");
|
||||||
|
}
|
||||||
|
test "y_object_duplicated_key.json" {
|
||||||
|
try ok("{\"a\":\"b\",\"a\":\"c\"}");
|
||||||
|
}
|
||||||
|
test "y_object_duplicated_key_and_value.json" {
|
||||||
|
try ok("{\"a\":\"b\",\"a\":\"b\"}");
|
||||||
|
}
|
||||||
|
test "y_object_empty.json" {
|
||||||
|
try ok("{}");
|
||||||
|
}
|
||||||
|
test "y_object_empty_key.json" {
|
||||||
|
try ok("{\"\":0}");
|
||||||
|
}
|
||||||
|
test "y_object_escaped_null_in_key.json" {
|
||||||
|
try ok("{\"foo\\u0000bar\": 42}");
|
||||||
|
}
|
||||||
|
test "y_object_extreme_numbers.json" {
|
||||||
|
try ok("{ \"min\": -1.0e+28, \"max\": 1.0e+28 }");
|
||||||
|
}
|
||||||
|
test "y_object_long_strings.json" {
|
||||||
|
try ok("{\"x\":[{\"id\": \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"}], \"id\": \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"}");
|
||||||
|
}
|
||||||
|
test "y_object_simple.json" {
|
||||||
|
try ok("{\"a\":[]}");
|
||||||
|
}
|
||||||
|
test "y_object_string_unicode.json" {
|
||||||
|
try ok("{\"title\":\"\\u041f\\u043e\\u043b\\u0442\\u043e\\u0440\\u0430 \\u0417\\u0435\\u043c\\u043b\\u0435\\u043a\\u043e\\u043f\\u0430\" }");
|
||||||
|
}
|
||||||
|
test "y_object_with_newlines.json" {
|
||||||
|
try ok("{\n\"a\": \"b\"\n}");
|
||||||
|
}
|
||||||
|
test "y_string_1_2_3_bytes_UTF-8_sequences.json" {
|
||||||
|
try ok("[\"\\u0060\\u012a\\u12AB\"]");
|
||||||
|
}
|
||||||
|
test "y_string_accepted_surrogate_pair.json" {
|
||||||
|
try ok("[\"\\uD801\\udc37\"]");
|
||||||
|
}
|
||||||
|
test "y_string_accepted_surrogate_pairs.json" {
|
||||||
|
try ok("[\"\\ud83d\\ude39\\ud83d\\udc8d\"]");
|
||||||
|
}
|
||||||
|
test "y_string_allowed_escapes.json" {
|
||||||
|
try ok("[\"\\\"\\\\\\/\\b\\f\\n\\r\\t\"]");
|
||||||
|
}
|
||||||
|
test "y_string_backslash_and_u_escaped_zero.json" {
|
||||||
|
try ok("[\"\\\\u0000\"]");
|
||||||
|
}
|
||||||
|
test "y_string_backslash_doublequotes.json" {
|
||||||
|
try ok("[\"\\\"\"]");
|
||||||
|
}
|
||||||
|
test "y_string_comments.json" {
|
||||||
|
try ok("[\"a/*b*/c/*d//e\"]");
|
||||||
|
}
|
||||||
|
test "y_string_double_escape_a.json" {
|
||||||
|
try ok("[\"\\\\a\"]");
|
||||||
|
}
|
||||||
|
test "y_string_double_escape_n.json" {
|
||||||
|
try ok("[\"\\\\n\"]");
|
||||||
|
}
|
||||||
|
test "y_string_escaped_control_character.json" {
|
||||||
|
try ok("[\"\\u0012\"]");
|
||||||
|
}
|
||||||
|
test "y_string_escaped_noncharacter.json" {
|
||||||
|
try ok("[\"\\uFFFF\"]");
|
||||||
|
}
|
||||||
|
test "y_string_in_array.json" {
|
||||||
|
try ok("[\"asd\"]");
|
||||||
|
}
|
||||||
|
test "y_string_in_array_with_leading_space.json" {
|
||||||
|
try ok("[ \"asd\"]");
|
||||||
|
}
|
||||||
|
test "y_string_last_surrogates_1_and_2.json" {
|
||||||
|
try ok("[\"\\uDBFF\\uDFFF\"]");
|
||||||
|
}
|
||||||
|
test "y_string_nbsp_uescaped.json" {
|
||||||
|
try ok("[\"new\\u00A0line\"]");
|
||||||
|
}
|
||||||
|
test "y_string_nonCharacterInUTF-8_U+10FFFF.json" {
|
||||||
|
try ok("[\"\xf4\x8f\xbf\xbf\"]");
|
||||||
|
}
|
||||||
|
test "y_string_nonCharacterInUTF-8_U+FFFF.json" {
|
||||||
|
try ok("[\"\xef\xbf\xbf\"]");
|
||||||
|
}
|
||||||
|
test "y_string_null_escape.json" {
|
||||||
|
try ok("[\"\\u0000\"]");
|
||||||
|
}
|
||||||
|
test "y_string_one-byte-utf-8.json" {
|
||||||
|
try ok("[\"\\u002c\"]");
|
||||||
|
}
|
||||||
|
test "y_string_pi.json" {
|
||||||
|
try ok("[\"\xcf\x80\"]");
|
||||||
|
}
|
||||||
|
test "y_string_reservedCharacterInUTF-8_U+1BFFF.json" {
|
||||||
|
try ok("[\"\xf0\x9b\xbf\xbf\"]");
|
||||||
|
}
|
||||||
|
test "y_string_simple_ascii.json" {
|
||||||
|
try ok("[\"asd \"]");
|
||||||
|
}
|
||||||
|
test "y_string_space.json" {
|
||||||
|
try ok("\" \"");
|
||||||
|
}
|
||||||
|
test "y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json" {
|
||||||
|
try ok("[\"\\uD834\\uDd1e\"]");
|
||||||
|
}
|
||||||
|
test "y_string_three-byte-utf-8.json" {
|
||||||
|
try ok("[\"\\u0821\"]");
|
||||||
|
}
|
||||||
|
test "y_string_two-byte-utf-8.json" {
|
||||||
|
try ok("[\"\\u0123\"]");
|
||||||
|
}
|
||||||
|
test "y_string_u+2028_line_sep.json" {
|
||||||
|
try ok("[\"\xe2\x80\xa8\"]");
|
||||||
|
}
|
||||||
|
test "y_string_u+2029_par_sep.json" {
|
||||||
|
try ok("[\"\xe2\x80\xa9\"]");
|
||||||
|
}
|
||||||
|
test "y_string_uEscape.json" {
|
||||||
|
try ok("[\"\\u0061\\u30af\\u30EA\\u30b9\"]");
|
||||||
|
}
|
||||||
|
test "y_string_uescaped_newline.json" {
|
||||||
|
try ok("[\"new\\u000Aline\"]");
|
||||||
|
}
|
||||||
|
test "y_string_unescaped_char_delete.json" {
|
||||||
|
try ok("[\"\x7f\"]");
|
||||||
|
}
|
||||||
|
test "y_string_unicode.json" {
|
||||||
|
try ok("[\"\\uA66D\"]");
|
||||||
|
}
|
||||||
|
test "y_string_unicodeEscapedBackslash.json" {
|
||||||
|
try ok("[\"\\u005C\"]");
|
||||||
|
}
|
||||||
|
test "y_string_unicode_2.json" {
|
||||||
|
try ok("[\"\xe2\x8d\x82\xe3\x88\xb4\xe2\x8d\x82\"]");
|
||||||
|
}
|
||||||
|
test "y_string_unicode_U+10FFFE_nonchar.json" {
|
||||||
|
try ok("[\"\\uDBFF\\uDFFE\"]");
|
||||||
|
}
|
||||||
|
test "y_string_unicode_U+1FFFE_nonchar.json" {
|
||||||
|
try ok("[\"\\uD83F\\uDFFE\"]");
|
||||||
|
}
|
||||||
|
test "y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json" {
|
||||||
|
try ok("[\"\\u200B\"]");
|
||||||
|
}
|
||||||
|
test "y_string_unicode_U+2064_invisible_plus.json" {
|
||||||
|
try ok("[\"\\u2064\"]");
|
||||||
|
}
|
||||||
|
test "y_string_unicode_U+FDD0_nonchar.json" {
|
||||||
|
try ok("[\"\\uFDD0\"]");
|
||||||
|
}
|
||||||
|
test "y_string_unicode_U+FFFE_nonchar.json" {
|
||||||
|
try ok("[\"\\uFFFE\"]");
|
||||||
|
}
|
||||||
|
test "y_string_unicode_escaped_double_quote.json" {
|
||||||
|
try ok("[\"\\u0022\"]");
|
||||||
|
}
|
||||||
|
test "y_string_utf8.json" {
|
||||||
|
try ok("[\"\xe2\x82\xac\xf0\x9d\x84\x9e\"]");
|
||||||
|
}
|
||||||
|
test "y_string_with_del_character.json" {
|
||||||
|
try ok("[\"a\x7fa\"]");
|
||||||
|
}
|
||||||
|
test "y_structure_lonely_false.json" {
|
||||||
|
try ok("false");
|
||||||
|
}
|
||||||
|
test "y_structure_lonely_int.json" {
|
||||||
|
try ok("42");
|
||||||
|
}
|
||||||
|
test "y_structure_lonely_negative_real.json" {
|
||||||
|
try ok("-0.1");
|
||||||
|
}
|
||||||
|
test "y_structure_lonely_null.json" {
|
||||||
|
try ok("null");
|
||||||
|
}
|
||||||
|
test "y_structure_lonely_string.json" {
|
||||||
|
try ok("\"asd\"");
|
||||||
|
}
|
||||||
|
test "y_structure_lonely_true.json" {
|
||||||
|
try ok("true");
|
||||||
|
}
|
||||||
|
test "y_structure_string_empty.json" {
|
||||||
|
try ok("\"\"");
|
||||||
|
}
|
||||||
|
test "y_structure_trailing_newline.json" {
|
||||||
|
try ok("[\"a\"]\n");
|
||||||
|
}
|
||||||
|
test "y_structure_true_in_array.json" {
|
||||||
|
try ok("[true]");
|
||||||
|
}
|
||||||
|
test "y_structure_whitespace_array.json" {
|
||||||
|
try ok(" [] ");
|
||||||
|
}
|
||||||
344
lib/std/json/dynamic.zig
Normal file
344
lib/std/json/dynamic.zig
Normal file
|
|
@ -0,0 +1,344 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const debug = std.debug;
|
||||||
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
const ArrayList = std.ArrayList;
|
||||||
|
const StringArrayHashMap = std.StringArrayHashMap;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const StringifyOptions = @import("./stringify.zig").StringifyOptions;
|
||||||
|
const stringify = @import("./stringify.zig").stringify;
|
||||||
|
|
||||||
|
const JsonScanner = @import("./scanner.zig").Scanner;
|
||||||
|
const AllocWhen = @import("./scanner.zig").AllocWhen;
|
||||||
|
const Token = @import("./scanner.zig").Token;
|
||||||
|
const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger;
|
||||||
|
|
||||||
|
pub const ValueTree = struct {
|
||||||
|
arena: *ArenaAllocator,
|
||||||
|
root: Value,
|
||||||
|
|
||||||
|
pub fn deinit(self: *ValueTree) void {
|
||||||
|
self.arena.deinit();
|
||||||
|
self.arena.child_allocator.destroy(self.arena);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ObjectMap = StringArrayHashMap(Value);
|
||||||
|
pub const Array = ArrayList(Value);
|
||||||
|
|
||||||
|
/// Represents a JSON value
|
||||||
|
/// Currently only supports numbers that fit into i64 or f64.
|
||||||
|
pub const Value = union(enum) {
|
||||||
|
null,
|
||||||
|
bool: bool,
|
||||||
|
integer: i64,
|
||||||
|
float: f64,
|
||||||
|
number_string: []const u8,
|
||||||
|
string: []const u8,
|
||||||
|
array: Array,
|
||||||
|
object: ObjectMap,
|
||||||
|
|
||||||
|
pub fn jsonStringify(
|
||||||
|
value: @This(),
|
||||||
|
options: StringifyOptions,
|
||||||
|
out_stream: anytype,
|
||||||
|
) @TypeOf(out_stream).Error!void {
|
||||||
|
switch (value) {
|
||||||
|
.null => try stringify(null, options, out_stream),
|
||||||
|
.bool => |inner| try stringify(inner, options, out_stream),
|
||||||
|
.integer => |inner| try stringify(inner, options, out_stream),
|
||||||
|
.float => |inner| try stringify(inner, options, out_stream),
|
||||||
|
.number_string => |inner| try out_stream.writeAll(inner),
|
||||||
|
.string => |inner| try stringify(inner, options, out_stream),
|
||||||
|
.array => |inner| try stringify(inner.items, options, out_stream),
|
||||||
|
.object => |inner| {
|
||||||
|
try out_stream.writeByte('{');
|
||||||
|
var field_output = false;
|
||||||
|
var child_options = options;
|
||||||
|
child_options.whitespace.indent_level += 1;
|
||||||
|
var it = inner.iterator();
|
||||||
|
while (it.next()) |entry| {
|
||||||
|
if (!field_output) {
|
||||||
|
field_output = true;
|
||||||
|
} else {
|
||||||
|
try out_stream.writeByte(',');
|
||||||
|
}
|
||||||
|
try child_options.whitespace.outputIndent(out_stream);
|
||||||
|
|
||||||
|
try stringify(entry.key_ptr.*, options, out_stream);
|
||||||
|
try out_stream.writeByte(':');
|
||||||
|
if (child_options.whitespace.separator) {
|
||||||
|
try out_stream.writeByte(' ');
|
||||||
|
}
|
||||||
|
try stringify(entry.value_ptr.*, child_options, out_stream);
|
||||||
|
}
|
||||||
|
if (field_output) {
|
||||||
|
try options.whitespace.outputIndent(out_stream);
|
||||||
|
}
|
||||||
|
try out_stream.writeByte('}');
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump(self: Value) void {
|
||||||
|
std.debug.getStderrMutex().lock();
|
||||||
|
defer std.debug.getStderrMutex().unlock();
|
||||||
|
|
||||||
|
const stderr = std.io.getStdErr().writer();
|
||||||
|
stringify(self, .{}, stderr) catch return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A non-stream JSON parser which constructs a tree of Value's.
|
||||||
|
pub const Parser = struct {
|
||||||
|
allocator: Allocator,
|
||||||
|
state: State,
|
||||||
|
alloc_when: AllocWhen,
|
||||||
|
// Stores parent nodes and un-combined Values.
|
||||||
|
stack: Array,
|
||||||
|
|
||||||
|
const State = enum {
|
||||||
|
object_key,
|
||||||
|
object_value,
|
||||||
|
array_value,
|
||||||
|
simple,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(allocator: Allocator, alloc_when: AllocWhen) Parser {
|
||||||
|
return Parser{
|
||||||
|
.allocator = allocator,
|
||||||
|
.state = .simple,
|
||||||
|
.alloc_when = alloc_when,
|
||||||
|
.stack = Array.init(allocator),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(p: *Parser) void {
|
||||||
|
p.stack.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(p: *Parser) void {
|
||||||
|
p.state = .simple;
|
||||||
|
p.stack.shrinkRetainingCapacity(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(p: *Parser, input: []const u8) !ValueTree {
|
||||||
|
var scanner = JsonScanner.initCompleteInput(p.allocator, input);
|
||||||
|
defer scanner.deinit();
|
||||||
|
|
||||||
|
var arena = try p.allocator.create(ArenaAllocator);
|
||||||
|
errdefer p.allocator.destroy(arena);
|
||||||
|
|
||||||
|
arena.* = ArenaAllocator.init(p.allocator);
|
||||||
|
errdefer arena.deinit();
|
||||||
|
|
||||||
|
const allocator = arena.allocator();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const token = try scanner.nextAlloc(allocator, p.alloc_when);
|
||||||
|
if (token == .end_of_document) break;
|
||||||
|
try p.transition(allocator, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug.assert(p.stack.items.len == 1);
|
||||||
|
|
||||||
|
return ValueTree{
|
||||||
|
.arena = arena,
|
||||||
|
.root = p.stack.items[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even though p.allocator exists, we take an explicit allocator so that allocation state
|
||||||
|
// can be cleaned up on error correctly during a `parse` on call.
|
||||||
|
fn transition(p: *Parser, allocator: Allocator, token: Token) !void {
|
||||||
|
switch (p.state) {
|
||||||
|
.object_key => switch (token) {
|
||||||
|
.object_end => {
|
||||||
|
if (p.stack.items.len == 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = p.stack.pop();
|
||||||
|
try p.pushToParent(&value);
|
||||||
|
},
|
||||||
|
.string => |s| {
|
||||||
|
try p.stack.append(Value{ .string = s });
|
||||||
|
p.state = .object_value;
|
||||||
|
},
|
||||||
|
.allocated_string => |s| {
|
||||||
|
try p.stack.append(Value{ .string = s });
|
||||||
|
p.state = .object_value;
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
},
|
||||||
|
.object_value => {
|
||||||
|
var object = &p.stack.items[p.stack.items.len - 2].object;
|
||||||
|
var key = p.stack.items[p.stack.items.len - 1].string;
|
||||||
|
|
||||||
|
switch (token) {
|
||||||
|
.object_begin => {
|
||||||
|
try p.stack.append(Value{ .object = ObjectMap.init(allocator) });
|
||||||
|
p.state = .object_key;
|
||||||
|
},
|
||||||
|
.array_begin => {
|
||||||
|
try p.stack.append(Value{ .array = Array.init(allocator) });
|
||||||
|
p.state = .array_value;
|
||||||
|
},
|
||||||
|
.string => |s| {
|
||||||
|
try object.put(key, Value{ .string = s });
|
||||||
|
_ = p.stack.pop();
|
||||||
|
p.state = .object_key;
|
||||||
|
},
|
||||||
|
.allocated_string => |s| {
|
||||||
|
try object.put(key, Value{ .string = s });
|
||||||
|
_ = p.stack.pop();
|
||||||
|
p.state = .object_key;
|
||||||
|
},
|
||||||
|
.number => |slice| {
|
||||||
|
try object.put(key, try p.parseNumber(slice));
|
||||||
|
_ = p.stack.pop();
|
||||||
|
p.state = .object_key;
|
||||||
|
},
|
||||||
|
.allocated_number => |slice| {
|
||||||
|
try object.put(key, try p.parseNumber(slice));
|
||||||
|
_ = p.stack.pop();
|
||||||
|
p.state = .object_key;
|
||||||
|
},
|
||||||
|
.true => {
|
||||||
|
try object.put(key, Value{ .bool = true });
|
||||||
|
_ = p.stack.pop();
|
||||||
|
p.state = .object_key;
|
||||||
|
},
|
||||||
|
.false => {
|
||||||
|
try object.put(key, Value{ .bool = false });
|
||||||
|
_ = p.stack.pop();
|
||||||
|
p.state = .object_key;
|
||||||
|
},
|
||||||
|
.null => {
|
||||||
|
try object.put(key, .null);
|
||||||
|
_ = p.stack.pop();
|
||||||
|
p.state = .object_key;
|
||||||
|
},
|
||||||
|
.object_end, .array_end, .end_of_document => unreachable,
|
||||||
|
.partial_number, .partial_string, .partial_string_escaped_1, .partial_string_escaped_2, .partial_string_escaped_3, .partial_string_escaped_4 => unreachable,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.array_value => {
|
||||||
|
var array = &p.stack.items[p.stack.items.len - 1].array;
|
||||||
|
|
||||||
|
switch (token) {
|
||||||
|
.array_end => {
|
||||||
|
if (p.stack.items.len == 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = p.stack.pop();
|
||||||
|
try p.pushToParent(&value);
|
||||||
|
},
|
||||||
|
.object_begin => {
|
||||||
|
try p.stack.append(Value{ .object = ObjectMap.init(allocator) });
|
||||||
|
p.state = .object_key;
|
||||||
|
},
|
||||||
|
.array_begin => {
|
||||||
|
try p.stack.append(Value{ .array = Array.init(allocator) });
|
||||||
|
p.state = .array_value;
|
||||||
|
},
|
||||||
|
.string => |s| {
|
||||||
|
try array.append(Value{ .string = s });
|
||||||
|
},
|
||||||
|
.allocated_string => |s| {
|
||||||
|
try array.append(Value{ .string = s });
|
||||||
|
},
|
||||||
|
.number => |slice| {
|
||||||
|
try array.append(try p.parseNumber(slice));
|
||||||
|
},
|
||||||
|
.allocated_number => |slice| {
|
||||||
|
try array.append(try p.parseNumber(slice));
|
||||||
|
},
|
||||||
|
.true => {
|
||||||
|
try array.append(Value{ .bool = true });
|
||||||
|
},
|
||||||
|
.false => {
|
||||||
|
try array.append(Value{ .bool = false });
|
||||||
|
},
|
||||||
|
.null => {
|
||||||
|
try array.append(.null);
|
||||||
|
},
|
||||||
|
.object_end, .end_of_document => unreachable,
|
||||||
|
.partial_number, .partial_string, .partial_string_escaped_1, .partial_string_escaped_2, .partial_string_escaped_3, .partial_string_escaped_4 => unreachable,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.simple => switch (token) {
|
||||||
|
.object_begin => {
|
||||||
|
try p.stack.append(Value{ .object = ObjectMap.init(allocator) });
|
||||||
|
p.state = .object_key;
|
||||||
|
},
|
||||||
|
.array_begin => {
|
||||||
|
try p.stack.append(Value{ .array = Array.init(allocator) });
|
||||||
|
p.state = .array_value;
|
||||||
|
},
|
||||||
|
.string => |s| {
|
||||||
|
try p.stack.append(Value{ .string = s });
|
||||||
|
},
|
||||||
|
.allocated_string => |s| {
|
||||||
|
try p.stack.append(Value{ .string = s });
|
||||||
|
},
|
||||||
|
.number => |slice| {
|
||||||
|
try p.stack.append(try p.parseNumber(slice));
|
||||||
|
},
|
||||||
|
.allocated_number => |slice| {
|
||||||
|
try p.stack.append(try p.parseNumber(slice));
|
||||||
|
},
|
||||||
|
.true => {
|
||||||
|
try p.stack.append(Value{ .bool = true });
|
||||||
|
},
|
||||||
|
.false => {
|
||||||
|
try p.stack.append(Value{ .bool = false });
|
||||||
|
},
|
||||||
|
.null => {
|
||||||
|
try p.stack.append(.null);
|
||||||
|
},
|
||||||
|
.object_end, .array_end, .end_of_document => unreachable,
|
||||||
|
.partial_number, .partial_string, .partial_string_escaped_1, .partial_string_escaped_2, .partial_string_escaped_3, .partial_string_escaped_4 => unreachable,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pushToParent(p: *Parser, value: *const Value) !void {
|
||||||
|
switch (p.stack.items[p.stack.items.len - 1]) {
|
||||||
|
// Object Parent -> [ ..., object, <key>, value ]
|
||||||
|
.string => |key| {
|
||||||
|
_ = p.stack.pop();
|
||||||
|
|
||||||
|
var object = &p.stack.items[p.stack.items.len - 1].object;
|
||||||
|
try object.put(key, value.*);
|
||||||
|
p.state = .object_key;
|
||||||
|
},
|
||||||
|
// Array Parent -> [ ..., <array>, value ]
|
||||||
|
.array => |*array| {
|
||||||
|
try array.append(value.*);
|
||||||
|
p.state = .array_value;
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
unreachable;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseNumber(p: *Parser, slice: []const u8) !Value {
|
||||||
|
_ = p;
|
||||||
|
return if (isNumberFormattedLikeAnInteger(slice))
|
||||||
|
Value{
|
||||||
|
.integer = std.fmt.parseInt(i64, slice, 10) catch |e| switch (e) {
|
||||||
|
error.Overflow => return Value{ .number_string = slice },
|
||||||
|
error.InvalidCharacter => |err| return err,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Value{ .float = try std.fmt.parseFloat(f64, slice) };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test {
|
||||||
|
_ = @import("dynamic_test.zig");
|
||||||
|
}
|
||||||
285
lib/std/json/dynamic_test.zig
Normal file
285
lib/std/json/dynamic_test.zig
Normal file
|
|
@ -0,0 +1,285 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const mem = std.mem;
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
const ObjectMap = @import("dynamic.zig").ObjectMap;
|
||||||
|
const Array = @import("dynamic.zig").Array;
|
||||||
|
const Value = @import("dynamic.zig").Value;
|
||||||
|
const Parser = @import("dynamic.zig").Parser;
|
||||||
|
|
||||||
|
test "json.parser.dynamic" {
|
||||||
|
var p = Parser.init(testing.allocator, .alloc_if_needed);
|
||||||
|
defer p.deinit();
|
||||||
|
|
||||||
|
const s =
|
||||||
|
\\{
|
||||||
|
\\ "Image": {
|
||||||
|
\\ "Width": 800,
|
||||||
|
\\ "Height": 600,
|
||||||
|
\\ "Title": "View from 15th Floor",
|
||||||
|
\\ "Thumbnail": {
|
||||||
|
\\ "Url": "http://www.example.com/image/481989943",
|
||||||
|
\\ "Height": 125,
|
||||||
|
\\ "Width": 100
|
||||||
|
\\ },
|
||||||
|
\\ "Animated" : false,
|
||||||
|
\\ "IDs": [116, 943, 234, 38793],
|
||||||
|
\\ "ArrayOfObject": [{"n": "m"}],
|
||||||
|
\\ "double": 1.3412,
|
||||||
|
\\ "LargeInt": 18446744073709551615
|
||||||
|
\\ }
|
||||||
|
\\}
|
||||||
|
;
|
||||||
|
|
||||||
|
var tree = try p.parse(s);
|
||||||
|
defer tree.deinit();
|
||||||
|
|
||||||
|
var root = tree.root;
|
||||||
|
|
||||||
|
var image = root.object.get("Image").?;
|
||||||
|
|
||||||
|
const width = image.object.get("Width").?;
|
||||||
|
try testing.expect(width.integer == 800);
|
||||||
|
|
||||||
|
const height = image.object.get("Height").?;
|
||||||
|
try testing.expect(height.integer == 600);
|
||||||
|
|
||||||
|
const title = image.object.get("Title").?;
|
||||||
|
try testing.expect(mem.eql(u8, title.string, "View from 15th Floor"));
|
||||||
|
|
||||||
|
const animated = image.object.get("Animated").?;
|
||||||
|
try testing.expect(animated.bool == false);
|
||||||
|
|
||||||
|
const array_of_object = image.object.get("ArrayOfObject").?;
|
||||||
|
try testing.expect(array_of_object.array.items.len == 1);
|
||||||
|
|
||||||
|
const obj0 = array_of_object.array.items[0].object.get("n").?;
|
||||||
|
try testing.expect(mem.eql(u8, obj0.string, "m"));
|
||||||
|
|
||||||
|
const double = image.object.get("double").?;
|
||||||
|
try testing.expect(double.float == 1.3412);
|
||||||
|
|
||||||
|
const large_int = image.object.get("LargeInt").?;
|
||||||
|
try testing.expect(mem.eql(u8, large_int.number_string, "18446744073709551615"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeStream = @import("./write_stream.zig").writeStream;
|
||||||
|
test "write json then parse it" {
|
||||||
|
var out_buffer: [1000]u8 = undefined;
|
||||||
|
|
||||||
|
var fixed_buffer_stream = std.io.fixedBufferStream(&out_buffer);
|
||||||
|
const out_stream = fixed_buffer_stream.writer();
|
||||||
|
var jw = writeStream(out_stream, 4);
|
||||||
|
|
||||||
|
try jw.beginObject();
|
||||||
|
|
||||||
|
try jw.objectField("f");
|
||||||
|
try jw.emitBool(false);
|
||||||
|
|
||||||
|
try jw.objectField("t");
|
||||||
|
try jw.emitBool(true);
|
||||||
|
|
||||||
|
try jw.objectField("int");
|
||||||
|
try jw.emitNumber(1234);
|
||||||
|
|
||||||
|
try jw.objectField("array");
|
||||||
|
try jw.beginArray();
|
||||||
|
|
||||||
|
try jw.arrayElem();
|
||||||
|
try jw.emitNull();
|
||||||
|
|
||||||
|
try jw.arrayElem();
|
||||||
|
try jw.emitNumber(12.34);
|
||||||
|
|
||||||
|
try jw.endArray();
|
||||||
|
|
||||||
|
try jw.objectField("str");
|
||||||
|
try jw.emitString("hello");
|
||||||
|
|
||||||
|
try jw.endObject();
|
||||||
|
|
||||||
|
var parser = Parser.init(testing.allocator, .alloc_if_needed);
|
||||||
|
defer parser.deinit();
|
||||||
|
var tree = try parser.parse(fixed_buffer_stream.getWritten());
|
||||||
|
defer tree.deinit();
|
||||||
|
|
||||||
|
try testing.expect(tree.root.object.get("f").?.bool == false);
|
||||||
|
try testing.expect(tree.root.object.get("t").?.bool == true);
|
||||||
|
try testing.expect(tree.root.object.get("int").?.integer == 1234);
|
||||||
|
try testing.expect(tree.root.object.get("array").?.array.items[0].null == {});
|
||||||
|
try testing.expect(tree.root.object.get("array").?.array.items[1].float == 12.34);
|
||||||
|
try testing.expect(mem.eql(u8, tree.root.object.get("str").?.string, "hello"));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testParse(arena_allocator: std.mem.Allocator, json_str: []const u8) !Value {
|
||||||
|
var p = Parser.init(arena_allocator, .alloc_if_needed);
|
||||||
|
return (try p.parse(json_str)).root;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parsing empty string gives appropriate error" {
|
||||||
|
var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena_allocator.deinit();
|
||||||
|
try testing.expectError(error.UnexpectedEndOfInput, testParse(arena_allocator.allocator(), ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse tree should not contain dangling pointers" {
|
||||||
|
var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena_allocator.deinit();
|
||||||
|
|
||||||
|
var p = Parser.init(arena_allocator.allocator(), .alloc_if_needed);
|
||||||
|
defer p.deinit();
|
||||||
|
|
||||||
|
var tree = try p.parse("[]");
|
||||||
|
defer tree.deinit();
|
||||||
|
|
||||||
|
// Allocation should succeed
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < 100) : (i += 1) {
|
||||||
|
try tree.root.array.append(Value{ .integer = 100 });
|
||||||
|
}
|
||||||
|
try testing.expectEqual(tree.root.array.items.len, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "integer after float has proper type" {
|
||||||
|
var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena_allocator.deinit();
|
||||||
|
const parsed = try testParse(arena_allocator.allocator(),
|
||||||
|
\\{
|
||||||
|
\\ "float": 3.14,
|
||||||
|
\\ "ints": [1, 2, 3]
|
||||||
|
\\}
|
||||||
|
);
|
||||||
|
try std.testing.expect(parsed.object.get("ints").?.array.items[0] == .integer);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "escaped characters" {
|
||||||
|
var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena_allocator.deinit();
|
||||||
|
const input =
|
||||||
|
\\{
|
||||||
|
\\ "backslash": "\\",
|
||||||
|
\\ "forwardslash": "\/",
|
||||||
|
\\ "newline": "\n",
|
||||||
|
\\ "carriagereturn": "\r",
|
||||||
|
\\ "tab": "\t",
|
||||||
|
\\ "formfeed": "\f",
|
||||||
|
\\ "backspace": "\b",
|
||||||
|
\\ "doublequote": "\"",
|
||||||
|
\\ "unicode": "\u0105",
|
||||||
|
\\ "surrogatepair": "\ud83d\ude02"
|
||||||
|
\\}
|
||||||
|
;
|
||||||
|
|
||||||
|
const obj = (try testParse(arena_allocator.allocator(), input)).object;
|
||||||
|
|
||||||
|
try testing.expectEqualSlices(u8, obj.get("backslash").?.string, "\\");
|
||||||
|
try testing.expectEqualSlices(u8, obj.get("forwardslash").?.string, "/");
|
||||||
|
try testing.expectEqualSlices(u8, obj.get("newline").?.string, "\n");
|
||||||
|
try testing.expectEqualSlices(u8, obj.get("carriagereturn").?.string, "\r");
|
||||||
|
try testing.expectEqualSlices(u8, obj.get("tab").?.string, "\t");
|
||||||
|
try testing.expectEqualSlices(u8, obj.get("formfeed").?.string, "\x0C");
|
||||||
|
try testing.expectEqualSlices(u8, obj.get("backspace").?.string, "\x08");
|
||||||
|
try testing.expectEqualSlices(u8, obj.get("doublequote").?.string, "\"");
|
||||||
|
try testing.expectEqualSlices(u8, obj.get("unicode").?.string, "ą");
|
||||||
|
try testing.expectEqualSlices(u8, obj.get("surrogatepair").?.string, "😂");
|
||||||
|
}
|
||||||
|
|
||||||
|
test "string copy option" {
|
||||||
|
const input =
|
||||||
|
\\{
|
||||||
|
\\ "noescape": "aą😂",
|
||||||
|
\\ "simple": "\\\/\n\r\t\f\b\"",
|
||||||
|
\\ "unicode": "\u0105",
|
||||||
|
\\ "surrogatepair": "\ud83d\ude02"
|
||||||
|
\\}
|
||||||
|
;
|
||||||
|
|
||||||
|
var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena_allocator.deinit();
|
||||||
|
const allocator = arena_allocator.allocator();
|
||||||
|
|
||||||
|
var parser = Parser.init(allocator, .alloc_if_needed);
|
||||||
|
const tree_nocopy = try parser.parse(input);
|
||||||
|
const obj_nocopy = tree_nocopy.root.object;
|
||||||
|
|
||||||
|
parser = Parser.init(allocator, .alloc_always);
|
||||||
|
const tree_copy = try parser.parse(input);
|
||||||
|
const obj_copy = tree_copy.root.object;
|
||||||
|
|
||||||
|
for ([_][]const u8{ "noescape", "simple", "unicode", "surrogatepair" }) |field_name| {
|
||||||
|
try testing.expectEqualSlices(u8, obj_nocopy.get(field_name).?.string, obj_copy.get(field_name).?.string);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nocopy_addr = &obj_nocopy.get("noescape").?.string[0];
|
||||||
|
const copy_addr = &obj_copy.get("noescape").?.string[0];
|
||||||
|
|
||||||
|
var found_nocopy = false;
|
||||||
|
for (input, 0..) |_, index| {
|
||||||
|
try testing.expect(copy_addr != &input[index]);
|
||||||
|
if (nocopy_addr == &input[index]) {
|
||||||
|
found_nocopy = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try testing.expect(found_nocopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Value.jsonStringify" {
|
||||||
|
{
|
||||||
|
var buffer: [10]u8 = undefined;
|
||||||
|
var fbs = std.io.fixedBufferStream(&buffer);
|
||||||
|
try @as(Value, .null).jsonStringify(.{}, fbs.writer());
|
||||||
|
try testing.expectEqualSlices(u8, fbs.getWritten(), "null");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var buffer: [10]u8 = undefined;
|
||||||
|
var fbs = std.io.fixedBufferStream(&buffer);
|
||||||
|
try (Value{ .bool = true }).jsonStringify(.{}, fbs.writer());
|
||||||
|
try testing.expectEqualSlices(u8, fbs.getWritten(), "true");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var buffer: [10]u8 = undefined;
|
||||||
|
var fbs = std.io.fixedBufferStream(&buffer);
|
||||||
|
try (Value{ .integer = 42 }).jsonStringify(.{}, fbs.writer());
|
||||||
|
try testing.expectEqualSlices(u8, fbs.getWritten(), "42");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var buffer: [10]u8 = undefined;
|
||||||
|
var fbs = std.io.fixedBufferStream(&buffer);
|
||||||
|
try (Value{ .number_string = "43" }).jsonStringify(.{}, fbs.writer());
|
||||||
|
try testing.expectEqualSlices(u8, fbs.getWritten(), "43");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var buffer: [10]u8 = undefined;
|
||||||
|
var fbs = std.io.fixedBufferStream(&buffer);
|
||||||
|
try (Value{ .float = 42 }).jsonStringify(.{}, fbs.writer());
|
||||||
|
try testing.expectEqualSlices(u8, fbs.getWritten(), "4.2e+01");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var buffer: [10]u8 = undefined;
|
||||||
|
var fbs = std.io.fixedBufferStream(&buffer);
|
||||||
|
try (Value{ .string = "weeee" }).jsonStringify(.{}, fbs.writer());
|
||||||
|
try testing.expectEqualSlices(u8, fbs.getWritten(), "\"weeee\"");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var buffer: [10]u8 = undefined;
|
||||||
|
var fbs = std.io.fixedBufferStream(&buffer);
|
||||||
|
var vals = [_]Value{
|
||||||
|
.{ .integer = 1 },
|
||||||
|
.{ .integer = 2 },
|
||||||
|
.{ .number_string = "3" },
|
||||||
|
};
|
||||||
|
try (Value{
|
||||||
|
.array = Array.fromOwnedSlice(undefined, &vals),
|
||||||
|
}).jsonStringify(.{}, fbs.writer());
|
||||||
|
try testing.expectEqualSlices(u8, fbs.getWritten(), "[1,2,3]");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var buffer: [10]u8 = undefined;
|
||||||
|
var fbs = std.io.fixedBufferStream(&buffer);
|
||||||
|
var obj = ObjectMap.init(testing.allocator);
|
||||||
|
defer obj.deinit();
|
||||||
|
try obj.putNoClobber("a", .{ .string = "b" });
|
||||||
|
try (Value{ .object = obj }).jsonStringify(.{}, fbs.writer());
|
||||||
|
try testing.expectEqualSlices(u8, fbs.getWritten(), "{\"a\":\"b\"}");
|
||||||
|
}
|
||||||
|
}
|
||||||
1764
lib/std/json/scanner.zig
Normal file
1764
lib/std/json/scanner.zig
Normal file
File diff suppressed because it is too large
Load diff
466
lib/std/json/scanner_test.zig
Normal file
466
lib/std/json/scanner_test.zig
Normal file
|
|
@ -0,0 +1,466 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const JsonScanner = @import("./scanner.zig").Scanner;
|
||||||
|
const jsonReader = @import("./scanner.zig").reader;
|
||||||
|
const JsonReader = @import("./scanner.zig").Reader;
|
||||||
|
const Token = @import("./scanner.zig").Token;
|
||||||
|
const TokenType = @import("./scanner.zig").TokenType;
|
||||||
|
const Diagnostics = @import("./scanner.zig").Diagnostics;
|
||||||
|
const Error = @import("./scanner.zig").Error;
|
||||||
|
const validate = @import("./scanner.zig").validate;
|
||||||
|
|
||||||
|
const example_document_str =
|
||||||
|
\\{
|
||||||
|
\\ "Image": {
|
||||||
|
\\ "Width": 800,
|
||||||
|
\\ "Height": 600,
|
||||||
|
\\ "Title": "View from 15th Floor",
|
||||||
|
\\ "Thumbnail": {
|
||||||
|
\\ "Url": "http://www.example.com/image/481989943",
|
||||||
|
\\ "Height": 125,
|
||||||
|
\\ "Width": 100
|
||||||
|
\\ },
|
||||||
|
\\ "Animated" : false,
|
||||||
|
\\ "IDs": [116, 943, 234, 38793]
|
||||||
|
\\ }
|
||||||
|
\\}
|
||||||
|
;
|
||||||
|
|
||||||
|
fn expectNext(scanner_or_reader: anytype, expected_token: Token) !void {
|
||||||
|
return expectEqualTokens(expected_token, try scanner_or_reader.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expectPeekNext(scanner_or_reader: anytype, expected_token_type: TokenType, expected_token: Token) !void {
|
||||||
|
try std.testing.expectEqual(expected_token_type, try scanner_or_reader.peekNextTokenType());
|
||||||
|
try expectEqualTokens(expected_token, try scanner_or_reader.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "json.token" {
|
||||||
|
var scanner = JsonScanner.initCompleteInput(std.testing.allocator, example_document_str);
|
||||||
|
defer scanner.deinit();
|
||||||
|
|
||||||
|
try expectNext(&scanner, .object_begin);
|
||||||
|
try expectNext(&scanner, Token{ .string = "Image" });
|
||||||
|
try expectNext(&scanner, .object_begin);
|
||||||
|
try expectNext(&scanner, Token{ .string = "Width" });
|
||||||
|
try expectNext(&scanner, Token{ .number = "800" });
|
||||||
|
try expectNext(&scanner, Token{ .string = "Height" });
|
||||||
|
try expectNext(&scanner, Token{ .number = "600" });
|
||||||
|
try expectNext(&scanner, Token{ .string = "Title" });
|
||||||
|
try expectNext(&scanner, Token{ .string = "View from 15th Floor" });
|
||||||
|
try expectNext(&scanner, Token{ .string = "Thumbnail" });
|
||||||
|
try expectNext(&scanner, .object_begin);
|
||||||
|
try expectNext(&scanner, Token{ .string = "Url" });
|
||||||
|
try expectNext(&scanner, Token{ .string = "http://www.example.com/image/481989943" });
|
||||||
|
try expectNext(&scanner, Token{ .string = "Height" });
|
||||||
|
try expectNext(&scanner, Token{ .number = "125" });
|
||||||
|
try expectNext(&scanner, Token{ .string = "Width" });
|
||||||
|
try expectNext(&scanner, Token{ .number = "100" });
|
||||||
|
try expectNext(&scanner, .object_end);
|
||||||
|
try expectNext(&scanner, Token{ .string = "Animated" });
|
||||||
|
try expectNext(&scanner, .false);
|
||||||
|
try expectNext(&scanner, Token{ .string = "IDs" });
|
||||||
|
try expectNext(&scanner, .array_begin);
|
||||||
|
try expectNext(&scanner, Token{ .number = "116" });
|
||||||
|
try expectNext(&scanner, Token{ .number = "943" });
|
||||||
|
try expectNext(&scanner, Token{ .number = "234" });
|
||||||
|
try expectNext(&scanner, Token{ .number = "38793" });
|
||||||
|
try expectNext(&scanner, .array_end);
|
||||||
|
try expectNext(&scanner, .object_end);
|
||||||
|
try expectNext(&scanner, .object_end);
|
||||||
|
try expectNext(&scanner, .end_of_document);
|
||||||
|
}
|
||||||
|
|
||||||
|
const all_types_test_case =
|
||||||
|
\\[
|
||||||
|
\\ "", "a\nb",
|
||||||
|
\\ 0, 0.0, -1.1e-1,
|
||||||
|
\\ true, false, null,
|
||||||
|
\\ {"a": {}},
|
||||||
|
\\ []
|
||||||
|
\\]
|
||||||
|
;
|
||||||
|
|
||||||
|
fn testAllTypes(source: anytype, large_buffer: bool) !void {
|
||||||
|
try expectPeekNext(source, .array_begin, .array_begin);
|
||||||
|
try expectPeekNext(source, .string, Token{ .string = "" });
|
||||||
|
try expectPeekNext(source, .string, Token{ .partial_string = "a" });
|
||||||
|
try expectPeekNext(source, .string, Token{ .partial_string_escaped_1 = "\n".* });
|
||||||
|
if (large_buffer) {
|
||||||
|
try expectPeekNext(source, .string, Token{ .string = "b" });
|
||||||
|
} else {
|
||||||
|
try expectPeekNext(source, .string, Token{ .partial_string = "b" });
|
||||||
|
try expectPeekNext(source, .string, Token{ .string = "" });
|
||||||
|
}
|
||||||
|
if (large_buffer) {
|
||||||
|
try expectPeekNext(source, .number, Token{ .number = "0" });
|
||||||
|
} else {
|
||||||
|
try expectPeekNext(source, .number, Token{ .partial_number = "0" });
|
||||||
|
try expectPeekNext(source, .number, Token{ .number = "" });
|
||||||
|
}
|
||||||
|
if (large_buffer) {
|
||||||
|
try expectPeekNext(source, .number, Token{ .number = "0.0" });
|
||||||
|
} else {
|
||||||
|
try expectPeekNext(source, .number, Token{ .partial_number = "0" });
|
||||||
|
try expectPeekNext(source, .number, Token{ .partial_number = "." });
|
||||||
|
try expectPeekNext(source, .number, Token{ .partial_number = "0" });
|
||||||
|
try expectPeekNext(source, .number, Token{ .number = "" });
|
||||||
|
}
|
||||||
|
if (large_buffer) {
|
||||||
|
try expectPeekNext(source, .number, Token{ .number = "-1.1e-1" });
|
||||||
|
} else {
|
||||||
|
try expectPeekNext(source, .number, Token{ .partial_number = "-" });
|
||||||
|
try expectPeekNext(source, .number, Token{ .partial_number = "1" });
|
||||||
|
try expectPeekNext(source, .number, Token{ .partial_number = "." });
|
||||||
|
try expectPeekNext(source, .number, Token{ .partial_number = "1" });
|
||||||
|
try expectPeekNext(source, .number, Token{ .partial_number = "e" });
|
||||||
|
try expectPeekNext(source, .number, Token{ .partial_number = "-" });
|
||||||
|
try expectPeekNext(source, .number, Token{ .partial_number = "1" });
|
||||||
|
try expectPeekNext(source, .number, Token{ .number = "" });
|
||||||
|
}
|
||||||
|
try expectPeekNext(source, .true, .true);
|
||||||
|
try expectPeekNext(source, .false, .false);
|
||||||
|
try expectPeekNext(source, .null, .null);
|
||||||
|
try expectPeekNext(source, .object_begin, .object_begin);
|
||||||
|
if (large_buffer) {
|
||||||
|
try expectPeekNext(source, .string, Token{ .string = "a" });
|
||||||
|
} else {
|
||||||
|
try expectPeekNext(source, .string, Token{ .partial_string = "a" });
|
||||||
|
try expectPeekNext(source, .string, Token{ .string = "" });
|
||||||
|
}
|
||||||
|
try expectPeekNext(source, .object_begin, .object_begin);
|
||||||
|
try expectPeekNext(source, .object_end, .object_end);
|
||||||
|
try expectPeekNext(source, .object_end, .object_end);
|
||||||
|
try expectPeekNext(source, .array_begin, .array_begin);
|
||||||
|
try expectPeekNext(source, .array_end, .array_end);
|
||||||
|
try expectPeekNext(source, .array_end, .array_end);
|
||||||
|
try expectPeekNext(source, .end_of_document, .end_of_document);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "peek all types" {
|
||||||
|
var scanner = JsonScanner.initCompleteInput(std.testing.allocator, all_types_test_case);
|
||||||
|
defer scanner.deinit();
|
||||||
|
try testAllTypes(&scanner, true);
|
||||||
|
|
||||||
|
var stream = std.io.fixedBufferStream(all_types_test_case);
|
||||||
|
var json_reader = jsonReader(std.testing.allocator, stream.reader());
|
||||||
|
defer json_reader.deinit();
|
||||||
|
try testAllTypes(&json_reader, true);
|
||||||
|
|
||||||
|
var tiny_stream = std.io.fixedBufferStream(all_types_test_case);
|
||||||
|
var tiny_json_reader = JsonReader(1, @TypeOf(tiny_stream.reader())).init(std.testing.allocator, tiny_stream.reader());
|
||||||
|
defer tiny_json_reader.deinit();
|
||||||
|
try testAllTypes(&tiny_json_reader, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "json.token mismatched close" {
|
||||||
|
var scanner = JsonScanner.initCompleteInput(std.testing.allocator, "[102, 111, 111 }");
|
||||||
|
defer scanner.deinit();
|
||||||
|
try expectNext(&scanner, .array_begin);
|
||||||
|
try expectNext(&scanner, Token{ .number = "102" });
|
||||||
|
try expectNext(&scanner, Token{ .number = "111" });
|
||||||
|
try expectNext(&scanner, Token{ .number = "111" });
|
||||||
|
try std.testing.expectError(error.SyntaxError, scanner.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "json.token premature object close" {
|
||||||
|
var scanner = JsonScanner.initCompleteInput(std.testing.allocator, "{ \"key\": }");
|
||||||
|
defer scanner.deinit();
|
||||||
|
try expectNext(&scanner, .object_begin);
|
||||||
|
try expectNext(&scanner, Token{ .string = "key" });
|
||||||
|
try std.testing.expectError(error.SyntaxError, scanner.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "JsonScanner basic" {
|
||||||
|
var scanner = JsonScanner.initCompleteInput(std.testing.allocator, example_document_str);
|
||||||
|
defer scanner.deinit();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const token = try scanner.next();
|
||||||
|
if (token == .end_of_document) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "JsonReader basic" {
|
||||||
|
var stream = std.io.fixedBufferStream(example_document_str);
|
||||||
|
|
||||||
|
var json_reader = jsonReader(std.testing.allocator, stream.reader());
|
||||||
|
defer json_reader.deinit();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const token = try json_reader.next();
|
||||||
|
if (token == .end_of_document) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const number_test_stems = .{
|
||||||
|
.{ "", "-" },
|
||||||
|
.{ "0", "1", "10", "9999999999999999999999999" },
|
||||||
|
.{ "", ".0", ".999999999999999999999999" },
|
||||||
|
.{ "", "e0", "E0", "e+0", "e-0", "e9999999999999999999999999999" },
|
||||||
|
};
|
||||||
|
const number_test_items = blk: {
|
||||||
|
comptime var ret: []const []const u8 = &[_][]const u8{};
|
||||||
|
for (number_test_stems[0]) |s0| {
|
||||||
|
for (number_test_stems[1]) |s1| {
|
||||||
|
for (number_test_stems[2]) |s2| {
|
||||||
|
for (number_test_stems[3]) |s3| {
|
||||||
|
ret = ret ++ &[_][]const u8{s0 ++ s1 ++ s2 ++ s3};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break :blk ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
test "numbers" {
|
||||||
|
for (number_test_items) |number_str| {
|
||||||
|
var scanner = JsonScanner.initCompleteInput(std.testing.allocator, number_str);
|
||||||
|
defer scanner.deinit();
|
||||||
|
|
||||||
|
const token = try scanner.next();
|
||||||
|
const value = token.number; // assert this is a number
|
||||||
|
try std.testing.expectEqualStrings(number_str, value);
|
||||||
|
|
||||||
|
try std.testing.expectEqual(Token.end_of_document, try scanner.next());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const string_test_cases = .{
|
||||||
|
// The left is JSON without the "quotes".
|
||||||
|
// The right is the expected unescaped content.
|
||||||
|
.{ "", "" },
|
||||||
|
.{ "\\\\", "\\" },
|
||||||
|
.{ "a\\\\b", "a\\b" },
|
||||||
|
.{ "a\\\"b", "a\"b" },
|
||||||
|
.{ "\\n", "\n" },
|
||||||
|
.{ "\\u000a", "\n" },
|
||||||
|
.{ "𝄞", "\u{1D11E}" },
|
||||||
|
.{ "\\uD834\\uDD1E", "\u{1D11E}" },
|
||||||
|
.{ "\\uff20", "@" },
|
||||||
|
};
|
||||||
|
|
||||||
|
test "strings" {
|
||||||
|
inline for (string_test_cases) |tuple| {
|
||||||
|
var stream = std.io.fixedBufferStream("\"" ++ tuple[0] ++ "\"");
|
||||||
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
var json_reader = jsonReader(std.testing.allocator, stream.reader());
|
||||||
|
defer json_reader.deinit();
|
||||||
|
|
||||||
|
const token = try json_reader.nextAlloc(arena.allocator(), .alloc_if_needed);
|
||||||
|
const value = switch (token) {
|
||||||
|
.string => |value| value,
|
||||||
|
.allocated_string => |value| value,
|
||||||
|
else => return error.ExpectedString,
|
||||||
|
};
|
||||||
|
try std.testing.expectEqualStrings(tuple[1], value);
|
||||||
|
|
||||||
|
try std.testing.expectEqual(Token.end_of_document, try json_reader.next());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nesting_test_cases = .{
|
||||||
|
.{ null, "[]" },
|
||||||
|
.{ null, "{}" },
|
||||||
|
.{ error.SyntaxError, "[}" },
|
||||||
|
.{ error.SyntaxError, "{]" },
|
||||||
|
.{ null, "[" ** 1000 ++ "]" ** 1000 },
|
||||||
|
.{ null, "{\"\":" ** 1000 ++ "0" ++ "}" ** 1000 },
|
||||||
|
.{ error.SyntaxError, "[" ** 1000 ++ "]" ** 999 ++ "}" },
|
||||||
|
.{ error.SyntaxError, "{\"\":" ** 1000 ++ "0" ++ "}" ** 999 ++ "]" },
|
||||||
|
.{ error.SyntaxError, "[" ** 1000 ++ "]" ** 1001 },
|
||||||
|
.{ error.SyntaxError, "{\"\":" ** 1000 ++ "0" ++ "}" ** 1001 },
|
||||||
|
.{ error.UnexpectedEndOfInput, "[" ** 1000 ++ "]" ** 999 },
|
||||||
|
.{ error.UnexpectedEndOfInput, "{\"\":" ** 1000 ++ "0" ++ "}" ** 999 },
|
||||||
|
};
|
||||||
|
|
||||||
|
test "nesting" {
|
||||||
|
inline for (nesting_test_cases) |tuple| {
|
||||||
|
const maybe_error = tuple[0];
|
||||||
|
const document_str = tuple[1];
|
||||||
|
|
||||||
|
expectMaybeError(document_str, maybe_error) catch |err| {
|
||||||
|
std.debug.print("in json document: {s}\n", .{document_str});
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expectMaybeError(document_str: []const u8, maybe_error: ?Error) !void {
|
||||||
|
var scanner = JsonScanner.initCompleteInput(std.testing.allocator, document_str);
|
||||||
|
defer scanner.deinit();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const token = scanner.next() catch |err| {
|
||||||
|
if (maybe_error) |expected_err| {
|
||||||
|
if (err == expected_err) return;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
if (token == .end_of_document) break;
|
||||||
|
}
|
||||||
|
if (maybe_error != null) return error.ExpectedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expectEqualTokens(expected_token: Token, actual_token: Token) !void {
|
||||||
|
try std.testing.expectEqual(std.meta.activeTag(expected_token), std.meta.activeTag(actual_token));
|
||||||
|
switch (expected_token) {
|
||||||
|
.number => |expected_value| {
|
||||||
|
try std.testing.expectEqualStrings(expected_value, actual_token.number);
|
||||||
|
},
|
||||||
|
.string => |expected_value| {
|
||||||
|
try std.testing.expectEqualStrings(expected_value, actual_token.string);
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testTinyBufferSize(document_str: []const u8) !void {
|
||||||
|
var tiny_stream = std.io.fixedBufferStream(document_str);
|
||||||
|
var normal_stream = std.io.fixedBufferStream(document_str);
|
||||||
|
|
||||||
|
var tiny_json_reader = JsonReader(1, @TypeOf(tiny_stream.reader())).init(std.testing.allocator, tiny_stream.reader());
|
||||||
|
defer tiny_json_reader.deinit();
|
||||||
|
var normal_json_reader = JsonReader(0x1000, @TypeOf(normal_stream.reader())).init(std.testing.allocator, normal_stream.reader());
|
||||||
|
defer normal_json_reader.deinit();
|
||||||
|
|
||||||
|
expectEqualStreamOfTokens(&normal_json_reader, &tiny_json_reader) catch |err| {
|
||||||
|
std.debug.print("in json document: {s}\n", .{document_str});
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fn expectEqualStreamOfTokens(control_json_reader: anytype, test_json_reader: anytype) !void {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
while (true) {
|
||||||
|
const control_token = try control_json_reader.nextAlloc(arena.allocator(), .alloc_always);
|
||||||
|
const test_token = try test_json_reader.nextAlloc(arena.allocator(), .alloc_always);
|
||||||
|
try expectEqualTokens(control_token, test_token);
|
||||||
|
if (control_token == .end_of_document) break;
|
||||||
|
_ = arena.reset(.retain_capacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "BufferUnderrun" {
|
||||||
|
try testTinyBufferSize(example_document_str);
|
||||||
|
for (number_test_items) |number_str| {
|
||||||
|
try testTinyBufferSize(number_str);
|
||||||
|
}
|
||||||
|
inline for (string_test_cases) |tuple| {
|
||||||
|
try testTinyBufferSize("\"" ++ tuple[0] ++ "\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "json.validate" {
|
||||||
|
try std.testing.expectEqual(true, try validate(std.testing.allocator, "{}"));
|
||||||
|
try std.testing.expectEqual(true, try validate(std.testing.allocator, "[]"));
|
||||||
|
try std.testing.expectEqual(false, try validate(std.testing.allocator, "[{[[[[{}]]]]}]"));
|
||||||
|
try std.testing.expectEqual(false, try validate(std.testing.allocator, "{]"));
|
||||||
|
try std.testing.expectEqual(false, try validate(std.testing.allocator, "[}"));
|
||||||
|
try std.testing.expectEqual(false, try validate(std.testing.allocator, "{{{{[]}}}]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testSkipValue(s: []const u8) !void {
|
||||||
|
var scanner = JsonScanner.initCompleteInput(std.testing.allocator, s);
|
||||||
|
defer scanner.deinit();
|
||||||
|
try scanner.skipValue();
|
||||||
|
try expectEqualTokens(.end_of_document, try scanner.next());
|
||||||
|
|
||||||
|
var stream = std.io.fixedBufferStream(s);
|
||||||
|
var json_reader = jsonReader(std.testing.allocator, stream.reader());
|
||||||
|
defer json_reader.deinit();
|
||||||
|
try json_reader.skipValue();
|
||||||
|
try expectEqualTokens(.end_of_document, try json_reader.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "skipValue" {
|
||||||
|
try testSkipValue("false");
|
||||||
|
try testSkipValue("true");
|
||||||
|
try testSkipValue("null");
|
||||||
|
try testSkipValue("42");
|
||||||
|
try testSkipValue("42.0");
|
||||||
|
try testSkipValue("\"foo\"");
|
||||||
|
try testSkipValue("[101, 111, 121]");
|
||||||
|
try testSkipValue("{}");
|
||||||
|
try testSkipValue("{\"foo\": \"bar\\nbaz\"}");
|
||||||
|
|
||||||
|
// An absurd number of nestings
|
||||||
|
const nestings = 1000;
|
||||||
|
try testSkipValue("[" ** nestings ++ "]" ** nestings);
|
||||||
|
|
||||||
|
// Would a number token cause problems in a deeply-nested array?
|
||||||
|
try testSkipValue("[" ** nestings ++ "0.118, 999, 881.99, 911.9, 725, 3" ++ "]" ** nestings);
|
||||||
|
|
||||||
|
// Mismatched brace/square bracket
|
||||||
|
try std.testing.expectError(error.SyntaxError, testSkipValue("[102, 111, 111}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testEnsureStackCapacity(do_ensure: bool) !void {
|
||||||
|
var fail_alloc = std.testing.FailingAllocator.init(std.testing.allocator, 1);
|
||||||
|
const failing_allocator = fail_alloc.allocator();
|
||||||
|
|
||||||
|
const nestings = 999; // intentionally not a power of 2.
|
||||||
|
var scanner = JsonScanner.initCompleteInput(failing_allocator, "[" ** nestings ++ "]" ** nestings);
|
||||||
|
defer scanner.deinit();
|
||||||
|
|
||||||
|
if (do_ensure) {
|
||||||
|
try scanner.ensureTotalStackCapacity(nestings);
|
||||||
|
}
|
||||||
|
|
||||||
|
try scanner.skipValue();
|
||||||
|
try std.testing.expectEqual(Token.end_of_document, try scanner.next());
|
||||||
|
}
|
||||||
|
test "ensureTotalStackCapacity" {
|
||||||
|
// Once to demonstrate failure.
|
||||||
|
try std.testing.expectError(error.OutOfMemory, testEnsureStackCapacity(false));
|
||||||
|
// Then to demonstrate it works.
|
||||||
|
try testEnsureStackCapacity(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testDiagnosticsFromSource(expected_error: ?anyerror, line: u64, col: u64, byte_offset: u64, source: anytype) !void {
|
||||||
|
var diagnostics = Diagnostics{};
|
||||||
|
source.enableDiagnostics(&diagnostics);
|
||||||
|
|
||||||
|
if (expected_error) |expected_err| {
|
||||||
|
try std.testing.expectError(expected_err, source.skipValue());
|
||||||
|
} else {
|
||||||
|
try source.skipValue();
|
||||||
|
try std.testing.expectEqual(Token.end_of_document, try source.next());
|
||||||
|
}
|
||||||
|
try std.testing.expectEqual(line, diagnostics.getLine());
|
||||||
|
try std.testing.expectEqual(col, diagnostics.getColumn());
|
||||||
|
try std.testing.expectEqual(byte_offset, diagnostics.getByteOffset());
|
||||||
|
}
|
||||||
|
fn testDiagnostics(expected_error: ?anyerror, line: u64, col: u64, byte_offset: u64, s: []const u8) !void {
|
||||||
|
var scanner = JsonScanner.initCompleteInput(std.testing.allocator, s);
|
||||||
|
defer scanner.deinit();
|
||||||
|
try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &scanner);
|
||||||
|
|
||||||
|
var tiny_stream = std.io.fixedBufferStream(s);
|
||||||
|
var tiny_json_reader = JsonReader(1, @TypeOf(tiny_stream.reader())).init(std.testing.allocator, tiny_stream.reader());
|
||||||
|
defer tiny_json_reader.deinit();
|
||||||
|
try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &tiny_json_reader);
|
||||||
|
|
||||||
|
var medium_stream = std.io.fixedBufferStream(s);
|
||||||
|
var medium_json_reader = JsonReader(5, @TypeOf(medium_stream.reader())).init(std.testing.allocator, medium_stream.reader());
|
||||||
|
defer medium_json_reader.deinit();
|
||||||
|
try testDiagnosticsFromSource(expected_error, line, col, byte_offset, &medium_json_reader);
|
||||||
|
}
|
||||||
|
test "enableDiagnostics" {
|
||||||
|
try testDiagnostics(error.UnexpectedEndOfInput, 1, 1, 0, "");
|
||||||
|
try testDiagnostics(null, 1, 3, 2, "[]");
|
||||||
|
try testDiagnostics(null, 2, 2, 3, "[\n]");
|
||||||
|
try testDiagnostics(null, 14, 2, example_document_str.len, example_document_str);
|
||||||
|
|
||||||
|
try testDiagnostics(error.SyntaxError, 3, 1, 25,
|
||||||
|
\\{
|
||||||
|
\\ "common": "mistake",
|
||||||
|
\\}
|
||||||
|
);
|
||||||
|
|
||||||
|
inline for ([_]comptime_int{ 5, 6, 7, 99 }) |reps| {
|
||||||
|
// The error happens 1 byte before the end.
|
||||||
|
const s = "[" ** reps ++ "}";
|
||||||
|
try testDiagnostics(error.SyntaxError, 1, s.len, s.len - 1, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
621
lib/std/json/static.zig
Normal file
621
lib/std/json/static.zig
Normal file
|
|
@ -0,0 +1,621 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const ArrayList = std.ArrayList;
|
||||||
|
|
||||||
|
const Scanner = @import("./scanner.zig").Scanner;
|
||||||
|
const Token = @import("./scanner.zig").Token;
|
||||||
|
const AllocWhen = @import("./scanner.zig").AllocWhen;
|
||||||
|
const default_max_value_len = @import("./scanner.zig").default_max_value_len;
|
||||||
|
const isNumberFormattedLikeAnInteger = @import("./scanner.zig").isNumberFormattedLikeAnInteger;
|
||||||
|
|
||||||
|
pub const ParseOptions = struct {
|
||||||
|
/// Behaviour when a duplicate field is encountered.
|
||||||
|
duplicate_field_behavior: enum {
|
||||||
|
use_first,
|
||||||
|
@"error",
|
||||||
|
use_last,
|
||||||
|
} = .@"error",
|
||||||
|
|
||||||
|
/// If false, finding an unknown field returns an error.
|
||||||
|
ignore_unknown_fields: bool = false,
|
||||||
|
|
||||||
|
/// Passed to json.Scanner.nextAllocMax() or json.Reader.nextAllocMax().
|
||||||
|
/// The default for parseFromSlice() or parseFromTokenSource() with a *json.Scanner input
|
||||||
|
/// is the length of the input slice, which means error.ValueTooLong will never be returned.
|
||||||
|
/// The default for parseFromTokenSource() with a *json.Reader is default_max_value_len.
|
||||||
|
max_value_len: ?usize = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Parses the json document from s and returns the result.
|
||||||
|
/// The provided allocator is used both for temporary allocations during parsing the document,
|
||||||
|
/// and also to allocate any pointer values in the return type.
|
||||||
|
/// If T contains any pointers, free the memory with `std.json.parseFree`.
|
||||||
|
/// Note that `error.BufferUnderrun` is not actually possible to return from this function.
|
||||||
|
pub fn parseFromSlice(comptime T: type, allocator: Allocator, s: []const u8, options: ParseOptions) ParseError(T, Scanner)!T {
|
||||||
|
var scanner = Scanner.initCompleteInput(allocator, s);
|
||||||
|
defer scanner.deinit();
|
||||||
|
|
||||||
|
return parseFromTokenSource(T, allocator, &scanner, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `scanner_or_reader` must be either a `*std.json.Scanner` with complete input or a `*std.json.Reader`.
|
||||||
|
/// allocator is used to allocate the data of T if necessary,
|
||||||
|
/// such as if T is `*u32` or `[]u32`.
|
||||||
|
/// If T contains any pointers, free the memory with `std.json.parseFree`.
|
||||||
|
/// If T contains no pointers, the allocator may sometimes be used for temporary allocations,
|
||||||
|
/// but no call to `std.json.parseFree` will be necessary;
|
||||||
|
/// all temporary allocations will be freed before this function returns.
|
||||||
|
/// Note that `error.BufferUnderrun` is not actually possible to return from this function.
|
||||||
|
pub fn parseFromTokenSource(comptime T: type, allocator: Allocator, scanner_or_reader: anytype, options: ParseOptions) ParseError(T, @TypeOf(scanner_or_reader.*))!T {
|
||||||
|
if (@TypeOf(scanner_or_reader.*) == Scanner) {
|
||||||
|
assert(scanner_or_reader.is_end_of_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolved_options = options;
|
||||||
|
if (resolved_options.max_value_len == null) {
|
||||||
|
if (@TypeOf(scanner_or_reader.*) == Scanner) {
|
||||||
|
resolved_options.max_value_len = scanner_or_reader.input.len;
|
||||||
|
} else {
|
||||||
|
resolved_options.max_value_len = default_max_value_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const r = try parseInternal(T, allocator, scanner_or_reader, resolved_options);
|
||||||
|
errdefer parseFree(T, allocator, r);
|
||||||
|
|
||||||
|
assert(.end_of_document == try scanner_or_reader.next());
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The error set that will be returned from parsing T from *Source.
|
||||||
|
/// Note that this may contain error.BufferUnderrun, but that error will never actually be returned.
|
||||||
|
pub fn ParseError(comptime T: type, comptime Source: type) type {
|
||||||
|
// `inferred_types` is used to avoid infinite recursion for recursive type definitions.
|
||||||
|
const inferred_types = [_]type{};
|
||||||
|
// A few of these will either always be present or present enough of the time that
|
||||||
|
// omitting them is more confusing than always including them.
|
||||||
|
return error{UnexpectedToken} || Source.NextError || Source.PeekError ||
|
||||||
|
ParseInternalErrorImpl(T, Source, &inferred_types);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ParseInternalErrorImpl(comptime T: type, comptime Source: type, comptime inferred_types: []const type) type {
|
||||||
|
for (inferred_types) |ty| {
|
||||||
|
if (T == ty) return error{};
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
.Bool => return error{},
|
||||||
|
.Float, .ComptimeFloat => return Source.AllocError || std.fmt.ParseFloatError,
|
||||||
|
.Int, .ComptimeInt => {
|
||||||
|
return Source.AllocError || error{ InvalidNumber, Overflow } ||
|
||||||
|
std.fmt.ParseIntError || std.fmt.ParseFloatError;
|
||||||
|
},
|
||||||
|
.Optional => |optional_info| return ParseInternalErrorImpl(optional_info.child, Source, inferred_types ++ [_]type{T}),
|
||||||
|
.Enum => return Source.AllocError || error{InvalidEnumTag},
|
||||||
|
.Union => |unionInfo| {
|
||||||
|
if (unionInfo.tag_type) |_| {
|
||||||
|
var errors = Source.AllocError || error{UnknownField};
|
||||||
|
for (unionInfo.fields) |u_field| {
|
||||||
|
errors = errors || ParseInternalErrorImpl(u_field.type, Source, inferred_types ++ [_]type{T});
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
} else {
|
||||||
|
@compileError("Unable to parse into untagged union '" ++ @typeName(T) ++ "'");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Struct => |structInfo| {
|
||||||
|
var errors = Scanner.AllocError || error{
|
||||||
|
DuplicateField,
|
||||||
|
UnknownField,
|
||||||
|
MissingField,
|
||||||
|
};
|
||||||
|
for (structInfo.fields) |field| {
|
||||||
|
errors = errors || ParseInternalErrorImpl(field.type, Source, inferred_types ++ [_]type{T});
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
},
|
||||||
|
.Array => |arrayInfo| {
|
||||||
|
return error{LengthMismatch} ||
|
||||||
|
ParseInternalErrorImpl(arrayInfo.child, Source, inferred_types ++ [_]type{T});
|
||||||
|
},
|
||||||
|
.Vector => |vecInfo| {
|
||||||
|
return error{LengthMismatch} ||
|
||||||
|
ParseInternalErrorImpl(vecInfo.child, Source, inferred_types ++ [_]type{T});
|
||||||
|
},
|
||||||
|
.Pointer => |ptrInfo| {
|
||||||
|
switch (ptrInfo.size) {
|
||||||
|
.One, .Slice => {
|
||||||
|
return ParseInternalErrorImpl(ptrInfo.child, Source, inferred_types ++ [_]type{T});
|
||||||
|
},
|
||||||
|
else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => return error{},
|
||||||
|
}
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseInternal(
|
||||||
|
comptime T: type,
|
||||||
|
allocator: Allocator,
|
||||||
|
source: anytype,
|
||||||
|
options: ParseOptions,
|
||||||
|
) ParseError(T, @TypeOf(source.*))!T {
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
.Bool => {
|
||||||
|
return switch (try source.next()) {
|
||||||
|
.true => true,
|
||||||
|
.false => false,
|
||||||
|
else => error.UnexpectedToken,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.Float, .ComptimeFloat => {
|
||||||
|
const token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?);
|
||||||
|
defer freeAllocated(allocator, token);
|
||||||
|
const slice = switch (token) {
|
||||||
|
.number, .string => |slice| slice,
|
||||||
|
.allocated_number, .allocated_string => |slice| slice,
|
||||||
|
else => return error.UnexpectedToken,
|
||||||
|
};
|
||||||
|
return try std.fmt.parseFloat(T, slice);
|
||||||
|
},
|
||||||
|
.Int, .ComptimeInt => {
|
||||||
|
const token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?);
|
||||||
|
defer freeAllocated(allocator, token);
|
||||||
|
const slice = switch (token) {
|
||||||
|
.number, .string => |slice| slice,
|
||||||
|
.allocated_number, .allocated_string => |slice| slice,
|
||||||
|
else => return error.UnexpectedToken,
|
||||||
|
};
|
||||||
|
if (isNumberFormattedLikeAnInteger(slice))
|
||||||
|
return std.fmt.parseInt(T, slice, 10);
|
||||||
|
// Try to coerce a float to an integer.
|
||||||
|
const float = try std.fmt.parseFloat(f128, slice);
|
||||||
|
if (@round(float) != float) return error.InvalidNumber;
|
||||||
|
if (float > std.math.maxInt(T) or float < std.math.minInt(T)) return error.Overflow;
|
||||||
|
return @floatToInt(T, float);
|
||||||
|
},
|
||||||
|
.Optional => |optionalInfo| {
|
||||||
|
switch (try source.peekNextTokenType()) {
|
||||||
|
.null => {
|
||||||
|
_ = try source.next();
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
return try parseInternal(optionalInfo.child, allocator, source, options);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Enum => |enumInfo| {
|
||||||
|
const token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?);
|
||||||
|
defer freeAllocated(allocator, token);
|
||||||
|
const slice = switch (token) {
|
||||||
|
.number, .string => |slice| slice,
|
||||||
|
.allocated_number, .allocated_string => |slice| slice,
|
||||||
|
else => return error.UnexpectedToken,
|
||||||
|
};
|
||||||
|
// Check for a named value.
|
||||||
|
if (std.meta.stringToEnum(T, slice)) |value| return value;
|
||||||
|
// Check for a numeric value.
|
||||||
|
if (!isNumberFormattedLikeAnInteger(slice)) return error.InvalidEnumTag;
|
||||||
|
const n = std.fmt.parseInt(enumInfo.tag_type, slice, 10) catch return error.InvalidEnumTag;
|
||||||
|
return try std.meta.intToEnum(T, n);
|
||||||
|
},
|
||||||
|
.Union => |unionInfo| {
|
||||||
|
const UnionTagType = unionInfo.tag_type orelse @compileError("Unable to parse into untagged union '" ++ @typeName(T) ++ "'");
|
||||||
|
|
||||||
|
if (.object_begin != try source.next()) return error.UnexpectedToken;
|
||||||
|
|
||||||
|
var result: ?T = null;
|
||||||
|
errdefer {
|
||||||
|
if (result) |r| {
|
||||||
|
inline for (unionInfo.fields) |u_field| {
|
||||||
|
if (r == @field(UnionTagType, u_field.name)) {
|
||||||
|
parseFree(u_field.type, allocator, @field(r, u_field.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var name_token: ?Token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?);
|
||||||
|
errdefer {
|
||||||
|
if (name_token) |t| {
|
||||||
|
freeAllocated(allocator, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const field_name = switch (name_token.?) {
|
||||||
|
.string => |slice| slice,
|
||||||
|
.allocated_string => |slice| slice,
|
||||||
|
else => return error.UnexpectedToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
inline for (unionInfo.fields) |u_field| {
|
||||||
|
if (std.mem.eql(u8, u_field.name, field_name)) {
|
||||||
|
// Free the name token now in case we're using an allocator that optimizes freeing the last allocated object.
|
||||||
|
// (Recursing into parseInternal() might trigger more allocations.)
|
||||||
|
freeAllocated(allocator, name_token.?);
|
||||||
|
name_token = null;
|
||||||
|
|
||||||
|
if (u_field.type == void) {
|
||||||
|
// void isn't really a json type, but we can support void payload union tags with {} as a value.
|
||||||
|
if (.object_begin != try source.next()) return error.UnexpectedToken;
|
||||||
|
if (.object_end != try source.next()) return error.UnexpectedToken;
|
||||||
|
result = @unionInit(T, u_field.name, {});
|
||||||
|
} else {
|
||||||
|
// Recurse.
|
||||||
|
result = @unionInit(T, u_field.name, try parseInternal(u_field.type, allocator, source, options));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Didn't match anything.
|
||||||
|
return error.UnknownField;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (.object_end != try source.next()) return error.UnexpectedToken;
|
||||||
|
|
||||||
|
return result.?;
|
||||||
|
},
|
||||||
|
|
||||||
|
.Struct => |structInfo| {
|
||||||
|
if (structInfo.is_tuple) {
|
||||||
|
if (.array_begin != try source.next()) return error.UnexpectedToken;
|
||||||
|
|
||||||
|
var r: T = undefined;
|
||||||
|
var fields_seen: usize = 0;
|
||||||
|
errdefer {
|
||||||
|
inline for (0..structInfo.fields.len) |i| {
|
||||||
|
if (i < fields_seen) {
|
||||||
|
parseFree(structInfo.fields[i].type, allocator, r[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline for (0..structInfo.fields.len) |i| {
|
||||||
|
r[i] = try parseInternal(structInfo.fields[i].type, allocator, source, options);
|
||||||
|
fields_seen = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (.array_end != try source.next()) return error.UnexpectedToken;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (.object_begin != try source.next()) return error.UnexpectedToken;
|
||||||
|
|
||||||
|
var r: T = undefined;
|
||||||
|
var fields_seen = [_]bool{false} ** structInfo.fields.len;
|
||||||
|
errdefer {
|
||||||
|
inline for (structInfo.fields, 0..) |field, i| {
|
||||||
|
if (fields_seen[i]) {
|
||||||
|
parseFree(field.type, allocator, @field(r, field.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
var name_token: ?Token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?);
|
||||||
|
errdefer {
|
||||||
|
if (name_token) |t| {
|
||||||
|
freeAllocated(allocator, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const field_name = switch (name_token.?) {
|
||||||
|
.object_end => break, // No more fields.
|
||||||
|
.string => |slice| slice,
|
||||||
|
.allocated_string => |slice| slice,
|
||||||
|
else => return error.UnexpectedToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
inline for (structInfo.fields, 0..) |field, i| {
|
||||||
|
if (field.is_comptime) @compileError("comptime fields are not supported: " ++ @typeName(T) ++ "." ++ field.name);
|
||||||
|
if (std.mem.eql(u8, field.name, field_name)) {
|
||||||
|
// Free the name token now in case we're using an allocator that optimizes freeing the last allocated object.
|
||||||
|
// (Recursing into parseInternal() might trigger more allocations.)
|
||||||
|
freeAllocated(allocator, name_token.?);
|
||||||
|
name_token = null;
|
||||||
|
|
||||||
|
if (fields_seen[i]) {
|
||||||
|
switch (options.duplicate_field_behavior) {
|
||||||
|
.use_first => {
|
||||||
|
// Parse and then delete the redundant value.
|
||||||
|
// We don't want to skip the value, because we want type checking.
|
||||||
|
const ignored_value = try parseInternal(field.type, allocator, source, options);
|
||||||
|
parseFree(field.type, allocator, ignored_value);
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
.@"error" => return error.DuplicateField,
|
||||||
|
.use_last => {
|
||||||
|
// Delete the stale value. We're about to get a new one.
|
||||||
|
parseFree(field.type, allocator, @field(r, field.name));
|
||||||
|
fields_seen[i] = false;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@field(r, field.name) = try parseInternal(field.type, allocator, source, options);
|
||||||
|
fields_seen[i] = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Didn't match anything.
|
||||||
|
freeAllocated(allocator, name_token.?);
|
||||||
|
if (options.ignore_unknown_fields) {
|
||||||
|
try source.skipValue();
|
||||||
|
} else {
|
||||||
|
return error.UnknownField;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline for (structInfo.fields, 0..) |field, i| {
|
||||||
|
if (!fields_seen[i]) {
|
||||||
|
if (field.default_value) |default_ptr| {
|
||||||
|
const default = @ptrCast(*align(1) const field.type, default_ptr).*;
|
||||||
|
@field(r, field.name) = default;
|
||||||
|
} else {
|
||||||
|
return error.MissingField;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
},
|
||||||
|
|
||||||
|
.Array => |arrayInfo| {
|
||||||
|
switch (try source.peekNextTokenType()) {
|
||||||
|
.array_begin => {
|
||||||
|
// Typical array.
|
||||||
|
return parseInternalArray(T, arrayInfo.child, arrayInfo.len, allocator, source, options);
|
||||||
|
},
|
||||||
|
.string => {
|
||||||
|
if (arrayInfo.child != u8) return error.UnexpectedToken;
|
||||||
|
// Fixed-length string.
|
||||||
|
|
||||||
|
var r: T = undefined;
|
||||||
|
var i: usize = 0;
|
||||||
|
while (true) {
|
||||||
|
switch (try source.next()) {
|
||||||
|
.string => |slice| {
|
||||||
|
if (i + slice.len != r.len) return error.LengthMismatch;
|
||||||
|
@memcpy(r[i..][0..slice.len], slice);
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
.partial_string => |slice| {
|
||||||
|
if (i + slice.len > r.len) return error.LengthMismatch;
|
||||||
|
@memcpy(r[i..][0..slice.len], slice);
|
||||||
|
i += slice.len;
|
||||||
|
},
|
||||||
|
.partial_string_escaped_1 => |arr| {
|
||||||
|
if (i + arr.len > r.len) return error.LengthMismatch;
|
||||||
|
@memcpy(r[i..][0..arr.len], arr[0..]);
|
||||||
|
i += arr.len;
|
||||||
|
},
|
||||||
|
.partial_string_escaped_2 => |arr| {
|
||||||
|
if (i + arr.len > r.len) return error.LengthMismatch;
|
||||||
|
@memcpy(r[i..][0..arr.len], arr[0..]);
|
||||||
|
i += arr.len;
|
||||||
|
},
|
||||||
|
.partial_string_escaped_3 => |arr| {
|
||||||
|
if (i + arr.len > r.len) return error.LengthMismatch;
|
||||||
|
@memcpy(r[i..][0..arr.len], arr[0..]);
|
||||||
|
i += arr.len;
|
||||||
|
},
|
||||||
|
.partial_string_escaped_4 => |arr| {
|
||||||
|
if (i + arr.len > r.len) return error.LengthMismatch;
|
||||||
|
@memcpy(r[i..][0..arr.len], arr[0..]);
|
||||||
|
i += arr.len;
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
},
|
||||||
|
|
||||||
|
else => return error.UnexpectedToken,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.Vector => |vecInfo| {
|
||||||
|
switch (try source.peekNextTokenType()) {
|
||||||
|
.array_begin => {
|
||||||
|
return parseInternalArray(T, vecInfo.child, vecInfo.len, allocator, source, options);
|
||||||
|
},
|
||||||
|
else => return error.UnexpectedToken,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.Pointer => |ptrInfo| {
|
||||||
|
switch (ptrInfo.size) {
|
||||||
|
.One => {
|
||||||
|
const r: *ptrInfo.child = try allocator.create(ptrInfo.child);
|
||||||
|
errdefer allocator.destroy(r);
|
||||||
|
r.* = try parseInternal(ptrInfo.child, allocator, source, options);
|
||||||
|
return r;
|
||||||
|
},
|
||||||
|
.Slice => {
|
||||||
|
switch (try source.peekNextTokenType()) {
|
||||||
|
.array_begin => {
|
||||||
|
_ = try source.next();
|
||||||
|
|
||||||
|
// Typical array.
|
||||||
|
var arraylist = ArrayList(ptrInfo.child).init(allocator);
|
||||||
|
errdefer {
|
||||||
|
while (arraylist.popOrNull()) |v| {
|
||||||
|
parseFree(ptrInfo.child, allocator, v);
|
||||||
|
}
|
||||||
|
arraylist.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
switch (try source.peekNextTokenType()) {
|
||||||
|
.array_end => {
|
||||||
|
_ = try source.next();
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
try arraylist.ensureUnusedCapacity(1);
|
||||||
|
arraylist.appendAssumeCapacity(try parseInternal(ptrInfo.child, allocator, source, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptrInfo.sentinel) |some| {
|
||||||
|
const sentinel_value = @ptrCast(*align(1) const ptrInfo.child, some).*;
|
||||||
|
return try arraylist.toOwnedSliceSentinel(sentinel_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return try arraylist.toOwnedSlice();
|
||||||
|
},
|
||||||
|
.string => {
|
||||||
|
if (ptrInfo.child != u8) return error.UnexpectedToken;
|
||||||
|
|
||||||
|
// Dynamic length string.
|
||||||
|
if (ptrInfo.sentinel) |sentinel_ptr| {
|
||||||
|
// Use our own array list so we can append the sentinel.
|
||||||
|
var value_list = ArrayList(u8).init(allocator);
|
||||||
|
errdefer value_list.deinit();
|
||||||
|
_ = try source.allocNextIntoArrayList(&value_list, .alloc_always);
|
||||||
|
return try value_list.toOwnedSliceSentinel(@ptrCast(*const u8, sentinel_ptr).*);
|
||||||
|
}
|
||||||
|
switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
|
||||||
|
.allocated_string => |slice| return slice,
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => return error.UnexpectedToken,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),
|
||||||
|
}
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseInternalArray(
|
||||||
|
comptime T: type,
|
||||||
|
comptime Child: type,
|
||||||
|
comptime len: comptime_int,
|
||||||
|
allocator: Allocator,
|
||||||
|
source: anytype,
|
||||||
|
options: ParseOptions,
|
||||||
|
) !T {
|
||||||
|
assert(.array_begin == try source.next());
|
||||||
|
|
||||||
|
var r: T = undefined;
|
||||||
|
var i: usize = 0;
|
||||||
|
errdefer {
|
||||||
|
// Without the len check `r[i]` is not allowed
|
||||||
|
if (len > 0) while (true) : (i -= 1) {
|
||||||
|
parseFree(Child, allocator, r[i]);
|
||||||
|
if (i == 0) break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
while (i < len) : (i += 1) {
|
||||||
|
r[i] = try parseInternal(Child, allocator, source, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (.array_end != try source.next()) return error.UnexpectedToken;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn freeAllocated(allocator: Allocator, token: Token) void {
|
||||||
|
switch (token) {
|
||||||
|
.allocated_number, .allocated_string => |slice| {
|
||||||
|
allocator.free(slice);
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Releases resources created by parseFromSlice() or parseFromTokenSource().
|
||||||
|
pub fn parseFree(comptime T: type, allocator: Allocator, value: T) void {
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
.Bool, .Float, .ComptimeFloat, .Int, .ComptimeInt, .Enum => {},
|
||||||
|
.Optional => {
|
||||||
|
if (value) |v| {
|
||||||
|
return parseFree(@TypeOf(v), allocator, v);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Union => |unionInfo| {
|
||||||
|
if (unionInfo.tag_type) |UnionTagType| {
|
||||||
|
inline for (unionInfo.fields) |u_field| {
|
||||||
|
if (value == @field(UnionTagType, u_field.name)) {
|
||||||
|
parseFree(u_field.type, allocator, @field(value, u_field.name));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Struct => |structInfo| {
|
||||||
|
inline for (structInfo.fields) |field| {
|
||||||
|
var should_free = true;
|
||||||
|
if (field.default_value) |default| {
|
||||||
|
switch (@typeInfo(field.type)) {
|
||||||
|
// We must not attempt to free pointers to struct default values
|
||||||
|
.Pointer => |fieldPtrInfo| {
|
||||||
|
const field_value = @field(value, field.name);
|
||||||
|
const field_ptr = switch (fieldPtrInfo.size) {
|
||||||
|
.One => field_value,
|
||||||
|
.Slice => field_value.ptr,
|
||||||
|
else => unreachable, // Other pointer types are not parseable
|
||||||
|
};
|
||||||
|
const field_addr = @ptrToInt(field_ptr);
|
||||||
|
|
||||||
|
const casted_default = @ptrCast(*const field.type, @alignCast(@alignOf(field.type), default)).*;
|
||||||
|
const default_ptr = switch (fieldPtrInfo.size) {
|
||||||
|
.One => casted_default,
|
||||||
|
.Slice => casted_default.ptr,
|
||||||
|
else => unreachable, // Other pointer types are not parseable
|
||||||
|
};
|
||||||
|
const default_addr = @ptrToInt(default_ptr);
|
||||||
|
|
||||||
|
if (field_addr == default_addr) {
|
||||||
|
should_free = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (should_free) {
|
||||||
|
parseFree(field.type, allocator, @field(value, field.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Array => |arrayInfo| {
|
||||||
|
for (value) |v| {
|
||||||
|
parseFree(arrayInfo.child, allocator, v);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Vector => |vecInfo| {
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < vecInfo.len) : (i += 1) {
|
||||||
|
parseFree(vecInfo.child, allocator, value[i]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Pointer => |ptrInfo| {
|
||||||
|
switch (ptrInfo.size) {
|
||||||
|
.One => {
|
||||||
|
parseFree(ptrInfo.child, allocator, value.*);
|
||||||
|
allocator.destroy(value);
|
||||||
|
},
|
||||||
|
.Slice => {
|
||||||
|
for (value) |v| {
|
||||||
|
parseFree(ptrInfo.child, allocator, v);
|
||||||
|
}
|
||||||
|
allocator.free(value);
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
_ = @import("./static_test.zig");
|
||||||
|
}
|
||||||
437
lib/std/json/static_test.zig
Normal file
437
lib/std/json/static_test.zig
Normal file
|
|
@ -0,0 +1,437 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
const parseFromSlice = @import("./static.zig").parseFromSlice;
|
||||||
|
const parseFromTokenSource = @import("./static.zig").parseFromTokenSource;
|
||||||
|
const parseFree = @import("./static.zig").parseFree;
|
||||||
|
const ParseOptions = @import("./static.zig").ParseOptions;
|
||||||
|
const JsonScanner = @import("./scanner.zig").Scanner;
|
||||||
|
const jsonReader = @import("./scanner.zig").reader;
|
||||||
|
|
||||||
|
test "parse" {
|
||||||
|
try testing.expectEqual(false, try parseFromSlice(bool, testing.allocator, "false", .{}));
|
||||||
|
try testing.expectEqual(true, try parseFromSlice(bool, testing.allocator, "true", .{}));
|
||||||
|
try testing.expectEqual(@as(u1, 1), try parseFromSlice(u1, testing.allocator, "1", .{}));
|
||||||
|
try testing.expectError(error.Overflow, parseFromSlice(u1, testing.allocator, "50", .{}));
|
||||||
|
try testing.expectEqual(@as(u64, 42), try parseFromSlice(u64, testing.allocator, "42", .{}));
|
||||||
|
try testing.expectEqual(@as(f64, 42), try parseFromSlice(f64, testing.allocator, "42.0", .{}));
|
||||||
|
try testing.expectEqual(@as(?bool, null), try parseFromSlice(?bool, testing.allocator, "null", .{}));
|
||||||
|
try testing.expectEqual(@as(?bool, true), try parseFromSlice(?bool, testing.allocator, "true", .{}));
|
||||||
|
|
||||||
|
try testing.expectEqual(@as([3]u8, "foo".*), try parseFromSlice([3]u8, testing.allocator, "\"foo\"", .{}));
|
||||||
|
try testing.expectEqual(@as([3]u8, "foo".*), try parseFromSlice([3]u8, testing.allocator, "[102, 111, 111]", .{}));
|
||||||
|
try testing.expectEqual(@as([0]u8, undefined), try parseFromSlice([0]u8, testing.allocator, "[]", .{}));
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(u64, 12345678901234567890), try parseFromSlice(u64, testing.allocator, "\"12345678901234567890\"", .{}));
|
||||||
|
try testing.expectEqual(@as(f64, 123.456), try parseFromSlice(f64, testing.allocator, "\"123.456\"", .{}));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse into enum" {
|
||||||
|
const T = enum(u32) {
|
||||||
|
Foo = 42,
|
||||||
|
Bar,
|
||||||
|
@"with\\escape",
|
||||||
|
};
|
||||||
|
try testing.expectEqual(@as(T, .Foo), try parseFromSlice(T, testing.allocator, "\"Foo\"", .{}));
|
||||||
|
try testing.expectEqual(@as(T, .Foo), try parseFromSlice(T, testing.allocator, "42", .{}));
|
||||||
|
try testing.expectEqual(@as(T, .@"with\\escape"), try parseFromSlice(T, testing.allocator, "\"with\\\\escape\"", .{}));
|
||||||
|
try testing.expectError(error.InvalidEnumTag, parseFromSlice(T, testing.allocator, "5", .{}));
|
||||||
|
try testing.expectError(error.InvalidEnumTag, parseFromSlice(T, testing.allocator, "\"Qux\"", .{}));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse into that allocates a slice" {
|
||||||
|
{
|
||||||
|
// string as string
|
||||||
|
const r = try parseFromSlice([]u8, testing.allocator, "\"foo\"", .{});
|
||||||
|
defer parseFree([]u8, testing.allocator, r);
|
||||||
|
try testing.expectEqualSlices(u8, "foo", r);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// string as array of u8 integers
|
||||||
|
const r = try parseFromSlice([]u8, testing.allocator, "[102, 111, 111]", .{});
|
||||||
|
defer parseFree([]u8, testing.allocator, r);
|
||||||
|
try testing.expectEqualSlices(u8, "foo", r);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const r = try parseFromSlice([]u8, testing.allocator, "\"with\\\\escape\"", .{});
|
||||||
|
defer parseFree([]u8, testing.allocator, r);
|
||||||
|
try testing.expectEqualSlices(u8, "with\\escape", r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse into sentinel slice" {
|
||||||
|
const result = try parseFromSlice([:0]const u8, testing.allocator, "\"\\n\"", .{});
|
||||||
|
defer parseFree([:0]const u8, testing.allocator, result);
|
||||||
|
try testing.expect(std.mem.eql(u8, result, "\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse into tagged union" {
|
||||||
|
const T = union(enum) {
|
||||||
|
nothing,
|
||||||
|
int: i32,
|
||||||
|
float: f64,
|
||||||
|
string: []const u8,
|
||||||
|
};
|
||||||
|
try testing.expectEqual(T{ .float = 1.5 }, try parseFromSlice(T, testing.allocator, "{\"float\":1.5}", .{}));
|
||||||
|
try testing.expectEqual(T{ .int = 1 }, try parseFromSlice(T, testing.allocator, "{\"int\":1}", .{}));
|
||||||
|
try testing.expectEqual(T{ .nothing = {} }, try parseFromSlice(T, testing.allocator, "{\"nothing\":{}}", .{}));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse into tagged union errors" {
|
||||||
|
const T = union(enum) {
|
||||||
|
nothing,
|
||||||
|
int: i32,
|
||||||
|
float: f64,
|
||||||
|
string: []const u8,
|
||||||
|
};
|
||||||
|
try testing.expectError(error.UnexpectedToken, parseFromSlice(T, testing.allocator, "42", .{}));
|
||||||
|
try testing.expectError(error.UnexpectedToken, parseFromSlice(T, testing.allocator, "{}", .{}));
|
||||||
|
try testing.expectError(error.UnknownField, parseFromSlice(T, testing.allocator, "{\"bogus\":1}", .{}));
|
||||||
|
try testing.expectError(error.UnexpectedToken, parseFromSlice(T, testing.allocator, "{\"int\":1, \"int\":1", .{}));
|
||||||
|
try testing.expectError(error.UnexpectedToken, parseFromSlice(T, testing.allocator, "{\"int\":1, \"float\":1.0}", .{}));
|
||||||
|
try testing.expectError(error.UnexpectedToken, parseFromSlice(T, testing.allocator, "{\"nothing\":null}", .{}));
|
||||||
|
try testing.expectError(error.UnexpectedToken, parseFromSlice(T, testing.allocator, "{\"nothing\":{\"no\":0}}", .{}));
|
||||||
|
|
||||||
|
// Allocator failure
|
||||||
|
var fail_alloc = testing.FailingAllocator.init(testing.allocator, 0);
|
||||||
|
const failing_allocator = fail_alloc.allocator();
|
||||||
|
try testing.expectError(error.OutOfMemory, parseFromSlice(T, failing_allocator, "{\"string\"\"foo\"}", .{}));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parseFree descends into tagged union" {
|
||||||
|
const T = union(enum) {
|
||||||
|
nothing,
|
||||||
|
int: i32,
|
||||||
|
float: f64,
|
||||||
|
string: []const u8,
|
||||||
|
};
|
||||||
|
const r = try parseFromSlice(T, testing.allocator, "{\"string\":\"foo\"}", .{});
|
||||||
|
try testing.expectEqualSlices(u8, "foo", r.string);
|
||||||
|
parseFree(T, testing.allocator, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse into struct with no fields" {
|
||||||
|
const T = struct {};
|
||||||
|
try testing.expectEqual(T{}, try parseFromSlice(T, testing.allocator, "{}", .{}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const test_const_value: usize = 123;
|
||||||
|
|
||||||
|
test "parse into struct with default const pointer field" {
|
||||||
|
const T = struct { a: *const usize = &test_const_value };
|
||||||
|
try testing.expectEqual(T{}, try parseFromSlice(T, testing.allocator, "{}", .{}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const test_default_usize: usize = 123;
|
||||||
|
const test_default_usize_ptr: *align(1) const usize = &test_default_usize;
|
||||||
|
const test_default_str: []const u8 = "test str";
|
||||||
|
const test_default_str_slice: [2][]const u8 = [_][]const u8{
|
||||||
|
"test1",
|
||||||
|
"test2",
|
||||||
|
};
|
||||||
|
|
||||||
|
test "freeing parsed structs with pointers to default values" {
|
||||||
|
const T = struct {
|
||||||
|
int: *const usize = &test_default_usize,
|
||||||
|
int_ptr: *allowzero align(1) const usize = test_default_usize_ptr,
|
||||||
|
str: []const u8 = test_default_str,
|
||||||
|
str_slice: []const []const u8 = &test_default_str_slice,
|
||||||
|
};
|
||||||
|
|
||||||
|
const parsed = try parseFromSlice(T, testing.allocator, "{}", .{});
|
||||||
|
try testing.expectEqual(T{}, parsed);
|
||||||
|
// This will panic if it tries to free global constants:
|
||||||
|
parseFree(T, testing.allocator, parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse into struct where destination and source lengths mismatch" {
|
||||||
|
const T = struct { a: [2]u8 };
|
||||||
|
try testing.expectError(error.LengthMismatch, parseFromSlice(T, testing.allocator, "{\"a\": \"bbb\"}", .{}));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse into struct with misc fields" {
|
||||||
|
const T = struct {
|
||||||
|
int: i64,
|
||||||
|
float: f64,
|
||||||
|
@"with\\escape": bool,
|
||||||
|
@"withąunicode😂": bool,
|
||||||
|
language: []const u8,
|
||||||
|
optional: ?bool,
|
||||||
|
default_field: i32 = 42,
|
||||||
|
static_array: [3]f64,
|
||||||
|
dynamic_array: []f64,
|
||||||
|
|
||||||
|
complex: struct {
|
||||||
|
nested: []const u8,
|
||||||
|
},
|
||||||
|
|
||||||
|
veryComplex: []struct {
|
||||||
|
foo: []const u8,
|
||||||
|
},
|
||||||
|
|
||||||
|
a_union: Union,
|
||||||
|
const Union = union(enum) {
|
||||||
|
x: u8,
|
||||||
|
float: f64,
|
||||||
|
string: []const u8,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
var document_str =
|
||||||
|
\\{
|
||||||
|
\\ "int": 420,
|
||||||
|
\\ "float": 3.14,
|
||||||
|
\\ "with\\escape": true,
|
||||||
|
\\ "with\u0105unicode\ud83d\ude02": false,
|
||||||
|
\\ "language": "zig",
|
||||||
|
\\ "optional": null,
|
||||||
|
\\ "static_array": [66.6, 420.420, 69.69],
|
||||||
|
\\ "dynamic_array": [66.6, 420.420, 69.69],
|
||||||
|
\\ "complex": {
|
||||||
|
\\ "nested": "zig"
|
||||||
|
\\ },
|
||||||
|
\\ "veryComplex": [
|
||||||
|
\\ {
|
||||||
|
\\ "foo": "zig"
|
||||||
|
\\ }, {
|
||||||
|
\\ "foo": "rocks"
|
||||||
|
\\ }
|
||||||
|
\\ ],
|
||||||
|
\\ "a_union": {
|
||||||
|
\\ "float": 100000
|
||||||
|
\\ }
|
||||||
|
\\}
|
||||||
|
;
|
||||||
|
const r = try parseFromSlice(T, testing.allocator, document_str, .{});
|
||||||
|
defer parseFree(T, testing.allocator, r);
|
||||||
|
try testing.expectEqual(@as(i64, 420), r.int);
|
||||||
|
try testing.expectEqual(@as(f64, 3.14), r.float);
|
||||||
|
try testing.expectEqual(true, r.@"with\\escape");
|
||||||
|
try testing.expectEqual(false, r.@"withąunicode😂");
|
||||||
|
try testing.expectEqualSlices(u8, "zig", r.language);
|
||||||
|
try testing.expectEqual(@as(?bool, null), r.optional);
|
||||||
|
try testing.expectEqual(@as(i32, 42), r.default_field);
|
||||||
|
try testing.expectEqual(@as(f64, 66.6), r.static_array[0]);
|
||||||
|
try testing.expectEqual(@as(f64, 420.420), r.static_array[1]);
|
||||||
|
try testing.expectEqual(@as(f64, 69.69), r.static_array[2]);
|
||||||
|
try testing.expectEqual(@as(usize, 3), r.dynamic_array.len);
|
||||||
|
try testing.expectEqual(@as(f64, 66.6), r.dynamic_array[0]);
|
||||||
|
try testing.expectEqual(@as(f64, 420.420), r.dynamic_array[1]);
|
||||||
|
try testing.expectEqual(@as(f64, 69.69), r.dynamic_array[2]);
|
||||||
|
try testing.expectEqualSlices(u8, r.complex.nested, "zig");
|
||||||
|
try testing.expectEqualSlices(u8, "zig", r.veryComplex[0].foo);
|
||||||
|
try testing.expectEqualSlices(u8, "rocks", r.veryComplex[1].foo);
|
||||||
|
try testing.expectEqual(T.Union{ .float = 100000 }, r.a_union);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse into struct with strings and arrays with sentinels" {
|
||||||
|
const T = struct {
|
||||||
|
language: [:0]const u8,
|
||||||
|
language_without_sentinel: []const u8,
|
||||||
|
data: [:99]const i32,
|
||||||
|
simple_data: []const i32,
|
||||||
|
};
|
||||||
|
var document_str =
|
||||||
|
\\{
|
||||||
|
\\ "language": "zig",
|
||||||
|
\\ "language_without_sentinel": "zig again!",
|
||||||
|
\\ "data": [1, 2, 3],
|
||||||
|
\\ "simple_data": [4, 5, 6]
|
||||||
|
\\}
|
||||||
|
;
|
||||||
|
const r = try parseFromSlice(T, testing.allocator, document_str, .{});
|
||||||
|
defer parseFree(T, testing.allocator, r);
|
||||||
|
|
||||||
|
try testing.expectEqualSentinel(u8, 0, "zig", r.language);
|
||||||
|
|
||||||
|
const data = [_:99]i32{ 1, 2, 3 };
|
||||||
|
try testing.expectEqualSentinel(i32, 99, data[0..data.len], r.data);
|
||||||
|
|
||||||
|
// Make sure that arrays who aren't supposed to have a sentinel still parse without one.
|
||||||
|
try testing.expectEqual(@as(?i32, null), std.meta.sentinel(@TypeOf(r.simple_data)));
|
||||||
|
try testing.expectEqual(@as(?u8, null), std.meta.sentinel(@TypeOf(r.language_without_sentinel)));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse into struct with duplicate field" {
|
||||||
|
// allow allocator to detect double frees by keeping bucket in use
|
||||||
|
const ballast = try testing.allocator.alloc(u64, 1);
|
||||||
|
defer testing.allocator.free(ballast);
|
||||||
|
|
||||||
|
const options_first = ParseOptions{ .duplicate_field_behavior = .use_first };
|
||||||
|
const options_last = ParseOptions{ .duplicate_field_behavior = .use_last };
|
||||||
|
|
||||||
|
const str = "{ \"a\": 1, \"a\": 0.25 }";
|
||||||
|
|
||||||
|
const T1 = struct { a: *u64 };
|
||||||
|
// both .use_first and .use_last should fail because second "a" value isn't a u64
|
||||||
|
try testing.expectError(error.InvalidNumber, parseFromSlice(T1, testing.allocator, str, options_first));
|
||||||
|
try testing.expectError(error.InvalidNumber, parseFromSlice(T1, testing.allocator, str, options_last));
|
||||||
|
|
||||||
|
const T2 = struct { a: f64 };
|
||||||
|
try testing.expectEqual(T2{ .a = 1.0 }, try parseFromSlice(T2, testing.allocator, str, options_first));
|
||||||
|
try testing.expectEqual(T2{ .a = 0.25 }, try parseFromSlice(T2, testing.allocator, str, options_last));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse into struct ignoring unknown fields" {
|
||||||
|
const T = struct {
|
||||||
|
int: i64,
|
||||||
|
language: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
var str =
|
||||||
|
\\{
|
||||||
|
\\ "int": 420,
|
||||||
|
\\ "float": 3.14,
|
||||||
|
\\ "with\\escape": true,
|
||||||
|
\\ "with\u0105unicode\ud83d\ude02": false,
|
||||||
|
\\ "optional": null,
|
||||||
|
\\ "static_array": [66.6, 420.420, 69.69],
|
||||||
|
\\ "dynamic_array": [66.6, 420.420, 69.69],
|
||||||
|
\\ "complex": {
|
||||||
|
\\ "nested": "zig"
|
||||||
|
\\ },
|
||||||
|
\\ "veryComplex": [
|
||||||
|
\\ {
|
||||||
|
\\ "foo": "zig"
|
||||||
|
\\ }, {
|
||||||
|
\\ "foo": "rocks"
|
||||||
|
\\ }
|
||||||
|
\\ ],
|
||||||
|
\\ "a_union": {
|
||||||
|
\\ "float": 100000
|
||||||
|
\\ },
|
||||||
|
\\ "language": "zig"
|
||||||
|
\\}
|
||||||
|
;
|
||||||
|
const r = try parseFromSlice(T, testing.allocator, str, .{ .ignore_unknown_fields = true });
|
||||||
|
defer parseFree(T, testing.allocator, r);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(i64, 420), r.int);
|
||||||
|
try testing.expectEqualSlices(u8, "zig", r.language);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse into tuple" {
|
||||||
|
const Union = union(enum) {
|
||||||
|
char: u8,
|
||||||
|
float: f64,
|
||||||
|
string: []const u8,
|
||||||
|
};
|
||||||
|
const T = std.meta.Tuple(&.{
|
||||||
|
i64,
|
||||||
|
f64,
|
||||||
|
bool,
|
||||||
|
[]const u8,
|
||||||
|
?bool,
|
||||||
|
struct {
|
||||||
|
foo: i32,
|
||||||
|
bar: []const u8,
|
||||||
|
},
|
||||||
|
std.meta.Tuple(&.{ u8, []const u8, u8 }),
|
||||||
|
Union,
|
||||||
|
});
|
||||||
|
var str =
|
||||||
|
\\[
|
||||||
|
\\ 420,
|
||||||
|
\\ 3.14,
|
||||||
|
\\ true,
|
||||||
|
\\ "zig",
|
||||||
|
\\ null,
|
||||||
|
\\ {
|
||||||
|
\\ "foo": 1,
|
||||||
|
\\ "bar": "zero"
|
||||||
|
\\ },
|
||||||
|
\\ [4, "två", 42],
|
||||||
|
\\ {"float": 12.34}
|
||||||
|
\\]
|
||||||
|
;
|
||||||
|
const r = try parseFromSlice(T, testing.allocator, str, .{});
|
||||||
|
defer parseFree(T, testing.allocator, r);
|
||||||
|
try testing.expectEqual(@as(i64, 420), r[0]);
|
||||||
|
try testing.expectEqual(@as(f64, 3.14), r[1]);
|
||||||
|
try testing.expectEqual(true, r[2]);
|
||||||
|
try testing.expectEqualSlices(u8, "zig", r[3]);
|
||||||
|
try testing.expectEqual(@as(?bool, null), r[4]);
|
||||||
|
try testing.expectEqual(@as(i32, 1), r[5].foo);
|
||||||
|
try testing.expectEqualSlices(u8, "zero", r[5].bar);
|
||||||
|
try testing.expectEqual(@as(u8, 4), r[6][0]);
|
||||||
|
try testing.expectEqualSlices(u8, "två", r[6][1]);
|
||||||
|
try testing.expectEqual(@as(u8, 42), r[6][2]);
|
||||||
|
try testing.expectEqual(Union{ .float = 12.34 }, r[7]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ParseIntoRecursiveUnionDefinitionValue = union(enum) {
|
||||||
|
integer: i64,
|
||||||
|
array: []const ParseIntoRecursiveUnionDefinitionValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
test "parse into recursive union definition" {
|
||||||
|
const T = struct {
|
||||||
|
values: ParseIntoRecursiveUnionDefinitionValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
const r = try parseFromSlice(T, testing.allocator, "{\"values\":{\"array\":[{\"integer\":58}]}}", .{});
|
||||||
|
defer parseFree(T, testing.allocator, r);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(i64, 58), r.values.array[0].integer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ParseIntoDoubleRecursiveUnionValueFirst = union(enum) {
|
||||||
|
integer: i64,
|
||||||
|
array: []const ParseIntoDoubleRecursiveUnionValueSecond,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ParseIntoDoubleRecursiveUnionValueSecond = union(enum) {
|
||||||
|
boolean: bool,
|
||||||
|
array: []const ParseIntoDoubleRecursiveUnionValueFirst,
|
||||||
|
};
|
||||||
|
|
||||||
|
test "parse into double recursive union definition" {
|
||||||
|
const T = struct {
|
||||||
|
values: ParseIntoDoubleRecursiveUnionValueFirst,
|
||||||
|
};
|
||||||
|
|
||||||
|
const r = try parseFromSlice(T, testing.allocator, "{\"values\":{\"array\":[{\"array\":[{\"integer\":58}]}]}}", .{});
|
||||||
|
defer parseFree(T, testing.allocator, r);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(i64, 58), r.values.array[0].array[0].integer);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse exponential into int" {
|
||||||
|
const T = struct { int: i64 };
|
||||||
|
const r = try parseFromSlice(T, testing.allocator, "{ \"int\": 4.2e2 }", .{});
|
||||||
|
try testing.expectEqual(@as(i64, 420), r.int);
|
||||||
|
try testing.expectError(error.InvalidNumber, parseFromSlice(T, testing.allocator, "{ \"int\": 0.042e2 }", .{}));
|
||||||
|
try testing.expectError(error.Overflow, parseFromSlice(T, testing.allocator, "{ \"int\": 18446744073709551616.0 }", .{}));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parseFromTokenSource" {
|
||||||
|
var scanner = JsonScanner.initCompleteInput(testing.allocator, "123");
|
||||||
|
defer scanner.deinit();
|
||||||
|
try testing.expectEqual(@as(u32, 123), try parseFromTokenSource(u32, testing.allocator, &scanner, .{}));
|
||||||
|
|
||||||
|
var stream = std.io.fixedBufferStream("123");
|
||||||
|
var json_reader = jsonReader(std.testing.allocator, stream.reader());
|
||||||
|
defer json_reader.deinit();
|
||||||
|
try testing.expectEqual(@as(u32, 123), try parseFromTokenSource(u32, testing.allocator, &json_reader, .{}));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "max_value_len" {
|
||||||
|
try testing.expectError(error.ValueTooLong, parseFromSlice([]u8, testing.allocator, "\"0123456789\"", .{ .max_value_len = 5 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse into vector" {
|
||||||
|
const T = struct {
|
||||||
|
vec_i32: @Vector(4, i32),
|
||||||
|
vec_f32: @Vector(2, f32),
|
||||||
|
};
|
||||||
|
var s =
|
||||||
|
\\{
|
||||||
|
\\ "vec_f32": [1.5, 2.5],
|
||||||
|
\\ "vec_i32": [4, 5, 6, 7]
|
||||||
|
\\}
|
||||||
|
;
|
||||||
|
const r = try parseFromSlice(T, testing.allocator, s, .{});
|
||||||
|
defer parseFree(T, testing.allocator, r);
|
||||||
|
try testing.expectApproxEqAbs(@as(f32, 1.5), r.vec_f32[0], 0.0000001);
|
||||||
|
try testing.expectApproxEqAbs(@as(f32, 2.5), r.vec_f32[1], 0.0000001);
|
||||||
|
try testing.expectEqual(@Vector(4, i32){ 4, 5, 6, 7 }, r.vec_i32);
|
||||||
|
}
|
||||||
313
lib/std/json/stringify.zig
Normal file
313
lib/std/json/stringify.zig
Normal file
|
|
@ -0,0 +1,313 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const mem = std.mem;
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
|
pub const StringifyOptions = struct {
|
||||||
|
pub const Whitespace = struct {
|
||||||
|
/// How many indentation levels deep are we?
|
||||||
|
indent_level: usize = 0,
|
||||||
|
|
||||||
|
/// What character(s) should be used for indentation?
|
||||||
|
indent: union(enum) {
|
||||||
|
space: u8,
|
||||||
|
tab: void,
|
||||||
|
none: void,
|
||||||
|
} = .{ .space = 4 },
|
||||||
|
|
||||||
|
/// After a colon, should whitespace be inserted?
|
||||||
|
separator: bool = true,
|
||||||
|
|
||||||
|
pub fn outputIndent(
|
||||||
|
whitespace: @This(),
|
||||||
|
out_stream: anytype,
|
||||||
|
) @TypeOf(out_stream).Error!void {
|
||||||
|
var char: u8 = undefined;
|
||||||
|
var n_chars: usize = undefined;
|
||||||
|
switch (whitespace.indent) {
|
||||||
|
.space => |n_spaces| {
|
||||||
|
char = ' ';
|
||||||
|
n_chars = n_spaces;
|
||||||
|
},
|
||||||
|
.tab => {
|
||||||
|
char = '\t';
|
||||||
|
n_chars = 1;
|
||||||
|
},
|
||||||
|
.none => return,
|
||||||
|
}
|
||||||
|
try out_stream.writeByte('\n');
|
||||||
|
n_chars *= whitespace.indent_level;
|
||||||
|
try out_stream.writeByteNTimes(char, n_chars);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Controls the whitespace emitted
|
||||||
|
whitespace: Whitespace = .{ .indent = .none, .separator = false },
|
||||||
|
|
||||||
|
/// Should optional fields with null value be written?
|
||||||
|
emit_null_optional_fields: bool = true,
|
||||||
|
|
||||||
|
string: StringOptions = StringOptions{ .String = .{} },
|
||||||
|
|
||||||
|
/// Should []u8 be serialised as a string? or an array?
|
||||||
|
pub const StringOptions = union(enum) {
|
||||||
|
Array,
|
||||||
|
String: StringOutputOptions,
|
||||||
|
|
||||||
|
/// String output options
|
||||||
|
const StringOutputOptions = struct {
|
||||||
|
/// Should '/' be escaped in strings?
|
||||||
|
escape_solidus: bool = false,
|
||||||
|
|
||||||
|
/// Should unicode characters be escaped in strings?
|
||||||
|
escape_unicode: bool = false,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
fn outputUnicodeEscape(
|
||||||
|
codepoint: u21,
|
||||||
|
out_stream: anytype,
|
||||||
|
) !void {
|
||||||
|
if (codepoint <= 0xFFFF) {
|
||||||
|
// If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF),
|
||||||
|
// then it may be represented as a six-character sequence: a reverse solidus, followed
|
||||||
|
// by the lowercase letter u, followed by four hexadecimal digits that encode the character's code point.
|
||||||
|
try out_stream.writeAll("\\u");
|
||||||
|
try std.fmt.formatIntValue(codepoint, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
|
||||||
|
} else {
|
||||||
|
assert(codepoint <= 0x10FFFF);
|
||||||
|
// To escape an extended character that is not in the Basic Multilingual Plane,
|
||||||
|
// the character is represented as a 12-character sequence, encoding the UTF-16 surrogate pair.
|
||||||
|
const high = @intCast(u16, (codepoint - 0x10000) >> 10) + 0xD800;
|
||||||
|
const low = @intCast(u16, codepoint & 0x3FF) + 0xDC00;
|
||||||
|
try out_stream.writeAll("\\u");
|
||||||
|
try std.fmt.formatIntValue(high, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
|
||||||
|
try out_stream.writeAll("\\u");
|
||||||
|
try std.fmt.formatIntValue(low, "x", std.fmt.FormatOptions{ .width = 4, .fill = '0' }, out_stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write `string` to `writer` as a JSON encoded string.
|
||||||
|
pub fn encodeJsonString(string: []const u8, options: StringifyOptions, writer: anytype) !void {
|
||||||
|
try writer.writeByte('\"');
|
||||||
|
try encodeJsonStringChars(string, options, writer);
|
||||||
|
try writer.writeByte('\"');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write `chars` to `writer` as JSON encoded string characters.
|
||||||
|
pub fn encodeJsonStringChars(chars: []const u8, options: StringifyOptions, writer: anytype) !void {
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < chars.len) : (i += 1) {
|
||||||
|
switch (chars[i]) {
|
||||||
|
// normal ascii character
|
||||||
|
0x20...0x21, 0x23...0x2E, 0x30...0x5B, 0x5D...0x7F => |c| try writer.writeByte(c),
|
||||||
|
// only 2 characters that *must* be escaped
|
||||||
|
'\\' => try writer.writeAll("\\\\"),
|
||||||
|
'\"' => try writer.writeAll("\\\""),
|
||||||
|
// solidus is optional to escape
|
||||||
|
'/' => {
|
||||||
|
if (options.string.String.escape_solidus) {
|
||||||
|
try writer.writeAll("\\/");
|
||||||
|
} else {
|
||||||
|
try writer.writeByte('/');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// control characters with short escapes
|
||||||
|
// TODO: option to switch between unicode and 'short' forms?
|
||||||
|
0x8 => try writer.writeAll("\\b"),
|
||||||
|
0xC => try writer.writeAll("\\f"),
|
||||||
|
'\n' => try writer.writeAll("\\n"),
|
||||||
|
'\r' => try writer.writeAll("\\r"),
|
||||||
|
'\t' => try writer.writeAll("\\t"),
|
||||||
|
else => {
|
||||||
|
const ulen = std.unicode.utf8ByteSequenceLength(chars[i]) catch unreachable;
|
||||||
|
// control characters (only things left with 1 byte length) should always be printed as unicode escapes
|
||||||
|
if (ulen == 1 or options.string.String.escape_unicode) {
|
||||||
|
const codepoint = std.unicode.utf8Decode(chars[i..][0..ulen]) catch unreachable;
|
||||||
|
try outputUnicodeEscape(codepoint, writer);
|
||||||
|
} else {
|
||||||
|
try writer.writeAll(chars[i..][0..ulen]);
|
||||||
|
}
|
||||||
|
i += ulen - 1;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stringify(
|
||||||
|
value: anytype,
|
||||||
|
options: StringifyOptions,
|
||||||
|
out_stream: anytype,
|
||||||
|
) !void {
|
||||||
|
const T = @TypeOf(value);
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
.Float, .ComptimeFloat => {
|
||||||
|
return std.fmt.formatFloatScientific(value, std.fmt.FormatOptions{}, out_stream);
|
||||||
|
},
|
||||||
|
.Int, .ComptimeInt => {
|
||||||
|
return std.fmt.formatIntValue(value, "", std.fmt.FormatOptions{}, out_stream);
|
||||||
|
},
|
||||||
|
.Bool => {
|
||||||
|
return out_stream.writeAll(if (value) "true" else "false");
|
||||||
|
},
|
||||||
|
.Null => {
|
||||||
|
return out_stream.writeAll("null");
|
||||||
|
},
|
||||||
|
.Optional => {
|
||||||
|
if (value) |payload| {
|
||||||
|
return try stringify(payload, options, out_stream);
|
||||||
|
} else {
|
||||||
|
return try stringify(null, options, out_stream);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Enum => {
|
||||||
|
if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
|
||||||
|
return value.jsonStringify(options, out_stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@compileError("Unable to stringify enum '" ++ @typeName(T) ++ "'");
|
||||||
|
},
|
||||||
|
.Union => {
|
||||||
|
if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
|
||||||
|
return value.jsonStringify(options, out_stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = @typeInfo(T).Union;
|
||||||
|
if (info.tag_type) |UnionTagType| {
|
||||||
|
try out_stream.writeByte('{');
|
||||||
|
var child_options = options;
|
||||||
|
child_options.whitespace.indent_level += 1;
|
||||||
|
inline for (info.fields) |u_field| {
|
||||||
|
if (value == @field(UnionTagType, u_field.name)) {
|
||||||
|
try child_options.whitespace.outputIndent(out_stream);
|
||||||
|
try encodeJsonString(u_field.name, options, out_stream);
|
||||||
|
try out_stream.writeByte(':');
|
||||||
|
if (child_options.whitespace.separator) {
|
||||||
|
try out_stream.writeByte(' ');
|
||||||
|
}
|
||||||
|
if (u_field.type == void) {
|
||||||
|
try out_stream.writeAll("{}");
|
||||||
|
} else {
|
||||||
|
try stringify(@field(value, u_field.name), child_options, out_stream);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unreachable; // No active tag?
|
||||||
|
}
|
||||||
|
try options.whitespace.outputIndent(out_stream);
|
||||||
|
try out_stream.writeByte('}');
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
@compileError("Unable to stringify untagged union '" ++ @typeName(T) ++ "'");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Struct => |S| {
|
||||||
|
if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
|
||||||
|
return value.jsonStringify(options, out_stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
try out_stream.writeByte(if (S.is_tuple) '[' else '{');
|
||||||
|
var field_output = false;
|
||||||
|
var child_options = options;
|
||||||
|
child_options.whitespace.indent_level += 1;
|
||||||
|
inline for (S.fields) |Field| {
|
||||||
|
// don't include void fields
|
||||||
|
if (Field.type == void) continue;
|
||||||
|
|
||||||
|
var emit_field = true;
|
||||||
|
|
||||||
|
// don't include optional fields that are null when emit_null_optional_fields is set to false
|
||||||
|
if (@typeInfo(Field.type) == .Optional) {
|
||||||
|
if (options.emit_null_optional_fields == false) {
|
||||||
|
if (@field(value, Field.name) == null) {
|
||||||
|
emit_field = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emit_field) {
|
||||||
|
if (!field_output) {
|
||||||
|
field_output = true;
|
||||||
|
} else {
|
||||||
|
try out_stream.writeByte(',');
|
||||||
|
}
|
||||||
|
try child_options.whitespace.outputIndent(out_stream);
|
||||||
|
if (!S.is_tuple) {
|
||||||
|
try encodeJsonString(Field.name, options, out_stream);
|
||||||
|
try out_stream.writeByte(':');
|
||||||
|
if (child_options.whitespace.separator) {
|
||||||
|
try out_stream.writeByte(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try stringify(@field(value, Field.name), child_options, out_stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (field_output) {
|
||||||
|
try options.whitespace.outputIndent(out_stream);
|
||||||
|
}
|
||||||
|
try out_stream.writeByte(if (S.is_tuple) ']' else '}');
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
.ErrorSet => return stringify(@as([]const u8, @errorName(value)), options, out_stream),
|
||||||
|
.Pointer => |ptr_info| switch (ptr_info.size) {
|
||||||
|
.One => switch (@typeInfo(ptr_info.child)) {
|
||||||
|
.Array => {
|
||||||
|
const Slice = []const std.meta.Elem(ptr_info.child);
|
||||||
|
return stringify(@as(Slice, value), options, out_stream);
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
// TODO: avoid loops?
|
||||||
|
return stringify(value.*, options, out_stream);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.Many, .Slice => {
|
||||||
|
if (ptr_info.size == .Many and ptr_info.sentinel == null)
|
||||||
|
@compileError("unable to stringify type '" ++ @typeName(T) ++ "' without sentinel");
|
||||||
|
const slice = if (ptr_info.size == .Many) mem.span(value) else value;
|
||||||
|
|
||||||
|
if (ptr_info.child == u8 and options.string == .String and std.unicode.utf8ValidateSlice(slice)) {
|
||||||
|
try encodeJsonString(slice, options, out_stream);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try out_stream.writeByte('[');
|
||||||
|
var child_options = options;
|
||||||
|
child_options.whitespace.indent_level += 1;
|
||||||
|
for (slice, 0..) |x, i| {
|
||||||
|
if (i != 0) {
|
||||||
|
try out_stream.writeByte(',');
|
||||||
|
}
|
||||||
|
try child_options.whitespace.outputIndent(out_stream);
|
||||||
|
try stringify(x, child_options, out_stream);
|
||||||
|
}
|
||||||
|
if (slice.len != 0) {
|
||||||
|
try options.whitespace.outputIndent(out_stream);
|
||||||
|
}
|
||||||
|
try out_stream.writeByte(']');
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
|
||||||
|
},
|
||||||
|
.Array => return stringify(&value, options, out_stream),
|
||||||
|
.Vector => |info| {
|
||||||
|
const array: [info.len]info.child = value;
|
||||||
|
return stringify(&array, options, out_stream);
|
||||||
|
},
|
||||||
|
else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
|
||||||
|
}
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same as `stringify` but accepts an Allocator and stores result in dynamically allocated memory instead of using a Writer.
|
||||||
|
// Caller owns returned memory.
|
||||||
|
pub fn stringifyAlloc(allocator: std.mem.Allocator, value: anytype, options: StringifyOptions) ![]const u8 {
|
||||||
|
var list = std.ArrayList(u8).init(allocator);
|
||||||
|
errdefer list.deinit();
|
||||||
|
try stringify(value, options, list.writer());
|
||||||
|
return list.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
_ = @import("./stringify_test.zig");
|
||||||
|
}
|
||||||
280
lib/std/json/stringify_test.zig
Normal file
280
lib/std/json/stringify_test.zig
Normal file
|
|
@ -0,0 +1,280 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const mem = std.mem;
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
const StringifyOptions = @import("stringify.zig").StringifyOptions;
|
||||||
|
const stringify = @import("stringify.zig").stringify;
|
||||||
|
const stringifyAlloc = @import("stringify.zig").stringifyAlloc;
|
||||||
|
|
||||||
|
test "stringify null optional fields" {
|
||||||
|
const MyStruct = struct {
|
||||||
|
optional: ?[]const u8 = null,
|
||||||
|
required: []const u8 = "something",
|
||||||
|
another_optional: ?[]const u8 = null,
|
||||||
|
another_required: []const u8 = "something else",
|
||||||
|
};
|
||||||
|
try teststringify(
|
||||||
|
\\{"optional":null,"required":"something","another_optional":null,"another_required":"something else"}
|
||||||
|
,
|
||||||
|
MyStruct{},
|
||||||
|
StringifyOptions{},
|
||||||
|
);
|
||||||
|
try teststringify(
|
||||||
|
\\{"required":"something","another_required":"something else"}
|
||||||
|
,
|
||||||
|
MyStruct{},
|
||||||
|
StringifyOptions{ .emit_null_optional_fields = false },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "stringify basic types" {
|
||||||
|
try teststringify("false", false, StringifyOptions{});
|
||||||
|
try teststringify("true", true, StringifyOptions{});
|
||||||
|
try teststringify("null", @as(?u8, null), StringifyOptions{});
|
||||||
|
try teststringify("null", @as(?*u32, null), StringifyOptions{});
|
||||||
|
try teststringify("42", 42, StringifyOptions{});
|
||||||
|
try teststringify("4.2e+01", 42.0, StringifyOptions{});
|
||||||
|
try teststringify("42", @as(u8, 42), StringifyOptions{});
|
||||||
|
try teststringify("42", @as(u128, 42), StringifyOptions{});
|
||||||
|
try teststringify("4.2e+01", @as(f32, 42), StringifyOptions{});
|
||||||
|
try teststringify("4.2e+01", @as(f64, 42), StringifyOptions{});
|
||||||
|
try teststringify("\"ItBroke\"", @as(anyerror, error.ItBroke), StringifyOptions{});
|
||||||
|
}
|
||||||
|
|
||||||
|
test "stringify string" {
|
||||||
|
try teststringify("\"hello\"", "hello", StringifyOptions{});
|
||||||
|
try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{});
|
||||||
|
try teststringify("\"with\\nescapes\\r\"", "with\nescapes\r", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
||||||
|
try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{});
|
||||||
|
try teststringify("\"with unicode\\u0001\"", "with unicode\u{1}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
||||||
|
try teststringify("\"with unicode\u{80}\"", "with unicode\u{80}", StringifyOptions{});
|
||||||
|
try teststringify("\"with unicode\\u0080\"", "with unicode\u{80}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
||||||
|
try teststringify("\"with unicode\u{FF}\"", "with unicode\u{FF}", StringifyOptions{});
|
||||||
|
try teststringify("\"with unicode\\u00ff\"", "with unicode\u{FF}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
||||||
|
try teststringify("\"with unicode\u{100}\"", "with unicode\u{100}", StringifyOptions{});
|
||||||
|
try teststringify("\"with unicode\\u0100\"", "with unicode\u{100}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
||||||
|
try teststringify("\"with unicode\u{800}\"", "with unicode\u{800}", StringifyOptions{});
|
||||||
|
try teststringify("\"with unicode\\u0800\"", "with unicode\u{800}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
||||||
|
try teststringify("\"with unicode\u{8000}\"", "with unicode\u{8000}", StringifyOptions{});
|
||||||
|
try teststringify("\"with unicode\\u8000\"", "with unicode\u{8000}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
||||||
|
try teststringify("\"with unicode\u{D799}\"", "with unicode\u{D799}", StringifyOptions{});
|
||||||
|
try teststringify("\"with unicode\\ud799\"", "with unicode\u{D799}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
||||||
|
try teststringify("\"with unicode\u{10000}\"", "with unicode\u{10000}", StringifyOptions{});
|
||||||
|
try teststringify("\"with unicode\\ud800\\udc00\"", "with unicode\u{10000}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
||||||
|
try teststringify("\"with unicode\u{10FFFF}\"", "with unicode\u{10FFFF}", StringifyOptions{});
|
||||||
|
try teststringify("\"with unicode\\udbff\\udfff\"", "with unicode\u{10FFFF}", StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
||||||
|
try teststringify("\"/\"", "/", StringifyOptions{});
|
||||||
|
try teststringify("\"\\/\"", "/", StringifyOptions{ .string = .{ .String = .{ .escape_solidus = true } } });
|
||||||
|
}
|
||||||
|
|
||||||
|
test "stringify many-item sentinel-terminated string" {
|
||||||
|
try teststringify("\"hello\"", @as([*:0]const u8, "hello"), StringifyOptions{});
|
||||||
|
try teststringify("\"with\\nescapes\\r\"", @as([*:0]const u8, "with\nescapes\r"), StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
||||||
|
try teststringify("\"with unicode\\u0001\"", @as([*:0]const u8, "with unicode\u{1}"), StringifyOptions{ .string = .{ .String = .{ .escape_unicode = true } } });
|
||||||
|
}
|
||||||
|
|
||||||
|
test "stringify tagged unions" {
|
||||||
|
const T = union(enum) {
|
||||||
|
nothing,
|
||||||
|
foo: u32,
|
||||||
|
bar: bool,
|
||||||
|
};
|
||||||
|
try teststringify("{\"nothing\":{}}", T{ .nothing = {} }, StringifyOptions{});
|
||||||
|
try teststringify("{\"foo\":42}", T{ .foo = 42 }, StringifyOptions{});
|
||||||
|
try teststringify("{\"bar\":true}", T{ .bar = true }, StringifyOptions{});
|
||||||
|
}
|
||||||
|
|
||||||
|
test "stringify struct" {
|
||||||
|
try teststringify("{\"foo\":42}", struct {
|
||||||
|
foo: u32,
|
||||||
|
}{ .foo = 42 }, StringifyOptions{});
|
||||||
|
}
|
||||||
|
|
||||||
|
test "stringify struct with string as array" {
|
||||||
|
try teststringify("{\"foo\":\"bar\"}", .{ .foo = "bar" }, StringifyOptions{});
|
||||||
|
try teststringify("{\"foo\":[98,97,114]}", .{ .foo = "bar" }, StringifyOptions{ .string = .Array });
|
||||||
|
}
|
||||||
|
|
||||||
|
test "stringify struct with indentation" {
|
||||||
|
try teststringify(
|
||||||
|
\\{
|
||||||
|
\\ "foo": 42,
|
||||||
|
\\ "bar": [
|
||||||
|
\\ 1,
|
||||||
|
\\ 2,
|
||||||
|
\\ 3
|
||||||
|
\\ ]
|
||||||
|
\\}
|
||||||
|
,
|
||||||
|
struct {
|
||||||
|
foo: u32,
|
||||||
|
bar: [3]u32,
|
||||||
|
}{
|
||||||
|
.foo = 42,
|
||||||
|
.bar = .{ 1, 2, 3 },
|
||||||
|
},
|
||||||
|
StringifyOptions{
|
||||||
|
.whitespace = .{},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
try teststringify(
|
||||||
|
"{\n\t\"foo\":42,\n\t\"bar\":[\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n}",
|
||||||
|
struct {
|
||||||
|
foo: u32,
|
||||||
|
bar: [3]u32,
|
||||||
|
}{
|
||||||
|
.foo = 42,
|
||||||
|
.bar = .{ 1, 2, 3 },
|
||||||
|
},
|
||||||
|
StringifyOptions{
|
||||||
|
.whitespace = .{
|
||||||
|
.indent = .tab,
|
||||||
|
.separator = false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
try teststringify(
|
||||||
|
\\{"foo":42,"bar":[1,2,3]}
|
||||||
|
,
|
||||||
|
struct {
|
||||||
|
foo: u32,
|
||||||
|
bar: [3]u32,
|
||||||
|
}{
|
||||||
|
.foo = 42,
|
||||||
|
.bar = .{ 1, 2, 3 },
|
||||||
|
},
|
||||||
|
StringifyOptions{
|
||||||
|
.whitespace = .{
|
||||||
|
.indent = .none,
|
||||||
|
.separator = false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "stringify struct with void field" {
|
||||||
|
try teststringify("{\"foo\":42}", struct {
|
||||||
|
foo: u32,
|
||||||
|
bar: void = {},
|
||||||
|
}{ .foo = 42 }, StringifyOptions{});
|
||||||
|
}
|
||||||
|
|
||||||
|
test "stringify array of structs" {
|
||||||
|
const MyStruct = struct {
|
||||||
|
foo: u32,
|
||||||
|
};
|
||||||
|
try teststringify("[{\"foo\":42},{\"foo\":100},{\"foo\":1000}]", [_]MyStruct{
|
||||||
|
MyStruct{ .foo = 42 },
|
||||||
|
MyStruct{ .foo = 100 },
|
||||||
|
MyStruct{ .foo = 1000 },
|
||||||
|
}, StringifyOptions{});
|
||||||
|
}
|
||||||
|
|
||||||
|
test "stringify struct with custom stringifier" {
|
||||||
|
try teststringify("[\"something special\",42]", struct {
|
||||||
|
foo: u32,
|
||||||
|
const Self = @This();
|
||||||
|
pub fn jsonStringify(
|
||||||
|
value: Self,
|
||||||
|
options: StringifyOptions,
|
||||||
|
out_stream: anytype,
|
||||||
|
) !void {
|
||||||
|
_ = value;
|
||||||
|
try out_stream.writeAll("[\"something special\",");
|
||||||
|
try stringify(42, options, out_stream);
|
||||||
|
try out_stream.writeByte(']');
|
||||||
|
}
|
||||||
|
}{ .foo = 42 }, StringifyOptions{});
|
||||||
|
}
|
||||||
|
|
||||||
|
test "stringify vector" {
|
||||||
|
try teststringify("[1,1]", @splat(2, @as(u32, 1)), StringifyOptions{});
|
||||||
|
}
|
||||||
|
|
||||||
|
test "stringify tuple" {
|
||||||
|
try teststringify("[\"foo\",42]", std.meta.Tuple(&.{ []const u8, usize }){ "foo", 42 }, StringifyOptions{});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions) !void {
|
||||||
|
const ValidationWriter = struct {
|
||||||
|
const Self = @This();
|
||||||
|
pub const Writer = std.io.Writer(*Self, Error, write);
|
||||||
|
pub const Error = error{
|
||||||
|
TooMuchData,
|
||||||
|
DifferentData,
|
||||||
|
};
|
||||||
|
|
||||||
|
expected_remaining: []const u8,
|
||||||
|
|
||||||
|
fn init(exp: []const u8) Self {
|
||||||
|
return .{ .expected_remaining = exp };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writer(self: *Self) Writer {
|
||||||
|
return .{ .context = self };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(self: *Self, bytes: []const u8) Error!usize {
|
||||||
|
if (self.expected_remaining.len < bytes.len) {
|
||||||
|
std.debug.print(
|
||||||
|
\\====== expected this output: =========
|
||||||
|
\\{s}
|
||||||
|
\\======== instead found this: =========
|
||||||
|
\\{s}
|
||||||
|
\\======================================
|
||||||
|
, .{
|
||||||
|
self.expected_remaining,
|
||||||
|
bytes,
|
||||||
|
});
|
||||||
|
return error.TooMuchData;
|
||||||
|
}
|
||||||
|
if (!mem.eql(u8, self.expected_remaining[0..bytes.len], bytes)) {
|
||||||
|
std.debug.print(
|
||||||
|
\\====== expected this output: =========
|
||||||
|
\\{s}
|
||||||
|
\\======== instead found this: =========
|
||||||
|
\\{s}
|
||||||
|
\\======================================
|
||||||
|
, .{
|
||||||
|
self.expected_remaining[0..bytes.len],
|
||||||
|
bytes,
|
||||||
|
});
|
||||||
|
return error.DifferentData;
|
||||||
|
}
|
||||||
|
self.expected_remaining = self.expected_remaining[bytes.len..];
|
||||||
|
return bytes.len;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var vos = ValidationWriter.init(expected);
|
||||||
|
try stringify(value, options, vos.writer());
|
||||||
|
if (vos.expected_remaining.len > 0) return error.NotEnoughData;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "stringify struct with custom stringify that returns a custom error" {
|
||||||
|
var ret = stringify(struct {
|
||||||
|
field: Field = .{},
|
||||||
|
|
||||||
|
pub const Field = struct {
|
||||||
|
field: ?[]*Field = null,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
pub fn jsonStringify(_: Self, _: StringifyOptions, _: anytype) error{CustomError}!void {
|
||||||
|
return error.CustomError;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}{}, StringifyOptions{}, std.io.null_writer);
|
||||||
|
|
||||||
|
try std.testing.expectError(error.CustomError, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "stringify alloc" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
const expected =
|
||||||
|
\\{"foo":"bar","answer":42,"my_friend":"sammy"}
|
||||||
|
;
|
||||||
|
const actual = try stringifyAlloc(allocator, .{ .foo = "bar", .answer = 42, .my_friend = "sammy" }, .{});
|
||||||
|
defer allocator.free(actual);
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings(expected, actual);
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,14 +1,19 @@
|
||||||
const std = @import("../std.zig");
|
const std = @import("std");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const maxInt = std.math.maxInt;
|
const maxInt = std.math.maxInt;
|
||||||
|
|
||||||
|
const StringifyOptions = @import("./stringify.zig").StringifyOptions;
|
||||||
|
const jsonStringify = @import("./stringify.zig").stringify;
|
||||||
|
|
||||||
|
const Value = @import("./dynamic.zig").Value;
|
||||||
|
|
||||||
const State = enum {
|
const State = enum {
|
||||||
Complete,
|
complete,
|
||||||
Value,
|
value,
|
||||||
ArrayStart,
|
array_start,
|
||||||
Array,
|
array,
|
||||||
ObjectStart,
|
object_start,
|
||||||
Object,
|
object,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Writes JSON ([RFC8259](https://tools.ietf.org/html/rfc8259)) formatted data
|
/// Writes JSON ([RFC8259](https://tools.ietf.org/html/rfc8259)) formatted data
|
||||||
|
|
@ -21,9 +26,9 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
|
||||||
|
|
||||||
pub const Stream = OutStream;
|
pub const Stream = OutStream;
|
||||||
|
|
||||||
whitespace: std.json.StringifyOptions.Whitespace = std.json.StringifyOptions.Whitespace{
|
whitespace: StringifyOptions.Whitespace = StringifyOptions.Whitespace{
|
||||||
.indent_level = 0,
|
.indent_level = 0,
|
||||||
.indent = .{ .Space = 1 },
|
.indent = .{ .space = 1 },
|
||||||
},
|
},
|
||||||
|
|
||||||
stream: OutStream,
|
stream: OutStream,
|
||||||
|
|
@ -36,38 +41,38 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
|
||||||
.state_index = 1,
|
.state_index = 1,
|
||||||
.state = undefined,
|
.state = undefined,
|
||||||
};
|
};
|
||||||
self.state[0] = .Complete;
|
self.state[0] = .complete;
|
||||||
self.state[1] = .Value;
|
self.state[1] = .value;
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn beginArray(self: *Self) !void {
|
pub fn beginArray(self: *Self) !void {
|
||||||
assert(self.state[self.state_index] == State.Value); // need to call arrayElem or objectField
|
assert(self.state[self.state_index] == State.value); // need to call arrayElem or objectField
|
||||||
try self.stream.writeByte('[');
|
try self.stream.writeByte('[');
|
||||||
self.state[self.state_index] = State.ArrayStart;
|
self.state[self.state_index] = State.array_start;
|
||||||
self.whitespace.indent_level += 1;
|
self.whitespace.indent_level += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn beginObject(self: *Self) !void {
|
pub fn beginObject(self: *Self) !void {
|
||||||
assert(self.state[self.state_index] == State.Value); // need to call arrayElem or objectField
|
assert(self.state[self.state_index] == State.value); // need to call arrayElem or objectField
|
||||||
try self.stream.writeByte('{');
|
try self.stream.writeByte('{');
|
||||||
self.state[self.state_index] = State.ObjectStart;
|
self.state[self.state_index] = State.object_start;
|
||||||
self.whitespace.indent_level += 1;
|
self.whitespace.indent_level += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arrayElem(self: *Self) !void {
|
pub fn arrayElem(self: *Self) !void {
|
||||||
const state = self.state[self.state_index];
|
const state = self.state[self.state_index];
|
||||||
switch (state) {
|
switch (state) {
|
||||||
.Complete => unreachable,
|
.complete => unreachable,
|
||||||
.Value => unreachable,
|
.value => unreachable,
|
||||||
.ObjectStart => unreachable,
|
.object_start => unreachable,
|
||||||
.Object => unreachable,
|
.object => unreachable,
|
||||||
.Array, .ArrayStart => {
|
.array, .array_start => {
|
||||||
if (state == .Array) {
|
if (state == .array) {
|
||||||
try self.stream.writeByte(',');
|
try self.stream.writeByte(',');
|
||||||
}
|
}
|
||||||
self.state[self.state_index] = .Array;
|
self.state[self.state_index] = .array;
|
||||||
self.pushState(.Value);
|
self.pushState(.value);
|
||||||
try self.indent();
|
try self.indent();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -76,16 +81,16 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
|
||||||
pub fn objectField(self: *Self, name: []const u8) !void {
|
pub fn objectField(self: *Self, name: []const u8) !void {
|
||||||
const state = self.state[self.state_index];
|
const state = self.state[self.state_index];
|
||||||
switch (state) {
|
switch (state) {
|
||||||
.Complete => unreachable,
|
.complete => unreachable,
|
||||||
.Value => unreachable,
|
.value => unreachable,
|
||||||
.ArrayStart => unreachable,
|
.array_start => unreachable,
|
||||||
.Array => unreachable,
|
.array => unreachable,
|
||||||
.Object, .ObjectStart => {
|
.object, .object_start => {
|
||||||
if (state == .Object) {
|
if (state == .object) {
|
||||||
try self.stream.writeByte(',');
|
try self.stream.writeByte(',');
|
||||||
}
|
}
|
||||||
self.state[self.state_index] = .Object;
|
self.state[self.state_index] = .object;
|
||||||
self.pushState(.Value);
|
self.pushState(.value);
|
||||||
try self.indent();
|
try self.indent();
|
||||||
try self.writeEscapedString(name);
|
try self.writeEscapedString(name);
|
||||||
try self.stream.writeByte(':');
|
try self.stream.writeByte(':');
|
||||||
|
|
@ -98,16 +103,16 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
|
||||||
|
|
||||||
pub fn endArray(self: *Self) !void {
|
pub fn endArray(self: *Self) !void {
|
||||||
switch (self.state[self.state_index]) {
|
switch (self.state[self.state_index]) {
|
||||||
.Complete => unreachable,
|
.complete => unreachable,
|
||||||
.Value => unreachable,
|
.value => unreachable,
|
||||||
.ObjectStart => unreachable,
|
.object_start => unreachable,
|
||||||
.Object => unreachable,
|
.object => unreachable,
|
||||||
.ArrayStart => {
|
.array_start => {
|
||||||
self.whitespace.indent_level -= 1;
|
self.whitespace.indent_level -= 1;
|
||||||
try self.stream.writeByte(']');
|
try self.stream.writeByte(']');
|
||||||
self.popState();
|
self.popState();
|
||||||
},
|
},
|
||||||
.Array => {
|
.array => {
|
||||||
self.whitespace.indent_level -= 1;
|
self.whitespace.indent_level -= 1;
|
||||||
try self.indent();
|
try self.indent();
|
||||||
self.popState();
|
self.popState();
|
||||||
|
|
@ -118,16 +123,16 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
|
||||||
|
|
||||||
pub fn endObject(self: *Self) !void {
|
pub fn endObject(self: *Self) !void {
|
||||||
switch (self.state[self.state_index]) {
|
switch (self.state[self.state_index]) {
|
||||||
.Complete => unreachable,
|
.complete => unreachable,
|
||||||
.Value => unreachable,
|
.value => unreachable,
|
||||||
.ArrayStart => unreachable,
|
.array_start => unreachable,
|
||||||
.Array => unreachable,
|
.array => unreachable,
|
||||||
.ObjectStart => {
|
.object_start => {
|
||||||
self.whitespace.indent_level -= 1;
|
self.whitespace.indent_level -= 1;
|
||||||
try self.stream.writeByte('}');
|
try self.stream.writeByte('}');
|
||||||
self.popState();
|
self.popState();
|
||||||
},
|
},
|
||||||
.Object => {
|
.object => {
|
||||||
self.whitespace.indent_level -= 1;
|
self.whitespace.indent_level -= 1;
|
||||||
try self.indent();
|
try self.indent();
|
||||||
self.popState();
|
self.popState();
|
||||||
|
|
@ -137,13 +142,13 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn emitNull(self: *Self) !void {
|
pub fn emitNull(self: *Self) !void {
|
||||||
assert(self.state[self.state_index] == State.Value);
|
assert(self.state[self.state_index] == State.value);
|
||||||
try self.stringify(null);
|
try self.stringify(null);
|
||||||
self.popState();
|
self.popState();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn emitBool(self: *Self, value: bool) !void {
|
pub fn emitBool(self: *Self, value: bool) !void {
|
||||||
assert(self.state[self.state_index] == State.Value);
|
assert(self.state[self.state_index] == State.value);
|
||||||
try self.stringify(value);
|
try self.stringify(value);
|
||||||
self.popState();
|
self.popState();
|
||||||
}
|
}
|
||||||
|
|
@ -154,7 +159,7 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
|
||||||
/// in a IEEE 754 double float, otherwise emitted as a string to the full precision.
|
/// in a IEEE 754 double float, otherwise emitted as a string to the full precision.
|
||||||
value: anytype,
|
value: anytype,
|
||||||
) !void {
|
) !void {
|
||||||
assert(self.state[self.state_index] == State.Value);
|
assert(self.state[self.state_index] == State.value);
|
||||||
switch (@typeInfo(@TypeOf(value))) {
|
switch (@typeInfo(@TypeOf(value))) {
|
||||||
.Int => |info| {
|
.Int => |info| {
|
||||||
if (info.bits < 53) {
|
if (info.bits < 53) {
|
||||||
|
|
@ -183,7 +188,7 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn emitString(self: *Self, string: []const u8) !void {
|
pub fn emitString(self: *Self, string: []const u8) !void {
|
||||||
assert(self.state[self.state_index] == State.Value);
|
assert(self.state[self.state_index] == State.value);
|
||||||
try self.writeEscapedString(string);
|
try self.writeEscapedString(string);
|
||||||
self.popState();
|
self.popState();
|
||||||
}
|
}
|
||||||
|
|
@ -194,9 +199,9 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes the complete json into the output stream
|
/// Writes the complete json into the output stream
|
||||||
pub fn emitJson(self: *Self, json: std.json.Value) Stream.Error!void {
|
pub fn emitJson(self: *Self, value: Value) Stream.Error!void {
|
||||||
assert(self.state[self.state_index] == State.Value);
|
assert(self.state[self.state_index] == State.value);
|
||||||
try self.stringify(json);
|
try self.stringify(value);
|
||||||
self.popState();
|
self.popState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,7 +220,7 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stringify(self: *Self, value: anytype) !void {
|
fn stringify(self: *Self, value: anytype) !void {
|
||||||
try std.json.stringify(value, std.json.StringifyOptions{
|
try jsonStringify(value, StringifyOptions{
|
||||||
.whitespace = self.whitespace,
|
.whitespace = self.whitespace,
|
||||||
}, self.stream);
|
}, self.stream);
|
||||||
}
|
}
|
||||||
|
|
@ -229,6 +234,8 @@ pub fn writeStream(
|
||||||
return WriteStream(@TypeOf(out_stream), max_depth).init(out_stream);
|
return WriteStream(@TypeOf(out_stream), max_depth).init(out_stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ObjectMap = @import("./dynamic.zig").ObjectMap;
|
||||||
|
|
||||||
test "json write stream" {
|
test "json write stream" {
|
||||||
var out_buf: [1024]u8 = undefined;
|
var out_buf: [1024]u8 = undefined;
|
||||||
var slice_stream = std.io.fixedBufferStream(&out_buf);
|
var slice_stream = std.io.fixedBufferStream(&out_buf);
|
||||||
|
|
@ -237,7 +244,7 @@ test "json write stream" {
|
||||||
var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
|
var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
defer arena_allocator.deinit();
|
defer arena_allocator.deinit();
|
||||||
|
|
||||||
var w = std.json.writeStream(out, 10);
|
var w = writeStream(out, 10);
|
||||||
|
|
||||||
try w.beginObject();
|
try w.beginObject();
|
||||||
|
|
||||||
|
|
@ -285,9 +292,9 @@ test "json write stream" {
|
||||||
try std.testing.expect(std.mem.eql(u8, expected, result));
|
try std.testing.expect(std.mem.eql(u8, expected, result));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getJsonObject(allocator: std.mem.Allocator) !std.json.Value {
|
fn getJsonObject(allocator: std.mem.Allocator) !Value {
|
||||||
var value = std.json.Value{ .Object = std.json.ObjectMap.init(allocator) };
|
var value = Value{ .object = ObjectMap.init(allocator) };
|
||||||
try value.Object.put("one", std.json.Value{ .Integer = @intCast(i64, 1) });
|
try value.object.put("one", Value{ .integer = @intCast(i64, 1) });
|
||||||
try value.Object.put("two", std.json.Value{ .Float = 2.0 });
|
try value.object.put("two", Value{ .float = 2.0 });
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -295,7 +295,7 @@ pub fn generateZirData(self: *Autodoc) !void {
|
||||||
try std.json.stringify(
|
try std.json.stringify(
|
||||||
data,
|
data,
|
||||||
.{
|
.{
|
||||||
.whitespace = .{ .indent = .None, .separator = false },
|
.whitespace = .{ .indent = .none, .separator = false },
|
||||||
.emit_null_optional_fields = true,
|
.emit_null_optional_fields = true,
|
||||||
},
|
},
|
||||||
out,
|
out,
|
||||||
|
|
@ -444,7 +444,7 @@ const DocData = struct {
|
||||||
w: anytype,
|
w: anytype,
|
||||||
) !void {
|
) !void {
|
||||||
var jsw = std.json.writeStream(w, 15);
|
var jsw = std.json.writeStream(w, 15);
|
||||||
if (opts.whitespace) |ws| jsw.whitespace = ws;
|
jsw.whitespace = opts.whitespace;
|
||||||
try jsw.beginObject();
|
try jsw.beginObject();
|
||||||
inline for (comptime std.meta.tags(std.meta.FieldEnum(DocData))) |f| {
|
inline for (comptime std.meta.tags(std.meta.FieldEnum(DocData))) |f| {
|
||||||
const f_name = @tagName(f);
|
const f_name = @tagName(f);
|
||||||
|
|
@ -495,7 +495,7 @@ const DocData = struct {
|
||||||
w: anytype,
|
w: anytype,
|
||||||
) !void {
|
) !void {
|
||||||
var jsw = std.json.writeStream(w, 15);
|
var jsw = std.json.writeStream(w, 15);
|
||||||
if (opts.whitespace) |ws| jsw.whitespace = ws;
|
jsw.whitespace = opts.whitespace;
|
||||||
|
|
||||||
try jsw.beginObject();
|
try jsw.beginObject();
|
||||||
inline for (comptime std.meta.tags(std.meta.FieldEnum(DocModule))) |f| {
|
inline for (comptime std.meta.tags(std.meta.FieldEnum(DocModule))) |f| {
|
||||||
|
|
@ -529,7 +529,7 @@ const DocData = struct {
|
||||||
w: anytype,
|
w: anytype,
|
||||||
) !void {
|
) !void {
|
||||||
var jsw = std.json.writeStream(w, 15);
|
var jsw = std.json.writeStream(w, 15);
|
||||||
if (opts.whitespace) |ws| jsw.whitespace = ws;
|
jsw.whitespace = opts.whitespace;
|
||||||
try jsw.beginArray();
|
try jsw.beginArray();
|
||||||
inline for (comptime std.meta.fields(Decl)) |f| {
|
inline for (comptime std.meta.fields(Decl)) |f| {
|
||||||
try jsw.arrayElem();
|
try jsw.arrayElem();
|
||||||
|
|
@ -556,7 +556,7 @@ const DocData = struct {
|
||||||
w: anytype,
|
w: anytype,
|
||||||
) !void {
|
) !void {
|
||||||
var jsw = std.json.writeStream(w, 15);
|
var jsw = std.json.writeStream(w, 15);
|
||||||
if (opts.whitespace) |ws| jsw.whitespace = ws;
|
jsw.whitespace = opts.whitespace;
|
||||||
try jsw.beginArray();
|
try jsw.beginArray();
|
||||||
inline for (comptime std.meta.fields(AstNode)) |f| {
|
inline for (comptime std.meta.fields(AstNode)) |f| {
|
||||||
try jsw.arrayElem();
|
try jsw.arrayElem();
|
||||||
|
|
@ -689,7 +689,7 @@ const DocData = struct {
|
||||||
) !void {
|
) !void {
|
||||||
const active_tag = std.meta.activeTag(self);
|
const active_tag = std.meta.activeTag(self);
|
||||||
var jsw = std.json.writeStream(w, 15);
|
var jsw = std.json.writeStream(w, 15);
|
||||||
if (opts.whitespace) |ws| jsw.whitespace = ws;
|
jsw.whitespace = opts.whitespace;
|
||||||
try jsw.beginArray();
|
try jsw.beginArray();
|
||||||
try jsw.arrayElem();
|
try jsw.arrayElem();
|
||||||
try jsw.emitNumber(@enumToInt(active_tag));
|
try jsw.emitNumber(@enumToInt(active_tag));
|
||||||
|
|
@ -831,7 +831,7 @@ const DocData = struct {
|
||||||
) @TypeOf(w).Error!void {
|
) @TypeOf(w).Error!void {
|
||||||
const active_tag = std.meta.activeTag(self);
|
const active_tag = std.meta.activeTag(self);
|
||||||
var jsw = std.json.writeStream(w, 15);
|
var jsw = std.json.writeStream(w, 15);
|
||||||
if (opts.whitespace) |ws| jsw.whitespace = ws;
|
jsw.whitespace = opts.whitespace;
|
||||||
try jsw.beginObject();
|
try jsw.beginObject();
|
||||||
if (active_tag == .declIndex) {
|
if (active_tag == .declIndex) {
|
||||||
try jsw.objectField("declRef");
|
try jsw.objectField("declRef");
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ pub fn cmdEnv(gpa: Allocator, args: []const []const u8, stdout: std.fs.File.Writ
|
||||||
var bw = std.io.bufferedWriter(stdout);
|
var bw = std.io.bufferedWriter(stdout);
|
||||||
const w = bw.writer();
|
const w = bw.writer();
|
||||||
|
|
||||||
var jws = std.json.WriteStream(@TypeOf(w), 6).init(w);
|
var jws = std.json.writeStream(w, 6);
|
||||||
try jws.beginObject();
|
try jws.beginObject();
|
||||||
|
|
||||||
try jws.objectField("zig_exe");
|
try jws.objectField("zig_exe");
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ pub fn cmdTargets(
|
||||||
|
|
||||||
var bw = io.bufferedWriter(stdout);
|
var bw = io.bufferedWriter(stdout);
|
||||||
const w = bw.writer();
|
const w = bw.writer();
|
||||||
var jws = std.json.WriteStream(@TypeOf(w), 6).init(w);
|
var jws = std.json.writeStream(w, 6);
|
||||||
|
|
||||||
try jws.beginObject();
|
try jws.beginObject();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,7 @@ pub fn main() !void {
|
||||||
// Required for json parsing.
|
// Required for json parsing.
|
||||||
@setEvalBranchQuota(10000);
|
@setEvalBranchQuota(10000);
|
||||||
|
|
||||||
var tokens = std.json.TokenStream.init(spec);
|
var registry = try std.json.parseFromSlice(g.Registry, allocator, spec, .{});
|
||||||
var registry = try std.json.parse(g.Registry, &tokens, .{ .allocator = allocator });
|
|
||||||
|
|
||||||
const core_reg = switch (registry) {
|
const core_reg = switch (registry) {
|
||||||
.core => |core_reg| core_reg,
|
.core => |core_reg| core_reg,
|
||||||
|
|
|
||||||
79
tools/generate_JSONTestSuite.zig
Normal file
79
tools/generate_JSONTestSuite.zig
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
// zig run this file inside the test_parsing/ directory of this repo: https://github.com/nst/JSONTestSuite
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
var allocator = gpa.allocator();
|
||||||
|
|
||||||
|
var output = std.io.getStdOut().writer();
|
||||||
|
try output.writeAll(
|
||||||
|
\\// This file was generated by _generate_JSONTestSuite.zig
|
||||||
|
\\// These test cases are sourced from: https://github.com/nst/JSONTestSuite
|
||||||
|
\\const ok = @import("./test.zig").ok;
|
||||||
|
\\const err = @import("./test.zig").err;
|
||||||
|
\\const any = @import("./test.zig").any;
|
||||||
|
\\
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
|
||||||
|
var names = std.ArrayList([]const u8).init(allocator);
|
||||||
|
var cwd = try std.fs.cwd().openIterableDir(".", .{});
|
||||||
|
var it = cwd.iterate();
|
||||||
|
while (try it.next()) |entry| {
|
||||||
|
try names.append(try allocator.dupe(u8, entry.name));
|
||||||
|
}
|
||||||
|
std.sort.sort([]const u8, names.items, {}, (struct {
|
||||||
|
fn lessThan(_: void, a: []const u8, b: []const u8) bool {
|
||||||
|
return std.mem.lessThan(u8, a, b);
|
||||||
|
}
|
||||||
|
}).lessThan);
|
||||||
|
|
||||||
|
for (names.items) |name| {
|
||||||
|
const contents = try std.fs.cwd().readFileAlloc(allocator, name, 250001);
|
||||||
|
try output.writeAll("test ");
|
||||||
|
try writeString(output, name);
|
||||||
|
try output.writeAll(" {\n try ");
|
||||||
|
switch (name[0]) {
|
||||||
|
'y' => try output.writeAll("ok"),
|
||||||
|
'n' => try output.writeAll("err"),
|
||||||
|
'i' => try output.writeAll("any"),
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
try output.writeByte('(');
|
||||||
|
try writeString(output, contents);
|
||||||
|
try output.writeAll(");\n}\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const i_structure_500_nested_arrays = "[" ** 500 ++ "]" ** 500;
|
||||||
|
const n_structure_100000_opening_arrays = "[" ** 100000;
|
||||||
|
const n_structure_open_array_object = "[{\"\":" ** 50000 ++ "\n";
|
||||||
|
|
||||||
|
fn writeString(writer: anytype, s: []const u8) !void {
|
||||||
|
if (s.len > 200) {
|
||||||
|
// There are a few of these we can compress with Zig expressions.
|
||||||
|
if (std.mem.eql(u8, s, i_structure_500_nested_arrays)) {
|
||||||
|
return writer.writeAll("\"[\" ** 500 ++ \"]\" ** 500");
|
||||||
|
} else if (std.mem.eql(u8, s, n_structure_100000_opening_arrays)) {
|
||||||
|
return writer.writeAll("\"[\" ** 100000");
|
||||||
|
} else if (std.mem.eql(u8, s, n_structure_open_array_object)) {
|
||||||
|
return writer.writeAll("\"[{\\\"\\\":\" ** 50000 ++ \"\\n\"");
|
||||||
|
}
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
try writer.writeByte('"');
|
||||||
|
for (s) |b| {
|
||||||
|
switch (b) {
|
||||||
|
0...('\n' - 1),
|
||||||
|
('\n' + 1)...0x1f,
|
||||||
|
0x7f...0xff,
|
||||||
|
=> try writer.print("\\x{x:0>2}", .{b}),
|
||||||
|
'\n' => try writer.writeAll("\\n"),
|
||||||
|
'"' => try writer.writeAll("\\\""),
|
||||||
|
'\\' => try writer.writeAll("\\\\"),
|
||||||
|
else => try writer.writeByte(b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try writer.writeByte('"');
|
||||||
|
}
|
||||||
|
|
@ -624,9 +624,9 @@ pub fn main() anyerror!void {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
var parser = json.Parser.init(allocator, false);
|
var parser = json.Parser.init(allocator, .alloc_if_needed);
|
||||||
const tree = try parser.parse(json_text);
|
const tree = try parser.parse(json_text);
|
||||||
const root_map = &tree.root.Object;
|
const root_map = &tree.root.object;
|
||||||
|
|
||||||
var all_objects = std.ArrayList(*json.ObjectMap).init(allocator);
|
var all_objects = std.ArrayList(*json.ObjectMap).init(allocator);
|
||||||
{
|
{
|
||||||
|
|
@ -634,14 +634,14 @@ pub fn main() anyerror!void {
|
||||||
it_map: while (it.next()) |kv| {
|
it_map: while (it.next()) |kv| {
|
||||||
if (kv.key_ptr.len == 0) continue;
|
if (kv.key_ptr.len == 0) continue;
|
||||||
if (kv.key_ptr.*[0] == '!') continue;
|
if (kv.key_ptr.*[0] == '!') continue;
|
||||||
if (kv.value_ptr.* != .Object) continue;
|
if (kv.value_ptr.* != .object) continue;
|
||||||
if (!kv.value_ptr.Object.contains("NumArgs")) continue;
|
if (!kv.value_ptr.object.contains("NumArgs")) continue;
|
||||||
if (!kv.value_ptr.Object.contains("Name")) continue;
|
if (!kv.value_ptr.object.contains("Name")) continue;
|
||||||
for (blacklisted_options) |blacklisted_key| {
|
for (blacklisted_options) |blacklisted_key| {
|
||||||
if (std.mem.eql(u8, blacklisted_key, kv.key_ptr.*)) continue :it_map;
|
if (std.mem.eql(u8, blacklisted_key, kv.key_ptr.*)) continue :it_map;
|
||||||
}
|
}
|
||||||
if (kv.value_ptr.Object.get("Name").?.String.len == 0) continue;
|
if (kv.value_ptr.object.get("Name").?.string.len == 0) continue;
|
||||||
try all_objects.append(&kv.value_ptr.Object);
|
try all_objects.append(&kv.value_ptr.object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Some options have multiple matches. As an example, "-Wl,foo" matches both
|
// Some options have multiple matches. As an example, "-Wl,foo" matches both
|
||||||
|
|
@ -666,12 +666,12 @@ pub fn main() anyerror!void {
|
||||||
);
|
);
|
||||||
|
|
||||||
for (all_objects.items) |obj| {
|
for (all_objects.items) |obj| {
|
||||||
const name = obj.get("Name").?.String;
|
const name = obj.get("Name").?.string;
|
||||||
var pd1 = false;
|
var pd1 = false;
|
||||||
var pd2 = false;
|
var pd2 = false;
|
||||||
var pslash = false;
|
var pslash = false;
|
||||||
for (obj.get("Prefixes").?.Array.items) |prefix_json| {
|
for (obj.get("Prefixes").?.array.items) |prefix_json| {
|
||||||
const prefix = prefix_json.String;
|
const prefix = prefix_json.string;
|
||||||
if (std.mem.eql(u8, prefix, "-")) {
|
if (std.mem.eql(u8, prefix, "-")) {
|
||||||
pd1 = true;
|
pd1 = true;
|
||||||
} else if (std.mem.eql(u8, prefix, "--")) {
|
} else if (std.mem.eql(u8, prefix, "--")) {
|
||||||
|
|
@ -790,9 +790,9 @@ const Syntax = union(enum) {
|
||||||
};
|
};
|
||||||
|
|
||||||
fn objSyntax(obj: *json.ObjectMap) ?Syntax {
|
fn objSyntax(obj: *json.ObjectMap) ?Syntax {
|
||||||
const num_args = @intCast(u8, obj.get("NumArgs").?.Integer);
|
const num_args = @intCast(u8, obj.get("NumArgs").?.integer);
|
||||||
for (obj.get("!superclasses").?.Array.items) |superclass_json| {
|
for (obj.get("!superclasses").?.array.items) |superclass_json| {
|
||||||
const superclass = superclass_json.String;
|
const superclass = superclass_json.string;
|
||||||
if (std.mem.eql(u8, superclass, "Joined")) {
|
if (std.mem.eql(u8, superclass, "Joined")) {
|
||||||
return .joined;
|
return .joined;
|
||||||
} else if (std.mem.eql(u8, superclass, "CLJoined")) {
|
} else if (std.mem.eql(u8, superclass, "CLJoined")) {
|
||||||
|
|
@ -831,20 +831,20 @@ fn objSyntax(obj: *json.ObjectMap) ?Syntax {
|
||||||
return .{ .multi_arg = num_args };
|
return .{ .multi_arg = num_args };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const name = obj.get("Name").?.String;
|
const name = obj.get("Name").?.string;
|
||||||
if (std.mem.eql(u8, name, "<input>")) {
|
if (std.mem.eql(u8, name, "<input>")) {
|
||||||
return .flag;
|
return .flag;
|
||||||
} else if (std.mem.eql(u8, name, "<unknown>")) {
|
} else if (std.mem.eql(u8, name, "<unknown>")) {
|
||||||
return .flag;
|
return .flag;
|
||||||
}
|
}
|
||||||
const kind_def = obj.get("Kind").?.Object.get("def").?.String;
|
const kind_def = obj.get("Kind").?.object.get("def").?.string;
|
||||||
if (std.mem.eql(u8, kind_def, "KIND_FLAG")) {
|
if (std.mem.eql(u8, kind_def, "KIND_FLAG")) {
|
||||||
return .flag;
|
return .flag;
|
||||||
}
|
}
|
||||||
const key = obj.get("!name").?.String;
|
const key = obj.get("!name").?.string;
|
||||||
std.debug.print("{s} (key {s}) has unrecognized superclasses:\n", .{ name, key });
|
std.debug.print("{s} (key {s}) has unrecognized superclasses:\n", .{ name, key });
|
||||||
for (obj.get("!superclasses").?.Array.items) |superclass_json| {
|
for (obj.get("!superclasses").?.array.items) |superclass_json| {
|
||||||
std.debug.print(" {s}\n", .{superclass_json.String});
|
std.debug.print(" {s}\n", .{superclass_json.string});
|
||||||
}
|
}
|
||||||
//std.process.exit(1);
|
//std.process.exit(1);
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -883,15 +883,15 @@ fn objectLessThan(context: void, a: *json.ObjectMap, b: *json.ObjectMap) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!a_match_with_eql and !b_match_with_eql) {
|
if (!a_match_with_eql and !b_match_with_eql) {
|
||||||
const a_name = a.get("Name").?.String;
|
const a_name = a.get("Name").?.string;
|
||||||
const b_name = b.get("Name").?.String;
|
const b_name = b.get("Name").?.string;
|
||||||
if (a_name.len != b_name.len) {
|
if (a_name.len != b_name.len) {
|
||||||
return a_name.len > b_name.len;
|
return a_name.len > b_name.len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const a_key = a.get("!name").?.String;
|
const a_key = a.get("!name").?.string;
|
||||||
const b_key = b.get("!name").?.String;
|
const b_key = b.get("!name").?.string;
|
||||||
return std.mem.lessThan(u8, a_key, b_key);
|
return std.mem.lessThan(u8, a_key, b_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1054,14 +1054,14 @@ fn processOneTarget(job: Job) anyerror!void {
|
||||||
var json_parse_progress = progress_node.start("parse JSON", 0);
|
var json_parse_progress = progress_node.start("parse JSON", 0);
|
||||||
json_parse_progress.activate();
|
json_parse_progress.activate();
|
||||||
|
|
||||||
var parser = json.Parser.init(arena, false);
|
var parser = json.Parser.init(arena, .alloc_if_needed);
|
||||||
const tree = try parser.parse(json_text);
|
const tree = try parser.parse(json_text);
|
||||||
json_parse_progress.end();
|
json_parse_progress.end();
|
||||||
|
|
||||||
var render_progress = progress_node.start("render zig code", 0);
|
var render_progress = progress_node.start("render zig code", 0);
|
||||||
render_progress.activate();
|
render_progress.activate();
|
||||||
|
|
||||||
const root_map = &tree.root.Object;
|
const root_map = &tree.root.object;
|
||||||
var features_table = std.StringHashMap(Feature).init(arena);
|
var features_table = std.StringHashMap(Feature).init(arena);
|
||||||
var all_features = std.ArrayList(Feature).init(arena);
|
var all_features = std.ArrayList(Feature).init(arena);
|
||||||
var all_cpus = std.ArrayList(Cpu).init(arena);
|
var all_cpus = std.ArrayList(Cpu).init(arena);
|
||||||
|
|
@ -1070,21 +1070,21 @@ fn processOneTarget(job: Job) anyerror!void {
|
||||||
root_it: while (it.next()) |kv| {
|
root_it: while (it.next()) |kv| {
|
||||||
if (kv.key_ptr.len == 0) continue;
|
if (kv.key_ptr.len == 0) continue;
|
||||||
if (kv.key_ptr.*[0] == '!') continue;
|
if (kv.key_ptr.*[0] == '!') continue;
|
||||||
if (kv.value_ptr.* != .Object) continue;
|
if (kv.value_ptr.* != .object) continue;
|
||||||
if (hasSuperclass(&kv.value_ptr.Object, "SubtargetFeature")) {
|
if (hasSuperclass(&kv.value_ptr.object, "SubtargetFeature")) {
|
||||||
const llvm_name = kv.value_ptr.Object.get("Name").?.String;
|
const llvm_name = kv.value_ptr.object.get("Name").?.string;
|
||||||
if (llvm_name.len == 0) continue;
|
if (llvm_name.len == 0) continue;
|
||||||
|
|
||||||
var zig_name = try llvmNameToZigName(arena, llvm_name);
|
var zig_name = try llvmNameToZigName(arena, llvm_name);
|
||||||
var desc = kv.value_ptr.Object.get("Desc").?.String;
|
var desc = kv.value_ptr.object.get("Desc").?.string;
|
||||||
var deps = std.ArrayList([]const u8).init(arena);
|
var deps = std.ArrayList([]const u8).init(arena);
|
||||||
var omit = false;
|
var omit = false;
|
||||||
var flatten = false;
|
var flatten = false;
|
||||||
const implies = kv.value_ptr.Object.get("Implies").?.Array;
|
const implies = kv.value_ptr.object.get("Implies").?.array;
|
||||||
for (implies.items) |imply| {
|
for (implies.items) |imply| {
|
||||||
const other_key = imply.Object.get("def").?.String;
|
const other_key = imply.object.get("def").?.string;
|
||||||
const other_obj = &root_map.getPtr(other_key).?.Object;
|
const other_obj = &root_map.getPtr(other_key).?.object;
|
||||||
const other_llvm_name = other_obj.get("Name").?.String;
|
const other_llvm_name = other_obj.get("Name").?.string;
|
||||||
const other_zig_name = (try llvmNameToZigNameOmit(
|
const other_zig_name = (try llvmNameToZigNameOmit(
|
||||||
arena,
|
arena,
|
||||||
llvm_target,
|
llvm_target,
|
||||||
|
|
@ -1126,17 +1126,17 @@ fn processOneTarget(job: Job) anyerror!void {
|
||||||
try all_features.append(feature);
|
try all_features.append(feature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasSuperclass(&kv.value_ptr.Object, "Processor")) {
|
if (hasSuperclass(&kv.value_ptr.object, "Processor")) {
|
||||||
const llvm_name = kv.value_ptr.Object.get("Name").?.String;
|
const llvm_name = kv.value_ptr.object.get("Name").?.string;
|
||||||
if (llvm_name.len == 0) continue;
|
if (llvm_name.len == 0) continue;
|
||||||
|
|
||||||
var zig_name = try llvmNameToZigName(arena, llvm_name);
|
var zig_name = try llvmNameToZigName(arena, llvm_name);
|
||||||
var deps = std.ArrayList([]const u8).init(arena);
|
var deps = std.ArrayList([]const u8).init(arena);
|
||||||
const features = kv.value_ptr.Object.get("Features").?.Array;
|
const features = kv.value_ptr.object.get("Features").?.array;
|
||||||
for (features.items) |feature| {
|
for (features.items) |feature| {
|
||||||
const feature_key = feature.Object.get("def").?.String;
|
const feature_key = feature.object.get("def").?.string;
|
||||||
const feature_obj = &root_map.getPtr(feature_key).?.Object;
|
const feature_obj = &root_map.getPtr(feature_key).?.object;
|
||||||
const feature_llvm_name = feature_obj.get("Name").?.String;
|
const feature_llvm_name = feature_obj.get("Name").?.string;
|
||||||
if (feature_llvm_name.len == 0) continue;
|
if (feature_llvm_name.len == 0) continue;
|
||||||
const feature_zig_name = (try llvmNameToZigNameOmit(
|
const feature_zig_name = (try llvmNameToZigNameOmit(
|
||||||
arena,
|
arena,
|
||||||
|
|
@ -1145,11 +1145,11 @@ fn processOneTarget(job: Job) anyerror!void {
|
||||||
)) orelse continue;
|
)) orelse continue;
|
||||||
try deps.append(feature_zig_name);
|
try deps.append(feature_zig_name);
|
||||||
}
|
}
|
||||||
const tune_features = kv.value_ptr.Object.get("TuneFeatures").?.Array;
|
const tune_features = kv.value_ptr.object.get("TuneFeatures").?.array;
|
||||||
for (tune_features.items) |feature| {
|
for (tune_features.items) |feature| {
|
||||||
const feature_key = feature.Object.get("def").?.String;
|
const feature_key = feature.object.get("def").?.string;
|
||||||
const feature_obj = &root_map.getPtr(feature_key).?.Object;
|
const feature_obj = &root_map.getPtr(feature_key).?.object;
|
||||||
const feature_llvm_name = feature_obj.get("Name").?.String;
|
const feature_llvm_name = feature_obj.get("Name").?.string;
|
||||||
if (feature_llvm_name.len == 0) continue;
|
if (feature_llvm_name.len == 0) continue;
|
||||||
const feature_zig_name = (try llvmNameToZigNameOmit(
|
const feature_zig_name = (try llvmNameToZigNameOmit(
|
||||||
arena,
|
arena,
|
||||||
|
|
@ -1431,8 +1431,8 @@ fn llvmNameToZigNameOmit(
|
||||||
|
|
||||||
fn hasSuperclass(obj: *json.ObjectMap, class_name: []const u8) bool {
|
fn hasSuperclass(obj: *json.ObjectMap, class_name: []const u8) bool {
|
||||||
const superclasses_json = obj.get("!superclasses") orelse return false;
|
const superclasses_json = obj.get("!superclasses") orelse return false;
|
||||||
for (superclasses_json.Array.items) |superclass_json| {
|
for (superclasses_json.array.items) |superclass_json| {
|
||||||
const superclass = superclass_json.String;
|
const superclass = superclass_json.string;
|
||||||
if (std.mem.eql(u8, superclass, class_name)) {
|
if (std.mem.eql(u8, superclass, class_name)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,8 +74,7 @@ pub fn main() !void {
|
||||||
|
|
||||||
const registry_path = try fs.path.join(allocator, &.{ spirv_headers_root, "include", "spirv", "unified1", "spirv.core.grammar.json" });
|
const registry_path = try fs.path.join(allocator, &.{ spirv_headers_root, "include", "spirv", "unified1", "spirv.core.grammar.json" });
|
||||||
const registry_json = try std.fs.cwd().readFileAlloc(allocator, registry_path, std.math.maxInt(usize));
|
const registry_json = try std.fs.cwd().readFileAlloc(allocator, registry_path, std.math.maxInt(usize));
|
||||||
var tokens = std.json.TokenStream.init(registry_json);
|
const registry = try std.json.parseFromSlice(g.CoreRegistry, allocator, registry_json, .{});
|
||||||
const registry = try std.json.parse(g.CoreRegistry, &tokens, .{ .allocator = allocator });
|
|
||||||
|
|
||||||
const capabilities = for (registry.operand_kinds) |opkind| {
|
const capabilities = for (registry.operand_kinds) |opkind| {
|
||||||
if (std.mem.eql(u8, opkind.kind, "Capability"))
|
if (std.mem.eql(u8, opkind.kind, "Capability"))
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue