std: Rewrite low-level json api to support streaming (#15602)

This commit is contained in:
Josh Wolfe 2023-05-13 14:31:53 -04:00 committed by GitHub
parent c7bf8bab38
commit 018b743c7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 5794 additions and 5846 deletions

File diff suppressed because it is too large Load Diff

View 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
View 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");
}

View 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

File diff suppressed because it is too large Load Diff

View 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
View 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");
}

View 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
View 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");
}

View 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

View File

@ -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;
} }

View File

@ -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");

View File

@ -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");

View File

@ -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();

View File

@ -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,

View 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('"');
}

View File

@ -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);
} }

View File

@ -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;
} }

View File

@ -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"))