From 40c7de0928ef3cfd3bdfcb3b1d4a17f06131c590 Mon Sep 17 00:00:00 2001 From: Romain Delamare Date: Tue, 12 Mar 2024 19:02:25 +0100 Subject: [PATCH] feat: first version of the KDL 2.0 parser --- .github/workflows/build.yml | 28 +- .github/workflows/release.yaml | 38 - LICENSE.md | 2 +- README.md | 70 +- build.gradle.kts | 6 + gradle.properties | 2 +- src/main/java/kdl/Fuzzer.java | 16 - src/main/java/kdl/KDLBoolean.java | 42 + src/main/java/kdl/KDLDocument.java | 52 + src/main/java/kdl/KDLNode.java | 305 ++++ src/main/java/kdl/KDLNull.java | 37 + src/main/java/kdl/KDLNumber.java | 405 +++++ src/main/java/kdl/KDLString.java | 58 + src/main/java/kdl/KDLValue.java | 98 ++ src/main/java/kdl/Properties.java | 104 ++ src/main/java/kdl/Property.java | 25 + src/main/java/kdl/objects/KDLBoolean.java | 96 -- src/main/java/kdl/objects/KDLDocument.java | 156 -- src/main/java/kdl/objects/KDLNode.java | 440 ------ src/main/java/kdl/objects/KDLNull.java | 79 - src/main/java/kdl/objects/KDLNumber.java | 234 --- src/main/java/kdl/objects/KDLObject.java | 42 - src/main/java/kdl/objects/KDLProperty.java | 61 - src/main/java/kdl/objects/KDLString.java | 106 -- src/main/java/kdl/objects/KDLValue.java | 109 -- src/main/java/kdl/parse/CharClasses.java | 346 ----- .../java/kdl/parse/KDLInternalException.java | 11 +- src/main/java/kdl/parse/KDLParseContext.java | 151 -- .../java/kdl/parse/KDLParseException.java | 9 +- src/main/java/kdl/parse/KDLParser.java | 1342 ++++++----------- src/main/java/kdl/parse/KDLParserFacade.java | 106 -- src/main/java/kdl/parse/error/ErrorUtils.java | 13 + src/main/java/kdl/parse/lexer/KDLReader.java | 195 +++ src/main/java/kdl/parse/lexer/Lexer.java | 615 ++++++++ src/main/java/kdl/parse/lexer/Token.java | 8 + src/main/java/kdl/parse/lexer/token/Bom.java | 24 + .../java/kdl/parse/lexer/token/Boolean.java | 28 + .../java/kdl/parse/lexer/token/Brace.java | 42 + .../kdl/parse/lexer/token/EqualsSign.java | 49 + .../java/kdl/parse/lexer/token/Escline.java | 38 + .../parse/lexer/token/MultiLineComment.java | 37 + .../java/kdl/parse/lexer/token/Newline.java | 67 + src/main/java/kdl/parse/lexer/token/Null.java | 22 + .../java/kdl/parse/lexer/token/Number.java | 185 +++ .../kdl/parse/lexer/token/Parentheses.java | 42 + .../java/kdl/parse/lexer/token/Semicolon.java | 21 + .../parse/lexer/token/SingleLineComment.java | 37 + .../java/kdl/parse/lexer/token/Slashdash.java | 23 + .../kdl/parse/lexer/token/StringToken.java | 116 ++ .../kdl/parse/lexer/token/Whitespace.java | 64 + .../java/kdl/print/KDLPrintException.java | 11 + src/main/java/kdl/print/KDLPrinter.java | 82 + .../java/kdl/print/KDLPrinterContext.java | 234 +++ src/main/java/kdl/print/PrintConfig.java | 270 ---- src/main/java/kdl/print/PrintUtil.java | 32 - .../java/kdl/print/PrinterConfiguration.java | 321 ++++ src/main/java/kdl/search/GeneralSearch.java | 185 --- src/main/java/kdl/search/PathedSearch.java | 178 --- src/main/java/kdl/search/RootSearch.java | 72 - src/main/java/kdl/search/Search.java | 46 - .../java/kdl/search/mutation/AddMutation.java | 104 -- .../java/kdl/search/mutation/Mutation.java | 13 - .../java/kdl/search/mutation/SetMutation.java | 102 -- .../kdl/search/mutation/SubtractMutation.java | 133 -- .../predicates/AnyContentPredicate.java | 15 - .../kdl/search/predicates/ArgPredicate.java | 32 - .../kdl/search/predicates/ChildPredicate.java | 30 - .../predicates/ConjunctionPredicate.java | 21 - .../predicates/DisjunctionPredicate.java | 21 - .../predicates/EmptyContentPredicate.java | 13 - .../search/predicates/NegatedPredicate.java | 19 - .../predicates/NodeContentPredicate.java | 16 - .../kdl/search/predicates/NodePredicate.java | 28 - .../predicates/PositionalArgPredicate.java | 28 - .../kdl/search/predicates/PropPredicate.java | 37 - src/main/java/module-info.java | 7 + src/test/java/kdl/RoundTripTest.java | 18 +- ...ConsumeWhitespaceAndBlockCommentsTest.java | 37 - .../ConsumeWhitespaceAndLinespaceTest.java | 43 - src/test/java/kdl/parse/GetEscapedTest.java | 59 - .../java/kdl/parse/GetSlashActionTest.java | 37 - src/test/java/kdl/parse/KDLParserTest.java | 463 ------ .../java/kdl/parse/ParseArgOrPropTest.java | 30 - .../kdl/parse/ParseBareIdentifierTest.java | 31 - src/test/java/kdl/parse/ParseChildTest.java | 45 - .../kdl/parse/ParseEscapedStringTest.java | 32 - .../java/kdl/parse/ParseIdentifierTest.java | 32 - src/test/java/kdl/parse/ParseNodeTest.java | 51 - src/test/java/kdl/parse/ParseNumberTest.java | 58 - .../java/kdl/parse/ParseRawStringTest.java | 35 - src/test/java/kdl/parse/ParseValueTest.java | 46 - src/test/java/kdl/parse/ParserTest.java | 59 - .../java/kdl/parse/lexer/KDLReaderTest.java | 319 ++++ src/test/java/kdl/parse/lexer/LexerTest.java | 362 +++++ .../java/kdl/search/GeneralSearchTest.java | 509 ------- src/test/java/kdl/search/NodeIDMatcher.java | 25 - .../java/kdl/search/PathedSearchTest.java | 534 ------- src/test/java/kdl/search/RootSearchTest.java | 84 -- .../kdl/search/mutation/AddMutationTest.java | 66 - .../kdl/search/mutation/SetMutationTest.java | 70 - .../search/mutation/SubtractMutationTest.java | 59 - .../search/predicate/ArgPredicateTest.java | 36 - .../search/predicate/ChildPredicateTest.java | 98 -- .../predicate/EmptyContentPredicateTest.java | 60 - .../predicate/LogicalPredicatesTest.java | 78 - .../search/predicate/NodePredicateTest.java | 46 - .../search/predicate/PropPredicateTest.java | 48 - src/test/resources/README.md | 60 +- .../test_cases/expected_kdl/all_escapes.kdl | 2 +- .../expected_kdl/all_node_fields.kdl | 2 +- .../expected_kdl/arg_and_prop_same_name.kdl | 2 +- .../test_cases/expected_kdl/arg_bare.kdl | 1 + .../expected_kdl/arg_false_type.kdl | 2 +- .../test_cases/expected_kdl/arg_hex_type.kdl | 2 +- .../test_cases/expected_kdl/arg_null_type.kdl | 2 +- .../expected_kdl/arg_raw_string_type.kdl | 2 +- .../expected_kdl/arg_string_type.kdl | 2 +- .../test_cases/expected_kdl/arg_true_type.kdl | 2 +- .../test_cases/expected_kdl/arg_type.kdl | 2 +- .../test_cases/expected_kdl/bare_emoji.kdl | 2 +- .../expected_kdl/bare_ident_dot.kdl | 1 + .../expected_kdl/bare_ident_sign.kdl | 1 + .../expected_kdl/bare_ident_sign_dot.kdl | 1 + .../test_cases/expected_kdl/binary.kdl | 2 +- .../binary_trailing_underscore.kdl | 2 +- .../expected_kdl/binary_underscore.kdl | 2 +- .../expected_kdl/blank_prop_type.kdl | 2 +- .../test_cases/expected_kdl/block_comment.kdl | 2 +- .../expected_kdl/block_comment_after_node.kdl | 2 +- .../test_cases/expected_kdl/bom_initial.kdl | 1 + .../test_cases/expected_kdl/boolean_arg.kdl | 2 +- .../test_cases/expected_kdl/boolean_prop.kdl | 2 +- .../expected_kdl/chevrons_in_bare_id.kdl | 1 + .../expected_kdl/comma_in_bare_id.kdl | 1 + .../expected_kdl/comment_after_arg_type.kdl | 1 + .../expected_kdl/comment_after_node_type.kdl | 1 + .../expected_kdl/comment_after_prop_type.kdl | 1 + ...mment_node.kdl => comment_and_newline.kdl} | 0 .../expected_kdl/comment_in_arg_type.kdl | 1 + .../expected_kdl/comment_in_node_type.kdl | 1 + .../expected_kdl/comment_in_prop_type.kdl | 1 + .../test_cases/expected_kdl/commented_arg.kdl | 2 +- .../expected_kdl/commented_child.kdl | 2 +- .../expected_kdl/commented_prop.kdl | 2 +- .../test_cases/expected_kdl/dash_dash.kdl | 1 + .../test_cases/expected_kdl/emoji.kdl | 2 +- .../test_cases/expected_kdl/empty_child.kdl | 3 +- .../empty_child_different_lines.kdl | 3 +- .../expected_kdl/empty_child_same_line.kdl | 3 +- .../expected_kdl/empty_child_whitespace.kdl | 3 +- .../expected_kdl/empty_line_comment.kdl | 1 + .../expected_kdl/empty_quoted_node_id.kdl | 2 +- .../expected_kdl/empty_quoted_prop_key.kdl | 2 +- .../expected_kdl/eof_after_escape.kdl | 1 + .../expected_kdl/escaped_whitespace.kdl | 1 + .../test_cases/expected_kdl/escline.kdl | 2 +- .../expected_kdl/escline_line_comment.kdl | 2 +- .../expected_kdl/false_prefix_in_bare_id.kdl | 1 + .../expected_kdl/false_prefix_in_prop_key.kdl | 1 + .../expected_kdl/floating_point_keywords.kdl | 1 + .../resources/test_cases/expected_kdl/hex.kdl | 2 +- .../test_cases/expected_kdl/hex_int.kdl | 2 +- .../expected_kdl/hex_int_underscores.kdl | 2 +- .../expected_kdl/hex_leading_zero.kdl | 2 +- .../expected_kdl/initial_slashdash.kdl | 1 + .../expected_kdl/leading_zero_binary.kdl | 2 +- .../expected_kdl/leading_zero_oct.kdl | 2 +- .../expected_kdl/multiline_comment.kdl | 2 +- .../expected_kdl/multiline_nodes.kdl | 2 +- .../expected_kdl/multiline_raw_string.kdl | 1 + .../multiline_raw_string_indented.kdl | 1 + .../expected_kdl/multiline_string.kdl | 2 +- .../multiline_string_indented.kdl | 1 + .../expected_kdl/nested_block_comment.kdl | 2 +- .../expected_kdl/nested_comments.kdl | 2 +- .../nested_multiline_block_comment.kdl | 2 +- .../newlines_in_block_comment.kdl | 2 +- .../test_cases/expected_kdl/node_false.kdl | 2 +- .../test_cases/expected_kdl/node_true.kdl | 2 +- .../test_cases/expected_kdl/null_arg.kdl | 2 +- .../expected_kdl/null_prefix_in_bare_id.kdl | 1 + .../expected_kdl/null_prefix_in_prop_key.kdl | 1 + .../test_cases/expected_kdl/null_prop.kdl | 2 +- .../test_cases/expected_kdl/octal.kdl | 2 +- .../expected_kdl/optional_child_semicolon.kdl | 5 + .../expected_kdl/parse_all_arg_types.kdl | 2 +- .../expected_kdl/prop_false_type.kdl | 2 +- .../test_cases/expected_kdl/prop_hex_type.kdl | 2 +- .../expected_kdl/prop_identifier_type.kdl | 1 + .../expected_kdl/prop_null_type.kdl | 2 +- .../expected_kdl/prop_raw_string_type.kdl | 2 +- .../expected_kdl/prop_string_type.kdl | 2 +- .../expected_kdl/prop_true_type.kdl | 2 +- .../test_cases/expected_kdl/prop_type.kdl | 2 +- .../question_mark_before_number.kdl | 1 + .../expected_kdl/quoted_prop_name.kdl | 2 +- .../expected_kdl/quoted_prop_type.kdl | 2 +- .../test_cases/expected_kdl/r_node.kdl | 2 +- .../test_cases/expected_kdl/raw_arg_type.kdl | 2 +- .../test_cases/expected_kdl/raw_prop_type.kdl | 2 +- .../expected_kdl/raw_string_arg.kdl | 5 +- .../expected_kdl/raw_string_newline.kdl | 2 +- .../expected_kdl/raw_string_prop.kdl | 5 +- .../test_cases/expected_kdl/repeated_arg.kdl | 2 +- .../test_cases/expected_kdl/same_args.kdl | 1 - .../test_cases/expected_kdl/single_arg.kdl | 2 +- .../test_cases/expected_kdl/single_prop.kdl | 2 +- .../slashdash_arg_after_newline_esc.kdl | 2 +- .../expected_kdl/slashdash_node_in_child.kdl | 3 +- .../expected_kdl/slashdash_prop.kdl | 2 +- .../expected_kdl/slashdash_repeated_prop.kdl | 1 + .../expected_kdl/space_after_arg_type.kdl | 1 + .../expected_kdl/space_after_node_type.kdl | 1 + .../expected_kdl/space_after_prop_type.kdl | 1 + .../expected_kdl/space_around_prop_marker.kdl | 1 + .../expected_kdl/space_in_arg_type.kdl | 1 + .../expected_kdl/space_in_node_type.kdl | 1 + .../expected_kdl/space_in_prop_type.kdl | 1 + .../test_cases/expected_kdl/string_arg.kdl | 2 +- .../string_escaped_literal_whitespace.kdl | 1 + .../test_cases/expected_kdl/string_prop.kdl | 2 +- .../expected_kdl/trailing_underscore_hex.kdl | 2 +- .../trailing_underscore_octal.kdl | 2 +- .../expected_kdl/true_prefix_in_bare_id.kdl | 1 + .../expected_kdl/true_prefix_in_prop_key.kdl | 1 + .../expected_kdl/underscore_before_number.kdl | 1 + .../expected_kdl/underscore_in_octal.kdl | 2 +- .../expected_kdl/unicode_equals_signs.kdl | 1 + .../unusual_bare_id_chars_in_quoted_id.kdl | 2 +- .../expected_kdl/unusual_chars_in_bare_id.kdl | 2 +- .../expected_kdl/vertical_tab_whitespace.kdl | 1 + .../test_cases/input/all_escapes.kdl | 2 +- .../test_cases/input/all_node_fields.kdl | 6 +- .../input/arg_and_prop_same_name.kdl | 2 +- .../resources/test_cases/input/arg_bare.kdl | 1 + .../test_cases/input/arg_false_type.kdl | 2 +- .../test_cases/input/arg_null_type.kdl | 2 +- .../test_cases/input/arg_raw_string_type.kdl | 2 +- .../test_cases/input/arg_string_type.kdl | 2 +- .../test_cases/input/arg_true_type.kdl | 2 +- .../resources/test_cases/input/arg_type.kdl | 2 +- .../test_cases/input/backslash_in_bare_id.kdl | 1 - .../resources/test_cases/input/bare_arg.kdl | 1 - .../resources/test_cases/input/bare_emoji.kdl | 2 +- .../test_cases/input/bare_ident_dot.kdl | 1 + .../test_cases/input/bare_ident_numeric.kdl | 1 + .../input/bare_ident_numeric_dot.kdl | 1 + .../input/bare_ident_numeric_sign.kdl | 1 + .../test_cases/input/bare_ident_sign.kdl | 1 + .../test_cases/input/bare_ident_sign_dot.kdl | 1 + .../test_cases/input/blank_prop_type.kdl | 2 +- .../test_cases/input/block_comment.kdl | 2 +- .../input/block_comment_after_node.kdl | 2 +- .../test_cases/input/bom_initial.kdl | 1 + .../resources/test_cases/input/bom_later.kdl | 1 + .../test_cases/input/boolean_arg.kdl | 2 +- .../test_cases/input/boolean_prop.kdl | 2 +- .../test_cases/input/brackets_in_bare_id.kdl | 2 +- .../test_cases/input/chevrons_in_bare_id.kdl | 2 +- .../test_cases/input/comma_in_bare_id.kdl | 2 +- .../input/comment_after_arg_type.kdl | 2 +- .../input/comment_after_node_type.kdl | 2 +- .../input/comment_after_prop_type.kdl | 2 +- .../test_cases/input/comment_and_newline.kdl | 2 + .../test_cases/input/comment_in_arg_type.kdl | 2 +- .../test_cases/input/comment_in_node_type.kdl | 2 +- .../test_cases/input/comment_in_prop_type.kdl | 2 +- .../test_cases/input/commented_arg.kdl | 2 +- .../test_cases/input/commented_child.kdl | 4 +- .../test_cases/input/commented_node.kdl | 1 + .../test_cases/input/commented_prop.kdl | 2 +- .../test_cases/input/crlf_between_nodes.kdl | 2 +- .../resources/test_cases/input/dash_dash.kdl | 2 +- src/test/resources/test_cases/input/emoji.kdl | 2 +- .../test_cases/input/empty_line_comment.kdl | 2 + .../test_cases/input/empty_prop_type.kdl | 2 +- .../test_cases/input/empty_quoted_node_id.kdl | 2 +- .../input/empty_quoted_prop_key.kdl | 2 +- .../test_cases/input/eof_after_escape.kdl | 1 + .../input/err_backslash_in_bare_id.kdl | 1 + .../test_cases/input/escaped_whitespace.kdl | 15 + .../resources/test_cases/input/escline.kdl | 2 +- .../test_cases/input/escline_comment_node.kdl | 3 - .../test_cases/input/escline_line_comment.kdl | 5 +- .../input/false_prefix_in_bare_id.kdl | 1 + .../input/false_prefix_in_prop_key.kdl | 1 + .../test_cases/input/false_prop_key.kdl | 1 + ...t_keyword_identifier_strings_error.kdl.kdl | 1 + .../input/floating_point_keywords.kdl | 1 + .../resources/test_cases/input/hash_in_id.kdl | 1 + .../test_cases/input/initial_slashdash.kdl | 2 + .../input/just_space_in_prop_type.kdl | 2 +- .../test_cases/input/multiline_comment.kdl | 2 +- .../test_cases/input/multiline_nodes.kdl | 4 +- .../test_cases/input/multiline_raw_string.kdl | 5 + .../input/multiline_raw_string_indented.kdl | 5 + ...ng_non_matching_prefix_character_error.kdl | 5 + ...string_non_matching_prefix_count_error.kdl | 5 + .../test_cases/input/multiline_string.kdl | 5 +- .../input/multiline_string_indented.kdl | 5 + ...ng_non_matching_prefix_character_error.kdl | 5 + ...string_non_matching_prefix_count_error.kdl | 5 + .../test_cases/input/nested_block_comment.kdl | 2 +- .../test_cases/input/nested_comments.kdl | 2 +- .../input/nested_multiline_block_comment.kdl | 3 +- .../input/newlines_in_block_comment.kdl | 2 +- .../test_cases/input/no_integer_digit.kdl | 1 + .../test_cases/input/no_solidus_escape.kdl | 1 + .../resources/test_cases/input/node_false.kdl | 2 +- .../resources/test_cases/input/node_true.kdl | 2 +- .../resources/test_cases/input/null_arg.kdl | 2 +- .../input/null_prefix_in_bare_id.kdl | 1 + .../input/null_prefix_in_prop_key.kdl | 1 + .../resources/test_cases/input/null_prop.kdl | 2 +- .../test_cases/input/null_prop_key.kdl | 1 + .../input/optional_child_semicolon.kdl | 1 + .../test_cases/input/parens_in_bare_id.kdl | 2 +- .../test_cases/input/parse_all_arg_types.kdl | 2 +- .../test_cases/input/prop_false_type.kdl | 2 +- .../test_cases/input/prop_identifier_type.kdl | 1 + .../test_cases/input/prop_null_type.kdl | 2 +- .../test_cases/input/prop_raw_string_type.kdl | 2 +- .../test_cases/input/prop_true_type.kdl | 2 +- .../resources/test_cases/input/prop_type.kdl | 2 +- .../input/question_mark_at_start_of_int.kdl | 1 - .../input/question_mark_before_number.kdl | 2 +- .../test_cases/input/quote_in_bare_id.kdl | 2 +- .../test_cases/input/quoted_prop_name.kdl | 2 +- .../test_cases/input/quoted_prop_type.kdl | 2 +- .../test_cases/input/raw_arg_type.kdl | 2 +- .../test_cases/input/raw_node_name.kdl | 2 +- .../test_cases/input/raw_prop_type.kdl | 2 +- .../test_cases/input/raw_string_arg.kdl | 5 +- .../test_cases/input/raw_string_backslash.kdl | 2 +- .../input/raw_string_hash_no_esc.kdl | 2 +- .../input/raw_string_just_backslash.kdl | 2 +- .../input/raw_string_just_quote.kdl | 2 +- .../input/raw_string_multiple_hash.kdl | 2 +- .../test_cases/input/raw_string_newline.kdl | 4 +- .../test_cases/input/raw_string_prop.kdl | 5 +- .../test_cases/input/raw_string_quote.kdl | 2 +- .../test_cases/input/repeated_arg.kdl | 2 +- .../resources/test_cases/input/same_args.kdl | 1 - .../resources/test_cases/input/single_arg.kdl | 2 +- .../test_cases/input/single_prop.kdl | 2 +- .../test_cases/input/slash_in_bare_id.kdl | 2 +- .../input/slashdash_arg_after_newline_esc.kdl | 2 +- .../slashdash_arg_before_newline_esc.kdl | 2 +- .../test_cases/input/slashdash_full_node.kdl | 5 +- .../test_cases/input/slashdash_prop.kdl | 2 +- .../input/slashdash_raw_prop_key.kdl | 2 +- .../input/slashdash_repeated_prop.kdl | 1 + .../input/space_after_prop_type.kdl | 2 +- .../input/space_around_prop_marker.kdl | 1 + .../test_cases/input/space_in_arg_type.kdl | 2 +- .../test_cases/input/space_in_prop_type.kdl | 2 +- .../input/square_bracket_in_bare_id.kdl | 2 +- .../string_escaped_literal_whitespace.kdl | 2 + .../input/true_prefix_in_bare_id.kdl | 1 + .../input/true_prefix_in_prop_key.kdl | 1 + .../test_cases/input/true_prop_key.kdl | 1 + .../input/unbalanced_raw_hashes.kdl | 2 +- .../input/underscore_at_start_of_int.kdl | 1 - .../test_cases/input/unicode_delete.kdl | 2 + .../test_cases/input/unicode_equals_signs.kdl | 4 + .../test_cases/input/unicode_fsi.kdl | 2 + .../test_cases/input/unicode_lre.kdl | 2 + .../test_cases/input/unicode_lri.kdl | 2 + .../test_cases/input/unicode_lrm.kdl | 2 + .../test_cases/input/unicode_lro.kdl | 2 + .../test_cases/input/unicode_pdf.kdl | 2 + .../test_cases/input/unicode_pdi.kdl | 2 + .../test_cases/input/unicode_rle.kdl | 2 + .../test_cases/input/unicode_rli.kdl | 2 + .../test_cases/input/unicode_rlm.kdl | 2 + .../test_cases/input/unicode_rlo.kdl | 2 + .../test_cases/input/unicode_under_0x20.kdl | 2 + .../unusual_bare_id_chars_in_quoted_id.kdl | 2 +- .../input/unusual_chars_in_bare_id.kdl | 2 +- .../input/vertical_tab_whitespace.kdl | 1 + 380 files changed, 4992 insertions(+), 7330 deletions(-) delete mode 100644 .github/workflows/release.yaml delete mode 100644 src/main/java/kdl/Fuzzer.java create mode 100644 src/main/java/kdl/KDLBoolean.java create mode 100644 src/main/java/kdl/KDLDocument.java create mode 100644 src/main/java/kdl/KDLNode.java create mode 100644 src/main/java/kdl/KDLNull.java create mode 100644 src/main/java/kdl/KDLNumber.java create mode 100644 src/main/java/kdl/KDLString.java create mode 100644 src/main/java/kdl/KDLValue.java create mode 100644 src/main/java/kdl/Properties.java create mode 100644 src/main/java/kdl/Property.java delete mode 100644 src/main/java/kdl/objects/KDLBoolean.java delete mode 100644 src/main/java/kdl/objects/KDLDocument.java delete mode 100644 src/main/java/kdl/objects/KDLNode.java delete mode 100644 src/main/java/kdl/objects/KDLNull.java delete mode 100644 src/main/java/kdl/objects/KDLNumber.java delete mode 100644 src/main/java/kdl/objects/KDLObject.java delete mode 100644 src/main/java/kdl/objects/KDLProperty.java delete mode 100644 src/main/java/kdl/objects/KDLString.java delete mode 100644 src/main/java/kdl/objects/KDLValue.java delete mode 100644 src/main/java/kdl/parse/CharClasses.java delete mode 100644 src/main/java/kdl/parse/KDLParseContext.java delete mode 100644 src/main/java/kdl/parse/KDLParserFacade.java create mode 100644 src/main/java/kdl/parse/error/ErrorUtils.java create mode 100644 src/main/java/kdl/parse/lexer/KDLReader.java create mode 100644 src/main/java/kdl/parse/lexer/Lexer.java create mode 100644 src/main/java/kdl/parse/lexer/Token.java create mode 100644 src/main/java/kdl/parse/lexer/token/Bom.java create mode 100644 src/main/java/kdl/parse/lexer/token/Boolean.java create mode 100644 src/main/java/kdl/parse/lexer/token/Brace.java create mode 100644 src/main/java/kdl/parse/lexer/token/EqualsSign.java create mode 100644 src/main/java/kdl/parse/lexer/token/Escline.java create mode 100644 src/main/java/kdl/parse/lexer/token/MultiLineComment.java create mode 100644 src/main/java/kdl/parse/lexer/token/Newline.java create mode 100644 src/main/java/kdl/parse/lexer/token/Null.java create mode 100644 src/main/java/kdl/parse/lexer/token/Number.java create mode 100644 src/main/java/kdl/parse/lexer/token/Parentheses.java create mode 100644 src/main/java/kdl/parse/lexer/token/Semicolon.java create mode 100644 src/main/java/kdl/parse/lexer/token/SingleLineComment.java create mode 100644 src/main/java/kdl/parse/lexer/token/Slashdash.java create mode 100644 src/main/java/kdl/parse/lexer/token/StringToken.java create mode 100644 src/main/java/kdl/parse/lexer/token/Whitespace.java create mode 100644 src/main/java/kdl/print/KDLPrintException.java create mode 100644 src/main/java/kdl/print/KDLPrinter.java create mode 100644 src/main/java/kdl/print/KDLPrinterContext.java delete mode 100644 src/main/java/kdl/print/PrintConfig.java delete mode 100644 src/main/java/kdl/print/PrintUtil.java create mode 100644 src/main/java/kdl/print/PrinterConfiguration.java delete mode 100644 src/main/java/kdl/search/GeneralSearch.java delete mode 100644 src/main/java/kdl/search/PathedSearch.java delete mode 100644 src/main/java/kdl/search/RootSearch.java delete mode 100644 src/main/java/kdl/search/Search.java delete mode 100644 src/main/java/kdl/search/mutation/AddMutation.java delete mode 100644 src/main/java/kdl/search/mutation/Mutation.java delete mode 100644 src/main/java/kdl/search/mutation/SetMutation.java delete mode 100644 src/main/java/kdl/search/mutation/SubtractMutation.java delete mode 100644 src/main/java/kdl/search/predicates/AnyContentPredicate.java delete mode 100644 src/main/java/kdl/search/predicates/ArgPredicate.java delete mode 100644 src/main/java/kdl/search/predicates/ChildPredicate.java delete mode 100644 src/main/java/kdl/search/predicates/ConjunctionPredicate.java delete mode 100644 src/main/java/kdl/search/predicates/DisjunctionPredicate.java delete mode 100644 src/main/java/kdl/search/predicates/EmptyContentPredicate.java delete mode 100644 src/main/java/kdl/search/predicates/NegatedPredicate.java delete mode 100644 src/main/java/kdl/search/predicates/NodeContentPredicate.java delete mode 100644 src/main/java/kdl/search/predicates/NodePredicate.java delete mode 100644 src/main/java/kdl/search/predicates/PositionalArgPredicate.java delete mode 100644 src/main/java/kdl/search/predicates/PropPredicate.java create mode 100644 src/main/java/module-info.java delete mode 100644 src/test/java/kdl/parse/ConsumeWhitespaceAndBlockCommentsTest.java delete mode 100644 src/test/java/kdl/parse/ConsumeWhitespaceAndLinespaceTest.java delete mode 100644 src/test/java/kdl/parse/GetEscapedTest.java delete mode 100644 src/test/java/kdl/parse/GetSlashActionTest.java delete mode 100644 src/test/java/kdl/parse/KDLParserTest.java delete mode 100644 src/test/java/kdl/parse/ParseArgOrPropTest.java delete mode 100644 src/test/java/kdl/parse/ParseBareIdentifierTest.java delete mode 100644 src/test/java/kdl/parse/ParseChildTest.java delete mode 100644 src/test/java/kdl/parse/ParseEscapedStringTest.java delete mode 100644 src/test/java/kdl/parse/ParseIdentifierTest.java delete mode 100644 src/test/java/kdl/parse/ParseNodeTest.java delete mode 100644 src/test/java/kdl/parse/ParseNumberTest.java delete mode 100644 src/test/java/kdl/parse/ParseRawStringTest.java delete mode 100644 src/test/java/kdl/parse/ParseValueTest.java delete mode 100644 src/test/java/kdl/parse/ParserTest.java create mode 100644 src/test/java/kdl/parse/lexer/KDLReaderTest.java create mode 100644 src/test/java/kdl/parse/lexer/LexerTest.java delete mode 100644 src/test/java/kdl/search/GeneralSearchTest.java delete mode 100644 src/test/java/kdl/search/NodeIDMatcher.java delete mode 100644 src/test/java/kdl/search/PathedSearchTest.java delete mode 100644 src/test/java/kdl/search/RootSearchTest.java delete mode 100644 src/test/java/kdl/search/mutation/AddMutationTest.java delete mode 100644 src/test/java/kdl/search/mutation/SetMutationTest.java delete mode 100644 src/test/java/kdl/search/mutation/SubtractMutationTest.java delete mode 100644 src/test/java/kdl/search/predicate/ArgPredicateTest.java delete mode 100644 src/test/java/kdl/search/predicate/ChildPredicateTest.java delete mode 100644 src/test/java/kdl/search/predicate/EmptyContentPredicateTest.java delete mode 100644 src/test/java/kdl/search/predicate/LogicalPredicatesTest.java delete mode 100644 src/test/java/kdl/search/predicate/NodePredicateTest.java delete mode 100644 src/test/java/kdl/search/predicate/PropPredicateTest.java create mode 100644 src/test/resources/test_cases/expected_kdl/arg_bare.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/bare_ident_dot.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/bare_ident_sign.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/bare_ident_sign_dot.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/bom_initial.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/chevrons_in_bare_id.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/comma_in_bare_id.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/comment_after_arg_type.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/comment_after_node_type.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/comment_after_prop_type.kdl rename src/test/resources/test_cases/expected_kdl/{escline_comment_node.kdl => comment_and_newline.kdl} (100%) create mode 100644 src/test/resources/test_cases/expected_kdl/comment_in_arg_type.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/comment_in_node_type.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/comment_in_prop_type.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/dash_dash.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/empty_line_comment.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/eof_after_escape.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/escaped_whitespace.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/false_prefix_in_bare_id.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/false_prefix_in_prop_key.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/floating_point_keywords.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/initial_slashdash.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/multiline_raw_string.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/multiline_raw_string_indented.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/multiline_string_indented.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/null_prefix_in_bare_id.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/null_prefix_in_prop_key.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/optional_child_semicolon.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/prop_identifier_type.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/question_mark_before_number.kdl delete mode 100644 src/test/resources/test_cases/expected_kdl/same_args.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/slashdash_repeated_prop.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/space_after_arg_type.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/space_after_node_type.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/space_after_prop_type.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/space_around_prop_marker.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/space_in_arg_type.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/space_in_node_type.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/space_in_prop_type.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/string_escaped_literal_whitespace.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/true_prefix_in_bare_id.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/true_prefix_in_prop_key.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/underscore_before_number.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/unicode_equals_signs.kdl create mode 100644 src/test/resources/test_cases/expected_kdl/vertical_tab_whitespace.kdl create mode 100644 src/test/resources/test_cases/input/arg_bare.kdl delete mode 100644 src/test/resources/test_cases/input/backslash_in_bare_id.kdl delete mode 100644 src/test/resources/test_cases/input/bare_arg.kdl create mode 100644 src/test/resources/test_cases/input/bare_ident_dot.kdl create mode 100644 src/test/resources/test_cases/input/bare_ident_numeric.kdl create mode 100644 src/test/resources/test_cases/input/bare_ident_numeric_dot.kdl create mode 100644 src/test/resources/test_cases/input/bare_ident_numeric_sign.kdl create mode 100644 src/test/resources/test_cases/input/bare_ident_sign.kdl create mode 100644 src/test/resources/test_cases/input/bare_ident_sign_dot.kdl create mode 100644 src/test/resources/test_cases/input/bom_initial.kdl create mode 100644 src/test/resources/test_cases/input/bom_later.kdl create mode 100644 src/test/resources/test_cases/input/comment_and_newline.kdl create mode 100644 src/test/resources/test_cases/input/empty_line_comment.kdl create mode 100644 src/test/resources/test_cases/input/eof_after_escape.kdl create mode 100644 src/test/resources/test_cases/input/err_backslash_in_bare_id.kdl create mode 100644 src/test/resources/test_cases/input/escaped_whitespace.kdl delete mode 100644 src/test/resources/test_cases/input/escline_comment_node.kdl create mode 100644 src/test/resources/test_cases/input/false_prefix_in_bare_id.kdl create mode 100644 src/test/resources/test_cases/input/false_prefix_in_prop_key.kdl create mode 100644 src/test/resources/test_cases/input/false_prop_key.kdl create mode 100644 src/test/resources/test_cases/input/floating_point_keyword_identifier_strings_error.kdl.kdl create mode 100644 src/test/resources/test_cases/input/floating_point_keywords.kdl create mode 100644 src/test/resources/test_cases/input/hash_in_id.kdl create mode 100644 src/test/resources/test_cases/input/initial_slashdash.kdl create mode 100644 src/test/resources/test_cases/input/multiline_raw_string.kdl create mode 100644 src/test/resources/test_cases/input/multiline_raw_string_indented.kdl create mode 100644 src/test/resources/test_cases/input/multiline_raw_string_non_matching_prefix_character_error.kdl create mode 100644 src/test/resources/test_cases/input/multiline_raw_string_non_matching_prefix_count_error.kdl create mode 100644 src/test/resources/test_cases/input/multiline_string_indented.kdl create mode 100644 src/test/resources/test_cases/input/multiline_string_non_matching_prefix_character_error.kdl create mode 100644 src/test/resources/test_cases/input/multiline_string_non_matching_prefix_count_error.kdl create mode 100644 src/test/resources/test_cases/input/no_integer_digit.kdl create mode 100644 src/test/resources/test_cases/input/no_solidus_escape.kdl create mode 100644 src/test/resources/test_cases/input/null_prefix_in_bare_id.kdl create mode 100644 src/test/resources/test_cases/input/null_prefix_in_prop_key.kdl create mode 100644 src/test/resources/test_cases/input/null_prop_key.kdl create mode 100644 src/test/resources/test_cases/input/optional_child_semicolon.kdl create mode 100644 src/test/resources/test_cases/input/prop_identifier_type.kdl delete mode 100644 src/test/resources/test_cases/input/question_mark_at_start_of_int.kdl delete mode 100644 src/test/resources/test_cases/input/same_args.kdl create mode 100644 src/test/resources/test_cases/input/slashdash_repeated_prop.kdl create mode 100644 src/test/resources/test_cases/input/space_around_prop_marker.kdl create mode 100644 src/test/resources/test_cases/input/string_escaped_literal_whitespace.kdl create mode 100644 src/test/resources/test_cases/input/true_prefix_in_bare_id.kdl create mode 100644 src/test/resources/test_cases/input/true_prefix_in_prop_key.kdl create mode 100644 src/test/resources/test_cases/input/true_prop_key.kdl delete mode 100644 src/test/resources/test_cases/input/underscore_at_start_of_int.kdl create mode 100644 src/test/resources/test_cases/input/unicode_delete.kdl create mode 100644 src/test/resources/test_cases/input/unicode_equals_signs.kdl create mode 100644 src/test/resources/test_cases/input/unicode_fsi.kdl create mode 100644 src/test/resources/test_cases/input/unicode_lre.kdl create mode 100644 src/test/resources/test_cases/input/unicode_lri.kdl create mode 100644 src/test/resources/test_cases/input/unicode_lrm.kdl create mode 100644 src/test/resources/test_cases/input/unicode_lro.kdl create mode 100644 src/test/resources/test_cases/input/unicode_pdf.kdl create mode 100644 src/test/resources/test_cases/input/unicode_pdi.kdl create mode 100644 src/test/resources/test_cases/input/unicode_rle.kdl create mode 100644 src/test/resources/test_cases/input/unicode_rli.kdl create mode 100644 src/test/resources/test_cases/input/unicode_rlm.kdl create mode 100644 src/test/resources/test_cases/input/unicode_rlo.kdl create mode 100644 src/test/resources/test_cases/input/unicode_under_0x20.kdl create mode 100644 src/test/resources/test_cases/input/vertical_tab_whitespace.kdl diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 58e703b..7e0b2fb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,24 +1,24 @@ -# This workflow will build a Java project with Gradle -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle - name: Gradle CI on: push: - branches: [ trunk ] + branches: + - trunk pull_request: - branches: [ trunk ] + branches: + - trunk jobs: build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 - with: - java-version: 1.8 - - name: Build with Gradle - run: gradle build + - uses: actions/checkout@v4 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + distribution: temurin + java-version: 11 + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + - name: Build with Gradle + run: ./gradlew build diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index 590a66e..0000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,38 +0,0 @@ -name: Publish release - -on: - release: - types: [published] - -jobs: - publish-release: - runs-on: ubuntu-latest - - steps: - - name: Checkout latest code - uses: actions/checkout@v2 - - - name: Set up JDK 8 - uses: actions/setup-java@v1 - with: - java-version: 8 - - - name: Setup build cache - uses: actions/cache@v2 - with: - path: ~/.gradle/caches - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: Publish artifact - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # The GITHUB_REF tag comes in the format 'refs/tags/xxx'. - # If we split on '/' and take the 3rd value, - # we can get the release name. - run: | - NEW_VERSION=$(echo "${GITHUB_REF}" | cut -d "/" -f3) - echo "New version: ${NEW_VERSION}" - echo "Github username: ${GITHUB_ACTOR}" - gradle -Pversion=${NEW_VERSION} publish diff --git a/LICENSE.md b/LICENSE.md index 9c88511..6cc26e1 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,5 @@ Copyright 2022 Hannah Kolbeck +Copyright 2024 Romain Delamare Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the @@ -12,4 +13,3 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/README.md b/README.md index 3707779..8c52acd 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,47 @@ -# KDL4j +# KDL4j v2 -A Java implementation of a parser for the [KDL Document Language](https://github.com/kdl-org/kdl). +A Java implementation of a parser for the [KDL Document Language](https://github.com/kdl-org/kdl). Supports KDL +version `2.0.0-draft.4`. + +This library targets Java 11 as a minimum version. ## Status ![Gradle CI](https://github.com/hkolbeck/kdl4j/workflows/Gradle%20CI/badge.svg) -This project is beta-quality. It's been extensively tested, but the spec it implements is still in flux. - ## Usage ### Parsing ```java -final KDLParser parser = new KDLParser(); - -final KDLDocument documentFromString = parser.parse("node_name \"arg\"") -// OR -final KDLDocument documentFromReader = parser.parse(new FileReader("some/file.kdl")) +import kdl.parse.KDLParser; + +// Parse from a String +var documentFromString = KDLParser.parse("node_name \"arg\""); +// Parse from an InputStream +var documentFromReader = KDLParser.parse(new ByteArrayInputStream(/* … */)); +// Parse from a file +var documentFromReader = KDLParser.parse(Paths.get("path", "to", "file")); ``` -`KDLDocument` objects, and all descendants of `KDLObject`, are immutable and threadsafe, though that is not true of their -`Builder` objects. If you need to make changes to a `KDLDocument`, use the `filter()` and `mutate()` functions explained below. - -### Searching and Mutating Documents - -Several utilities are provided for finding nodes in documents. Each presents the same interface, but the way they search -the document differs. There are three search types: - -* RootSearch - Searches entirely at the root, primarily used for mutations to the root as discussed below -* GeneralSearch - Searches for nodes anywhere in the tree matching a single, possibly compound, node predicate -* PathedSearch - Searches for nodes down a specified path. At each level a different node predicate can be specified - -Each provides four methods for searching or mutating documents: - -* `anyMatch(document)` - Returns true if any node matches the search, false otherwise -* `filter(document, trim)` - Removes all nodes from the tree not on a branch that matches the predicates of the search. if - `trim` is set, removes all their non-matching children -* `list(document, trim)` - Produces a new document with all matching nodes at the root. If `trim` is set, removes all - their non-matching children -* `mutate(document, mutation)` - Applies a provided `Mutation` to every matching node in the tree, depth first. - -There are 3 types of `Mutations` provided, and users may provide custom mutations. Provided are `AddMutation`, -`SubtractMutation`, and `SetMutation`. Each performs functions hinted at by the name. See individual javadocs for details. - ### Printing -By default, calling `document.toKDL()` or `document.writeKDL(writer)` will print the structure with: - -* 4 space indents -* No semicolons -* Printable ASCII characters which can be escaped, escaped -* Empty children printed -* `null` arguments and properties with `null` values printed -* `\n` (unicode `\u{0a}`) for newlines +The `KDLPrinter` class allows printing a KDL document to a `String`, a `Writer`, an `OutputStream` or to a file. By +default, it: + +- prints one character tabulation for each indentation level +- does not print node separators (`;`) +- does not print braces for nodes without children +- prints arguments and properties with null value +- uses `E` as the exponent character in decimal values -Any of these can be changed by creating a new PrintConfig object and passing it into the print method. See the javadocs -on PrintConfig for more information. +Any of these can be changed by creating a `PrintConfiguration` and passing it to the `KDLPrinter` constructor. ## Contributing Please read the Code of Conduct before opening any issues or pull requests. -Besides code fixes, the easiest way to contribute is by generating test cases. Check out -[the test cases directory](https://github.com/hkolbeck/kdl4j/tree/trunk/src/test/resources/test_cases) to see the existing ones. +Besides code fixes, the easiest way to contribute is by generating test cases. Check out +[the test cases directory](src/test/resources/test_cases) to see the +existing ones. See the README there for more details. diff --git a/build.gradle.kts b/build.gradle.kts index 7809e8b..bf2a358 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -37,6 +37,10 @@ java { } } +tasks.compileJava { + options.javaModuleVersion = provider { version as String } +} + tasks.test { useJUnitPlatform() finalizedBy(tasks.jacocoTestReport) @@ -55,6 +59,8 @@ tasks.jacocoTestReport { val mockitoVersion = "5.10.0" dependencies { + implementation("jakarta.annotation:jakarta.annotation-api:2.1.1") + testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") testRuntimeOnly("org.junit.platform:junit-platform-launcher") testImplementation("org.assertj:assertj-core:3.25.3") diff --git a/gradle.properties b/gradle.properties index 6204417..38ba3c3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=0.3.0 +version=2.0.0.beta-1 diff --git a/src/main/java/kdl/Fuzzer.java b/src/main/java/kdl/Fuzzer.java deleted file mode 100644 index 9447656..0000000 --- a/src/main/java/kdl/Fuzzer.java +++ /dev/null @@ -1,16 +0,0 @@ -package kdl; - -import kdl.parse.KDLParser; - -public class Fuzzer { - public static void main(String[] args) { - final KDLParser parser = new KDLParser(); - - try { - parser.parse(System.in); - } catch (Throwable t) { - t.printStackTrace(); - System.exit(1); - } - } -} diff --git a/src/main/java/kdl/KDLBoolean.java b/src/main/java/kdl/KDLBoolean.java new file mode 100644 index 0000000..4f04b30 --- /dev/null +++ b/src/main/java/kdl/KDLBoolean.java @@ -0,0 +1,42 @@ +package kdl; + +import jakarta.annotation.Nonnull; +import java.util.Objects; +import java.util.Optional; + +/** + * A KDL boolean value. + */ +public class KDLBoolean extends KDLValue { + /** + * Creates a new {@link KDLBoolean}. + * + * @param type the type of the value + * @param value the value + */ + public KDLBoolean(@Nonnull Optional type, boolean value) { + super(type); + this.value = value; + } + + @Nonnull + @Override + public Boolean getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (KDLBoolean) o; + return value == that.value && Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(value, type); + } + + private final boolean value; +} diff --git a/src/main/java/kdl/KDLDocument.java b/src/main/java/kdl/KDLDocument.java new file mode 100644 index 0000000..fb0cc3e --- /dev/null +++ b/src/main/java/kdl/KDLDocument.java @@ -0,0 +1,52 @@ +package kdl; + +import jakarta.annotation.Nonnull; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * A KDL document. + */ +public class KDLDocument { + /** + * Creates a new document + * + * @param nodes the nodes in the document + */ + public KDLDocument(@Nonnull List nodes) { + this.nodes = Collections.unmodifiableList(nodes); + } + + /** + * The nodes of the document. + * + * @return an immutable list containing the nodes of the document + */ + @Nonnull + public List getNodes() { + return nodes; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (KDLDocument) o; + return Objects.equals(nodes, that.nodes); + } + + @Override + public int hashCode() { + return Objects.hash(nodes); + } + + @Override + public String toString() { + return "KDLDocument[" + nodes.stream().map(KDLNode::toString).collect(Collectors.joining(", ")) + ']'; + } + + @Nonnull + private final List nodes; +} diff --git a/src/main/java/kdl/KDLNode.java b/src/main/java/kdl/KDLNode.java new file mode 100644 index 0000000..85fdad5 --- /dev/null +++ b/src/main/java/kdl/KDLNode.java @@ -0,0 +1,305 @@ +package kdl; + +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * A KDL node. + */ +public class KDLNode { + /** + * Creates a new KDL node. + * + * @param type the type of the node + * @param name the name of the node + * @param arguments the arguments of the node + * @param properties the properties of the node + * @param children the children of the node + */ + public KDLNode( + @Nonnull Optional type, + @Nonnull String name, + @Nonnull List> arguments, + @Nonnull Properties properties, + @Nonnull List children + ) { + this.type = type; + this.name = name; + this.arguments = Collections.unmodifiableList(arguments); + this.properties = properties; + this.children = Collections.unmodifiableList(children); + } + + /** + * @return the type of the node + */ + @Nonnull + public Optional getType() { + return type; + } + + /** + * @return the name of the node + */ + @Nonnull + public String getName() { + return name; + } + + /** + * @return the arguments of the node + */ + @Nonnull + public List> getArguments() { + return arguments; + } + + + /** + * @return the properties of the node + */ + @Nonnull + public Properties getProperties() { + return properties; + } + + /** + * Retrieves a property from the node. If a property is defined multiple times, the last value is returned. + * + * @param name the name of the property to retrieve + * @param the type of the property's value + * @return an option containing the last value of the property if it has any + */ + @Nonnull + public Optional> getProperty(String name) { + return properties.getValue(name); + } + + /** + * @return the children of the node + */ + @Nonnull + public List getChildren() { + return children; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var kdlNode = (KDLNode) o; + return Objects.equals(type, kdlNode.type) && Objects.equals(name, kdlNode.name) + && Objects.equals(arguments, kdlNode.arguments) && Objects.equals(properties, kdlNode.properties) + && Objects.equals(children, kdlNode.children); + } + + @Override + public int hashCode() { + return Objects.hash(type, name, arguments, properties, children); + } + + @Override + public String toString() { + var builder = new StringBuilder("KDLNode["); + type.ifPresent(type -> builder.append('(').append(type).append(')')); + builder.append(name); + arguments.forEach(argument -> builder.append(' ').append(argument)); + properties.forEach(property -> builder.append(' ').append(property.getName()).append('=').append(property.getValue())); + if (!children.isEmpty()) { + builder.append('[') + .append(children.stream().map(KDLNode::toString).collect(Collectors.joining(", "))) + .append(']'); + } + return builder.toString(); + } + + @Nonnull + private final Optional type; + @Nonnull + private final String name; + @Nonnull + private final List> arguments; + @Nonnull + private final Properties properties; + @Nonnull + private final List children; + + /** + * @return a new node builder + */ + @Nonnull + public static Builder builder() { + return new Builder(); + } + + /** + * A builder {@link KDLNode}. + */ + public static final class Builder { + /** + * Sets the name of the node. + * + * @param name the name of the node + * @return {@code this} + */ + @Nonnull + public Builder name(@Nonnull String name) { + this.name = name; + return this; + } + + /** + * Sets the type of the node. + * + * @param type the type of the node, or {@code null} if it has no type + * @return {@code this} + */ + @Nonnull + public Builder type(@Nullable String type) { + this.type = type; + return this; + } + + /** + * Adds an argument to the node. + * + * @param value the value of the argument + * @return {@code this} + */ + @Nonnull + public Builder argument(@Nullable Object value) { + arguments.add(KDLValue.from(value)); + return this; + } + + /** + * Adds a typed argument to the node. + * + * @param type the type of the argument + * @param value the value of the argument + * @return {@code this} + */ + @Nonnull + public Builder argument(@Nonnull String type, @Nullable Object value) { + arguments.add(KDLValue.from(Optional.of(type), value)); + return this; + } + + /** + * Adds a property to the node. + * + * @param name the name of the property + * @param value the value of the property + * @return {@code this} + */ + @Nonnull + public Builder property(@Nonnull String name, @Nullable Object value) { + properties.property(name, KDLValue.from(value)); + return this; + } + + /** + * Adds a typed property to the node. + * + * @param name the name of property + * @param type the type of the property + * @param value the value of the property + * @return {@code this} + */ + @Nonnull + public Builder property(@Nonnull String name, @Nonnull String type, @Nullable Object value) { + properties.property(name, KDLValue.from(Optional.of(type), value)); + return this; + } + + /** + * Adds a child to the node. + * + * @param node the child to add + * @return {@code this} + */ + @Nonnull + public Builder child(@Nonnull KDLNode node) { + children.add(node); + return this; + } + + /** + * Adds a child to the node. + * + * @param node a builder for the child to add + * @return {@code this} + */ + @Nonnull + public Builder child(@Nonnull Builder node) { + children.add(node.build()); + return this; + } + + /** + * Adds children to the node. + * + * @param nodes the children to add + * @return {@code this} + */ + @Nonnull + public Builder children(@Nonnull List nodes) { + children.addAll(nodes); + return this; + } + + /** + * Adds children to the node. + * + * @param nodes the children to add + * @return {@code this} + */ + @Nonnull + public Builder children(@Nonnull KDLNode... nodes) { + children.addAll(Arrays.asList(nodes)); + return this; + } + + /** + * Adds children to the node. + * + * @param nodes the builders of the children to add + * @return {@code this} + */ + @Nonnull + public Builder children(@Nonnull Builder... nodes) { + for (var node : nodes) { + children.add(node.build()); + } + return this; + } + + /** + * Builds the node. + * + * @return the built node + */ + @Nonnull + public KDLNode build() { + return new KDLNode(Optional.ofNullable(type), Objects.requireNonNull(name), arguments, properties.build(), children); + } + + @Nullable + private String type; + @Nullable + private String name; + @Nonnull + private final List> arguments = new ArrayList<>(); + @Nonnull + private final Properties.Builder properties = Properties.builder(); + @Nonnull + private final List children = new ArrayList<>(); + } +} diff --git a/src/main/java/kdl/KDLNull.java b/src/main/java/kdl/KDLNull.java new file mode 100644 index 0000000..c0ff865 --- /dev/null +++ b/src/main/java/kdl/KDLNull.java @@ -0,0 +1,37 @@ +package kdl; + +import jakarta.annotation.Nonnull; +import java.util.Objects; +import java.util.Optional; + +/** + * The KDL null value. + */ +public class KDLNull extends KDLValue { + /** + * Creates a new {@link KDLNull}. + * + * @param type the type of the value + */ + public KDLNull(@Nonnull Optional type) { + super(type); + } + + @Override + public Object getValue() { + return null; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (KDLNull) o; + return Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(type); + } +} diff --git a/src/main/java/kdl/KDLNumber.java b/src/main/java/kdl/KDLNumber.java new file mode 100644 index 0000000..f5d77bc --- /dev/null +++ b/src/main/java/kdl/KDLNumber.java @@ -0,0 +1,405 @@ +package kdl; + +import jakarta.annotation.Nonnull; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Objects; +import java.util.Optional; + +/** + * Supertype for all KDL number values. + * + * @param the inner representation of the number value + */ +public abstract class KDLNumber extends KDLValue { + KDLNumber(@Nonnull Optional type) { + super(type); + } + + /** + * @return the number converted to a byte + */ + public byte asByte() { + return getValue().byteValue(); + } + + /** + * @return the number converted to an int + */ + public int asInt() { + return getValue().intValue(); + } + + /** + * @return the number converted to a long + */ + public long asLong() { + return getValue().longValue(); + } + + /** + * @return the number converted to a {@link BigInteger} + */ + @Nonnull + public abstract BigInteger asBigInteger(); + + /** + * @return the number converted to a double + */ + public double asDouble() { + return getValue().longValue(); + } + + /** + * @return the number converted to a {@link BigDecimal} + */ + @Nonnull + public abstract BigDecimal asBigDecimal(); + + /** + * Creates a {@link KDLNumber} from a {@link Number}. + * + * @param number the number to represent + * @return the corresponding {@link KDLNumber} + */ + @Nonnull + public static KDLNumber from(@Nonnull Number number) { + return from(Optional.empty(), number); + } + + /** + * Creates a {@link KDLNumber} from a {@link Number}. + * + * @param type the type of the KDL number + * @param number the number to represent + * @return the corresponding {@link KDLNumber} + */ + @Nonnull + public static KDLNumber from(@Nonnull Optional type, @Nonnull Number number) { + if (number instanceof BigInteger) { + return new KDLNumber.Integer(type, (BigInteger) number); + } else if (number instanceof BigDecimal) { + return new KDLNumber.Decimal(type, (BigDecimal) number); + } else if (number instanceof Byte || number instanceof Short || number instanceof java.lang.Integer || number instanceof Long) { + return new KDLNumber.Integer(type, BigInteger.valueOf(number.longValue())); + } + return new KDLNumber.Decimal(type, new BigDecimal(number.toString())); + } + + /** + * The KDL Not-a-number value. + */ + public static final class NotANumber extends KDLNumber { + /** + * Creates a new {@link NotANumber}. + * + * @param type the type of the value + */ + public NotANumber(@Nonnull Optional type) { + super(type); + } + + @Override + public Double getValue() { + return Double.NaN; + } + + @Override + public byte asByte() { + throw new UnsupportedOperationException("Not a number cannot be converted to byte"); + } + + @Override + public int asInt() { + throw new UnsupportedOperationException("Not a number cannot be converted to int"); + } + + @Override + public long asLong() { + throw new UnsupportedOperationException("Not a number cannot be converted to long"); + } + + @Nonnull + @Override + public BigInteger asBigInteger() { + throw new UnsupportedOperationException("Not a number cannot be converted to BigInteger"); + } + + @Override + public double asDouble() { + return Double.NaN; + } + + @Nonnull + @Override + public BigDecimal asBigDecimal() { + throw new UnsupportedOperationException("Not a number cannot be converted to BigDecimal"); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (NotANumber) o; + return Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(type); + } + + @Override + public String toString() { + return "NaN"; + } + } + + /** + * The KDL positive infinity value. + */ + public static final class PositiveInfinity extends KDLNumber { + /** + * Creates a new {@link PositiveInfinity}. + * + * @param type the type of the value + */ + public PositiveInfinity(@Nonnull Optional type) { + super(type); + } + + @Override + public Double getValue() { + return Double.POSITIVE_INFINITY; + } + + @Override + public byte asByte() { + throw new UnsupportedOperationException("Positive infinity cannot be converted to byte"); + } + + @Override + public int asInt() { + throw new UnsupportedOperationException("Positive infinity cannot be converted to int"); + } + + @Override + public long asLong() { + throw new UnsupportedOperationException("Positive infinity cannot be converted to long"); + } + + @Nonnull + @Override + public BigInteger asBigInteger() { + throw new UnsupportedOperationException("Positive infinity cannot be converted to BigInteger"); + } + + @Override + public double asDouble() { + return Double.POSITIVE_INFINITY; + } + + @Nonnull + @Override + public BigDecimal asBigDecimal() { + throw new UnsupportedOperationException("Positive infinity cannot be converted to BigDecimal"); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (PositiveInfinity) o; + return Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(type); + } + + @Override + public String toString() { + return "+inf"; + } + } + + /** + * The negative infinity KDL value. + */ + public static final class NegativeInfinity extends KDLNumber { + /** + * Creates a new {@link NegativeInfinity}. + * + * @param type the type of the value + */ + public NegativeInfinity(@Nonnull Optional type) { + super(type); + } + + @Override + public Double getValue() { + return Double.NEGATIVE_INFINITY; + } + + @Override + public byte asByte() { + throw new UnsupportedOperationException("Negative infinity cannot be converted to byte"); + } + + @Override + public int asInt() { + throw new UnsupportedOperationException("Negative infinity cannot be converted to int"); + } + + @Override + public long asLong() { + throw new UnsupportedOperationException("Negative infinity cannot be converted to long"); + } + + @Nonnull + @Override + public BigInteger asBigInteger() { + throw new UnsupportedOperationException("Negative infinity cannot be converted to BigInteger"); + } + + @Override + public double asDouble() { + return Double.NEGATIVE_INFINITY; + } + + @Nonnull + @Override + public BigDecimal asBigDecimal() { + throw new UnsupportedOperationException("Negative infinity cannot be converted to BigDecimal"); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (NegativeInfinity) o; + return Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(type); + } + + @Override + public String toString() { + return "-inf"; + } + } + + /** + * A KDL number representing an integer. + */ + public static final class Integer extends KDLNumber { + /** + * Creates a new {@link Integer}. + * + * @param type the type of the value + * @param value the integer value + */ + public Integer(@Nonnull Optional type, @Nonnull BigInteger value) { + super(type); + this.value = value; + } + + @Override + @Nonnull + public BigInteger getValue() { + return value; + } + + @Nonnull + @Override + public BigInteger asBigInteger() { + return value; + } + + @Nonnull + @Override + public BigDecimal asBigDecimal() { + return new BigDecimal(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (Integer) o; + return Objects.equals(value, that.value) && Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(value, type); + } + + @Override + public String toString() { + return "Integer(" + value + ')'; + } + + @Nonnull + private final BigInteger value; + } + + /** + * A KDL number representing a decimal number. + */ + public static final class Decimal extends KDLNumber { + /** + * Creates a new {@link Decimal}. + * + * @param type the type of the value + * @param value the integer value + */ + public Decimal(@Nonnull Optional type, @Nonnull BigDecimal value) { + super(type); + this.value = value; + } + + @Override + @Nonnull + public BigDecimal getValue() { + return value; + } + + @Nonnull + @Override + public BigInteger asBigInteger() { + return value.toBigInteger(); + } + + @Nonnull + @Override + public BigDecimal asBigDecimal() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (Decimal) o; + return Objects.equals(value, that.value) && Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(value, type); + } + + @Override + public String toString() { + return "Decimal(" + value + ')'; + } + + @Nonnull + private final BigDecimal value; + } +} diff --git a/src/main/java/kdl/KDLString.java b/src/main/java/kdl/KDLString.java new file mode 100644 index 0000000..2dcdc5f --- /dev/null +++ b/src/main/java/kdl/KDLString.java @@ -0,0 +1,58 @@ +package kdl; + +import jakarta.annotation.Nonnull; +import java.util.Objects; +import java.util.Optional; + +/** + * A KDL string value. + */ +public class KDLString extends KDLValue { + /** + * Creates a new {@link KDLString}. + * + * @param value the value + */ + public KDLString(@Nonnull String value) { + super(Optional.empty()); + this.value = value; + } + + /** + * Creates a new {@link KDLString}. + * + * @param type the type of the value + * @param value the value + */ + public KDLString(@Nonnull Optional type, @Nonnull String value) { + super(type); + this.value = value; + } + + @Nonnull + @Override + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (KDLString) o; + return Objects.equals(value, that.value) && Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(value, type); + } + + @Override + public String toString() { + return "String(" + value + ')'; + } + + @Nonnull + private final String value; +} diff --git a/src/main/java/kdl/KDLValue.java b/src/main/java/kdl/KDLValue.java new file mode 100644 index 0000000..20b5361 --- /dev/null +++ b/src/main/java/kdl/KDLValue.java @@ -0,0 +1,98 @@ +package kdl; + +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.util.Optional; + +/** + * Supertype for all KDL values. + * + * @param the inner representation of the value. + */ +public abstract class KDLValue { + KDLValue(@Nonnull Optional type) { + this.type = type; + } + + /** + * @return the type of the value + */ + @Nonnull + public Optional getType() { + return type; + } + + /** + * @return the value + */ + public abstract T getValue(); + + /** + * @return whether this value is a KDL string + */ + public boolean isString() { + return false; + } + + /** + * @return whether this value is a KDL number + */ + public boolean isNumber() { + return false; + } + + /** + * @return whether this value is a KDL boolean + */ + public boolean isBoolean() { + return false; + } + + /** + * @return whether this value is a KDL null + */ + public boolean isNull() { + return false; + } + + @Nonnull + protected final Optional type; + + /** + * Creates a new KDL value from its representation. + * + * @param value the value to wrap in a {@link KDLValue} + * @return a corresponding {@link KDLValue} + */ + public static KDLValue from(Object value) { + if (value instanceof KDLValue) { + return (KDLValue) value; + } + return from(Optional.empty(), value); + } + + /** + * Creates a new KDL value from its representation. + * + * @param type the type of the value + * @param value the value to wrap in a {@link KDLValue} + * @return a corresponding {@link KDLValue} + */ + @Nonnull + public static KDLValue from(@Nonnull Optional type, @Nullable Object value) { + if (value == null) { + return new KDLNull(type); + } else if (value instanceof KDLValue) { + return (KDLValue) value; + } else if (value instanceof Boolean) { + return new KDLBoolean(type, (Boolean) value); + } else if (value instanceof Number) { + return KDLNumber.from(type, (Number) value); + } else if (value instanceof String) { + return new KDLString(type, (String) value); + } + + throw new IllegalArgumentException("Could not convert " + value + " to a KDL value"); + } + +} diff --git a/src/main/java/kdl/Properties.java b/src/main/java/kdl/Properties.java new file mode 100644 index 0000000..f499f67 --- /dev/null +++ b/src/main/java/kdl/Properties.java @@ -0,0 +1,104 @@ +package kdl; + +import jakarta.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * The properties of a {@link KDLNode}. + */ +public class Properties implements Iterable> { + private Properties(Map>> properties) { + this.properties = Collections.unmodifiableMap(properties); + } + + /** + * Retrieves the value of a property. If multiple values are defined for the property, the last one is returned. + * + * @param property the name of the property to retrieve. + * @param the type of the property's value + * @return an option containing the last value of the property if it has any + */ + @SuppressWarnings("unchecked") + @Nonnull + public Optional> getValue(@Nonnull String property) { + var values = properties.get(property); + return values == null || values.isEmpty() + ? Optional.empty() + : Optional.of((KDLValue) values.get(values.size() - 1)); + } + + @Nonnull + @Override + public Iterator> iterator() { + return new PropertiesIterator(); + } + + private final Map>> properties; + + /** + * @return a new properties builder. + */ + @Nonnull + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for {@link Properties}. + */ + public static final class Builder { + /** + * Adds a property. + * + * @param name the name of the property + * @param value the value of the property + * @return {@code this} + */ + @Nonnull + public Builder property(String name, KDLValue value) { + properties.compute(name, (k, values) -> { + if (values == null) { + values = new ArrayList<>(); + } + values.add(value); + return values; + }); + return this; + } + + /** + * Builds the properties + * + * @return the build properties + */ + @Nonnull + public Properties build() { + return new Properties(properties); + } + + private final Map>> properties = new HashMap<>(); + } + + private final class PropertiesIterator implements Iterator> { + @Override + public boolean hasNext() { + return names.hasNext(); + } + + @Override + public Property next() { + var name = names.next(); + var values = name == null ? null : properties.get(name); + return values == null ? null : new Property<>(name, values.get(values.size() - 1)); + } + + private final Iterator names = properties.keySet().stream().sorted().collect(Collectors.toList()).iterator(); + } +} diff --git a/src/main/java/kdl/Property.java b/src/main/java/kdl/Property.java new file mode 100644 index 0000000..ac74cea --- /dev/null +++ b/src/main/java/kdl/Property.java @@ -0,0 +1,25 @@ +package kdl; + +import jakarta.annotation.Nonnull; + +public class Property { + public Property(@Nonnull String name, @Nonnull KDLValue value) { + this.name = name; + this.value = value; + } + + @Nonnull + public String getName() { + return name; + } + + @Nonnull + public KDLValue getValue() { + return value; + } + + @Nonnull + private final String name; + @Nonnull + private final KDLValue value; +} diff --git a/src/main/java/kdl/objects/KDLBoolean.java b/src/main/java/kdl/objects/KDLBoolean.java deleted file mode 100644 index 9dd4a37..0000000 --- a/src/main/java/kdl/objects/KDLBoolean.java +++ /dev/null @@ -1,96 +0,0 @@ -package kdl.objects; - -import kdl.print.PrintConfig; - -import java.io.IOException; -import java.io.Writer; -import java.util.Objects; -import java.util.Optional; - -public class KDLBoolean extends KDLValue { - private final boolean value; - - public KDLBoolean(boolean value) { - this(value, Optional.empty()); - } - - public KDLBoolean(boolean value, Optional type) { - super(type); - this.value = value; - } - - public Boolean getValue() { - return value; - } - - @Override - public KDLString getAsString() { - return value ? new KDLString("true", type) : new KDLString("false", type); - } - - @Override - public Optional getAsNumber() { - return Optional.empty(); - } - - @Override - public Number getAsNumberOrElse(Number defaultValue) { - return defaultValue; - } - - @Override - public Optional getAsBoolean() { - return Optional.of(this); - } - - @Override - public boolean getAsBooleanOrElse(boolean defaultValue) { - return value; - } - - @Override - protected void writeKDLValue(Writer writer, PrintConfig printConfig) throws IOException { - writer.write(value ? "true" : "false"); - } - - @Override - protected String toKDLValue() { - return value ? "true" : "false"; - } - - @Override - public boolean isBoolean() { - return true; - } - - public static Optional fromString(String str, Optional type) { - if ("true".equals(str)) { - return Optional.of(new KDLBoolean(true, type)); - } else if ("false".equals(str)) { - return Optional.of(new KDLBoolean(false, type)); - } else { - return Optional.empty(); - } - } - - @Override - public String toString() { - return "KDLBoolean{" + - "value=" + value + - ", type=" + type + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof KDLBoolean)) return false; - KDLBoolean that = (KDLBoolean) o; - return value == that.value && type.equals(that.getType()); - } - - @Override - public int hashCode() { - return Objects.hash(type, value); - } -} diff --git a/src/main/java/kdl/objects/KDLDocument.java b/src/main/java/kdl/objects/KDLDocument.java deleted file mode 100644 index 6613de4..0000000 --- a/src/main/java/kdl/objects/KDLDocument.java +++ /dev/null @@ -1,156 +0,0 @@ -package kdl.objects; - -import kdl.print.PrintConfig; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; -import java.util.*; - -/** - * A model object representing a KDL Document. The only data in a document is the list of nodes, which may be empty. - */ -public class KDLDocument implements KDLObject { - private final List nodes; - - public KDLDocument(List nodes) { - this.nodes = Collections.unmodifiableList(Objects.requireNonNull(nodes)); - } - - public List getNodes() { - return nodes; - } - - @Override - public void writeKDL(Writer writer, PrintConfig printConfig) throws IOException { - writeKDLPretty(writer, printConfig); - } - - /** - * Writes a text representation of the document to the provided writer - * - * @param writer the writer to write to - * @param printConfig configuration controlling how the document is written - * @throws IOException if there's any error writing the document - */ - public void writeKDLPretty(Writer writer, PrintConfig printConfig) throws IOException { - writeKDL(writer, 0, printConfig); - } - - /** - * Write a text representation of the document to the provided writer with default 'pretty' settings - * - * @param writer the writer to write to - * @throws IOException if there's any error writing the document - */ - public void writeKDLPretty(Writer writer) throws IOException { - writeKDLPretty(writer, PrintConfig.PRETTY_DEFAULT); - } - - /** - * Get a string representation of the document - * - * @param printConfig configuration controlling how the document is written - * @return the string - */ - public String toKDLPretty(PrintConfig printConfig) { - final StringWriter writer = new StringWriter(); - final BufferedWriter bufferedWriter = new BufferedWriter(writer); - - try { - writeKDLPretty(bufferedWriter, printConfig); - bufferedWriter.flush(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - return writer.toString(); - } - - /** - * Get a string representation of the document with default 'pretty' settings - */ - public String toKDLPretty() { - return toKDLPretty(PrintConfig.PRETTY_DEFAULT); - } - - void writeKDL(Writer writer,int depth, PrintConfig printConfig) throws IOException { - if (nodes.isEmpty() && depth == 0) { - writer.write(printConfig.getNewline()); - return; - } - - for (KDLNode node : nodes) { - for (int i = 0; i < printConfig.getIndent() * depth; i++) { - writer.write(printConfig.getIndentChar()); - } - node.writeKDLPretty(writer, depth, printConfig); - if (printConfig.shouldRequireSemicolons()) { - writer.write(';'); - } - writer.write(printConfig.getNewline()); - } - } - - public Builder toBuilder() { - return KDLDocument.builder() - .addNodes(nodes); - } - - /** - * Get a document with no nodes - * - * @return the empty document - */ - public static KDLDocument empty() { - return new KDLDocument(new ArrayList<>()); - } - - /** - * Get a builder used to gradually build a document - * - * @return the builder - */ - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - final List nodes = new ArrayList<>(); - - public Builder addNode(KDLNode node) { - nodes.add(node); - return this; - } - - public Builder addNodes(Collection nodeCollection) { - nodes.addAll(nodeCollection); - return this; - } - - public KDLDocument build() { - return new KDLDocument(new ArrayList<>(nodes)); - } - } - - @Override - public String toString() { - return "KDLDocument{" + - "nodes=" + nodes + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof KDLDocument)) return false; - KDLDocument that = (KDLDocument) o; - return Objects.equals(nodes, that.nodes); - } - - @Override - public int hashCode() { - return Objects.hash(nodes); - } -} diff --git a/src/main/java/kdl/objects/KDLNode.java b/src/main/java/kdl/objects/KDLNode.java deleted file mode 100644 index 82c29b4..0000000 --- a/src/main/java/kdl/objects/KDLNode.java +++ /dev/null @@ -1,440 +0,0 @@ -package kdl.objects; - -import kdl.print.PrintConfig; -import kdl.print.PrintUtil; - -import java.io.IOException; -import java.io.Writer; -import java.math.BigDecimal; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Predicate; - -public class KDLNode implements KDLObject { - private final String identifier; - private final Optional type; - private final Map> props; - private final List> args; - private final Optional child; - - public KDLNode(String identifier, Optional type, Map> props, List> args, Optional child) { - this.identifier = Objects.requireNonNull(identifier); - this.type = type; - this.props = Collections.unmodifiableMap(Objects.requireNonNull(props)); - this.args = Collections.unmodifiableList(Objects.requireNonNull(args)); - this.child = Objects.requireNonNull(child); - } - - /** - * Get a builder used to gradually build a node - * - * @return the builder - */ - public static Builder builder() { - return new Builder(); - } - - public String getIdentifier() { - return identifier; - } - - public Optional getType() { - return type; - } - - public Map> getProps() { - return props; - } - - public List> getArgs() { - return args; - } - - public Optional getChild() { - return child; - } - - /** - * Writes a text representation of the node to the provided writer - * - * @param writer the writer to write to - * @param printConfig configuration controlling how the node is written - * @throws IOException if there's any error writing the node - */ - @Override - public void writeKDL(Writer writer, PrintConfig printConfig) throws IOException { - writeKDLPretty(writer, 0, printConfig); - } - - void writeKDLPretty(Writer writer, int depth, PrintConfig printConfig) throws IOException { - if (type.isPresent()) { - writer.write('('); - PrintUtil.writeStringQuotedAppropriately(writer, type.get(), true, printConfig); - writer.write(')'); - } - - PrintUtil.writeStringQuotedAppropriately(writer, identifier, true, printConfig); - if (!args.isEmpty() || !props.isEmpty() || child.isPresent()) { - writer.write(' '); - } - - for (int i = 0; i < this.args.size(); i++) { - final KDLValue value = this.args.get(i); - if (!(value instanceof KDLNull) || printConfig.shouldPrintNullArgs()) { - value.writeKDL(writer, printConfig); - if (i < this.args.size() - 1 || !props.isEmpty() || child.isPresent()) { - writer.write(' '); - } - } - } - - final ArrayList keys = new ArrayList<>(props.keySet()); - for (int i = 0; i < keys.size(); i++) { - final KDLValue value = props.get(keys.get(i)); - if (!(value instanceof KDLNull) || printConfig.shouldPrintNullProps()) { - PrintUtil.writeStringQuotedAppropriately(writer, keys.get(i), true, printConfig); - writer.write('='); - value.writeKDL(writer, printConfig); - if (i < keys.size() - 1 || child.isPresent()) { - writer.write(' '); - } - } - } - - if (child.isPresent()) { - if (!child.get().getNodes().isEmpty() || printConfig.shouldPrintEmptyChildren()) { - writer.write('{'); - writer.write(printConfig.getNewline()); - child.get().writeKDL(writer, depth + 1, printConfig); - for (int i = 0; i < printConfig.getIndent() * depth; i++) { - writer.write(printConfig.getIndentChar()); - } - writer.write('}'); - } - } - } - - /** - * Get a builder initialized with the contents of the current node - * - * @return the new builder - */ - public Builder toBuilder() { - return builder() - .setIdentifier(identifier) - .setType(type.orElse(null)) - .addAllArgs(args) - .addAllProps(props) - .setChild(child); - } - - @Override - public String toString() { - return "KDLNode{" + - "identifier=" + identifier + - ", type=" + type + - ", props=" + props + - ", args=" + args + - ", child=" + child + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof KDLNode)) return false; - KDLNode kdlNode = (KDLNode) o; - return Objects.equals(identifier, kdlNode.identifier) && Objects.equals(type, kdlNode.type) && Objects.equals(props, kdlNode.props) && Objects.equals(args, kdlNode.args) && Objects.equals(child, kdlNode.child); - } - - @Override - public int hashCode() { - return Objects.hash(identifier, type, props, args, child); - } - - public static class Builder { - private final List> args = new ArrayList<>(); - private final Map> props = new ConcurrentHashMap<>(); - - private String identifier = null; - private String type = null; - private Optional child = Optional.empty(); - - public Builder setIdentifier(String identifier) { - this.identifier = identifier; - return this; - } - - public Builder setType(String type) { - this.type = type; - return this; - } - - public Builder setChild(KDLDocument child) { - this.child = Optional.of(child); - return this; - } - - public Builder setChild(Optional child) { - this.child = child; - return this; - } - - public Builder addArg(KDLValue value) { - args.add(value); - return this; - } - - public Builder insertArgAt(int position, KDLValue value) { - if (position < args.size()) { - args.add(position, value); - } else { - while (args.size() < position - 1) { - args.add(new KDLNull()); - } - args.add(value); - } - return this; - } - - public Builder removeArgIf(Predicate> argPredicate) { - args.removeIf(argPredicate); - return this; - } - - public Builder removeArg(KDLValue arg) { - while (args.remove(arg)) ; - return this; - } - - public Builder removePropIf(Predicate keyPredicate) { - for (String key : props.keySet()) { - if (keyPredicate.test(key)) { - props.remove(key); - } - } - return this; - } - - public Builder removeProp(String key) { - props.remove(key); - return this; - } - - public Builder addArg(String strValue) { - return addArg(strValue, Optional.empty()); - } - - public Builder addArg(String strValue, Optional type) { - final KDLValue value; - if (strValue == null) { - value = new KDLNull(type); - } else { - value = new KDLString(strValue, type); - } - - args.add(value); - return this; - } - - public Builder addArg(BigDecimal bdValue) { - return addArg(bdValue, Optional.empty()); - } - - public Builder addArg(BigDecimal bdValue, Optional type) { - return addArg(bdValue, 10, type); - } - - public Builder addArg(BigDecimal bdValue, int radix, Optional type) { - final KDLValue value; - if (bdValue == null) { - value = new KDLNull(type); - } else { - value = new KDLNumber(bdValue, radix, type); - } - - args.add(value); - return this; - } - - public Builder addArg(long val) { - return addArg(val, 10); - } - - public Builder addArg(long val, int radix) { - return addArg(val, radix, Optional.empty()); - } - - public Builder addArg(long val, int radix, Optional type) { - args.add(new KDLNumber(new BigDecimal(val), radix, type)); - return this; - } - - public Builder addArg(double val) { - return addArg(val, 10); - } - - public Builder addArg(double val, int radix) { - return addArg(val, radix, Optional.empty()); - } - - public Builder addArg(double val, int radix, Optional type) { - args.add(new KDLNumber(new BigDecimal(val), radix, type)); - return this; - } - - public Builder addNullArg() { - return addNullArg(Optional.empty()); - } - - public Builder addNullArg(Optional type) { - args.add(new KDLNull(type)); - return this; - } - - public Builder addArg(boolean val) { - return addArg(val, Optional.empty()); - } - - public Builder addArg(boolean val, Optional type) { - args.add(new KDLBoolean(val, type)); - return this; - } - - public Builder addAllArgs(List> args) { - this.args.addAll(args); - return this; - } - - public Builder addProp(String key, KDLValue value) { - if (value == null) { - value = new KDLNull(); - } - - props.put(key, value); - return this; - } - - public Builder addProp(String key, String strValue) { - return addProp(key, strValue, Optional.empty()); - } - - public Builder addProp(String key, String strValue, Optional type) { - final KDLValue value; - if (strValue == null) { - value = new KDLNull(type); - } else { - value = new KDLString(strValue, type); - } - - props.put(key, value); - return this; - } - - public Builder addProp(String key, BigDecimal bdValue) { - return addProp(key, bdValue, Optional.empty()); - } - - public Builder addProp(String key, BigDecimal bdValue, Optional type) { - final KDLValue value; - if (bdValue == null) { - value = new KDLNull(type); - } else { - value = new KDLNumber(bdValue, 10, type); - } - - props.put(key, value); - return this; - } - - public Builder addProp(String key, BigDecimal bdValue, int radix) { - return addProp(key, bdValue, radix, Optional.empty()); - } - - public Builder addProp(String key, BigDecimal bdValue, int radix, Optional type) { - final KDLValue value; - if (bdValue == null) { - value = new KDLNull(type); - } else { - value = new KDLNumber(bdValue, radix, type); - } - - props.put(key, value); - return this; - } - - public Builder addProp(String key, int val) { - return addProp(key, val, Optional.empty()); - } - - public Builder addProp(String key, int val, Optional type) { - return addProp(key, val, 10, type); - } - - public Builder addProp(String key, int val, int radix) { - return addProp(key, val, radix, Optional.empty()); - } - - public Builder addProp(String key, int val, int radix, Optional type) { - props.put(key, new KDLNumber(new BigDecimal(val), radix, type)); - return this; - } - - public Builder addProp(String key, double val) { - return addProp(key, val, Optional.empty()); - } - - public Builder addProp(String key, double val, Optional type) { - return addProp(key, val, 10, type); - } - - public Builder addProp(String key, double val, int radix) { - return addProp(key, val, radix, Optional.empty()); - } - - public Builder addProp(String key, double val, int radix, Optional type) { - props.put(key, new KDLNumber(new BigDecimal(val), radix, type)); - return this; - } - - public Builder addProp(String key, boolean val) { - return addProp(key, val, Optional.empty()); - } - - public Builder addProp(String key, boolean val, Optional type) { - props.put(key, new KDLBoolean(val, type)); - return this; - } - - public Builder addProp(KDLProperty prop) { - props.put(prop.getKey(), prop.getValue()); - return this; - } - - public Builder addAllProps(Map> props) { - this.props.putAll(props); - return this; - } - - public Builder addNullProp(String key) { - props.put(key, new KDLNull()); - return this; - } - - public Builder clearArgs() { - args.clear(); - return this; - } - - public Builder clearProps() { - props.clear(); - return this; - } - - public KDLNode build() { - Objects.requireNonNull(identifier, "Identifier must be set"); - - return new KDLNode(identifier, Optional.ofNullable(type), new HashMap<>(props), new ArrayList<>(args), child); - } - } -} diff --git a/src/main/java/kdl/objects/KDLNull.java b/src/main/java/kdl/objects/KDLNull.java deleted file mode 100644 index 7e81eba..0000000 --- a/src/main/java/kdl/objects/KDLNull.java +++ /dev/null @@ -1,79 +0,0 @@ -package kdl.objects; - -import kdl.print.PrintConfig; - -import java.io.IOException; -import java.io.Writer; -import java.util.Objects; -import java.util.Optional; - -/** - * A model object representing the KDL 'null' value. - */ -public class KDLNull extends KDLValue { - public KDLNull() { - this(Optional.empty()); - } - - public KDLNull(Optional type) { - super(type); - } - - @Override - public Void getValue() { - return null; - } - - @Override - public KDLString getAsString() { - return new KDLString("null", type); - } - - @Override - public Optional getAsNumber() { - return Optional.empty(); - } - - @Override - public Number getAsNumberOrElse(Number defaultValue) { - return defaultValue; - } - - @Override - public Optional getAsBoolean() { - return Optional.empty(); - } - - @Override - public boolean getAsBooleanOrElse(boolean defaultValue) { - return defaultValue; - } - - @Override - protected void writeKDLValue(Writer writer, PrintConfig printConfig) throws IOException { - writer.write("null"); - } - - @Override - protected String toKDLValue() { - return "null"; - } - - @Override - public boolean isNull() { - return true; - } - - public String toString() { - return this.getClass().getSimpleName(); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof KDLNull && Objects.equals(type, ((KDLNull) obj).type); - } - - public int hashCode() { - return Objects.hash(type); - } -} diff --git a/src/main/java/kdl/objects/KDLNumber.java b/src/main/java/kdl/objects/KDLNumber.java deleted file mode 100644 index 16cfdc6..0000000 --- a/src/main/java/kdl/objects/KDLNumber.java +++ /dev/null @@ -1,234 +0,0 @@ -package kdl.objects; - -import kdl.parse.KDLParser; -import kdl.print.PrintConfig; - -import java.io.IOException; -import java.io.Writer; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.Objects; -import java.util.Optional; - -/** - * Representation of a KDL number. Numbers may be base 16, 10, 8, or 2 as stored in the radix field. Base 10 numbers may - * be fractional, but all others are limited to integers. {@link KDLParser} reads in all numbers as - * either {@link BigDecimal} (for decimal) or {@link BigInteger} (for non-decimal), so if you absolutely need - * that precision then don't hesitate to instanceof and cast. - */ -public class KDLNumber extends KDLValue { - private final Number value; - private final int radix; - - public KDLNumber(Number value, int radix) { - this(value, radix, Optional.empty()); - } - - public KDLNumber(Number value, int radix, Optional type) { - super(type); - this.value = Objects.requireNonNull(value); - this.radix = radix; - } - - @Override - public Number getValue() { - return value; - } - - @Override - public boolean isNumber() { - return true; - } - - @Override - public KDLString getAsString() { - return KDLString.from(value.toString(), type); - } - - @Override - public Optional getAsNumber() { - return Optional.of(this); - } - - @Override - public Number getAsNumberOrElse(Number defaultValue) { - return value; - } - - @Override - public Optional getAsBoolean() { - return Optional.empty(); - } - - @Override - public boolean getAsBooleanOrElse(boolean defaultValue) { - return defaultValue; - } - - @Override - protected void writeKDLValue(Writer writer, PrintConfig printConfig) throws IOException { - if (printConfig.shouldRespectRadix()) { - /* - Print out a number while respecting radix! - If it's binary, octal, or hex, this converts it to a `BigInteger` to format. - This is kludge-y, but someone *might* for some reason want to have a more-than-64-bit number in KDL. - I have no idea *why* you'd want that, but who am I to judge? - Plus one of our test cases uses an 80-bit hex number so let's just roll with it lol - ~LemmaEOF - */ - switch (radix) { - case 10: - writer.write(value.toString().replace('E', printConfig.getExponentChar())); - break; - case 2: - writer.write("0b"); - writer.write(new BigInteger(value.toString()).toString(radix)); - break; - case 8: - writer.write("0o"); - writer.write(new BigInteger(value.toString()).toString(radix)); - break; - case 16: - writer.write("0x"); - writer.write(new BigInteger(value.toString()).toString(radix)); - break; - } - } else { - writer.write(value.toString().replace('E', printConfig.getExponentChar())); - } - } - - @Override - protected String toKDLValue() { - return value.toString(); - } - - /** - * Get the Zero value for a given radix, which must be one of [2, 8, 10, 16] - * - * @return a new number with the value 0 and the given radix - */ - public static KDLNumber zero(int radix) { - return zero(radix, Optional.empty()); - } - public static KDLNumber zero(int radix, Optional type) { - switch (radix) { - case 2: - case 8: - case 10: - case 16: - return new KDLNumber(BigDecimal.ZERO, radix, type); - default: - throw new RuntimeException("Radix must be one of: [2, 8, 10, 16]"); - } - } - - public static KDLNumber from(Number val, int radix) { - return from(val, radix, Optional.empty()); - } - - public static KDLNumber from(Number val, int radix, Optional type) { - return new KDLNumber(val, radix, type); - } - - public static KDLNumber from(Number val) { - return from(val, Optional.empty()); - } - - public static KDLNumber from(Number val, Optional type) { - return from(val, 10, type); - } - - public static Optional from(String val, int radix) { - return from(val, radix, Optional.empty()); - } - - public static Optional from(String val, int radix, Optional type) { - return from(val, type).filter(v -> v.radix == radix); - } - - /** - * Parse the provided string into a KDLNumber if possible. - * - * @return an optional wrapping the new KDLNumber if the parse was successful, or empty() if not - */ - public static Optional from(String val) { - return from(val, Optional.empty()); - } - - public static Optional from(String val, Optional type) { - if (val == null || val.length() == 0) { - return Optional.empty(); - } - - final int radix; - final String toParse; - if (val.charAt(0) == '0') { - if (val.length() == 1) { - return Optional.of(KDLNumber.zero(10, type)); - } - - switch (val.charAt(1)) { - case 'x': - radix = 16; - toParse = val.substring(2); - break; - case 'o': - radix = 8; - toParse = val.substring(2); - break; - case 'b': - radix = 2; - toParse = val.substring(2); - break; - default: - radix = 10; - toParse = val; - } - } else { - radix = 10; - toParse = val; - } - - BigDecimal parsed; - try { - if (radix == 10) { - parsed = new BigDecimal(toParse); - } else { - parsed = new BigDecimal(new BigInteger(toParse, radix)); - } - } catch (NumberFormatException e) { - parsed = null; - } - - return Optional.ofNullable(parsed).map(bd -> from(bd, radix, type)); - } - - @Override - public String toString() { - return "KDLNumber{" + - "value=" + value + - ", radix=" + radix + - ", type=" + type + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof KDLNumber)) return false; - KDLNumber kdlNumber = (KDLNumber) o; - /* - Oh, right. You technically can't compare two `Number`s. Thaaaanks, Oracle. - Ah, well. Just use `toString` and check if those are equal. - Numbers in Java all stringify in the same way, so this should work fine. - ~LemmaEOF - */ - return radix == kdlNumber.radix && Objects.equals(value.toString(), kdlNumber.value.toString()) && Objects.equals(type, kdlNumber.type); - } - - @Override - public int hashCode() { - return Objects.hash(value, radix, type); - } -} diff --git a/src/main/java/kdl/objects/KDLObject.java b/src/main/java/kdl/objects/KDLObject.java deleted file mode 100644 index e13c91d..0000000 --- a/src/main/java/kdl/objects/KDLObject.java +++ /dev/null @@ -1,42 +0,0 @@ -package kdl.objects; - -import kdl.print.PrintConfig; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; - -/** - * The base interface all objects parsed from a KDL document must implement - */ -public interface KDLObject { - - /** - * Write the object to the provided stream. - * - * @param writer the Writer to write to - * @param printConfig a configuration object controlling how the object is printed - * @throws IOException if there is any issue writing the object - */ - void writeKDL(Writer writer, PrintConfig printConfig) throws IOException; - - /** - * Generate a string with the text representation of the given object. - * - * @return the string - */ - default String toKDL() { - final StringWriter writer = new StringWriter(); - final BufferedWriter bufferedWriter = new BufferedWriter(writer); - - try { - this.writeKDL(bufferedWriter, PrintConfig.PRETTY_DEFAULT); - bufferedWriter.flush(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - return writer.toString(); - } -} diff --git a/src/main/java/kdl/objects/KDLProperty.java b/src/main/java/kdl/objects/KDLProperty.java deleted file mode 100644 index 55c4011..0000000 --- a/src/main/java/kdl/objects/KDLProperty.java +++ /dev/null @@ -1,61 +0,0 @@ -package kdl.objects; - -import kdl.print.PrintConfig; -import kdl.print.PrintUtil; - -import java.io.IOException; -import java.io.Writer; -import java.util.Objects; - -/** - * An object presenting a key=value pair in a KDL document. Only used during parsing. - */ -public class KDLProperty implements KDLObject { - private final String key; - private final KDLValue value; - - public KDLProperty(String key, KDLValue value) { - this.key = Objects.requireNonNull(key); - this.value = Objects.requireNonNull(value); - } - - public KDLValue getValue() { - return value; - } - - public String getKey() { - return key; - } - - @Override - public void writeKDL(Writer writer, PrintConfig printConfig) throws IOException { - if (value instanceof KDLNull && !printConfig.shouldPrintNullProps()) { - return; - } - - PrintUtil.writeStringQuotedAppropriately(writer, key, true, printConfig); - writer.write('='); - value.writeKDL(writer, printConfig); - } - - @Override - public String toString() { - return "KDLProperty{" + - "key=" + key + - ", value=" + value + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof KDLProperty)) return false; - KDLProperty that = (KDLProperty) o; - return Objects.equals(key, that.key) && Objects.equals(value, that.value); - } - - @Override - public int hashCode() { - return Objects.hash(key, value); - } -} diff --git a/src/main/java/kdl/objects/KDLString.java b/src/main/java/kdl/objects/KDLString.java deleted file mode 100644 index aa46619..0000000 --- a/src/main/java/kdl/objects/KDLString.java +++ /dev/null @@ -1,106 +0,0 @@ -package kdl.objects; - -import kdl.print.PrintConfig; -import kdl.print.PrintUtil; - -import java.io.IOException; -import java.io.Writer; -import java.util.Objects; -import java.util.Optional; - -/** - * A model object representing a string in a KDL document. Note that even if quoted, identifiers are not KDLStrings. - */ -public class KDLString extends KDLValue { - private final String value; - - public KDLString(String value) { - this(value, Optional.empty()); - } - - public KDLString(String value, Optional type) { - super(type); - this.value = Objects.requireNonNull(value); - } - - public String getValue() { - return value; - } - - @Override - public boolean isString() { - return true; - } - - @Override - public KDLString getAsString() { - return this; - } - - @Override - public Optional getAsNumber() { - return KDLNumber.from(value, type); - } - - @Override - public Number getAsNumberOrElse(Number defaultValue) { - return defaultValue; - } - - @Override - public Optional getAsBoolean() { - return KDLBoolean.fromString(value, type); - } - - @Override - public boolean getAsBooleanOrElse(boolean defaultValue) { - return defaultValue; - } - - @Override - protected void writeKDLValue(Writer writer, PrintConfig printConfig) throws IOException { - PrintUtil.writeStringQuotedAppropriately(writer, value, false, printConfig); - } - - @Override - protected String toKDLValue() { - return value; - } - - public static KDLString from(String val) { - return from(val, Optional.empty()); - } - - public static KDLString from(String val, Optional type) { - return new KDLString(val, type); - } - - public static KDLString empty() { - return empty(Optional.empty()); - } - - public static KDLString empty(Optional type) { - return new KDLString("", type); - } - - @Override - public String toString() { - return "KDLString{" + - "value='" + value + '\'' + - ", type=" + type + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof KDLString)) return false; - KDLString kdlString = (KDLString) o; - return Objects.equals(value, kdlString.value) && Objects.equals(type, kdlString.getType()); - } - - @Override - public int hashCode() { - return Objects.hash(value, type); - } -} diff --git a/src/main/java/kdl/objects/KDLValue.java b/src/main/java/kdl/objects/KDLValue.java deleted file mode 100644 index 92486a4..0000000 --- a/src/main/java/kdl/objects/KDLValue.java +++ /dev/null @@ -1,109 +0,0 @@ -package kdl.objects; - -import kdl.print.PrintConfig; -import kdl.print.PrintUtil; - -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.Optional; - -public abstract class KDLValue implements KDLObject { - protected final Optional type; - - public KDLValue(Optional type) { - this.type = type; - } - - public abstract T getValue(); - - public abstract KDLString getAsString(); - - public abstract Optional getAsNumber(); - - public abstract Number getAsNumberOrElse(Number defaultValue); - - public abstract Optional getAsBoolean(); - - public abstract boolean getAsBooleanOrElse(boolean defaultValue); - - protected abstract void writeKDLValue(Writer writer, PrintConfig printConfig) throws IOException; - - protected abstract String toKDLValue(); - - public final Optional getType() { - return type; - } - - public boolean isString() { - return false; - } - - public boolean isNumber() { - return false; - } - - public boolean isBoolean() { - return false; - } - - public boolean isNull() { - return false; - } - - public static KDLValue from(Object o) { - return from(o, Optional.empty()); - } - - public static KDLValue from(Object o, Optional type) { - if (o == null) return new KDLNull(type); - if (o instanceof Boolean) { - return new KDLBoolean((Boolean) o, type); - } - if (o instanceof BigInteger) { - return new KDLNumber(new BigDecimal((BigInteger)o), 10, type); - } - if (o instanceof BigDecimal) { - return new KDLNumber((BigDecimal)o, 10, type); - } - if (o instanceof Number) { - return new KDLNumber(new BigDecimal(o.toString()), 10, type); - } - if (o instanceof String) { - return new KDLString((String) o, type); - } - if (o instanceof KDLValue) return (KDLValue) o; - - throw new RuntimeException(String.format("No KDLValue for object '%s'", o)); - } - - @Override - public final void writeKDL(Writer writer, PrintConfig printConfig) throws IOException { - if (type.isPresent()) { - writer.write('('); - PrintUtil.writeStringQuotedAppropriately(writer, type.get(), true, printConfig); - writer.write(')'); - } - writeKDLValue(writer, printConfig); - } - - @Override - public final String toKDL() { - final StringWriter writer = new StringWriter(); - if (type.isPresent()) { - writer.write('('); - try { - PrintUtil.writeStringQuotedAppropriately(writer, type.get(), true, PrintConfig.PRETTY_DEFAULT); - } catch (IOException e) { - throw new RuntimeException(String.format("Failed to convert KDL value to KDL: '%s'", this), e); - } - writer.write(')'); - } - - writer.write(toKDLValue()); - - return writer.toString(); - } -} diff --git a/src/main/java/kdl/parse/CharClasses.java b/src/main/java/kdl/parse/CharClasses.java deleted file mode 100644 index 9735913..0000000 --- a/src/main/java/kdl/parse/CharClasses.java +++ /dev/null @@ -1,346 +0,0 @@ -package kdl.parse; - -import java.util.Optional; - -/** - * Various functions used during parsing and printing to check character membership in various character classes. - *

- * Also contains functions for transforming characters into their escape sequences. - */ -public class CharClasses { - - /** - * Check if the character is valid at the beginning of a numeric value - * - * @param c the character to check - * @return true if the character is valid, false otherwise - */ - public static boolean isValidNumericStart(int c) { - switch (c) { - case '+': - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - return true; - default: - return false; - } - } - - /** - * Check if the character is valid in a bare identifier after the first character - * - * @param c the character to check - * @return true if the character is valid, false otherwise - */ - public static boolean isValidBareIdChar(int c) { - if (c <= 0x20 || c > 0x10FFFF) { - return false; - } - - switch (c) { - case '\u0085': - case '\u2028': - case '\u2029': - case '\\': - case '/': - case '(': - case ')': - case '{': - case '}': - case '<': - case '>': - case ';': - case '[': - case ']': - case '=': - case ',': - case '"': - case '\u00A0': - case '\u1680': - case '\u2000': - case '\u2001': - case '\u2002': - case '\u2003': - case '\u2004': - case '\u2005': - case '\u2006': - case '\u2007': - case '\u2008': - case '\u2009': - case '\u200A': - case '\u202F': - case '\u205F': - case '\u3000': - case '\uFEFF': - return false; - default: - return true; - } - } - - /** - * Check if the character is valid in a bare identifier as the first character - * - * @param c the character to check - * @return true if the character is valid, false otherwise - */ - public static boolean isValidBareIdStart(int c) { - return !isValidDecimalChar(c) && isValidBareIdChar(c); - } - - /** - * Check if a string is a valid bare identifier - * - * @param string the string to check - * @return true if the string is a valid bare id, false otherwise - */ - public static boolean isValidBareId(String string) { - if (string.isEmpty()) { - return false; - } - - final boolean validBareIdStart = isValidBareIdStart(string.charAt(0)); - if (string.length() == 1 || !validBareIdStart) { - return validBareIdStart; - } - - for (int i = 0; i < string.length(); i++) { - if (!isValidBareIdChar(string.charAt(i))) { - return false; - } - } - - return true; - } - - /** - * Check if the character is a valid decimal digit - * - * @param c the character to check - * @return true if the character is valid, false otherwise - */ - public static boolean isValidDecimalChar(int c) { - return '0' <= c && c <= '9'; - } - - /** - * Check if the character is a valid hexadecimal digit - * - * @param c the character to check - * @return true if the character is valid, false otherwise - */ - public static boolean isValidHexChar(int c) { - switch (c) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case 'A': - case 'a': - case 'B': - case 'b': - case 'C': - case 'c': - case 'D': - case 'd': - case 'E': - case 'e': - case 'F': - case 'f': - return true; - default: - return false; - } - } - - /** - * Check if the character is a valid octal digit - * - * @param c the character to check - * @return true if the character is valid, false otherwise - */ - public static boolean isValidOctalChar(int c) { - return '0' <= c && c <= '7'; - } - - /** - * Check if the character is a valid binary digit - * - * @param c the character to check - * @return true if the character is valid, false otherwise - */ - public static boolean isValidBinaryChar(int c) { - return c == '0' || c == '1'; - } - - /** - * Check if the character is contained in one of the three literal values: true, false, and null - * - * @param c the character to check - * @return true if the character appears in a literal, false otherwise - */ - public static boolean isLiteralChar(int c) { - switch (c) { - case 't': - case 'r': - case 'u': - case 'e': - case 'n': - case 'l': - case 'f': - case 'a': - case 's': - return true; - default: - return false; - } - } - - /** - * Check if the character is a unicode newline of any kind - * - * @param c the character to check - * @return true if the character is a unicode newline, false otherwise - */ - public static boolean isUnicodeLinespace(int c) { - switch (c) { - case '\r': - case '\n': - case '\u0085': - case '\u000C': - case '\u2028': - case '\u2029': - return true; - default: - return false; - } - } - - /** - * Check if the character is unicode whitespace of any kind - * - * @param c the character to check - * @return true if the character is unicode whitespace, false otherwise - */ - public static boolean isUnicodeWhitespace(int c) { - switch (c) { - case '\u0009': - case '\u0020': - case '\u00A0': - case '\u1680': - case '\u2000': - case '\u2001': - case '\u2002': - case '\u2003': - case '\u2004': - case '\u2005': - case '\u2006': - case '\u2007': - case '\u2008': - case '\u2009': - case '\u200A': - case '\u202F': - case '\u205F': - case '\u3000': - return true; - default: - return false; - } - } - - /** - * Check if the character is an ASCII character that can be printed unescaped - * - * @param c the character to check - * @return true if the character is printable unescaped, false otherwise - */ - public static boolean isPrintableAscii(int c) { - return ' ' <= c && c <= '~'; - } - - public static boolean isNonAscii(int c) { - return c > 127; - } - - public static boolean mustEscape(int c) { - return c == '\\' || c == '"'; - } - - private static final Optional ESC_BACKSLASH = Optional.of("\\\\"); - private static final Optional ESC_BACKSPACE = Optional.of("\\b"); - private static final Optional ESC_NEWLINE = Optional.of("\\n"); - private static final Optional ESC_FORM_FEED = Optional.of("\\f"); - private static final Optional ESC_FORWARD_SLASH = Optional.of("\\/"); - private static final Optional ESC_TAB = Optional.of("\\t"); - private static final Optional ESC_CR = Optional.of("\\r"); - private static final Optional ESC_QUOTE = Optional.of("\\\""); - - /** - * Get the escape sequence for characters from the ASCII character set - * - * @param c the character to check - * @return An Optional wrapping the escape sequence string if the character needs to be escaped, or false otherwise - */ - public static Optional getCommonEscape(int c) { - switch (c) { - case '\\': - return ESC_BACKSLASH; - case '\b': - return ESC_BACKSPACE; - case '\n': - return ESC_NEWLINE; - case '\f': - return ESC_FORM_FEED; - case '/': - return ESC_FORWARD_SLASH; - case '\t': - return ESC_TAB; - case '\r': - return ESC_CR; - case '"': - return ESC_QUOTE; - default: - return Optional.empty(); - } - } - - public static boolean isCommonEscape(int c) { - switch (c) { - case '\\': - case '\b': - case '\n': - case '\f': - case '\t': - case '\r': - case '"': - return true; - default: - return false; - } - } - - /** - * Get the escape sequence for any character - * - * @param c the character to check - * @return The escape sequence string - */ - public static String getEscapeIncludingUnicode(int c) { - return getCommonEscape(c).orElseGet(() -> String.format("\\u{%x}", c)); - } -} diff --git a/src/main/java/kdl/parse/KDLInternalException.java b/src/main/java/kdl/parse/KDLInternalException.java index fdb5040..829179d 100644 --- a/src/main/java/kdl/parse/KDLInternalException.java +++ b/src/main/java/kdl/parse/KDLInternalException.java @@ -2,14 +2,11 @@ /** * Thrown if an unexpected state is encountered while parsing a document. If you encounter this - * please create an issue on https://github.com/hkolbeck/kdl4j/issues with the offending document + * please report an issue with the offending document. */ public class KDLInternalException extends RuntimeException { - public KDLInternalException(String message) { - super(message); - } + public KDLInternalException(String message) { + super(message); + } - public KDLInternalException(String message, Throwable cause) { - super(message, cause); - } } diff --git a/src/main/java/kdl/parse/KDLParseContext.java b/src/main/java/kdl/parse/KDLParseContext.java deleted file mode 100644 index f0fe5cd..0000000 --- a/src/main/java/kdl/parse/KDLParseContext.java +++ /dev/null @@ -1,151 +0,0 @@ -package kdl.parse; - -import java.io.IOException; -import java.io.PushbackReader; -import java.io.Reader; -import java.util.ArrayDeque; -import java.util.Deque; - -import static kdl.parse.KDLParser.EOF; - -/** - * Internal class wrapping the stream containing the document being read. Maintains a list of the last three lines read - * in order to provide context in the event of a parse error. - */ -public class KDLParseContext { - private final PushbackReader reader; - private final Deque lines; - - private int positionInLine; - private int lineNumber; - - private boolean invalidated; - - public KDLParseContext(Reader reader) { - this.lines = new ArrayDeque<>(); - lines.push(new StringBuilder()); - - this.reader = new PushbackReader(reader, 2); - this.positionInLine = 0; - this.lineNumber = 1; - this.invalidated = false; - } - - /** - * Read a character from the underlying stream. Stores it in a buffer as well for error reporting. - * - * @return the character read or EOF if the stream has been exhausted - * @throws IOException if any error is encountered in the stream read operation - */ - public int read() throws IOException { - if (invalidated) { - throw new KDLInternalException("Attempt to read from an invalidated context"); - } - - int c = reader.read(); - if (c == EOF) { - return c; - } else if (CharClasses.isUnicodeLinespace(c)) { - // We're cheating a bit here and not checking for CRLF - positionInLine = 0; - lineNumber++; - lines.push(new StringBuilder()); - while (lines.size() > 3) { - lines.removeLast(); - } - } else { - positionInLine++; - lines.peek().appendCodePoint(c); - } - - return c; - } - - /** - * Pushes a single character back into the stream. If this method and the peek() function are invoked more than - * two times without a read() in between an exception will be thrown. - * - * @param c the character to be pushed - */ - public void unread(int c) { - if (invalidated) { - throw new KDLInternalException("Attempt to unread from an invalidated context"); - } - - if (CharClasses.isUnicodeLinespace(c)) { - lines.pop(); - lineNumber--; - positionInLine = lines.peek().length() - 1; - } else if (c == EOF) { - throw new KDLInternalException("Attempted to unread() EOF"); - } else { - positionInLine--; - final StringBuilder currLine = lines.peek(); - currLine.deleteCharAt(currLine.length() - 1); - } - - try { - reader.unread(c); - } catch (IOException e) { - throw new KDLInternalException("Attempted to unread more than 2 characters in sequence", e); - } - } - - /** - * Gets the next character in the stream without consuming it. See unread() for a note on calling this function - * - * @return the next character in the stream - * @throws IOException if any error occurs reading from the stream - */ - public int peek() throws IOException { - if (invalidated) { - throw new KDLInternalException("Attempt to peek at an invalidated context"); - } - - int c = reader.read(); - if (c != -1) { - reader.unread(c); - } - - return c; - } - - /** - * For use following parse and internal errors for error reporting. Invalidates the context, after which any - * following operation on the context will fail. Reads the remainder of the current line and returns a string - * holding the current line followed by a pointer to the character where the context had read to prior to this call. - * - * @return the string outlined above - */ - public String getErrorLocationAndInvalidateContext() { - if (invalidated) { - throw new KDLInternalException("Attempted to getErrorLocationAndInvalidateContext from an invalid context"); - } - invalidated = true; - - final StringBuilder stringBuilder = new StringBuilder(); - final StringBuilder line = lines.peek(); - if (line == null) { - throw new KDLInternalException("Attempted to report an error, but there were no line objects in the stack"); - } - - try { - int c = reader.read(); - while (!CharClasses.isUnicodeLinespace(c) && c != EOF) { - line.appendCodePoint(c); - c = reader.read(); - } - } catch (IOException e) { - line.append(""); - } - - stringBuilder.append("Line ").append(lineNumber).append(":\n") - .append(line).append('\n'); - - for (int i = 0; i < positionInLine - 1; i++) { - stringBuilder.append('-'); - } - - return stringBuilder.append('^').toString(); - } -} diff --git a/src/main/java/kdl/parse/KDLParseException.java b/src/main/java/kdl/parse/KDLParseException.java index 584f5b6..e4cfe5e 100644 --- a/src/main/java/kdl/parse/KDLParseException.java +++ b/src/main/java/kdl/parse/KDLParseException.java @@ -5,11 +5,8 @@ * and character where the parse failure occurred. */ public class KDLParseException extends RuntimeException { - public KDLParseException(String message) { - super(message); - } + public KDLParseException(String message) { + super(message); + } - public KDLParseException(String message, Throwable cause) { - super(message, cause); - } } diff --git a/src/main/java/kdl/parse/KDLParser.java b/src/main/java/kdl/parse/KDLParser.java index 2434fef..e7e3a70 100644 --- a/src/main/java/kdl/parse/KDLParser.java +++ b/src/main/java/kdl/parse/KDLParser.java @@ -1,906 +1,466 @@ package kdl.parse; -import java.io.BufferedReader; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.StringReader; -import java.math.BigDecimal; -import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedList; import java.util.List; -import java.util.Map; +import java.util.Objects; import java.util.Optional; -import java.util.TreeMap; import java.util.function.Predicate; -import kdl.objects.KDLBoolean; -import kdl.objects.KDLDocument; -import kdl.objects.KDLNode; -import kdl.objects.KDLNull; -import kdl.objects.KDLNumber; -import kdl.objects.KDLObject; -import kdl.objects.KDLProperty; -import kdl.objects.KDLString; -import kdl.objects.KDLValue; +import kdl.KDLBoolean; +import kdl.KDLDocument; +import kdl.KDLNode; +import kdl.KDLNull; +import kdl.KDLString; +import kdl.KDLValue; +import kdl.Properties; +import kdl.parse.lexer.Lexer; +import kdl.parse.lexer.Token; +import kdl.parse.lexer.token.Bom; +import kdl.parse.lexer.token.Boolean; +import kdl.parse.lexer.token.EqualsSign; +import kdl.parse.lexer.token.Escline; +import kdl.parse.lexer.token.MultiLineComment; +import kdl.parse.lexer.token.Newline; +import kdl.parse.lexer.token.Null; +import kdl.parse.lexer.token.Number; +import kdl.parse.lexer.token.Semicolon; +import kdl.parse.lexer.token.SingleLineComment; +import kdl.parse.lexer.token.Slashdash; +import kdl.parse.lexer.token.StringToken; +import kdl.parse.lexer.token.Whitespace; + +import static kdl.parse.lexer.token.Brace.CLOSING_BRACE; +import static kdl.parse.lexer.token.Brace.OPENING_BRACE; +import static kdl.parse.lexer.token.Parentheses.CLOSING_PARENTHESES; +import static kdl.parse.lexer.token.Parentheses.OPENING_PARENTHESES; /** - * The core parser object. Instances are stateless and safe to share between threads. + * Entry point for parsing a KDL document. */ public class KDLParser { - public static final int EOF = -1; - public static final int MAX_UNICODE = 0x10FFFF; - - enum WhitespaceResult { - NO_WHITESPACE, - END_NODE, - SKIP_NEXT, - NODE_SPACE - } - - enum SlashAction { - END_NODE, - SKIP_NEXT, - NOTHING - } - - /** - * Parse the given stream into a KDLDocument model object. - * - * @param reader the stream reader to parse from - * @return the parsed document - * @throws IOException if any error occurs while reading the stream - * @throws KDLParseException if the document is invalid for any reason - */ - public KDLDocument parse(Reader reader) throws IOException { - final KDLParseContext context = new KDLParseContext(reader); - - try { - return parseDocument(context, true); - } catch (KDLParseException e) { - final String message = String.format("%s\n%s", e.getMessage(), context.getErrorLocationAndInvalidateContext()); - throw new KDLParseException(message, e); - } catch (IOException e) { - throw new IOException(context.getErrorLocationAndInvalidateContext(), e); - } catch (KDLInternalException e) { - throw new KDLInternalException(context.getErrorLocationAndInvalidateContext(), e); - } catch (Throwable t) { - throw new KDLInternalException(String.format("Unexpected exception:\n%s", context.getErrorLocationAndInvalidateContext()), t); - } - } - - /** - * Parse the given stream into a KDLDocument model object. - * - * @param stream the stream to parse from - * @return the parsed document - * @throws IOException if any error occurs while reading the stream - * @throws KDLParseException if the document is invalid for any reason - */ - public KDLDocument parse(InputStream stream) throws IOException { - return parse(new BufferedReader(new InputStreamReader(stream))); - } - - /** - * Parse the given string into a KDLDocument model object. - * - * @param string the string to parse - * @return the parsed document - * @throws KDLParseException if the document is invalid for any reason - */ - public KDLDocument parse(String string) { - final StringReader reader = new StringReader(string); - try { - return parse(reader); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - KDLDocument parseDocument(KDLParseContext context, boolean root) throws IOException { - int c = context.peek(); - if (c == EOF) { - return new KDLDocument(Collections.emptyList()); - } - - final ArrayList nodes = new ArrayList<>(); - while (true) { - boolean skippingNode = false; - switch (consumeWhitespaceAndLinespace(context)) { - case NODE_SPACE: - case NO_WHITESPACE: - break; - case END_NODE: - c = context.peek(); - if (c == EOF) { - break; - } else { - continue; - } - case SKIP_NEXT: - skippingNode = true; - break; - } - - c = context.peek(); - if (c == EOF) { - if (root) { - return new KDLDocument(nodes); - } else { - throw new KDLParseException("Got EOF, expected a node or '}'"); - } - } else if (c == '}') { - if (root) { - throw new KDLParseException("Unexpected '}' in root document"); - } else { - return new KDLDocument(nodes); - } - } - - final Optional node = parseNode(context); - consumeAfterNode(context); - if (!skippingNode && node.isPresent()) { - nodes.add(node.get()); - } - } - } - - Optional parseNode(KDLParseContext context) throws IOException { - final List> args = new ArrayList<>(); - final Map> properties = new TreeMap<>(); - Optional child = Optional.empty(); - - int c = context.peek(); - if (c == '}') { - return Optional.empty(); - } - final Optional type = parseTypeIfPresent(context); - final String identifier = parseIdentifier(context); - while (true) { - final WhitespaceResult whitespaceResult = consumeWhitespaceAndBlockComments(context); - c = context.peek(); - switch (whitespaceResult) { - case NODE_SPACE: - if (c == '{') { - child = Optional.of(parseChild(context)); - return Optional.of(new KDLNode(identifier, type, properties, args, child)); - } else if (CharClasses.isUnicodeLinespace(c)) { - return Optional.of(new KDLNode(identifier, type, properties, args, child)); - } if (c == EOF) { - return Optional.of(new KDLNode(identifier, type, properties, args, child)); - } else { - final KDLObject object = parseArgOrProp(context); - if (object instanceof KDLValue) { - args.add((KDLValue) object); - } else if (object instanceof KDLProperty) { - final KDLProperty property = (KDLProperty) object; - properties.put(property.getKey(), property.getValue()); - } else { - throw new KDLInternalException( - String.format("Unexpected type found, expected property, arg, or child: '%s' type: %s", - object.toKDL(), object.getClass().getSimpleName())); - } - } - break; - - case NO_WHITESPACE: - if (c == '{') { - child = Optional.of(parseChild(context)); - return Optional.of(new KDLNode(identifier, type, properties, args, child)); - } else if (CharClasses.isUnicodeLinespace(c) || c == EOF) { - return Optional.of(new KDLNode(identifier, type, properties, args, child)); - } else if (c == ';') { - context.read(); - return Optional.of(new KDLNode(identifier, type, properties, args, child)); - } else { - throw new KDLParseException(String.format("Unexpected character: '%s' (\\u%06X)", (char) c, c)); - } - case END_NODE: - return Optional.of(new KDLNode(identifier, type, properties, args, child)); - case SKIP_NEXT: - if (c == '{') { - parseChild(context); //Ignored - return Optional.of(new KDLNode(identifier, type, properties, args, child)); - } else if (CharClasses.isUnicodeLinespace(c)) { - throw new KDLParseException("Unexpected skip marker before newline"); - } else if ( c == EOF) { - throw new KDLParseException("Unexpected EOF following skip marker"); - } else { - final KDLObject object = parseArgOrProp(context); - if (!(object instanceof KDLValue) && !(object instanceof KDLProperty)) { - throw new KDLInternalException( - String.format("Unexpected type found, expected property, arg, or child: '%s' type: %s", - object.toKDL(), object.getClass().getSimpleName())); - } - } - break; - } - } - } - - String parseIdentifier(KDLParseContext context) throws IOException { - int c = context.peek(); - if (c == '"') { - return parseEscapedString(context); - } else if (CharClasses.isValidBareIdStart(c)) { - if (c == 'r') { - context.read(); - int next = context.peek(); - context.unread('r'); - if (next == '"' || next == '#') { - return parseRawString(context); - } else { - return parseBareIdentifier(context); - } - } else { - return parseBareIdentifier(context); - } - } else { - throw new KDLParseException(String.format("Expected an identifier, but identifiers can't start with '%s' (\\u%06X)", (char) c, c)); - } - } - - KDLObject parseArgOrProp(KDLParseContext context) throws IOException { - final KDLObject object; - final Optional type = parseTypeIfPresent(context); - boolean isBare = false; - int c = context.peek(); - if (c == '"') { - object = new KDLString(parseEscapedString(context), type); - } else if (c == '+' || c == '-') { - final int sign = c; - context.read(); - c = context.peek(); - context.unread(sign); - if (CharClasses.isValidDecimalChar(c)) { - object = parseNumber(context, type); - } else { - isBare = true; - object = new KDLString(parseBareIdentifier(context)); - } - } else if (CharClasses.isValidNumericStart(c)) { - object = parseNumber(context, type); - } else if (CharClasses.isValidBareIdStart(c)) { - String strVal; - if (c == 'r') { - context.read(); - int next = context.peek(); - context.unread('r'); - if (next == '"' || next == '#') { - strVal = parseRawString(context); - } else { - isBare = true; - strVal = parseBareIdentifier(context); - } - } else { - isBare = true; - strVal = parseBareIdentifier(context); - } - - if (isBare) { - if ("true".equals(strVal)) { - object = new KDLBoolean(true, type); - } else if ("false".equals(strVal)) { - object = new KDLBoolean(false, type); - } else if ("null".equals(strVal)) { - object = new KDLNull(type); - } else { - object = new KDLString(strVal, type); - } - } else { - object = new KDLString(strVal, type); - } - } else { - throw new KDLParseException(String.format("Unexpected character: '%s'", (char) c)); - } - - if (object instanceof KDLString) { - c = context.peek(); - if (c == '=') { - if (type.isPresent()) { - throw new KDLParseException("Illegal type annotation before property, annotations should " + - "follow the '=' and precede the value"); - } - - context.read(); - final KDLValue value = parseValue(context); - return new KDLProperty(((KDLString) object).getValue(), value); - } else if (isBare) { - throw new KDLParseException(String.format("Arguments may not be bare: '%s'", ((KDLString) object).getValue())); - } else { - return object; - } - } else { - return object; - } - } - - KDLDocument parseChild(KDLParseContext context) throws IOException { - int c = context.read(); - if (c != '{') { - throw new KDLInternalException(String.format("Expected '{' but found '%s'", (char) c)); - } - - final KDLDocument document = parseDocument(context, false); - - switch (consumeWhitespaceAndLinespace(context)) { - case END_NODE: - throw new KDLInternalException("Got unexpected END_NODE"); - case SKIP_NEXT: - throw new KDLParseException("Trailing skip markers are not allowed"); - default: - //Fall through - } - - c = context.read(); - if (c != '}') { - throw new KDLParseException("No closing brace found for child"); - } - - return document; - } - - Optional parseTypeIfPresent(KDLParseContext context) throws IOException { - Optional type = Optional.empty(); - int c = context.peek(); - if (c == '(') { - context.read(); - type = Optional.of(parseIdentifier(context)); - c = context.read(); - if (c != ')') { - throw new KDLParseException("Un-terminated type annotation, missing closing paren."); - } - } - - return type; - } - - KDLValue parseValue(KDLParseContext context) throws IOException { - final Optional type = parseTypeIfPresent(context); - int c = context.peek(); - if (c == '"') { - return new KDLString(parseEscapedString(context), type); - } else if (c == 'r') { - return new KDLString(parseRawString(context), type); - } else if (CharClasses.isValidNumericStart(c)) { - return parseNumber(context, type); - } else { - final StringBuilder stringBuilder = new StringBuilder(); - - while (CharClasses.isLiteralChar(c)) { - context.read(); - stringBuilder.appendCodePoint(c); - c = context.peek(); - } - - final String strVal = stringBuilder.toString(); - switch (strVal) { - case "true": - return new KDLBoolean(true, type); - case "false": - return new KDLBoolean(false, type); - case "null": - return new KDLNull(type); - default: - throw new KDLParseException(String.format("Unknown literal in property value: '%s' Expected 'true', 'false', or 'null'", strVal)); - } - } - } - - KDLNumber parseNumber(KDLParseContext context, Optional type) throws IOException { - final int radix; - Predicate legalChars = null; - - int c = context.peek(); - char sign = '+'; - if (c == '+') { - context.read(); - c = context.peek(); - } else if (c == '-') { - sign = '-'; - context.read(); - c = context.peek(); - } - - if (c == '0') { - context.read(); - c = context.peek(); - if (c == 'x') { - context.read(); - radix = 16; - legalChars = CharClasses::isValidHexChar; - } else if (c == 'o') { - context.read(); - radix = 8; - legalChars = CharClasses::isValidOctalChar; - } else if (c == 'b') { - context.read(); - radix = 2; - legalChars = CharClasses::isValidBinaryChar; - } else { - context.unread('0'); - radix = 10; - } - } else { - radix = 10; - } - - if (radix == 10) { - return parseDecimalNumber(context, sign, type); - } else { - return parseNonDecimalNumber(context, legalChars, sign, radix, type); - } - } - - KDLNumber parseNonDecimalNumber(KDLParseContext context, Predicate legalChars, char sign, int radix, Optional type) throws IOException { - final StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(sign); - - int c = context.peek(); - if (c == '_') { - throw new KDLParseException("The first character after radix indicator must not be '_'"); - } - - while (legalChars.test(c) || c == '_') { - context.read(); - if (c != '_') { - stringBuilder.appendCodePoint(c); - } - c = context.peek(); - } - - final String str = stringBuilder.toString(); - if (str.length() == 1) { //str is only the radix - throw new KDLParseException("Must include at least one digit following radix marker"); - } - - try { - return KDLNumber.from(new BigInteger(str, radix), radix, type); - } catch (NumberFormatException e) { - throw new KDLInternalException(String.format("Couldn't parse pre-vetted input '%s' into a BigDecimal", str), e); - } - } - - // Unfortunately, in order to match the grammar we have to do a lot of parsing ourselves here - KDLNumber parseDecimalNumber(KDLParseContext context, char sign, Optional type) throws IOException { - final StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(sign); - - boolean inFraction = false; - boolean inExponent = false; - boolean signLegal = false; - int exponentLen = 0; - int c = context.peek(); - if (c == '_' || c == 'E' || c == 'e') { - throw new KDLParseException(String.format("Decimal numbers may not begin with an '%s' character", (char) c)); - } else if (c == '+' || c == '-') { - throw new KDLParseException("Numbers may not begin with multiple sign characters"); - } - - c = context.peek(); - while (CharClasses.isValidDecimalChar(c) || c == 'e' || c == 'E' || c == '_' || c == '.' || c == '-' || c == '+') { - context.read(); - if (inExponent && c != '-' && c != '+') { - exponentLen++; - } - - if (c == '.') { - if (inFraction || inExponent) { - throw new KDLParseException("The '.' character is not allowed in the fraction or exponent of a decimal"); - } - - if (!CharClasses.isValidDecimalChar(context.peek())) { - throw new KDLParseException("The character following '.' in a decimal number must be a decimal digit"); - } - - inFraction = true; - signLegal = false; - stringBuilder.appendCodePoint(c); - } else if (c == 'e' || c == 'E') { - if (inExponent) { - throw new KDLParseException(String.format("Found '%s' in exponent", (char) c)); - } - - inExponent = true; - inFraction = false; - signLegal = true; - stringBuilder.appendCodePoint(c); - - if (context.peek() == '_') { - throw new KDLParseException("Character following exponent marker must not be '_'"); - } - } else if (c == '_') { - signLegal = false; - } else if (c == '+' || c == '-') { - if (!signLegal) { - throw new KDLParseException(String.format("The sign character '%s' is not allowed here", (char) c)); - } - - signLegal = false; - stringBuilder.appendCodePoint(c); - } else { - signLegal = false; - stringBuilder.appendCodePoint(c); - } - - c = context.peek(); - } - - final String val = stringBuilder.toString(); - - if (exponentLen > 10) { //BigDecimal only accepts exponents up to 10 digits - throw new KDLInternalException(String.format("Exponent too long to be represented as a BigDecimal: '%s'", val)); - } - - try { - return KDLNumber.from(new BigDecimal(val), type); - } catch (NumberFormatException e) { - throw new KDLInternalException(String.format("Couldn't parse pre-vetted input '%s' into a BigDecimal", val), e); - } - } - - String parseBareIdentifier(KDLParseContext context) throws IOException { - int c = context.read(); - if (!CharClasses.isValidBareIdStart(c)) { - throw new KDLParseException("Illegal character at start of bare identifier"); - } else if (c == EOF) { - throw new KDLInternalException("EOF when a bare identifier expected"); - } - - final StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.appendCodePoint(c); - - c = context.peek(); - while (CharClasses.isValidBareIdChar(c) && c != EOF) { - stringBuilder.appendCodePoint(context.read()); - c = context.peek(); - } - - return stringBuilder.toString(); - } - - String parseEscapedString(KDLParseContext context) throws IOException { - int c = context.read(); - if (c != '"') { - throw new KDLInternalException("No quote at the beginning of escaped string"); - } - - final StringBuilder stringBuilder = new StringBuilder(); - boolean inEscape = false; - while (true) { - c = context.read(); - if (!inEscape && c == '\\') { - inEscape = true; - } else if (c == '"' && !inEscape) { - return stringBuilder.toString(); - } else if (inEscape) { - stringBuilder.appendCodePoint(getEscaped(c, context)); - inEscape = false; - } else if (c == EOF) { - throw new KDLParseException("EOF while reading an escaped string"); - } else { - stringBuilder.appendCodePoint(c); - } - } - } - - int getEscaped(int c, KDLParseContext context) throws IOException { - switch (c) { - case 'n': - return '\n'; - case 'r': - return '\r'; - case 't': - return '\t'; - case '\\': - return '\\'; - case '/': - return '/'; - case '"': - return '\"'; - case 'b': - return '\b'; - case 'f': - return '\f'; - case 'u': { - final StringBuilder stringBuilder = new StringBuilder(6); - c = context.read(); - if (c != '{') { - throw new KDLParseException("Unicode escape sequences must be surround by {} brackets"); - } - - c = context.read(); - while (c != '}') { - if (c == EOF) { - throw new KDLParseException("Reached EOF while reading unicode escape sequence"); - } else if (!CharClasses.isValidHexChar(c)) { - throw new KDLParseException(String.format("Unicode escape sequences must be valid hex chars, got: '%s'", (char) c)); - } - - stringBuilder.appendCodePoint(c); - c = context.read(); - } - - final String strCode = stringBuilder.toString(); - if (strCode.isEmpty() || strCode.length() > 6) { - throw new KDLParseException(String.format("Unicode escape sequences must be between 1 and 6 characters in length. Got: '%s'", strCode)); - } - - final int code; - try { - code = Integer.parseInt(strCode, 16); - } catch (NumberFormatException e) { - throw new KDLParseException(String.format("Couldn't parse '%s' as a hex integer", strCode)); - } - - if (code < 0 || MAX_UNICODE < code) { - throw new KDLParseException(String.format("Unicode code point is outside allowed range [0, %x]: %x", MAX_UNICODE, code)); - } else { - return code; - } - } - default: - throw new KDLParseException(String.format("Illegal escape sequence: '\\%s'", (char) c)); - } - } - - String parseRawString(KDLParseContext context) throws IOException { - int c = context.read(); - if (c != 'r') { - throw new KDLInternalException("Raw string should start with 'r'"); - } - - int hashDepth = 0; - c = context.read(); - while (c == '#') { - hashDepth++; - c = context.read(); - } - - if (c != '"') { - throw new KDLParseException("Malformed raw string"); - } - - final StringBuilder stringBuilder = new StringBuilder(); - while (true) { - c = context.read(); - if (c == '"') { - StringBuilder subStringBuilder = new StringBuilder(); - subStringBuilder.append('"'); - int hashDepthHere = 0; - while (true) { - c = context.peek(); - if (c == '#') { - context.read(); - hashDepthHere++; - subStringBuilder.append('#'); - } else { - break; - } - } - - if (hashDepthHere < hashDepth) { - stringBuilder.append(subStringBuilder); - } else if (hashDepthHere == hashDepth) { - return stringBuilder.toString(); - } else { - throw new KDLParseException("Too many # characters when closing raw string"); - } - } else if (c == EOF) { - throw new KDLParseException("EOF while reading raw string"); - } else { - stringBuilder.appendCodePoint(c); - } - } - } - - SlashAction getSlashAction(KDLParseContext context, boolean escaped) throws IOException { - int c = context.read(); - if (c != '/') { - throw new KDLInternalException(String.format("Expected '/' but found '%s' (\\u%06x)", (char) c, c)); - } - - c = context.read(); - switch (c) { - case '-': - return SlashAction.SKIP_NEXT; - case '*': - consumeBlockComment(context); - return SlashAction.NOTHING; - case '/': - consumeLineComment(context); - if (escaped) { - return SlashAction.NOTHING; - } else { - return SlashAction.END_NODE; - } - default: - throw new KDLParseException(String.format("Unexpected character: '%s'", (char) c)); - } - } - - void consumeAfterNode(KDLParseContext context) throws IOException { - int c = context.peek(); - while (c == ';' || CharClasses.isUnicodeWhitespace(c)) { - context.read(); - c = context.peek(); - } - } - - WhitespaceResult consumeWhitespaceAndBlockComments(KDLParseContext context) throws IOException { - boolean skipping = false; - boolean foundWhitespace = false; - boolean inLineEscape = false; - boolean foundSemicolon = false; - int c = context.peek(); - while (c == '/' || c == '\\' || c == ';' || c == '\uFEFF' || CharClasses.isUnicodeWhitespace(c) || CharClasses.isUnicodeLinespace(c)) { - if (c == '/') { - switch (getSlashAction(context, inLineEscape)) { - case END_NODE: - if (inLineEscape) { - foundWhitespace = true; - inLineEscape = false; - break; - } - return WhitespaceResult.END_NODE; - - case SKIP_NEXT: - if (inLineEscape) { - throw new KDLParseException("Found skip marker after line escape"); - } - - if (skipping) { - throw new KDLParseException("Node/Token skip may only be specified once per node/token"); - } else { - skipping = true; - } - break; - - case NOTHING: - foundWhitespace = true; - break; - } - } else if (c == ';') { - context.read(); - foundSemicolon = true; - } else if (c == '\\') { - context.read(); - inLineEscape = true; - } else if (CharClasses.isUnicodeLinespace(c)) { - if (inLineEscape) { - context.read(); - if (c == '\r') { - c = context.peek(); - if (c == '\n') { - context.read(); - } - } - - inLineEscape = false; - foundWhitespace = true; - } else { - break; - } - } else { - context.read(); - foundWhitespace = true; - } - - c = context.peek(); - } - - if (skipping) { - return WhitespaceResult.SKIP_NEXT; - } else if (foundSemicolon) { - return WhitespaceResult.END_NODE; - } else if (foundWhitespace) { - return WhitespaceResult.NODE_SPACE; - } else { - return WhitespaceResult.NO_WHITESPACE; - } - } - - void consumeLineComment(KDLParseContext context) throws IOException { - int c = context.peek(); - while (!CharClasses.isUnicodeLinespace(c) && c != EOF) { - context.read(); - if (c == '\r') { - c = context.peek(); - if (c == '\n') { - context.read(); - } - return; - } - c = context.peek(); - } - } - - void consumeBlockComment(KDLParseContext context) throws IOException { - while (true) { - int c = context.read(); - while (c != '/' && c != '*' && c != EOF) { - c = context.read(); - } - - if (c == EOF) { - throw new KDLParseException("Got EOF while reading block comment"); - } - - if (c == '/') { - c = context.peek(); - if (c == '*') { - context.read(); - consumeBlockComment(context); - } - } else { // c == '*' - c = context.peek(); - if (c == '/') { - context.read(); - return; - } - } - } - } - - WhitespaceResult consumeWhitespaceAndLinespace(KDLParseContext context) throws IOException { - boolean skipNext = false; - boolean foundWhitespace = false; - boolean inEscape = false; - while (true) { - int c = context.peek(); - boolean isLinespace = CharClasses.isUnicodeLinespace(c); - while (CharClasses.isUnicodeWhitespace(c) || isLinespace || c == '\uFEFF') { - foundWhitespace = true; - if (isLinespace && skipNext && !inEscape) { - throw new KDLParseException("Unexpected newline after skip marker"); - } - - if (isLinespace && inEscape) { - inEscape = false; - } - - context.read(); - c = context.peek(); - isLinespace = CharClasses.isUnicodeLinespace(c); - } - - if (c == '/') { - switch (getSlashAction(context, inEscape)) { - case END_NODE: - case NOTHING: - foundWhitespace = true; - break; - case SKIP_NEXT: - foundWhitespace = true; - skipNext = true; - } - } else if (c == '\\') { - context.read(); - foundWhitespace = true; - inEscape = true; - } else if (c == EOF) { - if (skipNext) { - throw new KDLParseException("Unexpected EOF after skip marker"); - } else if (inEscape) { - throw new KDLParseException("Unexpected EOF after line escape"); - } else { - return WhitespaceResult.END_NODE; - } - } else { - if (inEscape) { - throw new KDLParseException("Expected newline or line comment following escape"); - } - - if (skipNext) { - return WhitespaceResult.SKIP_NEXT; - } else if (foundWhitespace) { - return WhitespaceResult.NODE_SPACE; - } else { - return WhitespaceResult.NO_WHITESPACE; - } - } - } - } + /** + * Parses the provided string as a {@link KDLDocument}. + * + * @param document a string representation of a valid KDL document + * @return a {@link KDLDocument} corresponding to {@code document} + * @throws IOException if an error occurs while reading the input + */ + @Nonnull + public static KDLDocument parse(@Nonnull String document) throws IOException { + return new KDLParser(new ByteArrayInputStream(document.getBytes(StandardCharsets.UTF_8))).parse(); + } + + /** + * Parses a file as a {@link KDLDocument}. + * + * @param path path to a file containing a valid KDL document + * @return a {@link KDLDocument} corresponding to {@code document} + * @throws IOException if an error occurs while reading the input + */ + @Nonnull + public static KDLDocument parse(@Nonnull Path path) throws IOException { + try (var fileInputStream = Files.newInputStream(path)) { + return new KDLParser(new BufferedInputStream(fileInputStream)).parse(); + } + } + + /** + * Parses the provided input stream as a {@link KDLDocument}. The input stream + * + * @param inputStream an input stream returning a valid KDL document + * @return a {@link KDLDocument} corresponding to {@code document} + * @throws IOException if an error occurs while reading the input + */ + @Nonnull + public static KDLDocument parse(@Nonnull InputStream inputStream) throws IOException { + return new KDLParser(inputStream).parse(); + } + + private KDLParser(InputStream inputStream) { + lexer = new Lexer(inputStream); + } + + @Nonnull + private KDLDocument parse() throws IOException { + return document(); + } + + @Nonnull + private KDLDocument document() throws IOException { + consumeByteOrderMark(); + return new KDLDocument(nodes(true)); + } + + private void consumeByteOrderMark() throws IOException { + parseToken(Bom.INSTANCE::equals); + } + + @Nonnull + private List nodes(boolean nodeTerminatorRequired) throws IOException { + var nodes = new ArrayList(); + + while (true) { + repeat(this::lineSpace); + var node = node(); + if (node == null) { + break; + } + + nodes.add(node.node); + + if (!node.hasNodeTerminator) { + if (nodeTerminatorRequired) { + throw new KDLParseException(lexer.error("node terminator expected")); + } + break; + } + } + + return nodes; + } + + private boolean plainLineSpace() throws IOException { + return parseToken(is(Newline.class), this::isWhiteSpace, is(SingleLineComment.class)); + } + + private boolean plainNodeSpace() throws IOException { + var hasAtLeastOneWhiteSpace = repeat(this::whitespace); + + var token = lexer.peek(); + if (token instanceof Escline) { + lexer.next(); + repeat(this::whitespace); + return true; + } + + return hasAtLeastOneWhiteSpace; + } + + private boolean lineSpace() throws IOException { + if (lexer.peek() instanceof Slashdash) { + lexer.next(); + repeat(this::plainNodeSpace); + var node = node(); + if (node == null) { + throw new KDLParseException(lexer.error("a node is required after a slashdash ('/-')")); + } else if (!node.hasNodeTerminator) { + throw new KDLParseException(lexer.error("node terminator expected")); + } + return true; + } + return repeat(this::plainLineSpace); + } + + private boolean nodeSpace() throws IOException { + if (!repeat(this::plainNodeSpace)) { + return false; + } + + if (lexer.peek() instanceof Slashdash) { + lexer.next(); + repeat(this::plainNodeSpace); + var token = lexer.peek(); + if (token == OPENING_BRACE) { + nodeChildren(); + } else { + nodePropOrArg(); + } + } + + return true; + } + + private void requiredNodeSpace() throws IOException { + repeat(this::nodeSpace); + plainNodeSpace(); + } + + private void optionalNodeSpace() throws IOException { + repeat(this::nodeSpace); + } + + @Nullable + private KDLNode baseNode() throws IOException { + var type = type(); + optionalNodeSpace(); + + var name = string(); + if (name == null) { + if (type == null) { + return null; + } else { + throw new KDLParseException(lexer.error("node name expected")); + } + } + + var propOrArgs = new LinkedList(); + List children = null; + + while (true) { + var token = lexer.peek(); + if (!(isWhiteSpace(token) || token instanceof Escline)) { + break; + } + requiredNodeSpace(); + + token = lexer.peek(); + if (token instanceof StringToken) { + lexer.next(); + if (lexer.peek() instanceof EqualsSign) { + var value = propertyValue(token.value()); + propOrArgs.addLast(new PropertyOrArgument.Property(token.value(), value)); + } else { + propOrArgs.addLast(new PropertyOrArgument.Argument(new KDLString(Optional.empty(), token.value()))); + } + } else if (token instanceof EqualsSign) { + lexer.next(); + var previous = propOrArgs.pollLast(); + if (!(previous instanceof PropertyOrArgument.Argument)) { + throw new KDLParseException(lexer.error("unexpected equals sign (" + token.value() + ')')); + } + propOrArgs.addLast(((PropertyOrArgument.Argument) previous).asProperty(value())); + } else if (token == OPENING_BRACE) { + children = nodeChildren(); + break; + } else { + var value = value(); + if (value != null) { + propOrArgs.addLast(new PropertyOrArgument.Argument(value)); + } + } + } + + var arguments = new ArrayList>(); + var properties = Properties.builder(); + for (var propOrArg : propOrArgs) { + if (propOrArg instanceof PropertyOrArgument.Argument) { + arguments.add(((PropertyOrArgument.Argument) propOrArg).value); + } else { + var property = (PropertyOrArgument.Property) propOrArg; + properties.property(property.name, property.value); + } + } + + return new KDLNode( + Optional.ofNullable(type), + name.value(), + arguments, + properties.build(), + children == null ? Collections.emptyList() : children + ); + } + + @Nullable + private Node node() throws IOException { + var node = baseNode(); + if (node == null) { + return null; + } + + optionalNodeSpace(); + + return new Node(node, nodeTerminator()); + } + + private void nodePropOrArg() throws IOException { + var propOrArg = lexer.peek(); + if (propOrArg instanceof StringToken) { + lexer.next(); + optionalNodeSpace(); + if (lexer.peek() instanceof EqualsSign) { + lexer.next(); + optionalNodeSpace(); + var value = value(); + if (value == null) { + throw new KDLParseException(lexer.error("value expected for property " + propOrArg.value())); + } + } + return; + } + var value = value(); + if (value == null) { + throw new KDLParseException(lexer.error("property or argument expected")); + } + } + + @Nonnull + private KDLValue propertyValue(String property) throws IOException { + lexer.next(); + var value = value(); + if (value == null) { + throw new KDLParseException(lexer.error("value expected for property " + property)); + } + return value; + } + + @Nonnull + private List nodeChildren() throws IOException { + lexer.next(); + var nodes = nodes(false); + + if (!parseToken(CLOSING_BRACE::equals)) { + throw new KDLParseException(lexer.error("closing brace expected")); + } + + return nodes; + } + + private boolean nodeTerminator() throws IOException { + return parseToken(is(SingleLineComment.class), is(Newline.class), Semicolon.INSTANCE::equals, Objects::isNull); + } + + @Nullable + private KDLValue value() throws IOException { + var type = Optional.ofNullable(type()); + optionalNodeSpace(); + + var token = lexer.peek(); + if (token instanceof Number) { + lexer.next(); + return ((Number) token).asKDLNumber(type); + } else if (token instanceof Boolean) { + lexer.next(); + return new KDLBoolean(type, token == Boolean.TRUE); + } else if (token instanceof Null) { + lexer.next(); + return new KDLNull(type); + } else if (token instanceof StringToken) { + lexer.next(); + return new KDLString(type, token.value()); + } + + if (type.isPresent()) { + throw new KDLParseException(lexer.error("value expected after type")); + } + + return null; + } + + @Nullable + private String type() throws IOException { + if (lexer.peek() != OPENING_PARENTHESES) { + return null; + } + lexer.next(); + + optionalNodeSpace(); + var type = string(); + if (type == null) { + throw new KDLParseException(lexer.error("type expected")); + } + optionalNodeSpace(); + + if (lexer.peek() != CLOSING_PARENTHESES) { + throw new KDLParseException(lexer.error("closing parentheses expected")); + } + lexer.next(); + + return type.value(); + } + + @Nullable + private StringToken string() throws IOException { + var token = lexer.peek(); + return token instanceof StringToken ? (StringToken) lexer.next() : null; + } + + private boolean whitespace() throws IOException { + var token = lexer.peek(); + if (isWhiteSpace(token)) { + lexer.next(); + return true; + } + return false; + } + + private boolean isWhiteSpace(Token token) { + return token instanceof Whitespace || token instanceof MultiLineComment; + } + + @SafeVarargs + private boolean parseToken(Predicate... predicates) throws IOException { + var token = lexer.peek(); + + for (var predicate : predicates) { + if (predicate.test(token)) { + lexer.next(); + return true; + } + } + + return false; + } + + private final Lexer lexer; + + private static boolean repeat(ParseFunction parseFunction) throws IOException { + var result = false; + while (true) { + if (!parseFunction.parse()) { + return result; + } + result = true; + } + } + + @Nonnull + private static Predicate is(@Nonnull Class tokenClass) { + return token -> token != null && tokenClass.isAssignableFrom(token.getClass()); + } + + @FunctionalInterface + private interface ParseFunction { + + boolean parse() throws IOException; + } + + private interface PropertyOrArgument { + final class Property implements PropertyOrArgument { + public Property(@Nonnull String name, @Nonnull KDLValue value) { + this.name = name; + this.value = value; + } + + @Nonnull + private final String name; + @Nonnull + private final KDLValue value; + } + + final class Argument implements PropertyOrArgument { + public Argument(@Nonnull KDLValue value) { + this.value = value; + } + + @Nonnull + Property asProperty(KDLValue value) { + return new Property((String) this.value.getValue(), value); + } + + @Nonnull + private final KDLValue value; + } + } + + private static final class Node { + public Node(@Nonnull KDLNode node, boolean hasNodeTerminator) { + this.node = node; + this.hasNodeTerminator = hasNodeTerminator; + } + + @Nonnull + private final KDLNode node; + private final boolean hasNodeTerminator; + } } diff --git a/src/main/java/kdl/parse/KDLParserFacade.java b/src/main/java/kdl/parse/KDLParserFacade.java deleted file mode 100644 index bc82131..0000000 --- a/src/main/java/kdl/parse/KDLParserFacade.java +++ /dev/null @@ -1,106 +0,0 @@ -package kdl.parse; - -import kdl.objects.*; - -import java.io.IOException; -import java.util.Optional; -import java.util.function.Predicate; - -/** - * Internal class allowing access to the internal methods of KDLParser without confusing KDLParser's interface - */ -public class KDLParserFacade extends KDLParser { - public KDLDocument parseDocument(KDLParseContext context, boolean root) throws IOException { - return super.parseDocument(context, root); - } - - @Override - public Optional parseNode(KDLParseContext context) throws IOException { - return super.parseNode(context); - } - - @Override - public String parseIdentifier(KDLParseContext context) throws IOException { - return super.parseIdentifier(context); - } - - @Override - public KDLObject parseArgOrProp(KDLParseContext context) throws IOException { - return super.parseArgOrProp(context); - } - - @Override - public KDLDocument parseChild(KDLParseContext context) throws IOException { - return super.parseChild(context); - } - - @Override - public KDLValue parseValue(KDLParseContext context) throws IOException { - return super.parseValue(context); - } - - @Override - public KDLNumber parseNumber(KDLParseContext context, Optional type) throws IOException { - return super.parseNumber(context, type); - } - - @Override - public KDLNumber parseNonDecimalNumber(KDLParseContext context, Predicate legalChars, char sign, int radix, Optional type) throws IOException { - return super.parseNonDecimalNumber(context, legalChars, sign, radix, type); - } - - @Override - public KDLNumber parseDecimalNumber(KDLParseContext context, char sign, Optional type) throws IOException { - return super.parseDecimalNumber(context, sign, type); - } - - @Override - public String parseBareIdentifier(KDLParseContext context) throws IOException { - return super.parseBareIdentifier(context); - } - - @Override - public String parseEscapedString(KDLParseContext context) throws IOException { - return super.parseEscapedString(context); - } - - @Override - public int getEscaped(int c, KDLParseContext context) throws IOException { - return super.getEscaped(c, context); - } - - @Override - public String parseRawString(KDLParseContext context) throws IOException { - return super.parseRawString(context); - } - - @Override - public SlashAction getSlashAction(KDLParseContext context, boolean escaped) throws IOException { - return super.getSlashAction(context, escaped); - } - - @Override - public void consumeAfterNode(KDLParseContext context) throws IOException { - super.consumeAfterNode(context); - } - - @Override - public WhitespaceResult consumeWhitespaceAndBlockComments(KDLParseContext context) throws IOException { - return super.consumeWhitespaceAndBlockComments(context); - } - - @Override - public void consumeLineComment(KDLParseContext context) throws IOException { - super.consumeLineComment(context); - } - - @Override - public void consumeBlockComment(KDLParseContext context) throws IOException { - super.consumeBlockComment(context); - } - - @Override - public WhitespaceResult consumeWhitespaceAndLinespace(KDLParseContext context) throws IOException { - return super.consumeWhitespaceAndLinespace(context); - } -} diff --git a/src/main/java/kdl/parse/error/ErrorUtils.java b/src/main/java/kdl/parse/error/ErrorUtils.java new file mode 100644 index 0000000..2acb057 --- /dev/null +++ b/src/main/java/kdl/parse/error/ErrorUtils.java @@ -0,0 +1,13 @@ +package kdl.parse.error; + +public class ErrorUtils { + private ErrorUtils() { + } + + public static String errorMessage(CharSequence currentLine, CharSequence message, int line, int column) { + return "Error line " + line + " - " + message + ":\n" + + currentLine + "\n" + + " ".repeat(Math.max(0, column - 1)) + "▲\n" + + "─".repeat(Math.max(0, column - 1)) + '╯'; + } +} diff --git a/src/main/java/kdl/parse/lexer/KDLReader.java b/src/main/java/kdl/parse/lexer/KDLReader.java new file mode 100644 index 0000000..1eed7c5 --- /dev/null +++ b/src/main/java/kdl/parse/lexer/KDLReader.java @@ -0,0 +1,195 @@ +package kdl.parse.lexer; + +import jakarta.annotation.Nonnull; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import kdl.parse.KDLInternalException; +import kdl.parse.KDLParseException; +import kdl.parse.error.ErrorUtils; + +import static kdl.parse.lexer.token.Newline.CR; +import static kdl.parse.lexer.token.Newline.LF; +import static kdl.parse.lexer.token.Newline.isNewline; + +class KDLReader implements AutoCloseable { + public KDLReader(InputStream inputStream) { + this.inputStream = inputStream; + } + + @Override + public void close() throws Exception { + invalidated = true; + inputStream.close(); + } + + public int peek() throws IOException { + return peek(1); + } + + public int peek(int n) throws IOException { + ensureNotInvalidated("peek"); + + if (n < 0 || n > peekedChars.length) { + throw new KDLInternalException("Error while peeking: n should be between 1 and " + peekedChars.length + " included."); + } + + while (peekedCharsNumber < n) { + peekNextChar(); + } + + return peekedChars[n - 1]; + } + + private void peekNextChar() throws IOException { + peekedChars[peekedCharsNumber] = readNextChar(); + peekedCharsNumber += 1; + } + + public int read() throws IOException { + ensureNotInvalidated("read"); + + var c = nextChar(); + updatePosition(c); + + return c; + } + + private int nextChar() throws IOException { + int c; + + if (peekedCharsNumber > 0) { + c = peekedChars[0]; + peekedChars = Arrays.copyOfRange(peekedChars, 1, peekedChars.length + 1); + peekedCharsNumber -= 1; + } else { + c = readNextChar(); + } + + return c; + } + + private int readNextChar() throws IOException { + var c = inputStream.read(); + if (c < 0 || (c & 0x80) == 0) { + return checkCodePoint(c); + } else if ((c & 0xE0) == 0xC0) { + var c2 = inputStream.read(); + if ((c2 & 0xC0) == 0x80) { + return checkCodePoint(((c & 0x1F) << 6) | (c2 & 0x3F)); + } + } else if ((c & 0xF0) == 0xE0) { + var c2 = inputStream.read(); + if ((c2 & 0xC0) == 0x80) { + var c3 = inputStream.read(); + if ((c3 & 0xC0) == 0x80) { + return checkCodePoint(((c & 0xF) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F)); + } + } + } else if ((c & 0xF8) == 0xF0) { + var c2 = inputStream.read(); + if ((c2 & 0xC0) == 0x80) { + var c3 = inputStream.read(); + if ((c3 & 0xC0) == 0x80) { + var c4 = inputStream.read(); + if ((c4 & 0xC0) == 0x80) { + return checkCodePoint(((c & 0x7) << 18) | ((c2 & 0x3F) << 12) | ((c3 & 0x3F) << 6) | (c4 & 0x3F)); + } + } + } + } + throw new KDLParseException(error(String.format("Invalid character U+%x", c))); + } + + private int checkCodePoint(int codePoint) { + if (codePoint != EOF && isInvalid(codePoint)) { + currentLine.appendCodePoint(codePoint); + throw new KDLParseException(error("invalid codepoint")); + } + return codePoint; + } + + private boolean isInvalid(int codePoint) { + return codePoint <= 8 + || (codePoint >= 0x000E && codePoint <= 0x01F) + || codePoint == 0x007F + || (codePoint >= 0xD800 && codePoint <= 0xDFFF) + || (codePoint >= 0x200E && codePoint <= 0x200F) + || (codePoint >= 0x202A && codePoint <= 0x202E) + || (codePoint >= 0x2066 && codePoint <= 0x2069); + } + + private void updatePosition(int c) throws IOException { + if (c != EOF) { + if (isNewline(c)) { + if (c != CR || peek() != LF) { + currentLine.setLength(0); + line += 1; + column = 0; + } + } else { + currentLine.appendCodePoint(c); + column += 1; + } + } + } + + public int line() { + return line; + } + + public int column() { + return column; + } + + @Nonnull + public String error(String message) { + return error(message, line, column); + } + + @Nonnull + public String error(String message, int line, int column) { + invalidate(); + return ErrorUtils.errorMessage(currentLine, message, line, column); + } + + private void invalidate() { + if (!invalidated) { + try { + int c; + if (peekedCharsNumber > 0) { + c = peekedChars[0]; + peekedChars = Arrays.copyOfRange(peekedChars, 1, peekedChars.length + 1); + peekedCharsNumber -= 1; + } else { + c = inputStream.read(); + } + while (!isNewline(c) && c != EOF) { + currentLine.appendCodePoint(c); + c = inputStream.read(); + } + } catch (IOException e) { + currentLine.append(""); + } + } + + invalidated = true; + } + + private void ensureNotInvalidated(String action) { + if (invalidated) throw new KDLInternalException("Trying to " + action + " from an invalidated reader"); + } + + private final InputStream inputStream; + private int[] peekedChars = new int[MAX_PEEKS]; + private int peekedCharsNumber = 0; + + private final StringBuilder currentLine = new StringBuilder(); + private int line = 1; + private int column = 0; + private boolean invalidated = false; + + private static final int MAX_PEEKS = 2; + private static final int EOF = -1; + +} diff --git a/src/main/java/kdl/parse/lexer/Lexer.java b/src/main/java/kdl/parse/lexer/Lexer.java new file mode 100644 index 0000000..5a4130c --- /dev/null +++ b/src/main/java/kdl/parse/lexer/Lexer.java @@ -0,0 +1,615 @@ +package kdl.parse.lexer; + +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import kdl.parse.KDLParseException; +import kdl.parse.lexer.token.Bom; +import kdl.parse.lexer.token.Boolean; +import kdl.parse.lexer.token.EqualsSign; +import kdl.parse.lexer.token.Escline; +import kdl.parse.lexer.token.MultiLineComment; +import kdl.parse.lexer.token.Newline; +import kdl.parse.lexer.token.Null; +import kdl.parse.lexer.token.Number; +import kdl.parse.lexer.token.Semicolon; +import kdl.parse.lexer.token.SingleLineComment; +import kdl.parse.lexer.token.Slashdash; +import kdl.parse.lexer.token.StringToken; +import kdl.parse.lexer.token.Whitespace; + +import static kdl.parse.error.ErrorUtils.errorMessage; +import static kdl.parse.lexer.token.Brace.CLOSING_BRACE; +import static kdl.parse.lexer.token.Brace.OPENING_BRACE; +import static kdl.parse.lexer.token.EqualsSign.isEqualsSign; +import static kdl.parse.lexer.token.Newline.CR; +import static kdl.parse.lexer.token.Newline.LF; +import static kdl.parse.lexer.token.Newline.isNewline; +import static kdl.parse.lexer.token.Number.isDigit; +import static kdl.parse.lexer.token.Number.isHexDigit; +import static kdl.parse.lexer.token.Number.isSign; +import static kdl.parse.lexer.token.Parentheses.CLOSING_PARENTHESES; +import static kdl.parse.lexer.token.Parentheses.OPENING_PARENTHESES; +import static kdl.parse.lexer.token.StringToken.IdentifierString.isDisallowedIdentifier; +import static kdl.parse.lexer.token.StringToken.IdentifierString.isIdentifierChar; +import static kdl.parse.lexer.token.StringToken.IdentifierString.isUnambiguousIdentifierChar; +import static kdl.parse.lexer.token.StringToken.IdentifierString.isUnicodeScalarValue; +import static kdl.parse.lexer.token.Whitespace.isWhitespace; + +public class Lexer { + public Lexer(InputStream inputStream) { + this.reader = new KDLReader(inputStream); + } + + @Nullable + public Token next() throws IOException { + if (token != null) { + var result = token; + token = null; + return result; + } + return nextToken(); + } + + @Nullable + public Token peek() throws IOException { + if (token == null) { + token = nextToken(); + } + return token; + } + + @Nullable + private Token nextToken() throws IOException { + var c = reader.read(); + + switch (c) { + case EOF: + return null; + case Bom.VALUE: + return Bom.INSTANCE; + case '/': { + var second = reader.read(); + if (second == '/') { // <-- + return singleLineComment(); + } else if (second == '*') { + return multiLineComment(); + } else if (second == '-') { + return Slashdash.INSTANCE; + } + throw new KDLParseException(error("[/*-] expected after '/' but got '" + (char) second + "'")); + } + case '(': + return OPENING_PARENTHESES; + case ')': + return CLOSING_PARENTHESES; + case '{': + return OPENING_BRACE; + case '}': + return CLOSING_BRACE; + case ';': + return Semicolon.INSTANCE; + case '"': + return quotedString(); + case '#': { + var second = reader.peek(); + if (second == '#' || second == '"') { + return rawString(); + } + return keyword(); + } + case '\\': + return escline(); + } + + if (isNewline(c)) { + if (c == CR && reader.peek() == LF) { + reader.read(); + return new Newline("\r\n"); + } + return new Newline((char) c); + } else if (isWhitespace(c)) { + return new Whitespace((char) c); + } else if (isEqualsSign(c)) { + return new EqualsSign((char) c); + } else if (isSign(c) && isDigit(reader.peek()) || isDigit(c)) { + return number(c); + } else if (isIdentifierString(c)) { + return identifierString(c); + } + + throw new KDLParseException(error("invalid character '" + (char) c + '\'')); + } + + @Nonnull + public String error(@Nonnull String message) { + return reader.error(message); + } + + @Nonnull + private SingleLineComment singleLineComment() throws IOException { + var value = new StringBuilder("//"); + + while (true) { + var c = reader.read(); + if (c != EOF) { + value.appendCodePoint(c); + } + if (c == CR && reader.peek() == LF) { + value.appendCodePoint(reader.read()); + } + if (c == EOF || isNewline(c)) { + return new SingleLineComment(value.toString()); + } + } + } + + @Nonnull + private MultiLineComment multiLineComment() throws IOException { + var value = new StringBuilder("/*"); + var expectedEnds = 1; + + while (true) { + var c = reader.read(); + if (c == EOF) { + throw new KDLParseException(error("unexpected end of file inside multi-line comment")); + } + value.appendCodePoint(c); + if (c == '/' && reader.peek() == '*') { + expectedEnds += 1; + } + if (c == '*' && reader.peek() == '/') { + reader.read(); + value.append('/'); + expectedEnds -= 1; + if (expectedEnds == 0) { + return new MultiLineComment(value.toString()); + } + } + } + } + + private boolean isIdentifierString(int c) throws IOException { + if (isSign(c)) { + var c2 = reader.peek(); + if (c2 == '.') { + var c3 = reader.peek(2); + return !isDigit(c3); + } + return !isDigit(c2); + } + if (c == '.') { + var c2 = reader.peek(); + return !isDigit(c2); + } + return isUnambiguousIdentifierChar(c); + } + + @Nonnull + private StringToken.IdentifierString identifierString(int firstChar) throws IOException { + var value = new StringBuilder(); + value.appendCodePoint(firstChar); + + while (isIdentifierChar(reader.peek())) { + value.appendCodePoint(reader.read()); + } + + var stringValue = value.toString(); + if (isDisallowedIdentifier(stringValue)) { + throw new KDLParseException(reader.error( + "keyword used as identifier, use a quoted string instead", + reader.line(), + reader.column() - value.length() + )); + } + + return new StringToken.IdentifierString(stringValue); + } + + @Nonnull + private StringToken.QuotedString quotedString() throws IOException { + if (isNewline(reader.peek())) { + reader.read(); + return multiLineQuotedString(); + } + return singleLineQuotedString(); + } + + @Nonnull + private StringToken.QuotedString multiLineQuotedString() throws IOException { + var lines = new ArrayList(); + var currentLine = new StringBuilder(); + + while (true) { + var c = reader.read(); + if (c == '"') { + break; + } else if (isNewline(c)) { + lines.add(currentLine.toString()); + currentLine.setLength(0); + } else { + addStringCharacter(currentLine, c); + } + } + + var lastLine = currentLine.toString(); + return new StringToken.QuotedString(getMultiLineStringValue(lines, lastLine)); + } + + @Nonnull + private StringToken.QuotedString singleLineQuotedString() throws IOException { + var builder = new StringBuilder(); + while (true) { + var c = reader.read(); + if (c == '"') { + return new StringToken.QuotedString(builder.toString()); + } else if (isNewline(c)) { + throw new KDLParseException(error("unexpected newline inside quoted string, escape it or use a multi-line quoted string")); + } else { + addStringCharacter(builder, c); + } + } + } + + private void addStringCharacter(@Nonnull StringBuilder builder, int c) throws IOException { + if (c == '\\') { + var c2 = reader.read(); + var escapedCharacter = ESCAPED_CHARACTERS.get(c2); + if (escapedCharacter != null) { + builder.appendCodePoint(escapedCharacter); + } else if (c2 == 'u') { + builder.appendCodePoint(unicodeEscape()); + } else if (isWhitespace(c2) || isNewline(c2)) { + whitespaceEscape(); + } else { + throw new KDLParseException(error("invalid escape sequence '\\" + (char) c2 + '\'')); + } + } else if (c == EOF) { + throw new KDLParseException(error("unexpected end of file inside quoted string")); + } else { + builder.appendCodePoint(c); + } + } + + private int unicodeEscape() throws IOException { + if (reader.read() != '{') { + throw new KDLParseException(error("'{' expected at start of unicode escape")); + } + + var count = 0; + var hexValue = new StringBuilder(); + while (count < 6) { + var c = reader.read(); + if (c == '}') { + break; + } else if (isHexDigit(c)) { + count += 1; + hexValue.appendCodePoint(c); + } else { + throw new KDLParseException(error("unexpected character in unicode escape")); + } + } + + if (count == 0) { + throw new KDLParseException(error("at least one digit is required for a unicode escape")); + } else if (count == 6) { + var c = reader.read(); + if (c != '}') { + throw new KDLParseException(error("'}' expected at end of unicode escape")); + } + } + + var codePoint = Integer.parseInt(hexValue.toString(), 16); + if (!isUnicodeScalarValue(codePoint)) { + throw new KDLParseException(reader.error(String.format("invalid unicode value U+%X", codePoint), reader.line(), reader.column() - count)); + } + + return codePoint; + } + + private void whitespaceEscape() throws IOException { + while (true) { + var c = reader.peek(); + if (!isWhitespace(c) && !isNewline(c)) { + return; + } + reader.read(); + } + } + + @Nonnull + private StringToken.QuotedString rawString() throws IOException { + var openingSharpSigns = 1; + while (reader.peek() == '#') { + reader.read(); + openingSharpSigns += 1; + } + + if (reader.read() != '"') { + throw new KDLParseException(error("a quote is required to start a raw string")); + } + + if (isNewline(reader.peek())) { + reader.read(); + return multiLineRawString(openingSharpSigns); + } + + return singleLineRawString(openingSharpSigns); + } + + @Nonnull + private StringToken.QuotedString multiLineRawString(int openingSharpSigns) throws IOException { + var lines = new ArrayList(); + var currentLine = new StringBuilder(); + + var closingSharpSigns = 0; + while (true) { + var c = reader.read(); + if (c == EOF) { + throw new KDLParseException(error("unexpected end of file inside raw string")); + } else if (isNewline(c)) { + writeNonClosingSharpSigns(currentLine, closingSharpSigns); + closingSharpSigns = 0; + lines.add(currentLine.toString()); + currentLine.setLength(0); + } else if (c == '"' && reader.peek() == '#') { + reader.read(); + if (openingSharpSigns == 1) { + break; + } + closingSharpSigns = 1; + } else if (c == '#' && closingSharpSigns > 0) { + closingSharpSigns += 1; + if (closingSharpSigns == openingSharpSigns) { + break; + } + } else { + writeNonClosingSharpSigns(currentLine, closingSharpSigns); + closingSharpSigns = 0; + currentLine.appendCodePoint(c); + } + } + + var lastLine = currentLine.toString(); + return new StringToken.QuotedString(getMultiLineStringValue(lines, lastLine)); + } + + @Nonnull + private String getMultiLineStringValue(@Nonnull List lines, @Nonnull String lastLine) { + checkLastLine(lastLine); + var builder = new StringBuilder(); + + for (var i = 0; i < lines.size(); i++) { + builder.append(removeIndent(lines.get(i), reader.line() - lines.size() + i, lastLine)); + if (i < lines.size() - 1) { + builder.append('\n'); + } + } + + return builder.toString(); + } + + private void checkLastLine(@Nonnull String lastLine) { + var codePoints = lastLine.codePoints().boxed().collect(Collectors.toList()); + for (var column = 0; column < codePoints.size(); column++) { + if (!isWhitespace(codePoints.get(column))) { + throw new KDLParseException(reader.error("last line of a multi-line string must only contain whitespaces", reader.line(), column + 1)); + } + } + } + + @Nonnull + private String removeIndent(@Nonnull String line, int lineNumber, @Nonnull String lastLine) { + if (line.isEmpty()) { + return line; + } + if (!line.startsWith(lastLine)) { + throw new KDLParseException(errorMessage( + line, + "multi-line string indentation must match last line (\"" + lastLine + "\")", + lineNumber, + 1 + )); + } + return line.substring(lastLine.length()); + } + + @Nonnull + private StringToken.QuotedString singleLineRawString(int openingSharpSigns) throws IOException { + var builder = new StringBuilder(); + + var closingSharpSigns = 0; + while (true) { + var c = reader.read(); + if (c == EOF) { + throw new KDLParseException(error("unexpected end of file inside raw string")); + } else if (isNewline(c)) { + throw new KDLParseException(error("unexpected newline inside raw string, use a multi-line raw string")); + } else if (c == '"' && reader.peek() == '#') { + writeNonClosingSharpSigns(builder, closingSharpSigns); + reader.read(); + if (openingSharpSigns == 1) { + return new StringToken.QuotedString(builder.toString()); + } + closingSharpSigns = 1; + } else if (c == '#' && closingSharpSigns > 0) { + closingSharpSigns += 1; + if (closingSharpSigns == openingSharpSigns) { + return new StringToken.QuotedString(builder.toString()); + } + } else { + writeNonClosingSharpSigns(builder, closingSharpSigns); + closingSharpSigns = 0; + builder.appendCodePoint(c); + } + } + } + + private static void writeNonClosingSharpSigns(@Nonnull StringBuilder builder, int closingSharpSigns) { + if (closingSharpSigns > 0) { + builder.append('"'); + builder.append("#".repeat(closingSharpSigns)); + } + } + + @Nonnull + private Escline escline() throws IOException { + var value = new StringBuilder("\\"); + + while (true) { + var c = reader.read(); + if (isWhitespace(c)) { + value.appendCodePoint(c); + } else if (c == '/') { + var second = reader.read(); + if (second == '/') { + var comment = singleLineComment(); + value.append(comment.value()); + break; + } else if (second == '*') { + var comment = multiLineComment(); + value.append(comment.value()); + } else { + throw new KDLParseException(error("invalid character in escaped line")); + } + } else if (isNewline(c)) { + value.appendCodePoint(c); + break; + } else if (c == EOF) { + break; + } else { + throw new KDLParseException(error("invalid character in escaped line")); + } + } + + return new Escline(value.toString()); + } + + @Nonnull + private Token keyword() throws IOException { + var value = new StringBuilder(); + + while (isIdentifierChar(reader.peek())) { + value.appendCodePoint(reader.read()); + } + + switch (value.toString()) { + case "true": + return Boolean.TRUE; + case "false": + return Boolean.FALSE; + case "null": + return Null.INSTANCE; + case "inf": + return Number.INFINITY; + case "-inf": + return Number.MINUS_INFINITY; + case "nan": + return Number.NAN; + default: + throw new KDLParseException(reader.error("invalid value #" + value, reader.line(), reader.column() - value.length())); + } + } + + @Nonnull + private Number number(int firstChar) throws IOException { + var builder = new StringBuilder(); + if (firstChar == '-') { + builder.append('-'); + } + + var firstDigit = isDigit(firstChar) ? firstChar : reader.read(); + if (firstDigit == '0') { + var peek = reader.peek(); + if (peek == 'x') { + reader.read(); + return integer(builder, 16, Number::isHexDigit); + } else if (peek == 'o') { + reader.read(); + return integer(builder, 8, Number::isOctalDigit); + } else if (peek == 'b') { + reader.read(); + return integer(builder, 2, Number::isBinaryDigit); + } + } + + builder.appendCodePoint(firstDigit); + integer(builder, Number::isDigit); + var isDecimal = false; + + if (reader.peek() == '.') { + reader.read(); + isDecimal = true; + builder.append('.'); + if (!isDigit(reader.peek())) { + throw new KDLParseException(reader.error("digit expected immediately after '.'", reader.line(), reader.column() + 1)); + } + integer(builder, Number::isDigit); + } + + if (reader.peek() == 'e' | reader.peek() == 'E') { + isDecimal = true; + builder.appendCodePoint(reader.read()); + if (isSign(reader.peek())) { + builder.appendCodePoint(reader.read()); + } + if (!isDigit(reader.peek())) { + throw new KDLParseException(reader.error("digit expected at start of exponent", reader.line(), reader.column() + 1)); + } + integer(builder, Number::isDigit); + } + + return isDecimal + ? new Number.Decimal(new BigDecimal(builder.toString())) + : new Number.Integer(new BigInteger(builder.toString())); + } + + @Nonnull + private Number.Integer integer(@Nonnull StringBuilder builder, int radix, @Nonnull Predicate digitPredicate) throws IOException { + if (!digitPredicate.test(reader.peek())) { + throw new KDLParseException(reader.error("digit expected at start of number", reader.line(), reader.column() + 1)); + } + integer(builder, digitPredicate); + return new Number.Integer(new BigInteger(builder.toString(), radix)); + } + + private void integer(@Nonnull StringBuilder builder, @Nonnull Predicate digitPredicate) throws IOException { + while (true) { + var c = reader.peek(); + if (digitPredicate.test(c)) { + builder.appendCodePoint(reader.read()); + } else if (c == '_') { + reader.read(); + } else { + return; + } + } + } + + private final KDLReader reader; + private Token token; + + private static final int EOF = -1; + private static final Map ESCAPED_CHARACTERS = new HashMap<>(); + + static { + ESCAPED_CHARACTERS.put((int) '"', (int) '"'); + ESCAPED_CHARACTERS.put((int) '\\', (int) '\\'); + ESCAPED_CHARACTERS.put((int) 'b', (int) '\b'); + ESCAPED_CHARACTERS.put((int) 'f', (int) '\f'); + ESCAPED_CHARACTERS.put((int) 'r', (int) '\r'); + ESCAPED_CHARACTERS.put((int) 'n', (int) '\n'); + ESCAPED_CHARACTERS.put((int) 't', (int) '\t'); + ESCAPED_CHARACTERS.put((int) 's', (int) ' '); + } + +} diff --git a/src/main/java/kdl/parse/lexer/Token.java b/src/main/java/kdl/parse/lexer/Token.java new file mode 100644 index 0000000..fdeac8c --- /dev/null +++ b/src/main/java/kdl/parse/lexer/Token.java @@ -0,0 +1,8 @@ +package kdl.parse.lexer; + +import jakarta.annotation.Nonnull; + +public interface Token { + @Nonnull + String value(); +} diff --git a/src/main/java/kdl/parse/lexer/token/Bom.java b/src/main/java/kdl/parse/lexer/token/Bom.java new file mode 100644 index 0000000..a2b7701 --- /dev/null +++ b/src/main/java/kdl/parse/lexer/token/Bom.java @@ -0,0 +1,24 @@ +package kdl.parse.lexer.token; + +import jakarta.annotation.Nonnull; +import kdl.parse.lexer.Token; + +public class Bom implements Token { + + private Bom() { + } + + @Override + @Nonnull + public String value() { + return String.valueOf(VALUE); + } + + @Override + public String toString() { + return "BOM"; + } + + public static final char VALUE = 0xFEFF; + public static final Bom INSTANCE = new Bom(); +} diff --git a/src/main/java/kdl/parse/lexer/token/Boolean.java b/src/main/java/kdl/parse/lexer/token/Boolean.java new file mode 100644 index 0000000..5e8c955 --- /dev/null +++ b/src/main/java/kdl/parse/lexer/token/Boolean.java @@ -0,0 +1,28 @@ +package kdl.parse.lexer.token; + +import jakarta.annotation.Nonnull; +import kdl.parse.lexer.Token; + +public class Boolean implements Token { + + private Boolean(boolean value) { + this.value = value; + } + + @Override + @Nonnull + public String value() { + return value ? "#true" : "#false"; + } + + @Override + public String toString() { + return value(); + } + + private final boolean value; + + public static final Boolean TRUE = new Boolean(true); + public static final Boolean FALSE = new Boolean(false); + +} diff --git a/src/main/java/kdl/parse/lexer/token/Brace.java b/src/main/java/kdl/parse/lexer/token/Brace.java new file mode 100644 index 0000000..76be077 --- /dev/null +++ b/src/main/java/kdl/parse/lexer/token/Brace.java @@ -0,0 +1,42 @@ +package kdl.parse.lexer.token; + +import jakarta.annotation.Nonnull; +import kdl.parse.lexer.Token; + +public interface Brace extends Token { + + class OpeningBrace implements Brace { + private OpeningBrace() { + } + + @Override + @Nonnull + public String value() { + return "("; + } + + @Override + public String toString() { + return "OpeningBrace"; + } + } + + class ClosingBrace implements Brace { + private ClosingBrace() { + } + + @Override + @Nonnull + public String value() { + return ")"; + } + + @Override + public String toString() { + return "ClosingBrace"; + } + } + + OpeningBrace OPENING_BRACE = new OpeningBrace(); + ClosingBrace CLOSING_BRACE = new ClosingBrace(); +} diff --git a/src/main/java/kdl/parse/lexer/token/EqualsSign.java b/src/main/java/kdl/parse/lexer/token/EqualsSign.java new file mode 100644 index 0000000..93ff34a --- /dev/null +++ b/src/main/java/kdl/parse/lexer/token/EqualsSign.java @@ -0,0 +1,49 @@ +package kdl.parse.lexer.token; + +import jakarta.annotation.Nonnull; +import java.util.Objects; +import kdl.parse.lexer.Token; + +public class EqualsSign implements Token { + public EqualsSign(char value) { + this.value = value; + } + + @Override + @Nonnull + public String value() { + return String.valueOf(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var equalSign = (EqualsSign) o; + return Objects.equals(value, equalSign.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return "EqualsSign(" + value + ')'; + } + + private final char value; + + public static boolean isEqualsSign(int c) { + switch (c) { + case 0x003D: // = EQUALS SIGN + case 0xFE66: // ﹦ SMALL EQUALS SIGN + case 0xFF1D: // = FULLWIDTH EQUALS SIGN + case 0x1F7F0: // 🟰 HEAVY EQUALS SIGN + return true; + default: + return false; + } + } +} diff --git a/src/main/java/kdl/parse/lexer/token/Escline.java b/src/main/java/kdl/parse/lexer/token/Escline.java new file mode 100644 index 0000000..e3a64b3 --- /dev/null +++ b/src/main/java/kdl/parse/lexer/token/Escline.java @@ -0,0 +1,38 @@ +package kdl.parse.lexer.token; + +import jakarta.annotation.Nonnull; +import java.util.Objects; +import kdl.parse.lexer.Token; + +public class Escline implements Token { + + public Escline(String value) { + this.value = value; + } + + @Override + @Nonnull + public String value() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var escline = (Escline) o; + return Objects.equals(value, escline.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return "Escline(" + value + ')'; + } + + private final String value; +} diff --git a/src/main/java/kdl/parse/lexer/token/MultiLineComment.java b/src/main/java/kdl/parse/lexer/token/MultiLineComment.java new file mode 100644 index 0000000..90a21dd --- /dev/null +++ b/src/main/java/kdl/parse/lexer/token/MultiLineComment.java @@ -0,0 +1,37 @@ +package kdl.parse.lexer.token; + +import jakarta.annotation.Nonnull; +import java.util.Objects; +import kdl.parse.lexer.Token; + +public class MultiLineComment implements Token { + public MultiLineComment(String value) { + this.value = value; + } + + @Override + @Nonnull + public String value() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (MultiLineComment) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return "MultiLineComment(" + value + ')'; + } + + private final String value; +} diff --git a/src/main/java/kdl/parse/lexer/token/Newline.java b/src/main/java/kdl/parse/lexer/token/Newline.java new file mode 100644 index 0000000..f4cbd76 --- /dev/null +++ b/src/main/java/kdl/parse/lexer/token/Newline.java @@ -0,0 +1,67 @@ +package kdl.parse.lexer.token; + +import jakarta.annotation.Nonnull; +import java.util.Objects; +import kdl.parse.lexer.Token; + +public class Newline implements Token { + public Newline(String value) { + this.value = value; + } + + public Newline(char value) { + this.value = String.valueOf(value); + } + + @Nonnull + @Override + public String value() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var newline = (Newline) o; + return Objects.equals(value, newline.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + switch (value) { + case "\r": + return "Newline(CR)"; + case "\n": + return "Newline(LF)"; + case "\r\n": + return "Newline(CRLF)"; + default: + return String.format("Newline(\\u%04x)", value.codePointAt(0)); + } + } + + private final String value; + + public static boolean isNewline(int c) { + switch (c) { + case CR: // Carriage Return + case LF: // Line Feed + case 0x0085: // Next Line + case 0x000C: // Form Feed + case 0x2028: // Line Separator + case 0x2029: // Paragraph Separator + return true; + default: + return false; + } + } + + public static final int LF = 0x000A; + public static final int CR = 0x000D; +} diff --git a/src/main/java/kdl/parse/lexer/token/Null.java b/src/main/java/kdl/parse/lexer/token/Null.java new file mode 100644 index 0000000..c48e29c --- /dev/null +++ b/src/main/java/kdl/parse/lexer/token/Null.java @@ -0,0 +1,22 @@ +package kdl.parse.lexer.token; + +import jakarta.annotation.Nonnull; +import kdl.parse.lexer.Token; + +public class Null implements Token { + private Null() { + } + + @Override + @Nonnull + public String value() { + return "#null"; + } + + @Override + public String toString() { + return value(); + } + + public static final Null INSTANCE = new Null(); +} diff --git a/src/main/java/kdl/parse/lexer/token/Number.java b/src/main/java/kdl/parse/lexer/token/Number.java new file mode 100644 index 0000000..d244332 --- /dev/null +++ b/src/main/java/kdl/parse/lexer/token/Number.java @@ -0,0 +1,185 @@ +package kdl.parse.lexer.token; + +import jakarta.annotation.Nonnull; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Objects; +import java.util.Optional; +import kdl.KDLNumber; +import kdl.parse.lexer.Token; + +public interface Number extends Token { + KDLNumber asKDLNumber(Optional type); + + class Infinity implements Number { + private Infinity() { + } + + @Override + @Nonnull + public String value() { + return value; + } + + @Override + public String toString() { + return value; + } + + @Override + public KDLNumber asKDLNumber(Optional type) { + return new KDLNumber.PositiveInfinity(type); + } + + public final String value = "#inf"; + } + + class MinusInfinity implements Number { + private MinusInfinity() { + } + + @Override + @Nonnull + public String value() { + return value; + } + + @Override + public String toString() { + return value; + } + + @Override + public KDLNumber asKDLNumber(Optional type) { + return new KDLNumber.NegativeInfinity(type); + } + + public final String value = "#-inf"; + } + + class NaN implements Number { + private NaN() { + } + + @Override + @Nonnull + public String value() { + return value; + } + + @Override + public String toString() { + return value; + } + + @Override + public KDLNumber asKDLNumber(Optional type) { + return new KDLNumber.NotANumber(type); + } + + public final String value = "#nan"; + + } + + class Integer implements Number { + public Integer(@Nonnull BigInteger value) { + this.value = value; + } + + @Override + @Nonnull + public String value() { + return value.toString(); + } + + @Override + public KDLNumber asKDLNumber(Optional type) { + return new KDLNumber.Integer(type, value); + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + Integer integer = (Integer) object; + return Objects.equals(value, integer.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return "Integer(" + value + ')'; + } + + @Nonnull + private final BigInteger value; + + } + + class Decimal implements Number { + public Decimal(@Nonnull BigDecimal value) { + this.value = value; + } + + @Override + @Nonnull + public String value() { + return value.toString(); + } + + @Override + public KDLNumber asKDLNumber(Optional type) { + return new KDLNumber.Decimal(type, value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var decimal = (Decimal) o; + return Objects.equals(value, decimal.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return "Decimal(" + value + ")"; + } + + @Nonnull + private final BigDecimal value; + + } + + Infinity INFINITY = new Infinity(); + MinusInfinity MINUS_INFINITY = new MinusInfinity(); + NaN NAN = new NaN(); + + static boolean isDigit(int c) { + return c >= '0' && c <= '9'; + } + + static boolean isHexDigit(int c) { + return isDigit(c) || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F'; + } + + static boolean isOctalDigit(int c) { + return c >= '0' && c <= '7'; + } + + static boolean isBinaryDigit(int c) { + return c == '0' || c == '1'; + } + + static boolean isSign(int c) { + return c == '-' || c == '+'; + } +} diff --git a/src/main/java/kdl/parse/lexer/token/Parentheses.java b/src/main/java/kdl/parse/lexer/token/Parentheses.java new file mode 100644 index 0000000..0aec4c8 --- /dev/null +++ b/src/main/java/kdl/parse/lexer/token/Parentheses.java @@ -0,0 +1,42 @@ +package kdl.parse.lexer.token; + +import jakarta.annotation.Nonnull; +import kdl.parse.lexer.Token; + +public interface Parentheses extends Token { + + class OpeningParentheses implements Parentheses { + private OpeningParentheses() { + } + + @Override + @Nonnull + public String value() { + return "("; + } + + @Override + public String toString() { + return "OpeningParentheses"; + } + } + + class ClosingParentheses implements Parentheses { + private ClosingParentheses() { + } + + @Override + @Nonnull + public String value() { + return ")"; + } + + @Override + public String toString() { + return "ClosingParentheses"; + } + } + + OpeningParentheses OPENING_PARENTHESES = new OpeningParentheses(); + ClosingParentheses CLOSING_PARENTHESES = new ClosingParentheses(); +} diff --git a/src/main/java/kdl/parse/lexer/token/Semicolon.java b/src/main/java/kdl/parse/lexer/token/Semicolon.java new file mode 100644 index 0000000..3961bf0 --- /dev/null +++ b/src/main/java/kdl/parse/lexer/token/Semicolon.java @@ -0,0 +1,21 @@ +package kdl.parse.lexer.token; + +import jakarta.annotation.Nonnull; +import kdl.parse.lexer.Token; + +public class Semicolon implements Token { + private Semicolon() {} + + @Nonnull + @Override + public String value() { + return ";"; + } + + @Override + public String toString() { + return "Semicolon"; + } + + public static final Semicolon INSTANCE = new Semicolon(); +} diff --git a/src/main/java/kdl/parse/lexer/token/SingleLineComment.java b/src/main/java/kdl/parse/lexer/token/SingleLineComment.java new file mode 100644 index 0000000..41eb881 --- /dev/null +++ b/src/main/java/kdl/parse/lexer/token/SingleLineComment.java @@ -0,0 +1,37 @@ +package kdl.parse.lexer.token; + +import jakarta.annotation.Nonnull; +import java.util.Objects; +import kdl.parse.lexer.Token; + +public class SingleLineComment implements Token { + public SingleLineComment(String value) { + this.value = value; + } + + @Override + @Nonnull + public String value() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (SingleLineComment) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return "SingleLineComment(" + value + ')'; + } + + private final String value; +} diff --git a/src/main/java/kdl/parse/lexer/token/Slashdash.java b/src/main/java/kdl/parse/lexer/token/Slashdash.java new file mode 100644 index 0000000..373d7c7 --- /dev/null +++ b/src/main/java/kdl/parse/lexer/token/Slashdash.java @@ -0,0 +1,23 @@ +package kdl.parse.lexer.token; + +import jakarta.annotation.Nonnull; +import kdl.parse.lexer.Token; + +public class Slashdash implements Token { + private Slashdash() { + } + + @Override + @Nonnull + public String value() { + return VALUE; + } + + @Override + public String toString() { + return "Slashdash"; + } + + private static final String VALUE = "/-"; + public static final Slashdash INSTANCE = new Slashdash(); +} diff --git a/src/main/java/kdl/parse/lexer/token/StringToken.java b/src/main/java/kdl/parse/lexer/token/StringToken.java new file mode 100644 index 0000000..a956756 --- /dev/null +++ b/src/main/java/kdl/parse/lexer/token/StringToken.java @@ -0,0 +1,116 @@ +package kdl.parse.lexer.token; + +import jakarta.annotation.Nonnull; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import kdl.parse.lexer.Token; + +import static kdl.parse.lexer.token.EqualsSign.isEqualsSign; +import static kdl.parse.lexer.token.Newline.isNewline; +import static kdl.parse.lexer.token.Number.isDigit; +import static kdl.parse.lexer.token.Whitespace.isWhitespace; + +public interface StringToken extends Token { + class IdentifierString implements StringToken { + public IdentifierString(String value) { + this.value = value; + } + + @Override + @Nonnull + public String value() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (IdentifierString) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return "IdentifierString(" + value + ")"; + } + + private final String value; + + public static boolean isIdentifierChar(int c) { + return isUnicodeScalarValue(c) && !isWhitespace(c) && !isNewline(c) && !isSpecialCharacter(c) && !isEqualsSign(c); + } + + public static boolean isUnicodeScalarValue(int c) { + return c >= 0 && c <= 0xD7FF || c >= 0xE000 && c <= 0x10FFFF; + } + + public static boolean isSpecialCharacter(int c) { + switch (c) { + case '\\': + case '/': + case '(': + case ')': + case '{': + case '}': + case ';': + case '[': + case ']': + case '"': + case '#': + return true; + default: + return false; + } + } + + public static boolean isUnambiguousIdentifierChar(int c) { + return c != '-' && c != '+' && c != '.' && !isDigit(c) && isIdentifierChar(c); + } + + public static boolean isDisallowedIdentifier(String value) { + return DISALLOWED_IDENTIFIERS.contains(value); + } + + public static final Set DISALLOWED_IDENTIFIERS = new HashSet<>(List.of("true", "false", "null", "inf", "-inf", "nan")); + } + + class QuotedString implements StringToken { + public QuotedString(String value) { + this.value = value; + } + + @Override + @Nonnull + public String value() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + QuotedString that = (QuotedString) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return "QuotedString(" + value + ')'; + } + + private final String value; + } +} diff --git a/src/main/java/kdl/parse/lexer/token/Whitespace.java b/src/main/java/kdl/parse/lexer/token/Whitespace.java new file mode 100644 index 0000000..49f0d67 --- /dev/null +++ b/src/main/java/kdl/parse/lexer/token/Whitespace.java @@ -0,0 +1,64 @@ +package kdl.parse.lexer.token; + +import jakarta.annotation.Nonnull; +import java.util.Objects; +import kdl.parse.lexer.Token; + +public class Whitespace implements Token { + public Whitespace(char value) { + this.value = value; + } + + @Override + @Nonnull + public String value() { + return String.valueOf(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (Whitespace) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return String.format("Whitespace(\\u%04x)", (int) value); + } + + private final char value; + + public static boolean isWhitespace(int c) { + switch (c) { + case 0x0009: // Character Tabulation + case 0x000B: // Line Tabulation + case 0x0020: // Space + case 0x00A0: // No-Break Space + case 0x1680: // Ogham Space Mark + case 0x2000: // En Quad + case 0x2001: // Em Quad + case 0x2002: // En Space + case 0x2003: // Em Space + case 0x2004: // Three-Per-Em Space + case 0x2005: // Four-Per-Em Space + case 0x2006: // Six-Per-Em Space + case 0x2007: // Figure Space + case 0x2008: // Punctuation Space + case 0x2009: // Thin Space + case 0x200A: // Hair Space + case 0x202F: // Narrow No-Break Space + case 0x205F: // Medium Mathematical Space + case 0x3000: // Ideographic Space + return true; + default: + return false; + } + } +} diff --git a/src/main/java/kdl/print/KDLPrintException.java b/src/main/java/kdl/print/KDLPrintException.java new file mode 100644 index 0000000..ad176e0 --- /dev/null +++ b/src/main/java/kdl/print/KDLPrintException.java @@ -0,0 +1,11 @@ +package kdl.print; + +public class KDLPrintException extends RuntimeException { + public KDLPrintException(String message) { + super(message); + } + + public KDLPrintException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/kdl/print/KDLPrinter.java b/src/main/java/kdl/print/KDLPrinter.java new file mode 100644 index 0000000..eecf2d0 --- /dev/null +++ b/src/main/java/kdl/print/KDLPrinter.java @@ -0,0 +1,82 @@ +package kdl.print; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import kdl.KDLDocument; + +/** + * Entry point for printing a KDL document. + */ +public class KDLPrinter { + /** + * Creates a new {@link KDLPrinter} with the default configuration. + */ + public KDLPrinter() { + this.configuration = PrinterConfiguration.builder().build(); + } + + /** + * Creates a new {@link KDLPrinter} with a specific configuration. + * + * @param configuration the configuration to use when printing documents + */ + public KDLPrinter(PrinterConfiguration configuration) { + this.configuration = configuration; + } + + /** + * Prints a document into a string. + * + * @param document the document to print + * @return the printed document, in a {@link String} + */ + public String printToString(KDLDocument document) { + var writer = new StringWriter(); + new KDLPrinterContext(writer, configuration).printDocument(document); + return writer.toString(); + } + + /** + * Prints a document to a {@link Writer}. + * + * @param document the document to print + * @param writer the writer to write to + */ + public void print(KDLDocument document, Writer writer) { + var context = new KDLPrinterContext(writer, configuration); + context.printDocument(document); + } + + /** + * Prints a document to an {@link OutputStream}. + * + * @param document the document to print + * @param outputStream the stream to write to + */ + public void print(KDLDocument document, OutputStream outputStream) { + var context = new KDLPrinterContext(new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)), configuration); + context.printDocument(document); + } + + /** + * Prints a document to a file. + * + * @param document the document to print + * @param path the path of the file to write to + */ + public void print(KDLDocument document, Path path) throws IOException { + try (var writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) { + var context = new KDLPrinterContext(writer, configuration); + context.printDocument(document); + } + } + + private final PrinterConfiguration configuration; +} diff --git a/src/main/java/kdl/print/KDLPrinterContext.java b/src/main/java/kdl/print/KDLPrinterContext.java new file mode 100644 index 0000000..9f7885b --- /dev/null +++ b/src/main/java/kdl/print/KDLPrinterContext.java @@ -0,0 +1,234 @@ +package kdl.print; + +import jakarta.annotation.Nullable; +import java.io.IOException; +import java.io.Writer; +import java.util.List; +import java.util.Optional; +import java.util.stream.IntStream; +import kdl.KDLBoolean; +import kdl.KDLDocument; +import kdl.KDLNode; +import kdl.KDLNull; +import kdl.KDLNumber; +import kdl.KDLString; +import kdl.KDLValue; + +import static kdl.parse.lexer.token.Number.isSign; +import static kdl.parse.lexer.token.StringToken.IdentifierString.isIdentifierChar; +import static kdl.parse.lexer.token.StringToken.IdentifierString.isUnambiguousIdentifierChar; + +public class KDLPrinterContext { + KDLPrinterContext(Writer writer, PrinterConfiguration configuration) { + this.writer = writer; + this.configuration = configuration; + } + + void printDocument(KDLDocument document) { + var nodes = document.getNodes(); + printNodes(nodes); + } + + private void printNode(KDLNode node) { + printType(node.getType()); + writeString(node.getName()); + + for (var argument : node.getArguments()) { + if (configuration.shouldPrintNullArguments() || !(argument instanceof KDLNull)) { + write(' '); + printValue(argument); + } + } + + for (var property : node.getProperties()) { + if (configuration.shouldPrintNullProperties() || !(property.getValue() instanceof KDLNull)) { + write(' '); + writeString(property.getName()); + write('='); + printValue(property.getValue()); + } + } + + if (configuration.shouldPrintEmptyChildren() || !node.getChildren().isEmpty()) { + write(" {"); + write(configuration.getNewline()); + depth += 1; + printNodes(node.getChildren()); + depth -= 1; + printIndentation(); + write("}"); + } + } + + private void printValue(KDLValue value) { + if (value instanceof KDLNull) { + printNull((KDLNull) value); + } else if (value instanceof KDLString) { + printString((KDLString) value); + } else if (value instanceof KDLBoolean) { + printBoolean((KDLBoolean) value); + } else if (value instanceof KDLNumber) { + printNumber((KDLNumber) value); + } + } + + private void printNull(KDLNull kdlNull) { + printType(kdlNull.getType()); + write("#null"); + } + + private void printString(KDLString string) { + printType(string.getType()); + writeString(string.getValue()); + } + + private void printBoolean(KDLBoolean kdlBoolean) { + printType(kdlBoolean.getType()); + write(kdlBoolean.getValue() ? "#true" : "#false"); + } + + private void printNodes(List nodes) { + if (nodes.isEmpty() && depth == 0) { + write(configuration.getNewline()); + return; + } + + for (var node : nodes) { + printIndentation(); + printNode(node); + if (configuration.shouldPrintSemicolons()) { + write(';'); + } + write(configuration.getNewline()); + } + } + + private void printNumber(KDLNumber number) { + if (number instanceof KDLNumber.NotANumber) { + printNotANumber((KDLNumber.NotANumber) number); + } else if (number instanceof KDLNumber.PositiveInfinity) { + printPositiveInfinity((KDLNumber.PositiveInfinity) number); + } else if (number instanceof KDLNumber.NegativeInfinity) { + printNegativeInfinity((KDLNumber.NegativeInfinity) number); + } else if (number instanceof KDLNumber.Integer) { + printInteger((KDLNumber.Integer) number); + } else if (number instanceof KDLNumber.Decimal) { + printDecimal((KDLNumber.Decimal) number); + } + } + + private void printNotANumber(KDLNumber.NotANumber notANumber) { + printType(notANumber.getType()); + write("#nan"); + } + + private void printPositiveInfinity(KDLNumber.PositiveInfinity positiveInfinity) { + printType(positiveInfinity.getType()); + write("#inf"); + } + + private void printNegativeInfinity(KDLNumber.NegativeInfinity negativeInfinity) { + printType(negativeInfinity.getType()); + write("#-inf"); + } + + private void printInteger(KDLNumber.Integer integer) { + printType(integer.getType()); + write(integer.getValue().toString()); + } + + private void printDecimal(KDLNumber.Decimal decimal) { + printType(decimal.getType()); + write(configuration.getExponentChar().replaceExponentCharacter(decimal.getValue().toString())); + } + + private void printIndentation() { + IntStream.range(0, depth).forEach(i -> write(configuration.getIndentation())); + } + + private void printType(Optional type) { + if (type.isPresent()) { + write('('); + writeString(type.get()); + write(')'); + } + } + + private void writeString(String string) { + try { + if (string.isEmpty()) { + writer.write("\"\""); + } else { + var needsQuotes = !isValidStartOfIdentifier(string.codePointAt(0)); + var builder = new StringBuilder(); + + for (var c : string.codePoints().toArray()) { + var escaped = escape(c); + if (escaped != null) { + needsQuotes = true; + builder.append(escaped); + } else { + needsQuotes |= !isIdentifierChar(c); + builder.appendCodePoint(c); + } + } + + if (needsQuotes) { + write('"'); + write(builder.toString()); + write('"'); + } else { + write(builder.toString()); + } + } + } catch (IOException e) { + throw new KDLPrintException(e); + } + } + + private boolean isValidStartOfIdentifier(int c) { + return isSign(c) || c == '.' || isUnambiguousIdentifierChar(c); + } + + @Nullable + private String escape(int c) { + switch (c) { + case '\n': + return "\\n"; + case '\r': + return "\\r"; + case '\t': + return "\\t"; + case '\\': + return "\\\\"; + case '"': + return "\\\""; + case '\b': + return "\\b"; + case '\f': + return "\\f"; + default: + return null; + } + } + + private void write(char c) { + try { + writer.write(c); + } catch (IOException e) { + throw new KDLPrintException(e); + } + } + + private void write(String string) { + try { + writer.write(string); + } catch (IOException e) { + throw new KDLPrintException(e); + } + } + + private int depth = 0; + private final Writer writer; + private final PrinterConfiguration configuration; +} diff --git a/src/main/java/kdl/print/PrintConfig.java b/src/main/java/kdl/print/PrintConfig.java deleted file mode 100644 index fe86802..0000000 --- a/src/main/java/kdl/print/PrintConfig.java +++ /dev/null @@ -1,270 +0,0 @@ -package kdl.print; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import static kdl.parse.CharClasses.*; - -/** - * A config object controlling various aspects of how KDL documents are printed. - */ -public class PrintConfig { - public static final PrintConfig PRETTY_DEFAULT = PrintConfig.builder().build(); - public static final PrintConfig RAW_DEFAULT = PrintConfig.builder() - .setIndent(0) - .setEscapeNonAscii(false) - .setPrintEmptyChildren(false) - .build(); - - private final Map escapes; - private final boolean escapeNonPrintableAscii; - private final boolean escapeLinespace; - private final boolean escapeNonAscii; - private final boolean escapeCommon; - private final boolean requireSemicolons; - private final boolean respectRadix; - private final String newline; - private final int indent; - private final char indentChar; - private final char exponentChar; - private final boolean printEmptyChildren; - private final boolean printNullArgs; - private final boolean printNullProps; - - private PrintConfig(Map escapes, boolean escapeNonPrintableAscii, boolean escapeLinespace, - boolean escapeNonAscii, boolean escapeCommon, boolean requireSemicolons, boolean respectRadix, String newline, - int indent, char indentChar, char exponentChar, boolean printEmptyChildren, boolean printNullArgs, - boolean printNullProps) { - - this.escapes = Collections.unmodifiableMap(escapes); - this.escapeNonPrintableAscii = escapeNonPrintableAscii; - this.escapeLinespace = escapeLinespace; - this.escapeNonAscii = escapeNonAscii; - this.escapeCommon = escapeCommon; - this.requireSemicolons = requireSemicolons; - this.respectRadix = respectRadix; - this.newline = newline; - this.indent = indent; - this.indentChar = indentChar; - this.exponentChar = exponentChar; - this.printEmptyChildren = printEmptyChildren; - this.printNullArgs = printNullArgs; - this.printNullProps = printNullProps; - } - - public boolean requiresEscape(int c) { - if (shouldForceEscape(c)) { - return true; - } else if (mustEscape(c)) { - return true; - } else if (escapeLinespace && isUnicodeLinespace(c)) { - return true; - } else if (escapeNonPrintableAscii && !isNonAscii(c) && !isPrintableAscii(c)) { - return true; - } else if (escapeNonAscii && isNonAscii(c)) { - return true; - } else if (escapeCommon && isCommonEscape(c)) { - return true; - } else { - return false; - } - } - - /** - * Check if character has been set to force strings containing it to be escaped - * - * @param c the character to check - * @return true if the character should be escaped, false otherwise. - */ - public boolean shouldForceEscape(int c) { - return escapes.getOrDefault(c, false); - } - - public boolean shouldEscapeNonPrintableAscii() { - return escapeNonPrintableAscii; - } - - public boolean shouldEscapeStandard() { - return escapeCommon; - } - - /** - * @return how many getIndentChar() characters lines will be indented for each level they are away from the root. - * If 0, no indentation will be performed - */ - public int getIndent() { - return indent; - } - - /** - * @return the character used to indent lines - */ - public char getIndentChar() { - return indentChar; - } - - /** - * @return the character used to indicate the beginning of the exponent part of floating point numbers - */ - public char getExponentChar() { - return exponentChar; - } - - /** - * @return true if empty children should be printed with braces containing no nodes, false if they shouldn't be printed - */ - public boolean shouldPrintEmptyChildren() { - return printEmptyChildren; - } - - /** - * @return true if node arguments with the literal value 'null' will be printed - */ - public boolean shouldPrintNullArgs() { - return printNullArgs; - } - - /** - * @return true if node properties with the literal value 'null' will be printed - */ - public boolean shouldPrintNullProps() { - return printNullProps; - } - - /** - * @return true if each node should be terminated with a ';', false if semicolons will be omitted entirely - */ - public boolean shouldRequireSemicolons() { - return requireSemicolons; - } - - /** - * @return true if each number should be printed with its specified radix, false if they should be printed just base-10 - */ - public boolean shouldRespectRadix() { - return respectRadix; - } - - /** - * @return get the string used to print newlines - */ - public String getNewline() { - return newline; - } - - public static Builder builder() { - return new Builder(); - } - - /** - * See get()/should() methods above for explanation of each variable's meaning - */ - public static class Builder { - private final Map escapes = new HashMap<>(); - - private boolean requireSemicolons = false; - private boolean escapeNonAscii = false; - private boolean escapeNonPrintableAscii = true; - private boolean escapeCommon = true; - private boolean escapeLinespace = true; - private boolean respectRadix = true; - private String newline = "\n"; - private int indent = 4; - private char indentChar = ' '; - private char exponentChar = 'E'; - private boolean printEmptyChildren = true; - private boolean printNullArgs = true; - private boolean printNullProps = true; - - public Builder setForceEscapeChar(int c) { - escapes.put(c, true); - return this; - } - - public Builder setEscapeNonPrintableAscii(boolean escapeNonPrintableAscii) { - this.escapeNonPrintableAscii = escapeNonPrintableAscii; - return this; - } - - public Builder setEscapeNonAscii(boolean escapeNonAscii) { - this.escapeNonAscii = escapeNonAscii; - return this; - } - - public Builder setEscapeCommon(boolean escapeCommon) { - this.escapeCommon = escapeCommon; - return this; - } - - public Builder setEscapeLinespace(boolean escapeLinespace) { - this.escapeLinespace = escapeLinespace; - return this; - } - - public Builder setRespectRadix(boolean respectRadix) { - this.respectRadix = respectRadix; - return this; - } - - public Builder setIndent(int indent) { - this.indent = indent; - return this; - } - - public Builder setIndentChar(char indentChar) { - this.indentChar = indentChar; - return this; - } - - public Builder setExponentChar(char exponentChar) { - this.exponentChar = exponentChar; - return this; - } - - public Builder setPrintEmptyChildren(boolean printEmptyChildren) { - this.printEmptyChildren = printEmptyChildren; - return this; - } - - public Builder setPrintNullArgs(boolean printNullArgs) { - this.printNullArgs = printNullArgs; - return this; - } - - public Builder setPrintNullProps(boolean printNullProps) { - this.printNullProps = printNullProps; - return this; - } - - public Builder setRequireSemicolons(boolean requireSemicolons) { - this.requireSemicolons = requireSemicolons; - return this; - } - - public Builder setNewline(String newline) { - this.newline = newline; - return this; - } - - public PrintConfig build() { - if (exponentChar != 'e' && exponentChar != 'E') { - throw new IllegalArgumentException("Exponent character must be either 'e' or 'E'"); - } - - for (int i = 0; i < newline.length(); i++) { - if (!isUnicodeLinespace(newline.charAt(i))) { - throw new IllegalArgumentException("All characters in specified 'newline' must be unicode vertical space"); - } - } - - if (!isUnicodeWhitespace(indentChar)) { - throw new IllegalArgumentException("Indent character must be unicode whitespace"); - } - - return new PrintConfig(escapes, escapeNonPrintableAscii, escapeLinespace, escapeNonAscii, escapeCommon, - requireSemicolons, respectRadix, newline, indent, indentChar, exponentChar, - printEmptyChildren, printNullArgs, printNullProps); - } - } -} diff --git a/src/main/java/kdl/print/PrintUtil.java b/src/main/java/kdl/print/PrintUtil.java deleted file mode 100644 index cef53b2..0000000 --- a/src/main/java/kdl/print/PrintUtil.java +++ /dev/null @@ -1,32 +0,0 @@ -package kdl.print; - -import java.io.IOException; -import java.io.Writer; - -import static kdl.parse.CharClasses.getEscapeIncludingUnicode; -import static kdl.parse.CharClasses.isValidBareId; - -public class PrintUtil { - public static void writeStringQuotedAppropriately(Writer writer, String string, boolean bareAllowed, PrintConfig printConfig) throws IOException { - if (string.isEmpty()) { - writer.write("\"\""); - return; - } - - if (bareAllowed && isValidBareId(string)) { - writer.write(string); - return; - } - - writer.write('"'); - for (int i = 0; i < string.length(); i++) { - final int c = string.charAt(i); - if (printConfig.requiresEscape(c)) { - writer.write(getEscapeIncludingUnicode(c)); - } else { - writer.write(c); - } - } - writer.write('"'); - } -} diff --git a/src/main/java/kdl/print/PrinterConfiguration.java b/src/main/java/kdl/print/PrinterConfiguration.java new file mode 100644 index 0000000..716db4f --- /dev/null +++ b/src/main/java/kdl/print/PrinterConfiguration.java @@ -0,0 +1,321 @@ +package kdl.print; + +import jakarta.annotation.Nonnull; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Configures a {@link KDLPrinter}. + */ +public class PrinterConfiguration { + private PrinterConfiguration( + @Nonnull String indentation, + @Nonnull String newline, + @Nonnull ExponentCharacter exponentChar, + boolean printEmptyChildren, + boolean printNullArguments, + boolean printNullProperties, + boolean printSemicolons + ) { + this.indentation = indentation; + this.newline = newline; + this.exponentChar = exponentChar; + this.printEmptyChildren = printEmptyChildren; + this.printNullArguments = printNullArguments; + this.printNullProperties = printNullProperties; + this.printSemicolons = printSemicolons; + } + + /** + * @return the whitespace characters used for a level of indentation + */ + @Nonnull + public String getIndentation() { + return indentation; + } + + /** + * @return the newline characters used when printing a new line + */ + @Nonnull + public String getNewline() { + return newline; + } + + /** + * @return the character used for the exponent of decimal numbers + */ + @Nonnull + public ExponentCharacter getExponentChar() { + return exponentChar; + } + + /** + * @return whether empty children should be printed + */ + public boolean shouldPrintEmptyChildren() { + return printEmptyChildren; + } + + /** + * @return whether null arguments should be printed + */ + public boolean shouldPrintNullArguments() { + return printNullArguments; + } + + /** + * @return whether null properties should be printed + */ + public boolean shouldPrintNullProperties() { + return printNullProperties; + } + + /** + * @return whether semicolons should be printed after each node + */ + public boolean shouldPrintSemicolons() { + return printSemicolons; + } + + @Nonnull + private final String indentation; + @Nonnull + private final String newline; + @Nonnull + private final ExponentCharacter exponentChar; + private final boolean printEmptyChildren; + private final boolean printNullArguments; + private final boolean printNullProperties; + private final boolean printSemicolons; + + /** + * @return a new builder of {@link PrinterConfiguration}.a new builder of {@link PrinterConfiguration}. + */ + @Nonnull + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link PrinterConfiguration}. + */ + public static final class Builder { + /** + * Sets the indention to a whitespace character. Default is "\t". + * + * @param indentation the whitespace character to use for indentation + * @return {@code this} + */ + @Nonnull + public Builder indentation(@Nonnull Whitespace indentation) { + this.indentation = List.of(indentation); + return this; + } + + /** + * Sets the indentation to whitespace characters. Default is "\t". + * + * @param indentation the whitespace characters to use for indentation + * @return {@code this} + */ + @Nonnull + public Builder indentation(@Nonnull List indentation) { + this.indentation = indentation; + return this; + } + + /** + * Sets the newline character to use when printing a new line. Default is "\n". + * + * @param newline the newline character to use + * @return {@code this} + */ + @Nonnull + public Builder newline(@Nonnull Newline newline) { + this.newline = List.of(newline); + return this; + } + + /** + * Sets the newline characters to use when printing a new line. Default is "\n". + * + * @param newline the newline characters to use + * @return {@code this} + */ + @Nonnull + public Builder newline(@Nonnull List newline) { + this.newline = newline; + return this; + } + + /** + * Sets the exponent character to use when printing decimal numbers. Default is "E". + * + * @param exponentChar the exponent character to use + * @return {@code this} + */ + @Nonnull + public Builder exponentChar(@Nonnull ExponentCharacter exponentChar) { + this.exponentChar = exponentChar; + return this; + } + + /** + * Set that empty children should be printed. Default is {@code false}. + * + * @return {@code this} + */ + @Nonnull + public Builder printEmptyChildren() { + printEmptyChildren = true; + return this; + } + + /** + * Sets whether empty children should be printed. Default is {@code false}. + * + * @param printEmptyChildren whether empty children should be printed + * @return {@code this} + */ + @Nonnull + public Builder printEmptyChildren(boolean printEmptyChildren) { + this.printEmptyChildren = printEmptyChildren; + return this; + } + + /** + * Sets whether null arguments should be printed. Default is {@code true}. + * + * @param printNullArguments whether null arguments should be printed + * @return {@code this} + */ + @Nonnull + public Builder printNullArguments(boolean printNullArguments) { + this.printNullArguments = printNullArguments; + return this; + } + + /** + * Sets whether null properties should be printed. Default is {@code true}. + * + * @param printNullProperties whether null properties should be printed + * @return {@code this} + */ + @Nonnull + public Builder printNullProperties(boolean printNullProperties) { + this.printNullProperties = printNullProperties; + return this; + } + + /** + * Sets that semicolons should be printed after each node. Default is false. + * + * @return {@code this} + */ + @Nonnull + public Builder printSemicolons() { + printSemicolons = true; + return this; + } + + + /** + * Sets whether semicolons should be printed after each node. Default is false. + * + * @param printSemiColons whether semicolons should be printed after each node + * @return {@code this} + */ + @Nonnull + public Builder printSemicolons(boolean printSemiColons) { + this.printSemicolons = printSemiColons; + return this; + } + + @Nonnull + public PrinterConfiguration build() { + return new PrinterConfiguration( + indentation.stream().map(Whitespace::getValue).collect(Collectors.joining()), + newline.stream().map(Newline::getValue).collect(Collectors.joining()), + exponentChar, + printEmptyChildren, + printNullArguments, + printNullProperties, + printSemicolons + ); + } + + @Nonnull + private List indentation = List.of(Whitespace.CHARACTER_TABULATION); + @Nonnull + private List newline = List.of(Newline.LF); + @Nonnull + private ExponentCharacter exponentChar = ExponentCharacter.E; + private boolean printEmptyChildren = false; + private boolean printNullArguments = true; + private boolean printNullProperties = true; + private boolean printSemicolons = false; + } + + public enum Newline { + CR("\r"), + LF("\n"), + CRLF("\r\n"), + NEXT_LINE("\u0085"), + FORM_FEED("\u000C"), + LINE_SEPARATOR("\u2028"), + PARAGRAPH_SEPARATOR("\u2029"); + + Newline(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + private final String value; + } + + public enum Whitespace { + CHARACTER_TABULATION("\t"), + LINE_TABULATION("\u000B"), + SPACE(" "), + NO_BREAK_SPACE("\u00A0"), + OGHAM_SPACE_MARK("\u1680"), + EN_QUAD("\u2000"), + EM_QUAD("\u2001"), + EN_SPACE("\u2002"), + EM_SPACE("\u2003"), + THREE_PER_EM_SPACE("\u2004"), + FOUR_PER_EM_SPACE("\u2005"), + SIX_PER_EM_SPACE("\u2006"), + FIGURE_SPACE("\u2007"), + PUNCTUATION_SPACE("\u2008"), + THIN_SPACE("\u2009"), + HAIR_SPACE("\u200A"), + NARROW_NO_BREAK_SPACE("\u202F"), + MEDIUM_MATHEMATICAL_SPACE("\u205F"), + IDEOGRAPHIC_SPACE("\u3000"); + + Whitespace(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + private final String value; + } + + public enum ExponentCharacter { + e, E; + + public String replaceExponentCharacter(String decimalAsString) { + return this == E + ? decimalAsString + : decimalAsString.replace('E', 'e'); + } + } +} diff --git a/src/main/java/kdl/search/GeneralSearch.java b/src/main/java/kdl/search/GeneralSearch.java deleted file mode 100644 index ac70c25..0000000 --- a/src/main/java/kdl/search/GeneralSearch.java +++ /dev/null @@ -1,185 +0,0 @@ -package kdl.search; - -import kdl.objects.KDLDocument; -import kdl.objects.KDLNode; -import kdl.search.mutation.Mutation; -import kdl.search.predicates.NodePredicate; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Function; - -/** - * Searches through an entire document, bounded by depth limitations, for nodes matching a single predicate. - */ -public class GeneralSearch implements Search { - private final NodePredicate predicate; - private final int minDepth; - private final int maxDepth; - - public GeneralSearch(NodePredicate predicate, int minDepth, int maxDepth) { - this.predicate = predicate; - this.minDepth = minDepth; - this.maxDepth = maxDepth; - } - - /** - * {@inheritDoc} - */ - @Override - public KDLDocument list(KDLDocument document, boolean trim) { - final ArrayList nodes = new ArrayList<>(); - list(document, trim, 0, nodes); - return KDLDocument.builder().addNodes(nodes).build(); - } - - private void list(KDLDocument doc, boolean trim, int depth, List nodes) { - if (depth <= maxDepth) { - for (KDLNode node : doc.getNodes()) { - if (minDepth <= depth && predicate.test(node)) { - final KDLNode.Builder nodeBuilder = node.toBuilder(); - if (trim) { - nodeBuilder.setChild(Optional.empty()); - } - - nodes.add(nodeBuilder.build()); - } - - node.getChild().ifPresent(ch -> list(ch, trim, depth + 1, nodes)); - } - } - } - - /** - * {@inheritDoc} - */ - @Override - public KDLDocument filter(KDLDocument document, boolean trim) { - return filter(document, 0, trim).orElse(KDLDocument.empty()); - } - - private Optional filter(KDLDocument document, int depth, boolean trim) { - if (depth > maxDepth) { - return Optional.empty(); - } - - final KDLDocument.Builder builder = KDLDocument.builder(); - for (KDLNode node : document.getNodes()) { - final Optional newChild = node.getChild().flatMap(doc -> filter(doc, depth + 1, trim)); - if (newChild.isPresent()) { - builder.addNode(node.toBuilder().setChild(newChild).build()); - } else if (predicate.test(node)) { - if (trim) { - builder.addNode(node.toBuilder().setChild(Optional.empty()).build()); - } else { - builder.addNode(node); - } - } - } - - final KDLDocument returnDoc = builder.build(); - if (returnDoc.getNodes().isEmpty()) { - return Optional.empty(); - } else { - return Optional.of(returnDoc); - } - } - - /** - * {@inheritDoc} - */ - @Override - public KDLDocument mutate(KDLDocument document, Mutation fun) { - return mutate(fun, document, 0).orElse(KDLDocument.empty()); - } - - private Optional mutate(Function> fun, KDLDocument doc, int depth) { - if (depth > maxDepth) { - return Optional.of(doc); - } - - final KDLDocument.Builder docBuilder = KDLDocument.builder(); - for (KDLNode node : doc.getNodes()) { - if (depth >= minDepth && predicate.test(node)) { - if (node.getChild().isPresent()) { - final Optional newChild = node.getChild().flatMap(ch -> mutate(fun, ch, depth + 1)); - final KDLNode newNode = node.toBuilder().setChild(newChild).build(); - fun.apply(newNode).ifPresent(docBuilder::addNode); - } else { - fun.apply(node).ifPresent(docBuilder::addNode); - } - } else { - final Optional newChild = node.getChild().flatMap(ch -> mutate(fun, ch, depth + 1)); - docBuilder.addNode(node.toBuilder().setChild(newChild).build()); - } - } - - final KDLDocument newDoc = docBuilder.build(); - if (newDoc.getNodes().isEmpty()) { - return Optional.empty(); - } else { - return Optional.of(newDoc); - } - } - - /** - * {@inheritDoc} - */ - @Override - public boolean anyMatch(KDLDocument document) { - return anyMatch(document, 0); - } - - private boolean anyMatch(KDLDocument document, int depth) { - if (depth > maxDepth) { - return false; - } - - - for (KDLNode node : document.getNodes()) { - if (depth >= minDepth && predicate.test(node)) { - return true; - } else if (node.getChild().map(ch -> anyMatch(ch, depth + 1)).orElse(false)) { - return true; - } - } - - return false; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private NodePredicate predicate = null; - private int minDepth = 0; - private int maxDepth = Integer.MAX_VALUE; - - public Builder setPredicate(NodePredicate predicate) { - this.predicate = predicate; - return this; - } - - public Builder setMinDepth(int minDepth) { - this.minDepth = minDepth; - return this; - } - - public Builder setMaxDepth(int maxDepth) { - this.maxDepth = maxDepth; - return this; - } - - public GeneralSearch build() { - if (minDepth < 0 || maxDepth < 0) { - throw new IllegalArgumentException("Min depth and max depth must be greater than 0"); - } - - Objects.requireNonNull(predicate, "Predicate must be set"); - return new GeneralSearch(predicate, minDepth, maxDepth); - } - } -} diff --git a/src/main/java/kdl/search/PathedSearch.java b/src/main/java/kdl/search/PathedSearch.java deleted file mode 100644 index c7ccd9a..0000000 --- a/src/main/java/kdl/search/PathedSearch.java +++ /dev/null @@ -1,178 +0,0 @@ -package kdl.search; - -import kdl.objects.KDLDocument; -import kdl.objects.KDLNode; -import kdl.search.mutation.Mutation; -import kdl.search.predicates.NodePredicate; - -import java.util.*; - -/** - * Searches for nodes where the path to a node matches a series of predicates. The first predicate provided is used - * to filter nodes at the root, second to filter nodes that are children of the nodes at the root, and so on. - */ -public class PathedSearch implements Search { - private final NavigableMap path; - - private PathedSearch(NavigableMap path) { - this.path = path; - } - - /** - * {@inheritDoc} - */ - @Override - public KDLDocument filter(KDLDocument document, boolean trim) { - return filter(document, 0, trim).orElse(KDLDocument.empty()); - } - - private Optional filter(KDLDocument document, int depth, boolean trim) { - final NodePredicate predicate = path.get(depth); - if (predicate == null) { - return Optional.empty(); - } - - final Integer maxKey = path.floorKey(Integer.MAX_VALUE); - final KDLDocument.Builder builder = KDLDocument.builder(); - for (KDLNode node : document.getNodes()) { - if (predicate.test(node)) { - if (depth == maxKey) { - if (trim) { - builder.addNode(node.toBuilder().setChild(Optional.empty()).build()); - } else { - builder.addNode(node); - } - } else { - final Optional newChild = node.getChild().flatMap(ch -> filter(ch, depth + 1, trim)); - if (newChild.isPresent()) { - builder.addNode(node.toBuilder().setChild(newChild).build()); - } - } - } - } - - final KDLDocument returnDoc = builder.build(); - if (returnDoc.getNodes().isEmpty()) { - return Optional.empty(); - } else { - return Optional.of(returnDoc); - } - } - - /** - * {@inheritDoc} - */ - @Override - public KDLDocument list(KDLDocument document, boolean trim) { - final ArrayList nodes = new ArrayList<>(); - list(document, trim, 0, nodes); - return KDLDocument.builder().addNodes(nodes).build(); - } - - private void list(KDLDocument document, boolean trim, int depth, List nodes) { - final NodePredicate predicate = path.get(depth); - if (predicate == null) { - return; - } - - final Integer maxKey = path.floorKey(Integer.MAX_VALUE); - for (KDLNode node : document.getNodes()) { - if (predicate.test(node)) { - if (depth == maxKey) { - if (trim) { - nodes.add(node.toBuilder().setChild(Optional.empty()).build()); - } else { - nodes.add(node); - } - } else { - node.getChild().ifPresent(ch -> list(ch, trim, depth + 1, nodes)); - } - } - } - } - - /** - * {@inheritDoc} - */ - @Override - public KDLDocument mutate(KDLDocument document, Mutation mutation) { - return mutate(document, mutation, 0).orElse(KDLDocument.empty()); - } - - public Optional mutate(KDLDocument document, Mutation mutation, int depth) { - final NodePredicate predicate = path.get(depth); - if (predicate == null) { - return Optional.of(document); - } - - final Integer maxKey = path.floorKey(Integer.MAX_VALUE); - final KDLDocument.Builder docBuilder = KDLDocument.builder(); - for (KDLNode node : document.getNodes()) { - if (predicate.test(node)) { - if (depth == maxKey) { - mutation.apply(node).ifPresent(docBuilder::addNode); - } else { - final KDLNode.Builder nodeBuilder = node.toBuilder(); - node.getChild().ifPresent(ch -> nodeBuilder.setChild(mutate(ch, mutation, depth + 1))); - docBuilder.addNode(nodeBuilder.build()); - } - } else { - docBuilder.addNode(node); - } - } - - final KDLDocument newDoc = docBuilder.build(); - if (newDoc.getNodes().isEmpty()) { - return Optional.empty(); - } else { - return Optional.of(newDoc); - } - } - - /** - * {@inheritDoc} - */ - @Override - public boolean anyMatch(KDLDocument document) { - return anyMatch(document, 0); - } - - private boolean anyMatch(KDLDocument document, int depth) { - final NodePredicate predicate = path.get(depth); - if (predicate == null) { - return false; - } - - final Integer maxKey = path.floorKey(Integer.MAX_VALUE); - for (KDLNode node : document.getNodes()) { - if (predicate.test(node)) { - if (depth == maxKey) { - return true; - } else if (node.getChild().map(ch -> anyMatch(ch, depth +1)).orElse(false)) { - return true; - } - } - } - - return false; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private final NavigableMap predicates = new TreeMap<>(); - private int depth = 0; - - public Builder addLevel(NodePredicate predicate) { - predicates.put(depth, predicate); - depth++; - return this; - } - - public PathedSearch build() { - return new PathedSearch(predicates); - } - } -} diff --git a/src/main/java/kdl/search/RootSearch.java b/src/main/java/kdl/search/RootSearch.java deleted file mode 100644 index fa62bfc..0000000 --- a/src/main/java/kdl/search/RootSearch.java +++ /dev/null @@ -1,72 +0,0 @@ -package kdl.search; - -import kdl.objects.KDLDocument; -import kdl.objects.KDLNode; -import kdl.search.mutation.Mutation; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -/** - * A "Search" that operates purely on the root with no predicates. Primarily used for applying mutations to the root. - */ -public class RootSearch implements Search { - private static final KDLNode EMPTY_NODE = KDLNode.builder().setIdentifier("empty").build(); - - /** - * {@inheritDoc} - */ - @Override - public KDLDocument filter(KDLDocument document, boolean trim) { - if (trim) { - final KDLDocument.Builder builder = KDLDocument.builder(); - for (KDLNode node : document.getNodes()) { - builder.addNode(node.toBuilder().setChild(Optional.empty()).build()); - } - return builder.build(); - } else { - return document; - } - - } - - /** - * {@inheritDoc} - */ - @Override - public KDLDocument list(KDLDocument document, boolean trim) { - final ArrayList nodes = new ArrayList<>(); - list(document, trim, nodes); - return KDLDocument.builder().addNodes(nodes).build(); - } - - private void list(KDLDocument document, boolean trim, List nodes) { - for (KDLNode node : document.getNodes()) { - final KDLNode.Builder nodeBuilder = node.toBuilder(); - if (trim) { - nodeBuilder.setChild(Optional.empty()); - } - - nodes.add(nodeBuilder.build()); - node.getChild().ifPresent(doc -> list(doc, trim, nodes)); - } - } - - /** - * Applies mutation to a temporary, empty node. If the resulting node is present and has a child, any nodes - * contained in it are added to the root and the result returned. All other aspects of the returned node are ignored. - */ - @Override - public KDLDocument mutate(KDLDocument document, Mutation mutation) { - final KDLNode result = mutation.apply(EMPTY_NODE).orElse(EMPTY_NODE); - return document.toBuilder() - .addNodes(result.getChild().orElse(KDLDocument.empty()).getNodes()) - .build(); - } - - @Override - public boolean anyMatch(KDLDocument document) { - return !document.getNodes().isEmpty(); - } -} diff --git a/src/main/java/kdl/search/Search.java b/src/main/java/kdl/search/Search.java deleted file mode 100644 index 8ca9ded..0000000 --- a/src/main/java/kdl/search/Search.java +++ /dev/null @@ -1,46 +0,0 @@ -package kdl.search; - -import kdl.objects.KDLDocument; -import kdl.search.mutation.Mutation; - -/** - * Interface implemented by all search types - */ -public interface Search { - - /** - * Filter branches in the document to only those containing nodes matching the search. - * - * @param document the document to search - * @param trim if true, trim the resulting tree such that all leaves match the search - * @return a new document filtered to branches with matching nodes - */ - KDLDocument filter(KDLDocument document, boolean trim); - - /** - * Creates a new document with all nodes matching the search promoted to the root. If trim is not set a given node - * may appear multiple times in the resulting document if multiple nodes on a branch match the search. - * - * @param document the document to search - * @param trim if true, trim the children off of all resulting nodes - * @return a new document with all matching nodes at the root - */ - KDLDocument list(KDLDocument document, boolean trim); - - /** - * Applies a mutation to all nodes in the tree matching the search in a depth-first fashion, returning the full resulting tree - * - * @param document the document to mutate - * @param mutation the mutation to apply to all matching nodes - * @return a new document with the mutation applied - */ - KDLDocument mutate(KDLDocument document, Mutation mutation); - - /** - * Searches for any matching nodes in the tree - * - * @param document the document to search - * @return true if a matching node is found, or false otherwise - */ - boolean anyMatch(KDLDocument document); -} diff --git a/src/main/java/kdl/search/mutation/AddMutation.java b/src/main/java/kdl/search/mutation/AddMutation.java deleted file mode 100644 index 4d0d1f1..0000000 --- a/src/main/java/kdl/search/mutation/AddMutation.java +++ /dev/null @@ -1,104 +0,0 @@ -package kdl.search.mutation; - -import kdl.objects.KDLDocument; -import kdl.objects.KDLNode; -import kdl.objects.KDLValue; - -import java.util.*; - -/** - * Mutation adding fields to a node - * - If any positional args are specified, they will be inserted and other args shifted right to accommodate. - * - If any other args are specified, they'll be added to the right side of the argument list. - * - If any properties are specified, they'll be added to the properties of the map, overwriting any previous values - * if an existing key is specified. - * - If a child is specified, any nodes in the child will be appended to the existing child if one exists. If none exists, - * the child will be set to the provided child. - */ -public class AddMutation implements Mutation { - private final List> args; - private final Map> positionalArgs; - private final Map> props; - private final Optional child; - - private AddMutation(List> args, Map> positionalArgs, Map> props, Optional child) { - this.args = args; - this.positionalArgs = positionalArgs; - this.props = props; - this.child = child; - } - - public Map> getPositionalArgs() { - return positionalArgs; - } - - public List> getArgs() { - return args; - } - - public Map> getProps() { - return props; - } - - public Optional getChild() { - return child; - } - - @Override - public Optional apply(KDLNode node) { - final KDLNode.Builder builder = node.toBuilder(); - - for (Map.Entry> positionalArg : positionalArgs.entrySet()) { - builder.insertArgAt(positionalArg.getKey(), positionalArg.getValue()); - } - - builder.addAllArgs(args); - for (String key : props.keySet()) { - builder.addProp(key, props.get(key)); - } - - if (node.getChild().isPresent() && child.isPresent()) { - final KDLDocument newChild = node.getChild().get().toBuilder().addNodes(child.get().getNodes()).build(); - builder.setChild(newChild); - } else { - builder.setChild(child); - } - - return Optional.of(builder.build()); - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private final Map> positionalArgs = new HashMap<>(); - private final List> args = new ArrayList<>(); - private final Map> props = new HashMap<>(); - private Optional child = Optional.empty(); - - public Builder addArg(KDLValue arg) { - args.add(arg); - return this; - } - - public Builder addProp(String key, KDLValue value) { - props.put(key, value); - return this; - } - - public Builder setChild(KDLDocument child) { - this.child = Optional.of(child); - return this; - } - - public Builder addPositionalArg(int position, KDLValue arg) { - this.positionalArgs.put(position, arg); - return this; - } - - public AddMutation build() { - return new AddMutation(args, positionalArgs, props, child); - } - } -} diff --git a/src/main/java/kdl/search/mutation/Mutation.java b/src/main/java/kdl/search/mutation/Mutation.java deleted file mode 100644 index 42def32..0000000 --- a/src/main/java/kdl/search/mutation/Mutation.java +++ /dev/null @@ -1,13 +0,0 @@ -package kdl.search.mutation; - -import kdl.objects.KDLNode; - -import java.util.Optional; -import java.util.function.Function; - -/** - * Interface implemented by all classes or lambdas that make changes to a document tree based on a Search. If an - * implementation returns Optional.empty() the node will be deleted. - */ -public interface Mutation extends Function> { -} diff --git a/src/main/java/kdl/search/mutation/SetMutation.java b/src/main/java/kdl/search/mutation/SetMutation.java deleted file mode 100644 index 251e6cd..0000000 --- a/src/main/java/kdl/search/mutation/SetMutation.java +++ /dev/null @@ -1,102 +0,0 @@ -package kdl.search.mutation; - -import kdl.objects.KDLDocument; -import kdl.objects.KDLNode; -import kdl.objects.KDLValue; - -import java.util.*; - -/** - * Mutation setting various fields of a node. - * - If identifier is provided, will set the node's name to the provided value - * - If any non-positional args are provided the argument list of the node will be cleared and replaced by the provided - * arguments. Note that if positional arguments are provided the clear will occur prior to any arguments being set, - * then the positional arguments will be set, then any non-positional arguments appended to the right side. - * - If any (position, value) pairs are in positionalArgs, then the arguments at the specified index will be set to the - * provided value. If the current list is smaller than position, the argument list will be right-padded with 'null' - * values to allow the position to be set. - * - If any properties are specified in props, the key=value pair will be set on the node, or added if it wasn't present. - * - If child is provided, the current child will be discarded and replaced be the provided one. - */ -public class SetMutation implements Mutation { - private final Optional identifier; - private final Map> positionalArgs; - private final List> args; - private final Map> props; - private final Optional> child; - - private SetMutation(Optional identifier, Map> positionalArgs, List> args, - Map> props, Optional> child) { - - this.identifier = identifier; - this.positionalArgs = positionalArgs; - this.args = args; - this.props = props; - this.child = child; - } - - @Override - public Optional apply(KDLNode node) { - final KDLNode.Builder builder = node.toBuilder(); - identifier.ifPresent(builder::setIdentifier); - child.ifPresent(builder::setChild); - - if (!args.isEmpty()) { - builder.clearArgs(); - } - - for (Map.Entry> positionalArg : positionalArgs.entrySet()) { - builder.insertArgAt(positionalArg.getKey(), positionalArg.getValue()); - } - - builder.addAllArgs(args); - - if (!props.isEmpty()) { - builder.clearProps(); - builder.addAllProps(props); - } - - return Optional.of(builder.build()); - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private final Map> positionalArgs = new HashMap<>(); - private final List> args = new ArrayList<>(); - private final Map> props = new HashMap<>(); - private Optional identifier = Optional.empty(); - private Optional> child = Optional.empty(); - - public Builder addArg(KDLValue arg) { - args.add(arg); - return this; - } - - public Builder addProp(String key, KDLValue value) { - props.put(key, value); - return this; - } - - public Builder setChild(Optional child) { - this.child = Optional.of(child); - return this; - } - - public Builder setIdentifier(String identifier) { - this.identifier = Optional.of(identifier); - return this; - } - - public Builder addPositionalArg(int position, KDLValue arg) { - this.positionalArgs.put(position, arg); - return this; - } - - public SetMutation build() { - return new SetMutation(identifier, positionalArgs, args, props, child); - } - } -} diff --git a/src/main/java/kdl/search/mutation/SubtractMutation.java b/src/main/java/kdl/search/mutation/SubtractMutation.java deleted file mode 100644 index 4e4ebe4..0000000 --- a/src/main/java/kdl/search/mutation/SubtractMutation.java +++ /dev/null @@ -1,133 +0,0 @@ -package kdl.search.mutation; - -import kdl.objects.KDLDocument; -import kdl.objects.KDLNode; -import kdl.objects.KDLProperty; -import kdl.objects.KDLValue; -import kdl.parse.KDLInternalException; - -import java.util.*; -import java.util.function.Predicate; - -/** - * Mutation removing contents of a node or the node itself. - * - * Args are removed if their index is included in positionalArgs or are matched by an argPredicate - * Properties are removed if they match a predicate in propPredicates - * Only one of emptyChild and deleteChild may be set. - * - If set, emptyChild will remove all contents from the node's child, leaving an empty child - * - If set, deleteChild will remove the child entirely - */ -public class SubtractMutation implements Mutation { - private final Set positionalArgs; - private final List>> argPredicates; - private final List> propPredicates; - private final boolean emptyChild; - private final boolean deleteChild; - - private SubtractMutation(Set positionalArgs, List>> argPredicates, - List> propPredicates, boolean emptyChild, boolean deleteChild) { - if (emptyChild && deleteChild) { - throw new IllegalArgumentException("Only one of emptyChild and deleteChild may be set."); - } - - this.positionalArgs = positionalArgs; - this.argPredicates = argPredicates; - this.propPredicates = propPredicates; - this.emptyChild = emptyChild; - this.deleteChild = deleteChild; - } - - @Override - public Optional apply(KDLNode node) { - if (argPredicates.isEmpty() && propPredicates.isEmpty() && positionalArgs.isEmpty() && !emptyChild && !deleteChild) { - return Optional.empty(); - } - - final KDLNode.Builder builder = KDLNode.builder().setIdentifier(node.getIdentifier()); - - for (int i = 0; i < node.getArgs().size(); i++) { - if (!positionalArgs.contains(i)) { - boolean matchesAny = false; - for (Predicate> argPredicate : argPredicates) { - if (argPredicate.test(node.getArgs().get(i))) { - matchesAny = true; - } - } - - if (!matchesAny) { - builder.addArg(node.getArgs().get(i)); - } - } - } - - for (String propKey : node.getProps().keySet()) { - final KDLProperty property = new KDLProperty(propKey, node.getProps().get(propKey)); - boolean matchesAny = false; - for (Predicate propPredicate : propPredicates) { - matchesAny |= propPredicate.test(property); - } - - if (!matchesAny) { - builder.addProp(property); - } - } - - if (emptyChild && node.getChild().isPresent()) { - builder.setChild(KDLDocument.empty()); - } else if (!deleteChild) { - builder.setChild(node.getChild()); - } - - return Optional.of(builder.build()); - } - - public static SubtractMutation deleteNodeMutation() { - return new SubtractMutation(Collections.emptySet(), Collections.emptyList(), Collections.emptyList(), false, false); - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private final List>> argPredicates = new ArrayList<>(); - private final List> propPredicates = new ArrayList<>(); - private final Set positionalArgs = new HashSet<>(); - private boolean emptyChild = false; - private boolean deleteChild = false; - - public Builder addArg(Predicate> predicate) { - argPredicates.add(predicate); - return this; - } - - public Builder addProp(Predicate predicate) { - propPredicates.add(predicate); - return this; - } - - public Builder deleteChild() { - this.deleteChild = true; - return this; - } - - public Builder emptyChild() { - this.emptyChild = true; - return this; - } - - public Builder deleteArgAt(int position) { - this.positionalArgs.add(position); - return this; - } - - public SubtractMutation build() { - if (emptyChild && deleteChild) { - throw new KDLInternalException("Only one of empty child and delete child may be specified"); - } - - return new SubtractMutation(positionalArgs, argPredicates, propPredicates, emptyChild, deleteChild); - } - } -} diff --git a/src/main/java/kdl/search/predicates/AnyContentPredicate.java b/src/main/java/kdl/search/predicates/AnyContentPredicate.java deleted file mode 100644 index 0ccc968..0000000 --- a/src/main/java/kdl/search/predicates/AnyContentPredicate.java +++ /dev/null @@ -1,15 +0,0 @@ -package kdl.search.predicates; - -import kdl.objects.KDLNode; - -/** - * Returns true if a node has any contents, false otherwise - */ -public class AnyContentPredicate implements NodeContentPredicate { - @Override - public boolean test(KDLNode node) { - return !node.getArgs().isEmpty() - || !node.getProps().isEmpty() - || (node.getChild().isPresent() && !node.getChild().get().getNodes().isEmpty()); - } -} diff --git a/src/main/java/kdl/search/predicates/ArgPredicate.java b/src/main/java/kdl/search/predicates/ArgPredicate.java deleted file mode 100644 index e533cd8..0000000 --- a/src/main/java/kdl/search/predicates/ArgPredicate.java +++ /dev/null @@ -1,32 +0,0 @@ -package kdl.search.predicates; - -import kdl.objects.KDLNode; -import kdl.objects.KDLValue; - -import java.util.function.Predicate; - -/** - * Matches nodes based on applying a given predicate to all arguments, returning true if any match. - */ -public class ArgPredicate implements NodeContentPredicate { - private final Predicate predicate; - - public ArgPredicate(Predicate predicate) { - this.predicate = predicate; - } - - @Override - public boolean test(KDLNode node) { - for (KDLValue arg : node.getArgs()) { - if (predicate.test(arg)) { - return true; - } - } - - return false; - } - - public Predicate getPredicate() { - return predicate; - } -} diff --git a/src/main/java/kdl/search/predicates/ChildPredicate.java b/src/main/java/kdl/search/predicates/ChildPredicate.java deleted file mode 100644 index f136c68..0000000 --- a/src/main/java/kdl/search/predicates/ChildPredicate.java +++ /dev/null @@ -1,30 +0,0 @@ -package kdl.search.predicates; - -import kdl.objects.KDLNode; -import kdl.search.Search; - -import java.util.Optional; - -/** - * Matches nodes based on the contents, or absence, of a child - */ -public class ChildPredicate implements NodeContentPredicate { - private final Optional search; - - public ChildPredicate(Optional search) { - this.search = search; - } - - @Override - public boolean test(KDLNode node) { - if (!search.isPresent()) { - return !node.getChild().isPresent() || node.getChild().get().getNodes().isEmpty(); - } - - return node.getChild().map(ch -> search.get().anyMatch(ch)).orElse(false); - } - - public static ChildPredicate empty() { - return new ChildPredicate(Optional.empty()); - } -} diff --git a/src/main/java/kdl/search/predicates/ConjunctionPredicate.java b/src/main/java/kdl/search/predicates/ConjunctionPredicate.java deleted file mode 100644 index f71357e..0000000 --- a/src/main/java/kdl/search/predicates/ConjunctionPredicate.java +++ /dev/null @@ -1,21 +0,0 @@ -package kdl.search.predicates; - -import kdl.objects.KDLNode; - -/** - * Returns true only if both wrapped predicates return true - */ -public class ConjunctionPredicate implements NodeContentPredicate { - private final NodeContentPredicate predOne; - private final NodeContentPredicate predTwo; - - public ConjunctionPredicate(NodeContentPredicate predOne, NodeContentPredicate predTwo) { - this.predOne = predOne; - this.predTwo = predTwo; - } - - @Override - public boolean test(KDLNode node) { - return predOne.test(node) && predTwo.test(node); - } -} diff --git a/src/main/java/kdl/search/predicates/DisjunctionPredicate.java b/src/main/java/kdl/search/predicates/DisjunctionPredicate.java deleted file mode 100644 index c52e70d..0000000 --- a/src/main/java/kdl/search/predicates/DisjunctionPredicate.java +++ /dev/null @@ -1,21 +0,0 @@ -package kdl.search.predicates; - -import kdl.objects.KDLNode; - -/** - * Returns true if either or both wrapped predicates return true - */ -public class DisjunctionPredicate implements NodeContentPredicate { - private final NodeContentPredicate predOne; - private final NodeContentPredicate predTwo; - - public DisjunctionPredicate(NodeContentPredicate predOne, NodeContentPredicate predTwo) { - this.predOne = predOne; - this.predTwo = predTwo; - } - - @Override - public boolean test(KDLNode node) { - return predOne.test(node) || predTwo.test(node); - } -} diff --git a/src/main/java/kdl/search/predicates/EmptyContentPredicate.java b/src/main/java/kdl/search/predicates/EmptyContentPredicate.java deleted file mode 100644 index 27ba544..0000000 --- a/src/main/java/kdl/search/predicates/EmptyContentPredicate.java +++ /dev/null @@ -1,13 +0,0 @@ -package kdl.search.predicates; - -import kdl.objects.KDLNode; - -/** - * Matches any node with no content, ie only a name. - */ -public class EmptyContentPredicate implements NodeContentPredicate { - @Override - public boolean test(KDLNode node) { - return node.getArgs().isEmpty() && node.getProps().isEmpty() && !node.getChild().isPresent(); - } -} diff --git a/src/main/java/kdl/search/predicates/NegatedPredicate.java b/src/main/java/kdl/search/predicates/NegatedPredicate.java deleted file mode 100644 index eeb5517..0000000 --- a/src/main/java/kdl/search/predicates/NegatedPredicate.java +++ /dev/null @@ -1,19 +0,0 @@ -package kdl.search.predicates; - -import kdl.objects.KDLNode; - -/** - * Inverts the test of the wrapped node content predicate - */ -public class NegatedPredicate implements NodeContentPredicate { - private final NodeContentPredicate predicate; - - public NegatedPredicate(NodeContentPredicate predicate) { - this.predicate = predicate; - } - - @Override - public boolean test(KDLNode node) { - return !predicate.test(node); - } -} diff --git a/src/main/java/kdl/search/predicates/NodeContentPredicate.java b/src/main/java/kdl/search/predicates/NodeContentPredicate.java deleted file mode 100644 index 116bbb7..0000000 --- a/src/main/java/kdl/search/predicates/NodeContentPredicate.java +++ /dev/null @@ -1,16 +0,0 @@ -package kdl.search.predicates; - -import kdl.objects.KDLNode; - -import java.util.function.Predicate; - -/** - * Interface implemented by all predicates that operate on the contents of a node, where contents are arguments, - * properties, and child - */ -public interface NodeContentPredicate extends Predicate { - - static NodeContentPredicate any() { - return node -> true; - } -} diff --git a/src/main/java/kdl/search/predicates/NodePredicate.java b/src/main/java/kdl/search/predicates/NodePredicate.java deleted file mode 100644 index 65892e9..0000000 --- a/src/main/java/kdl/search/predicates/NodePredicate.java +++ /dev/null @@ -1,28 +0,0 @@ -package kdl.search.predicates; - -import kdl.objects.KDLNode; - -import java.util.function.Predicate; - -public class NodePredicate implements Predicate { - private final Predicate identifierPredicate; - private final NodeContentPredicate contentPredicate; - - public NodePredicate(Predicate identifierPredicate, NodeContentPredicate contentPredicate) { - this.identifierPredicate = identifierPredicate; - this.contentPredicate = contentPredicate; - } - - @Override - public boolean test(KDLNode node) { - return identifierPredicate.test(node.getIdentifier()) && contentPredicate.test(node); - } - - public static NodePredicate hasName(String name) { - return new NodePredicate(Predicate.isEqual(name), NodeContentPredicate.any()); - } - - public static NodePredicate any() { - return new NodePredicate(id -> true, node -> true); - } -} diff --git a/src/main/java/kdl/search/predicates/PositionalArgPredicate.java b/src/main/java/kdl/search/predicates/PositionalArgPredicate.java deleted file mode 100644 index 636e8e1..0000000 --- a/src/main/java/kdl/search/predicates/PositionalArgPredicate.java +++ /dev/null @@ -1,28 +0,0 @@ -package kdl.search.predicates; - -import kdl.objects.KDLNode; -import kdl.objects.KDLValue; - -import java.util.function.Predicate; - -/** - * Predicate matching a node based on a specific argument to a node based on that arguments position in the argument list. - */ -public class PositionalArgPredicate implements NodeContentPredicate { - private final int position; - private final Predicate predicate; - - public PositionalArgPredicate(int position, Predicate predicate) { - this.position = position; - this.predicate = predicate; - } - - @Override - public boolean test(KDLNode node) { - if (position < node.getArgs().size()) { - return predicate.test(node.getArgs().get(position)); - } else { - return false; - } - } -} diff --git a/src/main/java/kdl/search/predicates/PropPredicate.java b/src/main/java/kdl/search/predicates/PropPredicate.java deleted file mode 100644 index cae0b51..0000000 --- a/src/main/java/kdl/search/predicates/PropPredicate.java +++ /dev/null @@ -1,37 +0,0 @@ -package kdl.search.predicates; - -import java.util.function.Predicate; -import kdl.objects.KDLNode; -import kdl.objects.KDLValue; - -/** - * Predicate matching a KDLNode property - */ -public class PropPredicate implements NodeContentPredicate { - private final Predicate keyPredicate; - private final Predicate> valuePredicate; - - public PropPredicate(Predicate keyPredicate, Predicate> valuePredicate) { - this.keyPredicate = keyPredicate; - this.valuePredicate = valuePredicate; - } - - @Override - public boolean test(KDLNode node) { - for (String key : node.getProps().keySet()) { - if (keyPredicate.test(key) && valuePredicate.test(node.getProps().get(key))) { - return true; - } - } - - return false; - } - - public Predicate getKeyPredicate() { - return keyPredicate; - } - - public Predicate> getValuePredicate() { - return valuePredicate; - } -} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..757ccdd --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,7 @@ +module kdl { + requires jakarta.annotation; + exports kdl; + exports kdl.parse; + exports kdl.print; + exports kdl.parse.error; +} diff --git a/src/test/java/kdl/RoundTripTest.java b/src/test/java/kdl/RoundTripTest.java index 0ff2ff6..f43ecad 100644 --- a/src/test/java/kdl/RoundTripTest.java +++ b/src/test/java/kdl/RoundTripTest.java @@ -9,11 +9,13 @@ import java.util.stream.Collectors; import kdl.parse.KDLParseException; import kdl.parse.KDLParser; -import kdl.print.PrintConfig; +import kdl.print.KDLPrinter; +import kdl.print.PrinterConfiguration; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import static kdl.print.PrinterConfiguration.Whitespace.SPACE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -23,8 +25,8 @@ public class RoundTripTest { @MethodSource("inputs") void roundTripTest(String input, Optional expected) throws IOException { try { - var document = parser.parse(Files.newInputStream(getInputPath(input))); - var output = document.toKDLPretty(PRINT_CONFIG); + var document = KDLParser.parse(getInputPath(input)); + var output = PRINTER.printToString(document); if (expected.isEmpty()) { fail("Parse exception expected but got:\n%s", output); @@ -58,12 +60,12 @@ static List inputs() throws IOException { } } - private final KDLParser parser = new KDLParser(); + private static final KDLPrinter PRINTER = new KDLPrinter( + PrinterConfiguration.builder() + .indentation(List.of(SPACE, SPACE, SPACE, SPACE)) + .build() + ); private static final Path INPUT_FOLDER = Paths.get("src/test/resources/test_cases/input"); private static final Path EXPECTED_FOLDER = Paths.get("src/test/resources/test_cases/expected_kdl"); - private static final PrintConfig PRINT_CONFIG = PrintConfig.builder() - .setEscapeLinespace(true) - .build(); - } diff --git a/src/test/java/kdl/parse/ConsumeWhitespaceAndBlockCommentsTest.java b/src/test/java/kdl/parse/ConsumeWhitespaceAndBlockCommentsTest.java deleted file mode 100644 index 824a8ad..0000000 --- a/src/test/java/kdl/parse/ConsumeWhitespaceAndBlockCommentsTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package kdl.parse; - -import java.io.IOException; -import java.util.Optional; -import java.util.stream.Stream; -import kdl.parse.KDLParser.WhitespaceResult; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import static kdl.parse.KDLParser.WhitespaceResult.NODE_SPACE; -import static kdl.parse.KDLParser.WhitespaceResult.NO_WHITESPACE; -import static kdl.parse.ParserTest.testParser; - -public class ConsumeWhitespaceAndBlockCommentsTest { - public static Stream getCases() { - return Stream.of( - Arguments.of("", Optional.of(NO_WHITESPACE), ""), - Arguments.of("\\\r\na", Optional.of(NODE_SPACE), "a"), - Arguments.of(" \\\r\n \\\n \\\ra", Optional.of(NODE_SPACE), "a"), - Arguments.of(" a ", Optional.of(NODE_SPACE), "a "), - Arguments.of("a", Optional.of(NO_WHITESPACE), "a"), - Arguments.of("\\\na", Optional.of(NODE_SPACE), "a"), - Arguments.of("\\\ra", Optional.of(NODE_SPACE), "a"), - Arguments.of("\t", Optional.of(NODE_SPACE), ""), - Arguments.of("/* comment */a", Optional.of(NODE_SPACE), "a"), - Arguments.of("\t a", Optional.of(NODE_SPACE), "a"), - Arguments.of("/- /- a", Optional.empty(), " a") - ); - } - - @ParameterizedTest(name = "\"{0}\"") - @MethodSource("getCases") - void testConsumeWhitespaceAndBlockComments(String input, Optional expectedResult, String expectedRemainder) throws IOException { - testParser(input, KDLParser::consumeWhitespaceAndBlockComments, expectedResult, expectedRemainder); - } -} diff --git a/src/test/java/kdl/parse/ConsumeWhitespaceAndLinespaceTest.java b/src/test/java/kdl/parse/ConsumeWhitespaceAndLinespaceTest.java deleted file mode 100644 index fdb542e..0000000 --- a/src/test/java/kdl/parse/ConsumeWhitespaceAndLinespaceTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package kdl.parse; - -import java.io.IOException; -import java.util.Optional; -import java.util.stream.Stream; -import kdl.parse.KDLParser.WhitespaceResult; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import static kdl.parse.KDLParser.WhitespaceResult.END_NODE; -import static kdl.parse.KDLParser.WhitespaceResult.NODE_SPACE; -import static kdl.parse.KDLParser.WhitespaceResult.NO_WHITESPACE; -import static kdl.parse.KDLParser.WhitespaceResult.SKIP_NEXT; -import static kdl.parse.ParserTest.testParser; - -public class ConsumeWhitespaceAndLinespaceTest { - public static Stream getCases() { - return Stream.of( - Arguments.of("", Optional.of(END_NODE), ""), - Arguments.of("\n", Optional.of(END_NODE), ""), - Arguments.of(" \n/- a", Optional.of(SKIP_NEXT), "a"), - Arguments.of("\\\r\na", Optional.of(NODE_SPACE), "a"), - Arguments.of(" \\\r\n \\\n \\\ra", Optional.of(NODE_SPACE), "a"), - Arguments.of(" a ", Optional.of(NODE_SPACE), "a "), - Arguments.of("a", Optional.of(NO_WHITESPACE), "a"), - Arguments.of("\\\na", Optional.of(NODE_SPACE), "a"), - Arguments.of("\\\ra", Optional.of(NODE_SPACE), "a"), - Arguments.of("\t", Optional.of(END_NODE), ""), - Arguments.of("/* comment */a", Optional.of(NODE_SPACE), "a"), - Arguments.of("\t a", Optional.of(NODE_SPACE), "a"), - Arguments.of("\\ a", Optional.empty(), "a"), - Arguments.of("/- ", Optional.empty(), ""), - Arguments.of("/- \n", Optional.empty(), "\n") - ); - } - - @ParameterizedTest(name = "\"{0}\"") - @MethodSource("getCases") - void testConsumeWhitespaceAndLinespace(String input, Optional expectedResult, String expectedRemainder) throws IOException { - testParser(input, KDLParser::consumeWhitespaceAndLinespace, expectedResult, expectedRemainder); - } -} diff --git a/src/test/java/kdl/parse/GetEscapedTest.java b/src/test/java/kdl/parse/GetEscapedTest.java deleted file mode 100644 index f2e1dcf..0000000 --- a/src/test/java/kdl/parse/GetEscapedTest.java +++ /dev/null @@ -1,59 +0,0 @@ -package kdl.parse; - -import java.io.IOException; -import java.io.StringReader; -import java.util.stream.Stream; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import static org.assertj.core.api.Assertions.assertThat; - -public class GetEscapedTest { - public static Stream getCases() { - return Stream.of( - Arguments.of("n", '\n'), - Arguments.of("r", '\r'), - Arguments.of("t", '\t'), - Arguments.of("\\", '\\'), - Arguments.of("/", '/'), - Arguments.of("\"", '\"'), - Arguments.of("b", '\b'), - Arguments.of("f", '\f'), - Arguments.of("u{1}", '\u0001'), - Arguments.of("u{01}", '\u0001'), - Arguments.of("u{001}", '\u0001'), - Arguments.of("u{001}", '\u0001'), - Arguments.of("u{0001}", '\u0001'), - Arguments.of("u{00001}", '\u0001'), - Arguments.of("u{000001}", '\u0001'), - Arguments.of("u{10FFFF}", 0x10FFFF), - Arguments.of("i", -2), - Arguments.of("ux", -2), - Arguments.of("u{x}", -2), - Arguments.of("u{0001", -2), - Arguments.of("u{AX}", -2), - Arguments.of("u{}", -2), - Arguments.of("u{0000001}", -2), - Arguments.of("u{110000}", -2) - ); - } - - @ParameterizedTest(name = "\"{0}\"") - @MethodSource("getCases") - public void testGetEscaped(String input, int expectedResult) throws IOException { - var context = new KDLParseContext(new StringReader(input)); - var initial = context.read(); - - try { - var result = parser.getEscaped(initial, context); - assertThat(result).isEqualTo(expectedResult); - } catch (KDLParseException e) { - if (expectedResult > 0) { - throw new KDLParseException("Expected no errors", e); - } - } - } - - private static final KDLParser parser = new KDLParser(); -} diff --git a/src/test/java/kdl/parse/GetSlashActionTest.java b/src/test/java/kdl/parse/GetSlashActionTest.java deleted file mode 100644 index 0b108b5..0000000 --- a/src/test/java/kdl/parse/GetSlashActionTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package kdl.parse; - -import java.io.IOException; -import java.util.Optional; -import java.util.stream.Stream; -import kdl.parse.KDLParser.SlashAction; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import static kdl.parse.KDLParser.SlashAction.END_NODE; -import static kdl.parse.KDLParser.SlashAction.NOTHING; -import static kdl.parse.KDLParser.SlashAction.SKIP_NEXT; -import static kdl.parse.ParserTest.testParser; - -public class GetSlashActionTest { - public static Stream getCases() { - return Stream.of( - Arguments.of("// stuff\n", Optional.of(END_NODE), "\n"), - Arguments.of("// stuff \r\n", Optional.of(END_NODE), "\r\n"), - Arguments.of("/- stuff", Optional.of(SKIP_NEXT), " stuff"), - Arguments.of("/* comment */", Optional.of(NOTHING), ""), - Arguments.of("/* comment */", Optional.of(NOTHING), ""), - Arguments.of("/**/", Optional.of(NOTHING), ""), - Arguments.of("/*/**/*/", Optional.of(NOTHING), ""), - Arguments.of("/* /* */*/", Optional.of(NOTHING), ""), - Arguments.of("/* ", Optional.empty(), ""), - Arguments.of("/? ", Optional.empty(), null) - ); - } - - @ParameterizedTest(name = "\"{0}\"") - @MethodSource("getCases") - void testGetSlashAction(String input, Optional expectedResult, String expectedRemainder) throws IOException { - testParser(input, (parser, context) -> parser.getSlashAction(context, false), expectedResult, expectedRemainder); - } -} diff --git a/src/test/java/kdl/parse/KDLParserTest.java b/src/test/java/kdl/parse/KDLParserTest.java deleted file mode 100644 index fe01dc8..0000000 --- a/src/test/java/kdl/parse/KDLParserTest.java +++ /dev/null @@ -1,463 +0,0 @@ -package kdl.parse; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import kdl.objects.KDLBoolean; -import kdl.objects.KDLDocument; -import kdl.objects.KDLNode; -import kdl.objects.KDLNull; -import kdl.objects.KDLNumber; -import kdl.objects.KDLString; -import kdl.objects.KDLValue; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -public class KDLParserTest { - @Test - public void test_parseEmptyString() { - assertThat(parser.parse("")).isEqualTo(doc()); - assertThat(parser.parse(" ")).isEqualTo(doc()); - assertThat(parser.parse("\n")).isEqualTo(doc()); - } - - @Test - public void test_nodes() { - assertThat(parser.parse("node")).isEqualTo(doc(node("node"))); - assertThat(parser.parse("node\n")).isEqualTo(doc(node("node"))); - assertThat(parser.parse("\nnode\n")).isEqualTo(doc(node("node"))); - assertThat(parser.parse("node1\nnode2")).isEqualTo(doc(node("node1"), node("node2"))); - } - - @Test - public void test_node() { - assertThat(parser.parse("node;")).isEqualTo(doc(node("node"))); - assertThat(parser.parse("node 1")).isEqualTo(doc(node("node", list(1)))); - assertThat(parser.parse("node 1 2 \"3\" true false null")).isEqualTo(doc(node("node", list(1, 2, "3", true, false, new KDLNull())))); - assertThat(parser.parse("node {\n node2\n}")).isEqualTo(doc(node("node", node("node2")))); - } - - @Test - public void test_slashDashComment() { - assertThat(parser.parse("/-node")).isEqualTo(doc()); - assertThat(parser.parse("/- node")).isEqualTo(doc()); - assertThat(parser.parse("/- node\n")).isEqualTo(doc()); - assertThat(parser.parse("/-node 1 2 3")).isEqualTo(doc()); - assertThat(parser.parse("/-node key=false")).isEqualTo(doc()); - assertThat(parser.parse("/-node{\nnode\n}")).isEqualTo(doc()); - assertThat(parser.parse("/-node 1 2 3 key=\"value\" \\\n{\nnode\n}")).isEqualTo(doc()); - } - - @Test - public void test_argSlashdashComment() { - assertThat(parser.parse("node /-1")).isEqualTo(doc(node("node"))); - assertThat(parser.parse("node /-1 2")).isEqualTo(doc(node("node", list(2)))); - assertThat(parser.parse("node 1 /- 2 3")).isEqualTo(doc(node("node", list(1, 3)))); - assertThat(parser.parse("node /--1")).isEqualTo(doc(node("node"))); - assertThat(parser.parse("node /- -1")).isEqualTo(doc(node("node"))); - assertThat(parser.parse("node \\\n/- -1")).isEqualTo(doc(node("node"))); - } - - @Test - public void test_prop_slashdash_comment() { - assertThat(parser.parse("node /-key=1")).isEqualTo(doc(node("node"))); - assertThat(parser.parse("node /- key=1")).isEqualTo(doc(node("node"))); - assertThat(parser.parse("node key=1 /-key2=2")).isEqualTo(doc(node("node", map("key", 1)))); - } - - @Test - public void test_childrenSlashdashComment() { - assertThat(parser.parse("node /-{}")).isEqualTo(doc(node("node"))); - assertThat(parser.parse("node /- {}")).isEqualTo(doc(node("node"))); - assertThat(parser.parse("node /-{\nnode2\n}")).isEqualTo(doc(node("node"))); - } - - @Test - public void test_string() { - assertThat(parser.parse("node \"\"")).isEqualTo(doc(node("node", list("")))); - assertThat(parser.parse("node \"hello\"")).isEqualTo(doc(node("node", list("hello")))); - assertThat(parser.parse("node \"hello\\nworld\"")).isEqualTo(doc(node("node", list("hello\nworld")))); - assertThat(parser.parse("node \"\\u{1F408}\"")).isEqualTo(doc(node("node", list("\uD83D\uDC08")))); - assertThat(parser.parse("node \"\\\"\\\\\\/\\b\\f\\n\\r\\t\"")).isEqualTo(doc(node("node", list("\"\\/\u0008\u000C\n\r\t")))); - assertThat(parser.parse("node \"\\u{10}\"")).isEqualTo(doc(node("node", list("\u0010")))); - - assertThatThrownBy(() -> parser.parse("node \"\\i\"")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node \"\\u{c0ffee}\"")).isInstanceOf(KDLParseException.class); - } - - @Test - public void test_float() { - assertThat(parser.parse("node 1.0")).isEqualTo(doc(node("node", list(1.0)))); - assertThat(parser.parse("node 0.0")).isEqualTo(doc(node("node", list(0.0)))); - assertThat(parser.parse("node -1.0")).isEqualTo(doc(node("node", list(-1.0)))); - assertThat(parser.parse("node +1.0")).isEqualTo(doc(node("node", list(1.0)))); - assertThat(parser.parse("node 1.0e10")).isEqualTo(doc(node("node", list(1.0e10)))); - assertThat(parser.parse("node 1.0e-10")).isEqualTo(doc(node("node", list(1.0e-10)))); - assertThat(parser.parse("node 123_456_789.0")).isEqualTo(doc(node("node", list(new BigDecimal("123456789.0"))))); - assertThat(parser.parse("node 123_456_789.0_1")).isEqualTo(doc(node("node", list(new BigDecimal("123456789.01"))))); - - assertThatThrownBy(() -> parser.parse("node ?1.0")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node _1.0")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node .0")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node 1.0E100E10")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node 1.0E1.10")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node 1.0.0")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node 1.0.0E7")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node 1.E7")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node 1._0")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node 1.")).isInstanceOf(KDLParseException.class); - } - - @Test - public void test_integer() { - assertThat(parser.parse("node 0")).isEqualTo(doc(node("node", list(0)))); - assertThat(parser.parse("node 0123456789")).isEqualTo(doc(node("node", list(123456789)))); - assertThat(parser.parse("node 0123_456_789")).isEqualTo(doc(node("node", list(123456789)))); - assertThat(parser.parse("node 0123_456_789_")).isEqualTo(doc(node("node", list(123456789)))); - assertThat(parser.parse("node +0123456789")).isEqualTo(doc(node("node", list(123456789)))); - assertThat(parser.parse("node -0123456789")).isEqualTo(doc(node("node", list(-123456789)))); - - assertThatThrownBy(() -> parser.parse("node ?0123456789")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node _0123456789")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node a")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node --")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node 0x")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node 0x_1")).isInstanceOf(KDLParseException.class); - } - - @Test - public void test_hexadecimal() { - KDLNumber kdlNumber = new KDLNumber(new BigDecimal(new BigInteger("0123456789abcdef", 16)), 16); - - assertThat(parser.parse("node 0x0123456789abcdef")).isEqualTo(doc(node("node", list(kdlNumber)))); - assertThat(parser.parse("node 0x01234567_89abcdef")).isEqualTo(doc(node("node", list(kdlNumber)))); - assertThat(parser.parse("node 0x01234567_89abcdef_")).isEqualTo(doc(node("node", list(kdlNumber)))); - - assertThatThrownBy(() -> parser.parse("node 0x_123")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node 0xg")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node 0xx")).isInstanceOf(KDLParseException.class); - } - - @Test - public void test_octal() { - KDLNumber kdlNumber = new KDLNumber(new BigDecimal(342391), 8); - - assertThat(parser.parse("node 0o01234567")).isEqualTo(doc(node("node", list(kdlNumber)))); - assertThat(parser.parse("node 0o0123_4567")).isEqualTo(doc(node("node", list(kdlNumber)))); - assertThat(parser.parse("node 0o01234567_")).isEqualTo(doc(node("node", list(kdlNumber)))); - - assertThatThrownBy(() -> parser.parse("node 0o_123")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node 0o8")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node 0oo")).isInstanceOf(KDLParseException.class); - } - - @Test - public void test_binary() { - KDLNumber kdlNumber = new KDLNumber(new BigDecimal(6), 2); - - assertThat(parser.parse("node 0b0110")).isEqualTo(doc(node("node", list(kdlNumber)))); - assertThat(parser.parse("node 0b01_10")).isEqualTo(doc(node("node", list(kdlNumber)))); - assertThat(parser.parse("node 0b01___10")).isEqualTo(doc(node("node", list(kdlNumber)))); - assertThat(parser.parse("node 0b0110_")).isEqualTo(doc(node("node", list(kdlNumber)))); - - assertThatThrownBy(() -> parser.parse("node 0b_0110")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node 0b20")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node 0bb")).isInstanceOf(KDLParseException.class); - } - - @Test - public void test_raw_string() { - assertThat(parser.parse("node r\"foo\"")).isEqualTo(doc(node("node", list("foo")))); - assertThat(parser.parse("node r\"foo\\nbar\"")).isEqualTo(doc(node("node", list("foo\\nbar")))); - assertThat(parser.parse("node r#\"foo\"#")).isEqualTo(doc(node("node", list("foo")))); - assertThat(parser.parse("node r##\"foo\"##")).isEqualTo(doc(node("node", list("foo")))); - assertThat(parser.parse("node r\"\\nfoo\\r\"")).isEqualTo(doc(node("node", list("\\nfoo\\r")))); - assertThat(parser.parse("node r#\"hello\"world\"#")).isEqualTo(doc(node("node", list("hello\"world")))); - - assertThatThrownBy(() -> parser.parse("node r##\"foo\"#")).isInstanceOf(KDLParseException.class); - } - - @Test - public void test_boolean() { - assertThat(parser.parse("node true")).isEqualTo(doc(node("node", list(true)))); - assertThat(parser.parse("node false")).isEqualTo(doc(node("node", list(false)))); - } - - @Test - public void test_node_space() { - assertThat(parser.parse("node 1")).isEqualTo(doc(node("node", list(1)))); - assertThat(parser.parse("node\t1")).isEqualTo(doc(node("node", list(1)))); - assertThat(parser.parse("node\t \\\n 1")).isEqualTo(doc(node("node", list(1)))); - assertThat(parser.parse("node\t \\ // hello\n 1")).isEqualTo(doc(node("node", list(1)))); - } - - @Test - public void test_single_line_comment() { - assertThat(parser.parse("//hello")).isEqualTo(doc()); - assertThat(parser.parse("// \thello")).isEqualTo(doc()); - assertThat(parser.parse("//hello\n")).isEqualTo(doc()); - assertThat(parser.parse("//hello\r\n")).isEqualTo(doc()); - assertThat(parser.parse("//hello\n\r")).isEqualTo(doc()); - assertThat(parser.parse("//hello\rworld")).isEqualTo(doc(node("world"))); - assertThat(parser.parse("//hello\nworld\r\n")).isEqualTo(doc(node("world"))); - } - - @Test - public void test_multi_line_comment() { - assertThat(parser.parse("/*hello*/")).isEqualTo(doc()); - assertThat(parser.parse("/*hello*/\n")).isEqualTo(doc()); - assertThat(parser.parse("/*\nhello\r\n*/")).isEqualTo(doc()); - assertThat(parser.parse("/*\nhello** /\n*/")).isEqualTo(doc()); - assertThat(parser.parse("/**\nhello** /\n*/")).isEqualTo(doc()); - assertThat(parser.parse("/*hello*/world")).isEqualTo(doc(node("world"))); - } - - @Test - public void test_escline() { - assertThat(parser.parse("\\\nfoo")).isEqualTo(doc(node("foo"))); - assertThat(parser.parse("\\\n foo")).isEqualTo(doc(node("foo"))); - assertThat(parser.parse("\\ \t \nfoo")).isEqualTo(doc(node("foo"))); - assertThat(parser.parse("\\ // test \nfoo")).isEqualTo(doc(node("foo"))); - assertThat(parser.parse("\\ // test \n foo")).isEqualTo(doc(node("foo"))); - } - - @Test - public void test_whitespace() { - assertThat(parser.parse(" node")).isEqualTo(doc(node("node"))); - assertThat(parser.parse("\tnode")).isEqualTo(doc(node("node"))); - assertThat(parser.parse("/* \nfoo\r\n */ etc")).isEqualTo(doc(node("etc"))); - } - - @Test - public void test_newline() { - assertThat(parser.parse("node1\nnode2")).isEqualTo(doc(node("node1"), node("node2"))); - assertThat(parser.parse("node1\rnode2")).isEqualTo(doc(node("node1"), node("node2"))); - assertThat(parser.parse("node1\r\nnode2")).isEqualTo(doc(node("node1"), node("node2"))); - assertThat(parser.parse("node1\n\nnode2")).isEqualTo(doc(node("node1"), node("node2"))); - } - - @Test - public void test_nestedChildNodes() { - KDLDocument actual = parser.parse( - "content { \n" + - " section \"First section\" {\n" + - " paragraph \"This is the first paragraph\"\n" + - " paragraph \"This is the second paragraph\"\n" + - " }\n" + - "}" - ); - - KDLDocument expected = doc( - node("content", - node("section", list("First section"), - node("paragraph", list("This is the first paragraph")), - node("paragraph", list("This is the second paragraph")) - ) - ) - ); - - assertThat(actual).isEqualTo(expected); - } - - @Test - public void test_semicolon() { - assertThat(parser.parse("node1; node2; node3")).isEqualTo(doc(node("node1"), node("node2"), node("node3"))); - assertThat(parser.parse("node1 { node2; }; node3")).isEqualTo(doc(node("node1", node("node2")), node("node3"))); - } - - @Test - public void test_multiline_strings() { - assertThat(parser.parse("string \"my\nmultiline\nvalue\"")).isEqualTo(doc(node("string", list("my\nmultiline\nvalue")))); - } - - @Test - public void test_comments() { - KDLDocument actual = parser.parse( - "// C style\n" + - "/*\n" + - "C style multiline\n" + - "*/\n" + - "tag /*foo=true*/ bar=false\n" + - "/*/*\n" + - "hello\n" + - "*/*/" - ); - - KDLDocument expected = doc( - node("tag", map("bar", false)) - ); - - assertThat(actual).isEqualTo(expected); - } - - @Test - public void test_multiline_nodes() { - KDLDocument actual = parser.parse( - "title \\\n" + - " \"Some title\"\n" + - "my-node 1 2 \\ // comments are ok after \\\n" + - " 3 4\n" - ); - - KDLDocument expected = doc( - node("title", list("Some title")), - node("my-node", list(1, 2, 3, 4)) - ); - - assertThat(actual).isEqualTo(expected); - } - - @Test - public void test_utf8() { - assertThat(parser.parse("smile \"😁\"")).isEqualTo(doc(node("smile", list("😁")))); - assertThat(parser.parse("ノード お名前=\"☜(゚ヮ゚☜)\"")).isEqualTo(doc(node("ノード", map("お名前", "☜(゚ヮ゚☜)")))); - } - - @Test - public void test_node_names() { - assertThat(parser.parse("\"!@#$@$%Q#$%~@!40\" \"1.2.3\" \"!!!!!\"=true")).isEqualTo(doc(node("!@#$@$%Q#$%~@!40", list("1.2.3"), map("!!!!!", true)))); - assertThat(parser.parse("foo123~!@#$%^&*.:'|?+ \"weeee\"")).isEqualTo(doc(node("foo123~!@#$%^&*.:'|?+", list("weeee")))); - } - - @Test - public void test_node_type() { - assertThat(parser.parse("node")).isEqualTo(doc(node("node", Optional.empty()))); - assertThat(parser.parse("(type)node")).isEqualTo(doc(node("node", Optional.of("type")))); - assertThat(parser.parse("(type)\"node\"")).isEqualTo(doc(node("node", Optional.of("type")))); - assertThat(parser.parse("(\"type\")node")).isEqualTo(doc(node("node", Optional.of("type")))); - assertThat(parser.parse("(\"t\")node")).isEqualTo(doc(node("node", Optional.of("t")))); - assertThat(parser.parse("(r\"t\")node")).isEqualTo(doc(node("node", Optional.of("t")))); - assertThat(parser.parse("(\"\")node")).isEqualTo(doc(node("node", Optional.of("")))); - assertThat(parser.parse("(r\"\")node")).isEqualTo(doc(node("node", Optional.of("")))); - - assertThatThrownBy(() -> parser.parse("()node")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("( )node")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("(type)")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("()")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("( type)node")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("(type )node")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("(\ntype)node")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("(type\n)node")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("(type) node")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("(type)\nnode")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("(type)/*whee*/node")).isInstanceOf(KDLParseException.class); - } - - @Test - public void test_arg_type() { - assertThat(parser.parse("node \"val\"")).isEqualTo(doc(node("node", list(KDLString.from("val"))))); - assertThat(parser.parse("node (type)\"val\"")).isEqualTo(doc(node("node", list(KDLString.from("val", Optional.of("type")))))); - assertThat(parser.parse("node (type)r\"val\"")).isEqualTo(doc(node("node", list(KDLString.from("val", Optional.of("type")))))); - assertThat(parser.parse("node (type)10")).isEqualTo(doc(node("node", list(KDLNumber.from(10, Optional.of("type")))))); - assertThat(parser.parse("node (\"type\")10")).isEqualTo(doc(node("node", list(KDLNumber.from(10, Optional.of("type")))))); - assertThat(parser.parse("node (r\"type\")10")).isEqualTo(doc(node("node", list(KDLNumber.from(10, Optional.of("type")))))); - assertThat(parser.parse("node (\"\")10")).isEqualTo(doc(node("node", list(KDLNumber.from(10, Optional.of("")))))); - assertThat(parser.parse("node (type)0x10")).isEqualTo(doc(node("node", list(KDLNumber.from(16, 16, Optional.of("type")))))); - assertThat(parser.parse("node (type)0o10")).isEqualTo(doc(node("node", list(KDLNumber.from(8, 8, Optional.of("type")))))); - assertThat(parser.parse("node (type)0b10")).isEqualTo(doc(node("node", list(KDLNumber.from(2, 2, Optional.of("type")))))); - assertThat(parser.parse("node (type)1.0E2")).isEqualTo(doc(node("node", list(KDLNumber.from(new BigDecimal("1.0E2"), 10, Optional.of("type")))))); - assertThat(parser.parse("node (type)10.1")).isEqualTo(doc(node("node", list(KDLNumber.from(new BigDecimal("10.1"), 10, Optional.of("type")))))); - assertThat(parser.parse("node (type)true")).isEqualTo(doc(node("node", list(new KDLBoolean(true, Optional.of("type")))))); - assertThat(parser.parse("node (type)false")).isEqualTo(doc(node("node", list(new KDLBoolean(false, Optional.of("type")))))); - assertThat(parser.parse("node (type)null")).isEqualTo(doc(node("node", list(new KDLNull(Optional.of("type")))))); - - assertThatThrownBy(() -> parser.parse("node (type)")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node (type) 10")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node ()10")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node (type)bare")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node (type)fare")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node ( )10")).isInstanceOf(KDLParseException.class); - } - - @Test - public void test_property_type() { - assertThat(parser.parse("node key=\"val\"")).isEqualTo(doc(node("node", map("key", KDLString.from("val"))))); - assertThat(parser.parse("node key=(type)\"val\"")).isEqualTo(doc(node("node", map("key", KDLString.from("val", Optional.of("type")))))); - assertThat(parser.parse("node key=(\"type\")\"val\"")).isEqualTo(doc(node("node", map("key", KDLString.from("val", Optional.of("type")))))); - assertThat(parser.parse("node key=(r\"type\")\"val\"")).isEqualTo(doc(node("node", map("key", KDLString.from("val", Optional.of("type")))))); - assertThat(parser.parse("node key=(\"\")\"val\"")).isEqualTo(doc(node("node", map("key", KDLString.from("val", Optional.of("")))))); - assertThat(parser.parse("node key=(type)10")).isEqualTo(doc(node("node", map("key", KDLNumber.from(10, Optional.of("type")))))); - assertThat(parser.parse("node key=(type)true")).isEqualTo(doc(node("node", map("key", new KDLBoolean(true, Optional.of("type")))))); - assertThat(parser.parse("node key=(type)false")).isEqualTo(doc(node("node", map("key", new KDLBoolean(false, Optional.of("type")))))); - assertThat(parser.parse("node key=(type)null")).isEqualTo(doc(node("node", map("key", new KDLNull(Optional.of("type")))))); - - assertThatThrownBy(() -> parser.parse("node (type)key=10")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node key= (type)10")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node key=(type) 10")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node key=()10")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node key=( )10")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node key=(\n)10")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node key=()\n10")).isInstanceOf(KDLParseException.class); - assertThatThrownBy(() -> parser.parse("node key=(type)bare")).isInstanceOf(KDLParseException.class); - } - - private KDLDocument doc(KDLNode... nodes) { - List nodeList = new ArrayList<>(); - Collections.addAll(nodeList, nodes); - return new KDLDocument(nodeList); - } - - private KDLNode node(String ident, Optional type, List args, Map props, KDLNode... nodes) { - List> argValues = new ArrayList<>(); - for (Object o : args) { - argValues.add(KDLValue.from(o)); - } - Map> propValues = new HashMap<>(); - for (Map.Entry e : props.entrySet()) { - propValues.put(e.getKey(), KDLValue.from(e.getValue())); - } - Optional children = Optional.empty(); - if (nodes.length > 0) { - children = Optional.of(doc(nodes)); - } - return new KDLNode(ident, type, propValues, argValues, children); - } - - private KDLNode node(String ident, List args, Map props, KDLNode... nodes) { - return node(ident, Optional.empty(), args, props, nodes); - } - - private KDLNode node(String ident, List args, KDLNode... nodes) { - return node(ident, Optional.empty(), args, nodes); - } - - private KDLNode node(String ident, Optional type, List args, KDLNode... nodes) { - return node(ident, type, args, Collections.emptyMap(), nodes); - } - - private KDLNode node(String ident, Map props, KDLNode... nodes) { - return node(ident, Optional.empty(), props, nodes); - } - - private KDLNode node(String ident, Optional type, Map props, KDLNode... nodes) { - return node(ident, type, Collections.emptyList(), props, nodes); - } - - private KDLNode node(String ident, KDLNode... nodes) { - return node(ident, Optional.empty(), nodes); - } - - private KDLNode node(String ident, Optional type, KDLNode... nodes) { - return node(ident, type, Collections.emptyList(), nodes); - } - - private List list(Object... values) { - final ArrayList kdlValues = new ArrayList<>(); - Collections.addAll(kdlValues, values); - - return kdlValues; - } - - private Map map(String key, Object value) { - return Collections.singletonMap(key, value); - } - - private static final KDLParser parser = new KDLParser(); -} diff --git a/src/test/java/kdl/parse/ParseArgOrPropTest.java b/src/test/java/kdl/parse/ParseArgOrPropTest.java deleted file mode 100644 index 04cfa7c..0000000 --- a/src/test/java/kdl/parse/ParseArgOrPropTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package kdl.parse; - -import java.io.IOException; -import java.util.Optional; -import java.util.stream.Stream; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import static kdl.parse.ParserTest.testParser; - -public class ParseArgOrPropTest { - public static Stream getCases() { - return Stream.of( - Arguments.of("bare", Optional.of("bare")), - Arguments.of("-10", Optional.of("-10")), - Arguments.of("r", Optional.of("r")), - Arguments.of("rrrr", Optional.of("rrrr")), - Arguments.of("r\"raw\"", Optional.of("raw")), - Arguments.of("#goals", Optional.of("#goals")), - Arguments.of("=goals", Optional.empty()) - ); - } - - @ParameterizedTest(name = "\"{0}\"") - @MethodSource("getCases") - public void testParseArgOrProp(String input, Optional expectedResult) throws IOException { - testParser(input, KDLParser::parseIdentifier, expectedResult); - } -} diff --git a/src/test/java/kdl/parse/ParseBareIdentifierTest.java b/src/test/java/kdl/parse/ParseBareIdentifierTest.java deleted file mode 100644 index 4afffbd..0000000 --- a/src/test/java/kdl/parse/ParseBareIdentifierTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package kdl.parse; - -import java.io.IOException; -import java.util.Optional; -import java.util.stream.Stream; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import static kdl.parse.ParserTest.testParser; - -public class ParseBareIdentifierTest { - public static Stream getCases() { - return Stream.of( - Arguments.of("r", Optional.of("r")), - Arguments.of("bare", Optional.of("bare")), - Arguments.of("ぁ", Optional.of("ぁ")), - Arguments.of("-r", Optional.of("-r")), - Arguments.of("-1", Optional.of("-1")), //Yes, really. Should it be is another question - Arguments.of("0hno", Optional.empty()), - Arguments.of("=no", Optional.empty()), - Arguments.of("", Optional.empty()) - ); - } - - @ParameterizedTest(name = "\"{0}\"") - @MethodSource("getCases") - public void testParseBareIdentifier(String input, Optional expectedResult) throws IOException { - testParser(input, KDLParser::parseBareIdentifier, expectedResult); - } -} diff --git a/src/test/java/kdl/parse/ParseChildTest.java b/src/test/java/kdl/parse/ParseChildTest.java deleted file mode 100644 index 7cf6feb..0000000 --- a/src/test/java/kdl/parse/ParseChildTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package kdl.parse; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import kdl.objects.KDLDocument; -import kdl.objects.KDLNode; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import static kdl.parse.ParserTest.testParser; - -public class ParseChildTest { - public static Stream getCases() { - return Stream.of( - Arguments.of("{}", Optional.of(doc())), - Arguments.of("{\n\n}", Optional.of(doc())), - Arguments.of("{\na\n}", Optional.of(doc("a"))), - Arguments.of("{\n\na\n\nb\n}", Optional.of(doc("a", "b"))), - Arguments.of("{\na\nb\n}", Optional.of(doc("a", "b"))), - Arguments.of("", Optional.empty()), - Arguments.of("{", Optional.empty()), - Arguments.of("{\n", Optional.empty()), - Arguments.of("{\na /-", Optional.empty()), - Arguments.of("{\na\n/-", Optional.empty()) - ); - } - - private static KDLDocument doc(String... identifiers) { - final List nodes = Arrays.stream(identifiers) - .map(id -> KDLNode.builder().setIdentifier(id).build()) - .collect(Collectors.toList()); - return new KDLDocument(nodes); - } - - @ParameterizedTest(name = "\"{0}\"") - @MethodSource("getCases") - public void testParseChild(String input, Optional expectedResult) throws IOException { - testParser(input, KDLParser::parseChild, expectedResult); - } -} diff --git a/src/test/java/kdl/parse/ParseEscapedStringTest.java b/src/test/java/kdl/parse/ParseEscapedStringTest.java deleted file mode 100644 index ab4b2ce..0000000 --- a/src/test/java/kdl/parse/ParseEscapedStringTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package kdl.parse; - -import java.io.IOException; -import java.util.Optional; -import java.util.stream.Stream; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import static kdl.parse.ParserTest.testParser; - -public class ParseEscapedStringTest { - public static Stream getCases() { - return Stream.of( - Arguments.of("\"\"", Optional.of("")), - Arguments.of("\"a\"", Optional.of("a")), - Arguments.of("\"a\nb\"", Optional.of("a\nb")), - Arguments.of("\"\\n\"", Optional.of("\n")), - Arguments.of("\"\\u{0001}\"", Optional.of("\u0001")), - Arguments.of("\"ぁ\"", Optional.of("ぁ")), - Arguments.of("\"\\u{3041}\"", Optional.of("ぁ")), - Arguments.of("\"", Optional.empty()), - Arguments.of("", Optional.empty()) - ); - } - - @ParameterizedTest(name = "\"{0}\"") - @MethodSource("getCases") - public void testParseEscapedString(String input, Optional expectedResult) throws IOException { - testParser(input, KDLParser::parseEscapedString, expectedResult); - } -} diff --git a/src/test/java/kdl/parse/ParseIdentifierTest.java b/src/test/java/kdl/parse/ParseIdentifierTest.java deleted file mode 100644 index 81bad5a..0000000 --- a/src/test/java/kdl/parse/ParseIdentifierTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package kdl.parse; - -import java.io.IOException; -import java.util.Optional; -import java.util.stream.Stream; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import static kdl.parse.ParserTest.testParser; - -public class ParseIdentifierTest { - public static Stream getCases() { - return Stream.of( - Arguments.of("r", Optional.of("r")), - Arguments.of("bare", Optional.of("bare")), - Arguments.of("ぁ", Optional.of("ぁ")), - Arguments.of("-r", Optional.of("-r")), - Arguments.of("-1", Optional.of("-1")), //Yes, really. Should it be is another question - - Arguments.of("0hno", Optional.empty()), - Arguments.of("=no", Optional.empty()), - Arguments.of("", Optional.empty()) - ); - } - - @ParameterizedTest(name = "\"{0}\"") - @MethodSource("getCases") - public void testParseIdentifier(String input, Optional expectedResult) throws IOException { - testParser(input, KDLParser::parseIdentifier, expectedResult); - } -} diff --git a/src/test/java/kdl/parse/ParseNodeTest.java b/src/test/java/kdl/parse/ParseNodeTest.java deleted file mode 100644 index 86568b8..0000000 --- a/src/test/java/kdl/parse/ParseNodeTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package kdl.parse; - -import java.io.IOException; -import java.util.Optional; -import java.util.stream.Stream; -import kdl.objects.KDLDocument; -import kdl.objects.KDLNode; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import static kdl.parse.ParserTest.testParser; - -public class ParseNodeTest { - public static Stream getCases() { - return Stream.of( - Arguments.of("a", Optional.of(KDLNode.builder().setIdentifier("a").build())), - Arguments.of("a\n", Optional.of(KDLNode.builder().setIdentifier("a").build())), - Arguments.of("\"a\"", Optional.of(KDLNode.builder().setIdentifier("a").build())), - Arguments.of("r\"a\"", Optional.of(KDLNode.builder().setIdentifier("a").build())), - Arguments.of("r", Optional.of(KDLNode.builder().setIdentifier("r").build())), - Arguments.of("rrrr", Optional.of(KDLNode.builder().setIdentifier("rrrr").build())), - Arguments.of("a // stuff", Optional.of(KDLNode.builder().setIdentifier("a").build())), - Arguments.of("a \"arg\"", Optional.of(KDLNode.builder().setIdentifier("a").addArg("arg").build())), - Arguments.of("a key=\"val\"", Optional.of(KDLNode.builder().setIdentifier("a").addProp("key", "val").build())), - Arguments.of("a \"key\"=true", Optional.of(KDLNode.builder().setIdentifier("a").addProp("key", true).build())), - Arguments.of("a \"arg\" key=\"val\"", Optional.of(KDLNode.builder().setIdentifier("a").addProp("key", "val").addArg("arg").build())), - Arguments.of("a r#\"arg\"\"# key=\"val\"", Optional.of(KDLNode.builder().setIdentifier("a").addProp("key", "val").addArg("arg\"").build())), - Arguments.of("a true false null", Optional.of(KDLNode.builder().setIdentifier("a").addArg(true).addArg(false).addNullArg().build())), - Arguments.of("a /- \"arg1\" \"arg2\"", Optional.of(KDLNode.builder().setIdentifier("a").addArg("arg2").build())), - Arguments.of("a key=\"val\" key=\"val2\"", Optional.of(KDLNode.builder().setIdentifier("a").addProp("key", "val2").build())), - Arguments.of("a key=\"val\" /- key=\"val2\"", Optional.of(KDLNode.builder().setIdentifier("a").addProp("key", "val").build())), - Arguments.of("a {}", Optional.of(KDLNode.builder().setIdentifier("a").setChild(KDLDocument.empty()).build())), - Arguments.of("a {\nb\n}", Optional.of(KDLNode.builder().setIdentifier("a") - .setChild(KDLDocument.builder().addNode(KDLNode.builder().setIdentifier("b").build()).build()).build())), - Arguments.of("a \"arg\" key=null \\\n{\nb\n}", Optional.of(KDLNode.builder().setIdentifier("a").addArg("arg").addNullProp("key") - .setChild(KDLDocument.builder().addNode(KDLNode.builder().setIdentifier("b").build()).build()).build())), - Arguments.of("a {\n\n}", Optional.of(KDLNode.builder().setIdentifier("a").setChild(KDLDocument.empty()).build())), - Arguments.of("a{\n\n}", Optional.of(KDLNode.builder().setIdentifier("a").setChild(KDLDocument.empty()).build())), - Arguments.of("a\"arg\"", Optional.empty()), - Arguments.of("a=", Optional.empty()), - Arguments.of("a /-", Optional.empty()) - ); - } - - @ParameterizedTest(name = "\"{0}\" -> \"{1}\"") - @MethodSource("getCases") - public void testParseNode(String input, Optional expectedResult) throws IOException { - testParser(input, (parser, context) -> parser.parseNode(context).orElseThrow(), expectedResult); - } -} diff --git a/src/test/java/kdl/parse/ParseNumberTest.java b/src/test/java/kdl/parse/ParseNumberTest.java deleted file mode 100644 index 9c427be..0000000 --- a/src/test/java/kdl/parse/ParseNumberTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package kdl.parse; - -import java.io.IOException; -import java.util.Optional; -import java.util.stream.Stream; -import kdl.objects.KDLNumber; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -public class ParseNumberTest { - public static Stream getCases() { - return Stream.of( - Arguments.of("0", Optional.of(KDLNumber.from(0, 10))), - Arguments.of("-0", Optional.of(KDLNumber.from(0, 10))), - Arguments.of("1", Optional.of(KDLNumber.from(1, 10))), - Arguments.of("01", Optional.of(KDLNumber.from(1, 10))), - Arguments.of("10", Optional.of(KDLNumber.from(10, 10))), - Arguments.of("1_0", Optional.of(KDLNumber.from(10, 10))), - Arguments.of("-10", Optional.of(KDLNumber.from(-10, 10))), - Arguments.of("+10", Optional.of(KDLNumber.from(10, 10))), - Arguments.of("1.0", KDLNumber.from("1.0", 10)), - Arguments.of("1e10", KDLNumber.from("1e10", 10)), - Arguments.of("1e+10", KDLNumber.from("1e10", 10)), - Arguments.of("1E+10", KDLNumber.from("1e10", 10)), - Arguments.of("1e-10", KDLNumber.from("1e-10", 10)), - Arguments.of("-1e-10", KDLNumber.from("-1e-10", 10)), - Arguments.of("+1e-10", KDLNumber.from("1e-10", 10)), - Arguments.of("0x0", Optional.of(KDLNumber.from(0, 16))), - Arguments.of("0xFF", Optional.of(KDLNumber.from(255, 16))), - Arguments.of("0xF_F", Optional.of(KDLNumber.from(255, 16))), - Arguments.of("-0xFF", Optional.of(KDLNumber.from(-255, 16))), - Arguments.of("0o0", Optional.of(KDLNumber.from(0, 8))), - Arguments.of("0o7", Optional.of(KDLNumber.from(7, 8))), - Arguments.of("0o77", Optional.of(KDLNumber.from(63, 8))), - Arguments.of("0o7_7", Optional.of(KDLNumber.from(63, 8))), - Arguments.of("-0o77", Optional.of(KDLNumber.from(-63, 8))), - Arguments.of("0b0", Optional.of(KDLNumber.from(0, 2))), - Arguments.of("0b1", Optional.of(KDLNumber.from(1, 2))), - Arguments.of("0b10", Optional.of(KDLNumber.from(2, 2))), - Arguments.of("0b1_0", Optional.of(KDLNumber.from(2, 2))), - Arguments.of("-0b10", Optional.of(KDLNumber.from(-2, 2))), - Arguments.of("A", Optional.empty()), - Arguments.of("_", Optional.empty()), - Arguments.of("_1", Optional.empty()), - Arguments.of("+_1", Optional.empty()), - Arguments.of("0xRR", Optional.empty()), - Arguments.of("0o8", Optional.empty()), - Arguments.of("0b2", Optional.empty()) - ); - } - - @ParameterizedTest(name = "\"{0}\"") - @MethodSource("getCases") - public void testParseNumber(String input, Optional expectedResult) throws IOException { - ParserTest.testParser(input, (parser, context) -> parser.parseNumber(context, Optional.empty()), expectedResult); - } -} diff --git a/src/test/java/kdl/parse/ParseRawStringTest.java b/src/test/java/kdl/parse/ParseRawStringTest.java deleted file mode 100644 index 59f8a1c..0000000 --- a/src/test/java/kdl/parse/ParseRawStringTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package kdl.parse; - -import java.io.IOException; -import java.util.Optional; -import java.util.stream.Stream; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import static kdl.parse.ParserTest.testParser; - -public class ParseRawStringTest { - public static Stream getCases() { - return Stream.of( - Arguments.of("r\"\"", Optional.of(""), ""), - Arguments.of("r\"\n\"", Optional.of("\n"), ""), - Arguments.of("r\"\\n\"", Optional.of("\\n"), ""), - Arguments.of("r\"\\u{0001}\"", Optional.of("\\u{0001}"), ""), - Arguments.of("r#\"\"#", Optional.of(""), ""), - Arguments.of("r#\"a\"#", Optional.of("a"), ""), - Arguments.of("r##\"\"#\"##", Optional.of("\"#"), ""), - Arguments.of("\"\"", Optional.empty(), "\""), - Arguments.of("r", Optional.empty(), ""), - Arguments.of("r\"", Optional.empty(), ""), - Arguments.of("r#\"a\"##", Optional.empty(), ""), - Arguments.of("", Optional.empty(), "") - ); - } - - @ParameterizedTest(name = "\"{0}\"") - @MethodSource("getCases") - public void testParseRawString(String input, Optional expectedResult, String expectedRemainder) throws IOException { - testParser(input, KDLParser::parseRawString, expectedResult, expectedRemainder); - } -} diff --git a/src/test/java/kdl/parse/ParseValueTest.java b/src/test/java/kdl/parse/ParseValueTest.java deleted file mode 100644 index 05a7116..0000000 --- a/src/test/java/kdl/parse/ParseValueTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package kdl.parse; - -import java.io.IOException; -import java.util.Optional; -import java.util.stream.Stream; -import kdl.objects.KDLBoolean; -import kdl.objects.KDLNull; -import kdl.objects.KDLNumber; -import kdl.objects.KDLString; -import kdl.objects.KDLValue; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import static kdl.parse.ParserTest.testParser; - -public class ParseValueTest { - public static Stream getCases() { - return Stream.of( - Arguments.of("0", Optional.of(KDLNumber.from(0, 10))), - Arguments.of("10", Optional.of(KDLNumber.from(10, 10))), - Arguments.of("-10", Optional.of(KDLNumber.from(-10, 10))), - Arguments.of("+10", Optional.of(KDLNumber.from(10, 10))), - Arguments.of("\"\"", Optional.of(KDLString.from(""))), - Arguments.of("\"r\"", Optional.of(KDLString.from("r"))), - Arguments.of("\"\n\"", Optional.of(KDLString.from("\n"))), - Arguments.of("\"\\n\"", Optional.of(KDLString.from("\n"))), - Arguments.of("r\"\"", Optional.of(KDLString.from(""))), - Arguments.of("r\"\n\"", Optional.of(KDLString.from("\n"))), - Arguments.of("r\"\\n\"", Optional.of(KDLString.from("\\n"))), - Arguments.of("true", Optional.of(new KDLBoolean(true))), - Arguments.of("false", Optional.of(new KDLBoolean(false))), - Arguments.of("null", Optional.of(new KDLNull())), - Arguments.of("\"true\"", Optional.of(KDLString.from("true"))), - Arguments.of("\"false\"", Optional.of(KDLString.from("false"))), - Arguments.of("\"null\"", Optional.of(KDLString.from("null"))), - Arguments.of("garbage", Optional.empty()) - ); - } - - @ParameterizedTest(name = "\"{0}\"") - @MethodSource("getCases") - void testParseValue(String input, Optional> expectedResult) throws IOException { - testParser(input, KDLParser::parseValue, expectedResult); - } -} diff --git a/src/test/java/kdl/parse/ParserTest.java b/src/test/java/kdl/parse/ParserTest.java deleted file mode 100644 index cf08ed8..0000000 --- a/src/test/java/kdl/parse/ParserTest.java +++ /dev/null @@ -1,59 +0,0 @@ -package kdl.parse; - -import java.io.IOException; -import java.io.StringReader; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -public class ParserTest { - - static void testParser(String input, ParseFunction parseFunction, Optional expectedResult, Optional expectedRemainder) throws IOException { - var context = new KDLParseContext(new StringReader(input)); - - try { - var result = parseFunction.parse(parser, context); - assertThat(expectedResult).contains(result); - } catch (KDLParseException | KDLInternalException e) { - if (expectedResult.isPresent()) { - fail("Unexpected error", e); - } - } - - if (expectedRemainder.isPresent()) { - var remainder = readRemainder(context); - assertThat(remainder).isEqualTo(expectedRemainder.get()); - } - } - - private static String readRemainder(KDLParseContext context) { - final StringBuilder stringBuilder = new StringBuilder(); - try { - int read = context.read(); - while (read != KDLParser.EOF) { - stringBuilder.appendCodePoint(read); - read = context.read(); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - - return stringBuilder.toString(); - } - - static void testParser(String input, ParseFunction parseFunction, Optional expectedResult, String expectedRemainder) throws IOException { - testParser(input, parseFunction, expectedResult, Optional.ofNullable(expectedRemainder)); - } - - static void testParser(String input, ParseFunction parseFunction, Optional expectedResult) throws IOException { - testParser(input, parseFunction, expectedResult, Optional.empty()); - } - - private static final KDLParser parser = new KDLParser(); - - @FunctionalInterface - public interface ParseFunction { - T parse(KDLParser parser, KDLParseContext context) throws IOException; - } -} diff --git a/src/test/java/kdl/parse/lexer/KDLReaderTest.java b/src/test/java/kdl/parse/lexer/KDLReaderTest.java new file mode 100644 index 0000000..862a03c --- /dev/null +++ b/src/test/java/kdl/parse/lexer/KDLReaderTest.java @@ -0,0 +1,319 @@ +package kdl.parse.lexer; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import kdl.parse.KDLInternalException; +import kdl.parse.KDLParseException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class KDLReaderTest { + + @Test + @DisplayName("peek() should return the next character in the stream when it is available") + void peekOne() throws IOException { + var reader = new KDLReader(new ByteArrayInputStream("a".getBytes(StandardCharsets.UTF_8))); + + var result = reader.peek(); + + assertThat(result).isEqualTo('a'); + } + + @Test + @DisplayName("peek() should return the same character when it is called twice") + void peekOneTwice() throws IOException { + var reader = new KDLReader(new ByteArrayInputStream("a".getBytes(StandardCharsets.UTF_8))); + + var first = reader.peek(); + var second = reader.peek(); + + assertThat(first).isEqualTo(second); + } + + @Test + @DisplayName("peek() should return -1 when there are no more characters in the stream") + void peekOneEOF() throws IOException { + var reader = new KDLReader(new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8))); + + var result = reader.peek(); + + assertThat(result).isEqualTo(-1); + } + + @Test + @DisplayName("peek(2) should return the second character in the stream when it is available") + void peekTwo() throws IOException { + var reader = new KDLReader(new ByteArrayInputStream("ab".getBytes(StandardCharsets.UTF_8))); + + var result = reader.peek(2); + + assertThat(result).isEqualTo('b'); + } + + @Test + @DisplayName("peek(2) should return -1 when there is only one character available") + void peekTwoEOF() throws IOException { + var reader = new KDLReader(new ByteArrayInputStream("a".getBytes(StandardCharsets.UTF_8))); + + var result = reader.peek(2); + + assertThat(result).isEqualTo(-1); + } + + @Test + @DisplayName("peek(3) should fail") + void peekThree() { + var reader = new KDLReader(new ByteArrayInputStream("abc".getBytes(StandardCharsets.UTF_8))); + + assertThatThrownBy(() -> reader.peek(3)) + .isInstanceOf(KDLInternalException.class) + .hasMessage("Error while peeking: n should be between 1 and 2 included."); + } + + @Test + @DisplayName("read() should return the next character in the stream when it is available") + void read() throws IOException { + var reader = new KDLReader(new ByteArrayInputStream("a".getBytes(StandardCharsets.UTF_8))); + + var result = reader.read(); + + assertThat(result).isEqualTo('a'); + } + + @Test + @DisplayName("read() should return -1 when there are no more characters in the stream") + void readEOF() throws IOException { + var reader = new KDLReader(new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8))); + + var result = reader.read(); + + assertThat(result).isEqualTo(-1); + } + + @Test + @DisplayName("read() should return the next three characters in the stream when called thrice") + void readThrice() throws IOException { + var reader = new KDLReader(new ByteArrayInputStream("abc".getBytes(StandardCharsets.UTF_8))); + + int[] result = {reader.read(), reader.read(), reader.read()}; + + assertThat(result).isEqualTo(new int[]{'a', 'b', 'c'}); + } + + @Test + @DisplayName("read() should return the peeked character when it is called after peek()") + void readAfterPeek() throws IOException { + var reader = new KDLReader(new ByteArrayInputStream("a".getBytes(StandardCharsets.UTF_8))); + + var peeked = reader.peek(); + var read = reader.read(); + + assertThat(read).isEqualTo(peeked); + } + + @Test + @DisplayName("read() should return the peeked characters when it is called twice after peek(2)") + void readTwiceAfterPeekTwo() throws IOException { + var reader = new KDLReader(new ByteArrayInputStream("ab".getBytes(StandardCharsets.UTF_8))); + + reader.peek(2); + int[] result = {reader.read(), reader.read()}; + + assertThat(result).isEqualTo(new int[]{'a', 'b'}); + } + + @Test + @DisplayName("read() should return 0xC9 when next character is É") + void readUtf8TwoBytesCodePoint() throws IOException { + var reader = new KDLReader(new ByteArrayInputStream("É".getBytes(StandardCharsets.UTF_8))); + + var result = reader.read(); + + assertThat(result).isEqualTo(0xC9); + } + + @Test + @DisplayName("read() should return 0x4000 when next character is 䀀") + void readUtf8ThreeBytesCodePoint() throws IOException { + var reader = new KDLReader(new ByteArrayInputStream("䀀".getBytes(StandardCharsets.UTF_8))); + + var result = reader.read(); + + assertThat(result).isEqualTo(0x4000); + } + + @Test + @DisplayName("read() should return 0x1F601 when next character is \uD83D\uDE01") + void readUtf8FourBytesCodePoint() throws IOException { + var reader = new KDLReader(new ByteArrayInputStream("\uD83D\uDE01".getBytes(StandardCharsets.UTF_8))); + + var result = reader.read(); + + assertThat(result).isEqualTo(0x1F601); + } + + @Test + @DisplayName("read() should fail when next byte is 0xF0") + void readInvalidCodePoint() { + var reader = new KDLReader(new ByteArrayInputStream(new byte[]{(byte) 0xF0})); + + assertThatThrownBy(reader::read) + .isInstanceOf(KDLParseException.class) + .hasMessage( + "Error line 1 - Invalid character U+f0:\n" + + "\n" + + "▲\n" + + "╯" + ); + } + + @Test + @DisplayName("peek() then read() can be repeated until the end of the stream") + void peekThenReadThrice() throws IOException { + var reader = new KDLReader(new ByteArrayInputStream("abcd".getBytes(StandardCharsets.UTF_8))); + var peeked = new ArrayList(); + var read = new ArrayList(); + + for (var i = 0; i < 3; i++) { + peeked.add(reader.peek()); + read.add(reader.read()); + } + + assertThat(read).isEqualTo(peeked); + } + + @Test + @DisplayName("getErrorMessage() should return a message with the current line and position when on first line") + void testFail() throws IOException { + var reader = new KDLReader(new ByteArrayInputStream("abcdef".getBytes(StandardCharsets.UTF_8))); + reader.read(); + reader.read(); + reader.read(); + + var result = reader.error("error"); + assertThat(result).isEqualTo( + "Error line 1 - error:\n" + + "abcdef\n" + + " ▲\n" + + "──╯" + ); + } + + @Test + @DisplayName("getErrorMessage() should return a message with the current line and position when on second line") + void testFailSecondLine() throws IOException { + var reader = new KDLReader(new ByteArrayInputStream("\nabcdef".getBytes(StandardCharsets.UTF_8))); + reader.read(); + reader.read(); + reader.read(); + + var result = reader.error("error"); + assertThat(result).isEqualTo( + "Error line 2 - error:\n" + + "abcdef\n" + + " ▲\n" + + "─╯" + ); + } + + @Test + @DisplayName("getErrorMessage() should count CRLF as only one line") + void testFailSecondLineWithCrLf() throws IOException { + var reader = new KDLReader(new ByteArrayInputStream("\r\nabcdef".getBytes(StandardCharsets.UTF_8))); + reader.read(); + reader.read(); + reader.read(); + + var result = reader.error("error"); + assertThat(result).isEqualTo( + "Error line 2 - error:\n" + + "abcdef\n" + + "▲\n" + + "╯" + ); + } + + @Test + @DisplayName("read() should fail when the reader has been invalidated") + void readInvalidatedReader() { + var reader = new KDLReader(new ByteArrayInputStream("\r\nabcdef".getBytes(StandardCharsets.UTF_8))); + reader.error("error"); + + assertThatThrownBy(reader::read) + .isInstanceOf(KDLInternalException.class) + .hasMessage("Trying to read from an invalidated reader"); + } + + @Test + @DisplayName("peek() should fail when the reader has been invalidated") + void peekInvalidatedReader() { + var reader = new KDLReader(new ByteArrayInputStream("\r\nabcdef".getBytes(StandardCharsets.UTF_8))); + reader.error("error"); + + assertThatThrownBy(reader::peek) + .isInstanceOf(KDLInternalException.class) + .hasMessage("Trying to peek from an invalidated reader"); + } + + @Test + @DisplayName("peek(2) should fail when the reader has been invalidated") + void peekTwoInvalidatedReader() { + var reader = new KDLReader(new ByteArrayInputStream("\r\nabcdef".getBytes(StandardCharsets.UTF_8))); + reader.error("error"); + + assertThatThrownBy(() -> reader.peek(2)) + .isInstanceOf(KDLInternalException.class) + .hasMessage("Trying to peek from an invalidated reader"); + } + + @Test + @DisplayName("read() should fail when the next character is unicode delete") + void unicodeDelete() { + var reader = new KDLReader(new ByteArrayInputStream(new byte[]{0x7F})); + + assertThatThrownBy(reader::read) + .isInstanceOf(KDLParseException.class) + .hasMessage( + "Error line 1 - invalid codepoint:\n" + + "\u007f\n" + + "▲\n" + + "╯" + ); + } + + @Test + @DisplayName("read() should fail when the next character is first strong isolate") + void firstStrongIsolate() { + var reader = new KDLReader(new ByteArrayInputStream("\u2068".getBytes(StandardCharsets.UTF_8))); + + assertThatThrownBy(reader::read) + .isInstanceOf(KDLParseException.class) + .hasMessage( + "Error line 1 - invalid codepoint:\n" + + "\u2068\n" + + "▲\n" + + "╯" + ); + } + + @Test + @DisplayName("read() should fail when the next character is left-to-right embedding") + void leftToRigthEmbedding() { + var reader = new KDLReader(new ByteArrayInputStream("\u202A".getBytes(StandardCharsets.UTF_8))); + + assertThatThrownBy(reader::read) + .isInstanceOf(KDLParseException.class) + .hasMessage( + "Error line 1 - invalid codepoint:\n" + + "\u202A\n" + + "▲\n" + + "╯" + ); + } + +} diff --git a/src/test/java/kdl/parse/lexer/LexerTest.java b/src/test/java/kdl/parse/lexer/LexerTest.java new file mode 100644 index 0000000..61a88dd --- /dev/null +++ b/src/test/java/kdl/parse/lexer/LexerTest.java @@ -0,0 +1,362 @@ +package kdl.parse.lexer; + +import java.io.ByteArrayInputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.stream.Stream; +import kdl.parse.KDLParseException; +import kdl.parse.lexer.token.Bom; +import kdl.parse.lexer.token.Boolean; +import kdl.parse.lexer.token.EqualsSign; +import kdl.parse.lexer.token.Escline; +import kdl.parse.lexer.token.MultiLineComment; +import kdl.parse.lexer.token.Newline; +import kdl.parse.lexer.token.Null; +import kdl.parse.lexer.token.Number; +import kdl.parse.lexer.token.Semicolon; +import kdl.parse.lexer.token.SingleLineComment; +import kdl.parse.lexer.token.Slashdash; +import kdl.parse.lexer.token.StringToken.IdentifierString; +import kdl.parse.lexer.token.StringToken.QuotedString; +import kdl.parse.lexer.token.Whitespace; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.aggregator.ArgumentsAccessor; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static kdl.parse.lexer.token.Brace.CLOSING_BRACE; +import static kdl.parse.lexer.token.Brace.OPENING_BRACE; +import static kdl.parse.lexer.token.Parentheses.CLOSING_PARENTHESES; +import static kdl.parse.lexer.token.Parentheses.OPENING_PARENTHESES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class LexerTest { + static Stream validTestCases() { + return Stream.of( + Arguments.of("", null), + Arguments.of("\ufeff", Bom.INSTANCE), + Arguments.of("\n", new Newline("\n")), + Arguments.of("\r", new Newline("\r")), + Arguments.of("\r\n", new Newline("\r\n")), + Arguments.of("\u000C", new Newline("\u000C")), + Arguments.of(" ", new Whitespace(' ')), + Arguments.of("\t", new Whitespace('\t')), + Arguments.of("//\na", new SingleLineComment("//\n"), new IdentifierString("a")), + Arguments.of("//\r\na", new SingleLineComment("//\r\n"), new IdentifierString("a")), + Arguments.of("/* hi!\n */a", new MultiLineComment("/* hi!\n */"), new IdentifierString("a")), + Arguments.of("/* hi /* there */ everyone */", new MultiLineComment("/* hi /* there */ everyone */")), + Arguments.of("/-", Slashdash.INSTANCE), + Arguments.of("=", new EqualsSign('=')), + Arguments.of("(", OPENING_PARENTHESES), + Arguments.of(")", CLOSING_PARENTHESES), + Arguments.of("{", OPENING_BRACE), + Arguments.of("}", CLOSING_BRACE), + Arguments.of(";", Semicolon.INSTANCE), + Arguments.of("a", new IdentifierString("a")), + Arguments.of("abc", new IdentifierString("abc")), + Arguments.of("-", new IdentifierString("-")), + Arguments.of("-abc", new IdentifierString("-abc")), + Arguments.of(". ", new IdentifierString("."), new Whitespace(' ')), + Arguments.of(".abc ", new IdentifierString(".abc"), new Whitespace(' ')), + Arguments.of("+.abc", new IdentifierString("+.abc")), + Arguments.of("\"\"", new QuotedString("")), + Arguments.of("\"abc\"", new QuotedString("abc")), + Arguments.of("\"\\\"\"", new QuotedString("\"")), + Arguments.of("\"\\\\\"", new QuotedString("\\")), + Arguments.of("\"\\b\"", new QuotedString("\b")), + Arguments.of("\"\\f\"", new QuotedString("\f")), + Arguments.of("\"\\r\"", new QuotedString("\r")), + Arguments.of("\"\\n\"", new QuotedString("\n")), + Arguments.of("\"\\t\"", new QuotedString("\t")), + Arguments.of("\"\\u{1}\"", new QuotedString("\u0001")), + Arguments.of("\"\\u{1234}\"", new QuotedString("ሴ")), + Arguments.of("\"\\u{1F643}\"", new QuotedString("\uD83D\uDE43")), + Arguments.of("\"a\\ \n\nb\\ c\\ \n\"", new QuotedString("abc")), + Arguments.of("\"\nHello,\nWorld!\n\"", new QuotedString("Hello,\nWorld!")), + Arguments.of("\"\n Hello,\n World!\n \"", new QuotedString("Hello,\nWorld!")), + Arguments.of("\"\n Hello,\n\n World!\n \"", new QuotedString("Hello,\n\n World!")), + Arguments.of("#\"\"\"#", new QuotedString("\"")), + Arguments.of("##\"Hello\\n\\r\\asd\"#world\"##", new QuotedString("Hello\\n\\r\\asd\"#world")), + Arguments.of("#\"\nHello,\nWorld!\n\"#", new QuotedString("Hello,\nWorld!")), + Arguments.of("####\"\n Hello,\\n\n World!\"###\n \"####", new QuotedString("Hello,\\n\n World!\"###")), + Arguments.of("###\"\"#\"##\"###", new QuotedString("\"#\"##")), + Arguments.of("\\", new Escline("\\")), + Arguments.of("\\ // single-line comment", new Escline("\\ // single-line comment")), + Arguments.of("\\ /* multi-line\n comment */ // single-line comment", new Escline("\\ /* multi-line\n comment */ // single-line comment")), + Arguments.of("\\ \na", new Escline("\\ \n"), new IdentifierString("a")), + Arguments.of("#true", Boolean.TRUE), + Arguments.of("#false", Boolean.FALSE), + Arguments.of("#null", Null.INSTANCE), + Arguments.of("#inf", Number.INFINITY), + Arguments.of("#-inf", Number.MINUS_INFINITY), + Arguments.of("#nan", Number.NAN), + Arguments.of("123", new Number.Integer(BigInteger.valueOf(123))), + Arguments.of("+123", new Number.Integer(BigInteger.valueOf(123))), + Arguments.of("-123", new Number.Integer(BigInteger.valueOf(-123))), + Arguments.of("-1_2_3", new Number.Integer(BigInteger.valueOf(-123))), + Arguments.of("-_123", new IdentifierString("-_123")), + Arguments.of("0x12", new Number.Integer(BigInteger.valueOf(18L))), + Arguments.of("0x1_2", new Number.Integer(BigInteger.valueOf(18L))), + Arguments.of("-0x12", new Number.Integer(BigInteger.valueOf(-18L))), + Arguments.of("0o12", new Number.Integer(BigInteger.valueOf(10L))), + Arguments.of("0o1_2", new Number.Integer(BigInteger.valueOf(10L))), + Arguments.of("-0o12", new Number.Integer(BigInteger.valueOf(-10L))), + Arguments.of("0b101", new Number.Integer(BigInteger.valueOf(5L))), + Arguments.of("0b1_01", new Number.Integer(BigInteger.valueOf(5L))), + Arguments.of("-0b101", new Number.Integer(BigInteger.valueOf(-5L))), + Arguments.of("2.5", new Number.Decimal(new BigDecimal("2.5"))), + Arguments.of("123.456", new Number.Decimal(new BigDecimal("123.456"))), + Arguments.of("123_456.789", new Number.Decimal(new BigDecimal("123456.789"))), + Arguments.of("-123.456", new Number.Decimal(new BigDecimal("-123.456"))), + Arguments.of("123e3", new Number.Decimal(new BigDecimal("1.23E5"))), + Arguments.of("1_2_3e3_", new Number.Decimal(new BigDecimal("1.23E5"))), + Arguments.of("-123.456e-3", new Number.Decimal(new BigDecimal("-0.123456"))), + Arguments.of("node (type/*hey*/)10", new IdentifierString("node"), new Whitespace(' '), OPENING_PARENTHESES, new IdentifierString("type"), new MultiLineComment("/*hey*/"), CLOSING_PARENTHESES, new Number.Integer(BigInteger.valueOf(10L))) + ); + } + + @ParameterizedTest + @MethodSource("validTestCases") + void validLexerTest(String input, ArgumentsAccessor expectedTokens) throws Exception { + try (var inputStream = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))) { + var lexer = new Lexer(inputStream); + for (var i = 1; i < expectedTokens.size(); i++) { + assertThat(lexer.next()).isEqualTo(expectedTokens.get(i, Token.class)); + } + assertThat(lexer.next()).isNull(); + } + } + + @ParameterizedTest + @MethodSource("validTestCases") + void peekTest(String input) throws Exception { + try (var inputStream = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))) { + var lexer = new Lexer(inputStream); + var peeked = lexer.peek(); + var read = lexer.next(); + + assertThat(read).isEqualTo(peeked); + } + } + + static Stream errorTestCases() { + return Stream.of( + Arguments.of( + "/* hi!", + "Error line 1 - unexpected end of file inside multi-line comment:\n" + + "/* hi!\n" + + " ▲\n" + + "─────╯" + ), + Arguments.of( + "/ ", + "Error line 1 - [/*-] expected after '/' but got ' ':\n" + + "/ \n" + + " ▲\n" + + "─╯" + ), + Arguments.of( + "true", + "Error line 1 - keyword used as identifier, use a quoted string instead:\n" + + "true\n" + + "▲\n" + + "╯" + ), + Arguments.of( + "\"", + "Error line 1 - unexpected end of file inside quoted string:\n" + + "\"\n" + + "▲\n" + + "╯" + ), + Arguments.of( + "\"a\n\"", + "Error line 2 - unexpected newline inside quoted string, escape it or use a multi-line quoted string:\n" + + "\"\n" + + "▲\n" + + "╯" + ), + Arguments.of( + "\"\\a\"", + "Error line 1 - invalid escape sequence '\\a':\n" + + "\"\\a\"\n" + + " ▲\n" + + "──╯" + ), + Arguments.of( + "\"\\u1337\"", + "Error line 1 - '{' expected at start of unicode escape:\n" + + "\"\\u1337\"\n" + + " ▲\n" + + "───╯" + ), + Arguments.of( + "\"\\u{12g4}\"", + "Error line 1 - unexpected character in unicode escape:\n" + + "\"\\u{12g4}\"\n" + + " ▲\n" + + "──────╯" + ), + Arguments.of( + "\"\\u{}\"", + "Error line 1 - at least one digit is required for a unicode escape:\n" + + "\"\\u{}\"\n" + + " ▲\n" + + "────╯" + ), + Arguments.of( + "\"\\u{1234567}\"", + "Error line 1 - '}' expected at end of unicode escape:\n" + + "\"\\u{1234567}\"\n" + + " ▲\n" + + "──────────╯" + ), + Arguments.of( + "\"\\u{Fff456}\"", + "Error line 1 - invalid unicode value U+FFF456:\n" + + "\"\\u{Fff456}\"\n" + + " ▲\n" + + "────╯" + ), + Arguments.of( + "\"\n ab\n cde\"", + "Error line 3 - last line of a multi-line string must only contain whitespaces:\n" + + " cde\"\n" + + " ▲\n" + + "──╯" + ), + Arguments.of( + "\"\n ab\n cde\n \"", + "Error line 3 - multi-line string indentation must match last line (\" \"):\n" + + " cde\n" + + "▲\n" + + "╯" + ), + Arguments.of( + "\"\n ab\n c\n \"", + "Error line 3 - multi-line string indentation must match last line (\" \"):\n" + + " c\n" + + "▲\n" + + "╯" + ), + Arguments.of( + "##abc##", + "Error line 1 - a quote is required to start a raw string:\n" + + "##abc##\n" + + " ▲\n" + + "──╯" + ), + Arguments.of( + "#\"", + "Error line 1 - unexpected end of file inside raw string:\n" + + "#\"\n" + + " ▲\n" + + "─╯" + ), + Arguments.of( + "#\"\n", + "Error line 2 - unexpected end of file inside raw string:\n" + + "\n" + + "▲\n" + + "╯" + ), + Arguments.of( + "##\"a\"#\n\"##", + "Error line 2 - unexpected newline inside raw string, use a multi-line raw string:\n" + + "\"##\n" + + "▲\n" + + "╯" + ), + Arguments.of( + "\\ /a", + "Error line 1 - invalid character in escaped line:\n" + + "\\ /a\n" + + " ▲\n" + + "───╯" + ), + Arguments.of( + "\\ a", + "Error line 1 - invalid character in escaped line:\n" + + "\\ a\n" + + " ▲\n" + + "──╯" + ), + Arguments.of( + "#abc#", + "Error line 1 - invalid value #abc:\n" + + "#abc#\n" + + "▲\n" + + "╯" + ), + Arguments.of( + "0bx01", + "Error line 1 - digit expected at start of number:\n" + + "0bx01\n" + + " ▲\n" + + "──╯" + ), + Arguments.of( + "0b701", + "Error line 1 - digit expected at start of number:\n" + + "0b701\n" + + " ▲\n" + + "──╯" + ), + Arguments.of( + "2.", + "Error line 1 - digit expected immediately after '.':\n" + + "2.\n" + + " ▲\n" + + "──╯" + ), + Arguments.of( + "2._", + "Error line 1 - digit expected immediately after '.':\n" + + "2._\n" + + " ▲\n" + + "──╯" + ), + Arguments.of( + ".0", + "Error line 1 - invalid character '.':\n" + + ".0\n" + + "▲\n" + + "╯" + ), + Arguments.of( + "1._0", + "Error line 1 - digit expected immediately after '.':\n" + + "1._0\n" + + " ▲\n" + + "──╯" + ), + Arguments.of( + "1.0e_3", + "Error line 1 - digit expected at start of exponent:\n" + + "1.0e_3\n" + + " ▲\n" + + "────╯" + ), + Arguments.of( + "0x_10", + "Error line 1 - digit expected at start of number:\n" + + "0x_10\n" + + " ▲\n" + + "──╯" + ) + ); + } + + @ParameterizedTest + @MethodSource("errorTestCases") + void errorLexerTest(String input, String expectedMessage) throws Exception { + try (var inputStream = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))) { + var lexer = new Lexer(inputStream); + assertThatThrownBy(lexer::next) + .isInstanceOf(KDLParseException.class) + .hasMessage(expectedMessage); + } + } +} diff --git a/src/test/java/kdl/search/GeneralSearchTest.java b/src/test/java/kdl/search/GeneralSearchTest.java deleted file mode 100644 index db3ade4..0000000 --- a/src/test/java/kdl/search/GeneralSearchTest.java +++ /dev/null @@ -1,509 +0,0 @@ -package kdl.search; - -import java.util.Optional; -import kdl.objects.KDLDocument; -import kdl.objects.KDLNode; -import kdl.parse.KDLParser; -import kdl.search.mutation.Mutation; -import kdl.search.predicates.NodePredicate; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import static kdl.search.NodeIDMatcher.hasId; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -public class GeneralSearchTest { - private static final KDLParser parser = new KDLParser(); - - @Mock - public Mutation mutation; - - @Mock - public NodePredicate predicate; - - @Test - public void testMutateEmpty() { - var inputDoc = KDLDocument.empty(); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(inputDoc); - verifyNoInteractions(mutation); - } - - @Test - public void testMutateEmptyWithPredicate() { - var inputDoc = KDLDocument.empty(); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(inputDoc); - verifyNoInteractions(mutation); - } - - @Test - public void testMutateNothingMatchesPredicate() { - var inputDoc = parser.parse("node1; node2 {node4;}; node3"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(inputDoc); - verifyNoInteractions(mutation); - } - - @Test - public void testMutateRootNodeMatchesPredicateDelete() { - var inputDoc = parser.parse("node1; node2; node3"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node2")))).thenReturn(true); - when(mutation.apply(any())).thenReturn(Optional.empty()); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(parser.parse("node1; node3")); - verify(mutation, times(1)).apply(argThat(hasId("node2"))); - } - - @Test - public void testMutateBranchNodeMatchesPredicateDelete() { - var inputDoc = parser.parse("node1; node2 {node4 {node5;};}; node3"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node4")))).thenReturn(true); - when(mutation.apply(any())).thenReturn(Optional.empty()); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(parser.parse("node1; node2; node3")); - verify(mutation, times(1)).apply(argThat(hasId("node4"))); - } - - @Test - public void testMutateLeafNodeMatchesPredicateDelete() { - var inputDoc = parser.parse("node1; node2 {node4;}; node3"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node4")))).thenReturn(true); - when(mutation.apply(any())).thenReturn(Optional.empty()); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(parser.parse("node1; node2; node3")); - verify(mutation, times(1)).apply(argThat(hasId("node4"))); - } - - @Test - public void testMutateEverythingMatchesPredicate() { - var inputDoc = parser.parse("node1; node2 {node4 {node5;};}; node3"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(true); - when(mutation.apply(any())).thenAnswer(invocation -> { - KDLNode node = invocation.getArgument(0); - return Optional.of(node.toBuilder().addArg(1).build()); - }); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(parser.parse("node1 1; node2 1 {node4 1 {node5 1;};}; node3 1")); - verify(mutation, times(5)).apply(any()); - verifyNoMoreInteractions(mutation); - } - - @Test - public void testMutateRootNodeMatchesPredicate() { - var inputDoc = parser.parse("node1; node2; node3"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node2")))).thenReturn(true); - when(mutation.apply(any())).thenAnswer(invocation -> { - KDLNode node = invocation.getArgument(0); - return Optional.of(node.toBuilder().addArg(1).build()); - }); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(parser.parse("node1; node2 1; node3")); - verify(mutation, times(1)).apply(argThat(hasId("node2"))); - } - - @Test - public void testMutateBranchNodeMatchesPredicate() { - var inputDoc = parser.parse("node1; node2 {node4 {node5;};}; node3"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node4")))).thenReturn(true); - when(mutation.apply(any())).thenAnswer(invocation -> { - KDLNode node = invocation.getArgument(0); - return Optional.of(node.toBuilder().addArg(1).build()); - }); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(parser.parse("node1; node2 {node4 1 {node5;};}; node3")); - verify(mutation, times(1)).apply(argThat(hasId("node4"))); - } - - @Test - public void testMutateLeafNodeMatchesPredicate() { - var inputDoc = parser.parse("node1; node2 {node4;}; node3"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node4")))).thenReturn(true); - when(mutation.apply(any())).thenAnswer(invocation -> { - KDLNode node = invocation.getArgument(0); - return Optional.of(node.toBuilder().addArg(1).build()); - }); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(parser.parse("node1; node2 {node4 1;}; node3")); - verify(mutation, times(1)).apply(argThat(hasId("node4"))); - } - - @Test - public void testMutateEverythingMatchesPredicateAddChild() { - var inputDoc = parser.parse("node1; node2 {node4 {node5;};}; node3"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(true); - when(mutation.apply(any())).thenAnswer(invocation -> { - KDLNode node = invocation.getArgument(0); - var docBuilder = KDLDocument.builder(); - node.getChild().ifPresent(ch -> docBuilder.addNodes(ch.getNodes())); - docBuilder.addNode(KDLNode.builder().setIdentifier("added").build()); - - return Optional.of(node.toBuilder().setChild(docBuilder.build()).build()); - }); - - assertThat(search.mutate(inputDoc, mutation)) - .isEqualTo(parser.parse("node1 {added;}; node2 {node4 {node5 {added;}; added;}; added;}; node3 {added;}")); - verify(mutation, times(5)).apply(any()); - verifyNoMoreInteractions(mutation); - } - - @Test - public void testMutateRootNodeMatchesPredicateAddChild() { - var inputDoc = parser.parse("node1; node2; node3"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node2")))).thenReturn(true); - when(mutation.apply(any())).thenAnswer(invocation -> { - KDLNode node = invocation.getArgument(0); - var docBuilder = KDLDocument.builder(); - node.getChild().ifPresent(ch -> docBuilder.addNodes(ch.getNodes())); - docBuilder.addNode(KDLNode.builder().setIdentifier("added").build()); - - return Optional.of(node.toBuilder().setChild(docBuilder.build()).build()); - }); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(parser.parse("node1; node2 {added;}; node3")); - verify(mutation, times(1)).apply(argThat(hasId("node2"))); - } - - @Test - public void testMutateBranchNodeMatchesPredicateAddChild() { - var inputDoc = parser.parse("node1; node2 {node4 {node5;};}; node3"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node4")))).thenReturn(true); - when(mutation.apply(any())).thenAnswer(invocation -> { - KDLNode node = invocation.getArgument(0); - var docBuilder = KDLDocument.builder(); - node.getChild().ifPresent(ch -> docBuilder.addNodes(ch.getNodes())); - docBuilder.addNode(KDLNode.builder().setIdentifier("added").build()); - - return Optional.of(node.toBuilder().setChild(docBuilder.build()).build()); - }); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(parser.parse("node1; node2 {node4 {node5; added;};}; node3")); - verify(mutation, times(1)).apply(argThat(hasId("node4"))); - } - - @Test - public void testMutateLeafNodeMatchesPredicateAddChild() { - var inputDoc = parser.parse("node1; node2 {node4;}; node3"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node4")))).thenReturn(true); - when(mutation.apply(any())).thenAnswer(invocation -> { - KDLNode node = invocation.getArgument(0); - var docBuilder = KDLDocument.builder(); - node.getChild().ifPresent(ch -> docBuilder.addNodes(ch.getNodes())); - docBuilder.addNode(KDLNode.builder().setIdentifier("added").build()); - - return Optional.of(node.toBuilder().setChild(docBuilder.build()).build()); - }); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(parser.parse("node1; node2 {node4 {added;};}; node3")); - verify(mutation, times(1)).apply(argThat(hasId("node4"))); - } - - @Test - public void testMutateMinDepthAddChild() { - var inputDoc = parser.parse("node1; node2 {node4;}; node3"); - var search = GeneralSearch.builder() - .setPredicate(predicate) - .setMaxDepth(0) - .build(); - when(predicate.test(any())).thenReturn(false); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(inputDoc); - verify(mutation, never()).apply(any()); - } - - @Test - public void testMutateEverythingMatchesButDepth() { - var inputDoc = parser.parse("node1; node2 {node4 {node5;};}; node3"); - var search = GeneralSearch.builder() - .setPredicate(predicate) - .setMinDepth(1) - .setMaxDepth(1) - .build(); - when(predicate.test(any())).thenReturn(true); - when(mutation.apply(any())).thenAnswer(invocation -> { - KDLNode node = invocation.getArgument(0); - return Optional.of(node.toBuilder().addArg(1).build()); - }); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(parser.parse("node1; node2 {node4 1 {node5;};}; node3")); - verify(mutation, times(1)).apply(argThat(hasId("node4"))); - verifyNoMoreInteractions(mutation); - } - - @Test - public void testFilterEmpty() { - var inputDoc = KDLDocument.empty(); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - - assertThat(search.filter(inputDoc, true)).isEqualTo(KDLDocument.empty()); - } - - @Test - public void testFilterNothingMatches() { - var inputDoc = parser.parse("node1; node2 {node3;}"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - - assertThat(search.filter(inputDoc, true)).isEqualTo(KDLDocument.empty()); - } - - @Test - public void testFilterRootNodeMatches() { - var inputDoc = parser.parse("node1; node2 {node3;}"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node2")))).thenReturn(true); - - assertThat(search.filter(inputDoc, true)).isEqualTo(parser.parse("node2")); - } - - @Test - public void testFilterBranchNodeMatches() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node3")))).thenReturn(true); - - assertThat(search.filter(inputDoc, true)).isEqualTo(parser.parse("node2 {node3;}")); - } - - @Test - public void testFilterLeafNodeMatches() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node4")))).thenReturn(true); - - assertThat(search.filter(inputDoc, true)).isEqualTo(parser.parse("node2 {node3 {node4;};}")); - } - - @Test - public void testFilterMultipleLeavesMatch() { - var inputDoc = parser.parse("node1; node2 {node3 {node4; node5;};}"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node4")))).thenReturn(true); - when(predicate.test(argThat(hasId("node5")))).thenReturn(true); - - assertThat(search.filter(inputDoc, true)).isEqualTo(parser.parse("node2 {node3 {node4; node5;};}")); - } - - @Test - public void testFilterEverythingMatches() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(true); - - assertThat(search.filter(inputDoc, true)).isEqualTo(parser.parse("node1; node2 {node3 {node4;};}")); - } - - @Test - public void testListEmpty() { - var inputDoc = KDLDocument.empty(); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - - assertThat(search.list(inputDoc, false)).isEqualTo(KDLDocument.empty()); - } - - @Test - public void testListNothingMatches() { - var inputDoc = parser.parse("node1; node2"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - - assertThat(search.list(inputDoc, false)).isEqualTo(KDLDocument.empty()); - } - - @Test - public void testListNothingMatchesTrim() { - var inputDoc = parser.parse("node1; node2"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - - assertThat(search.list(inputDoc, true)).isEqualTo(KDLDocument.empty()); - } - - @Test - public void testListEverythingMatches() { - var inputDoc = parser.parse("node1; node2 {node3;}"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(true); - - assertThat(search.list(inputDoc, false)).isEqualTo(parser.parse("node1; node2 {node3;}; node3")); - } - - @Test - public void testListEverythingMatchesTrim() { - var inputDoc = parser.parse("node1; node2 {node3;}"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(true); - - assertThat(search.list(inputDoc, true)).isEqualTo(parser.parse("node1; node2; node3")); - } - - @Test - public void testListRootNodeMatches() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node2")))).thenReturn(true); - - assertThat(search.list(inputDoc, false)).isEqualTo(parser.parse("node2 {node3 {node4;};}")); - } - - @Test - public void testListRootNodeMatchesTrim() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node2")))).thenReturn(true); - - assertThat(search.list(inputDoc, true)).isEqualTo(parser.parse("node2")); - } - - @Test - public void testListBranchNodeMatches() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node3")))).thenReturn(true); - - assertThat(search.list(inputDoc, false)).isEqualTo(parser.parse("node3 {node4;}")); - } - - @Test - public void testListBranchNodeMatchesTrim() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node3")))).thenReturn(true); - - assertThat(search.list(inputDoc, true)).isEqualTo(parser.parse("node3")); - } - - @Test - public void testListLeafNodeMatches() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node4")))).thenReturn(true); - - assertThat(search.list(inputDoc, false)).isEqualTo(parser.parse("node4")); - } - - @Test - public void testListLeafNodeMatchesTrim() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node4")))).thenReturn(true); - - assertThat(search.list(inputDoc, true)).isEqualTo(parser.parse("node4")); - } - - @Test - public void testAnyMatchEmpty() { - var inputDoc = KDLDocument.empty(); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - - assertThat(search.anyMatch(inputDoc)).isFalse(); - verifyNoInteractions(predicate); - } - - @Test - public void testAnyMatchNothingMatches() { - var inputDoc = parser.parse("node1; node2"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - - assertThat(search.anyMatch(inputDoc)).isFalse(); - verify(predicate, times(1)).test(argThat(hasId("node1"))); - verify(predicate, times(1)).test(argThat(hasId("node2"))); - } - - @Test - public void testAnyMatchEverythingMatches() { - var inputDoc = parser.parse("node1; node2"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(true); - - assertThat(search.anyMatch(inputDoc)).isTrue(); - verify(predicate, never()).test(argThat(hasId("node2"))); - } - - @Test - public void testAnyMatchRootNodeMatches() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node2")))).thenReturn(true); - - assertThat(search.anyMatch(inputDoc)).isTrue(); - verify(predicate, times(1)).test(argThat(hasId("node1"))); - verify(predicate, times(1)).test(argThat(hasId("node2"))); - verify(predicate, never()).test(argThat(hasId("node3"))); - verify(predicate, never()).test(argThat(hasId("node4"))); - } - - @Test - public void testAnyMatchBranchNodeMatches() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node3")))).thenReturn(true); - - assertThat(search.anyMatch(inputDoc)).isTrue(); - verify(predicate, times(1)).test(argThat(hasId("node1"))); - verify(predicate, times(1)).test(argThat(hasId("node2"))); - verify(predicate, times(1)).test(argThat(hasId("node3"))); - verify(predicate, never()).test(argThat(hasId("node4"))); - } - - @Test - public void testAnyMatchLeafNodeMatches() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}"); - var search = GeneralSearch.builder().setPredicate(predicate).build(); - when(predicate.test(any())).thenReturn(false); - when(predicate.test(argThat(hasId("node4")))).thenReturn(true); - - assertThat(search.anyMatch(inputDoc)).isTrue(); - verify(predicate, times(1)).test(argThat(hasId("node1"))); - verify(predicate, times(1)).test(argThat(hasId("node2"))); - verify(predicate, times(1)).test(argThat(hasId("node3"))); - verify(predicate, times(1)).test(argThat(hasId("node4"))); - } -} diff --git a/src/test/java/kdl/search/NodeIDMatcher.java b/src/test/java/kdl/search/NodeIDMatcher.java deleted file mode 100644 index 6d9ab28..0000000 --- a/src/test/java/kdl/search/NodeIDMatcher.java +++ /dev/null @@ -1,25 +0,0 @@ -package kdl.search; - -import kdl.objects.KDLNode; -import org.mockito.ArgumentMatcher; - -public class NodeIDMatcher implements ArgumentMatcher { - private final String id; - - private NodeIDMatcher(String id) { - this.id = id; - } - - @Override - public boolean matches(KDLNode argument) { - if (argument == null) { - return false; - } - - return id.equals(argument.getIdentifier()); - } - - public static NodeIDMatcher hasId(String id) { - return new NodeIDMatcher(id); - } -} diff --git a/src/test/java/kdl/search/PathedSearchTest.java b/src/test/java/kdl/search/PathedSearchTest.java deleted file mode 100644 index e2626ed..0000000 --- a/src/test/java/kdl/search/PathedSearchTest.java +++ /dev/null @@ -1,534 +0,0 @@ -package kdl.search; - -import java.util.Optional; -import kdl.objects.KDLDocument; -import kdl.objects.KDLNode; -import kdl.parse.KDLParser; -import kdl.search.mutation.Mutation; -import kdl.search.predicates.NodePredicate; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import static kdl.search.NodeIDMatcher.hasId; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -public class PathedSearchTest { - private static final KDLParser parser = new KDLParser(); - - @Mock - public Mutation mutation; - - @Mock - public NodePredicate predicate1; - - @Mock - public NodePredicate predicate2; - - @Mock - public NodePredicate predicate3; - - @Test - public void testMutateEmpty() { - var inputDoc = KDLDocument.empty(); - var search = PathedSearch.builder() - .addLevel(predicate1) - .build(); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(KDLDocument.empty()); - verifyNoInteractions(mutation, predicate1); - } - - @Test - public void testMutateNothingMatches() { - var inputDoc = parser.parse("node1; node2 {node3;}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .build(); - when(predicate1.test(any())).thenReturn(false); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(inputDoc); - verifyNoInteractions(mutation); - } - - @Test - public void testMutateEverythingAtRootMatches() { - var inputDoc = parser.parse("node1; node2 {node3;}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .build(); - when(predicate1.test(any())).thenReturn(true); - when(mutation.apply(any())).thenAnswer(invocation -> { - KDLNode node = invocation.getArgument(0); - return Optional.of(node.toBuilder().addArg(1).build()); - }); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(parser.parse("node1 1; node2 1 {node3;}")); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verify(mutation, times(1)).apply(argThat(hasId("node1"))); - verify(mutation, times(1)).apply(argThat(hasId("node2"))); - verifyNoMoreInteractions(predicate1, mutation); - } - - @Test - public void testMutateEverythingAtSecondLevelMatches() { - var inputDoc = parser.parse("node1; node2 {node3; node4;}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .addLevel(predicate2) - .build(); - when(predicate1.test(any())).thenReturn(true); - when(predicate2.test(any())).thenReturn(true); - when(mutation.apply(any())).thenAnswer(invocation -> { - KDLNode node = invocation.getArgument(0); - return Optional.of(node.toBuilder().addArg(1).build()); - }); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(parser.parse("node1; node2 {node3 1; node4 1;}")); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verify(predicate2, times(1)).test(argThat(hasId("node3"))); - verify(predicate2, times(1)).test(argThat(hasId("node4"))); - verify(mutation, times(1)).apply(argThat(hasId("node3"))); - verify(mutation, times(1)).apply(argThat(hasId("node4"))); - verifyNoMoreInteractions(predicate1, predicate2, mutation); - } - - @Test - public void testMutateEverythingAtThirdLevelMatches() { - var inputDoc = parser.parse("node1; node2 {node3 {node5;}; node4 {node6;};}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .addLevel(predicate2) - .addLevel(predicate3) - .build(); - when(predicate1.test(any())).thenReturn(true); - when(predicate2.test(any())).thenReturn(true); - when(predicate3.test(any())).thenReturn(true); - when(mutation.apply(any())).thenAnswer(invocation -> { - KDLNode node = invocation.getArgument(0); - return Optional.of(node.toBuilder().addArg(1).build()); - }); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(parser.parse("node1; node2 {node3 {node5 1;}; node4 {node6 1;};}")); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verify(predicate2, times(1)).test(argThat(hasId("node3"))); - verify(predicate2, times(1)).test(argThat(hasId("node4"))); - verify(predicate3, times(1)).test(argThat(hasId("node5"))); - verify(predicate3, times(1)).test(argThat(hasId("node6"))); - verify(mutation, times(1)).apply(argThat(hasId("node5"))); - verify(mutation, times(1)).apply(argThat(hasId("node6"))); - verifyNoMoreInteractions(predicate1, predicate2, predicate3, mutation); - } - - @Test - public void testMutateSomeThingsAtFirstLevelEverythingAtSecondLevelMatches() { - var inputDoc = parser.parse("node1; node2 {node3; node4;}; node5 {node6;}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .addLevel(predicate2) - .build(); - when(predicate1.test(any())).thenReturn(false); - when(predicate1.test(argThat(hasId("node5")))).thenReturn(true); - when(predicate2.test(any())).thenReturn(true); - when(mutation.apply(any())).thenAnswer(invocation -> { - KDLNode node = invocation.getArgument(0); - return Optional.of(node.toBuilder().addArg(1).build()); - }); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(parser.parse("node1; node2 {node3; node4;}; node5 {node6 1;}")); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verify(predicate1, times(1)).test(argThat(hasId("node5"))); - verify(predicate2, times(1)).test(argThat(hasId("node6"))); - verify(mutation, times(1)).apply(argThat(hasId("node6"))); - verifyNoMoreInteractions(predicate1, predicate2, mutation); - } - - @Test - public void testMutateNothingAtFirstLevelEverythingAtSecondLevelMatches() { - var inputDoc = parser.parse("node1; node2 {node3; node4;}; node5 {node6;}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .addLevel(predicate2) - .build(); - when(predicate1.test(any())).thenReturn(false); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(parser.parse("node1; node2 {node3; node4;}; node5 {node6;}")); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verify(predicate1, times(1)).test(argThat(hasId("node5"))); - verifyNoMoreInteractions(predicate1, predicate2, mutation); - } - - @Test - public void testMutateEverythingAtFirstLevelNothingAtSecondLevelMatches() { - var inputDoc = parser.parse("node1; node2 {node3; node4;}; node5 {node6;}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .addLevel(predicate2) - .build(); - when(predicate1.test(any())).thenReturn(true); - when(predicate2.test(any())).thenReturn(false); - - assertThat(search.mutate(inputDoc, mutation)).isEqualTo(parser.parse("node1; node2 {node3; node4;}; node5 {node6;}")); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verify(predicate1, times(1)).test(argThat(hasId("node5"))); - verify(predicate2, times(1)).test(argThat(hasId("node3"))); - verify(predicate2, times(1)).test(argThat(hasId("node4"))); - verify(predicate2, times(1)).test(argThat(hasId("node6"))); - verifyNoMoreInteractions(predicate1, predicate2, mutation); - } - - @Test - public void testListEmpty() { - var inputDoc = KDLDocument.empty(); - var search = PathedSearch.builder() - .addLevel(predicate1) - .build(); - - assertThat(search.list(inputDoc, false)).isEqualTo(KDLDocument.empty()); - verifyNoInteractions(predicate1); - } - - @Test - public void testListNothingMatches() { - var inputDoc = parser.parse("node1; node2"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .build(); - when(predicate1.test(any())).thenReturn(false); - - assertThat(search.list(inputDoc, false)).isEqualTo(KDLDocument.empty()); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verifyNoMoreInteractions(predicate1); - } - - @Test - public void testListEverythingMatches() { - var inputDoc = parser.parse("node1; node2 {node3;}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .build(); - when(predicate1.test(any())).thenReturn(true); - - assertThat(search.list(inputDoc, false)).isEqualTo(parser.parse("node1; node2 {node3;}")); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verifyNoMoreInteractions(predicate1); - } - - @Test - public void testListEverythingMatchesTrim() { - var inputDoc = parser.parse("node1; node2 {node3;}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .build(); - when(predicate1.test(any())).thenReturn(true); - - assertThat(search.list(inputDoc, true)).isEqualTo(parser.parse("node1; node2")); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verifyNoMoreInteractions(predicate1); - } - - @Test - public void testListBranch() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .addLevel(predicate2) - .build(); - when(predicate1.test(any())).thenReturn(true); - when(predicate2.test(any())).thenReturn(true); - - assertThat(search.list(inputDoc, false)).isEqualTo(parser.parse("node3 {node4;}")); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verify(predicate2, times(1)).test(argThat(hasId("node3"))); - verifyNoMoreInteractions(predicate1, predicate2); - } - - @Test - public void testListBranchTrim() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .addLevel(predicate2) - .build(); - when(predicate1.test(any())).thenReturn(true); - when(predicate2.test(any())).thenReturn(true); - - assertThat(search.list(inputDoc, true)).isEqualTo(parser.parse("node3")); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verify(predicate2, times(1)).test(argThat(hasId("node3"))); - verifyNoMoreInteractions(predicate1, predicate2); - } - - @Test - public void testListLeaf() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .addLevel(predicate2) - .addLevel(predicate3) - .build(); - when(predicate1.test(any())).thenReturn(true); - when(predicate2.test(any())).thenReturn(true); - when(predicate3.test(any())).thenReturn(true); - - assertThat(search.list(inputDoc, false)).isEqualTo(parser.parse("node4")); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verify(predicate2, times(1)).test(argThat(hasId("node3"))); - verify(predicate3, times(1)).test(argThat(hasId("node4"))); - verifyNoMoreInteractions(predicate1, predicate2, predicate3); - } - - @Test - public void testListLeafTrim() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .addLevel(predicate2) - .addLevel(predicate3) - .build(); - when(predicate1.test(any())).thenReturn(true); - when(predicate2.test(any())).thenReturn(true); - when(predicate3.test(any())).thenReturn(true); - - assertThat(search.list(inputDoc, true)).isEqualTo(parser.parse("node4")); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verify(predicate2, times(1)).test(argThat(hasId("node3"))); - verify(predicate3, times(1)).test(argThat(hasId("node4"))); - verifyNoMoreInteractions(predicate1, predicate2, predicate3); - } - - @Test - public void testAnyMatchEmpty() { - var inputDoc = KDLDocument.empty(); - var search = PathedSearch.builder() - .addLevel(predicate1) - .build(); - - assertThat(search.anyMatch(inputDoc)).isFalse(); - verifyNoInteractions(predicate1); - } - - @Test - public void testAnyMatchNothingMatches() { - var inputDoc = parser.parse("node1; node2 {node3;};"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .addLevel(predicate2) - .build(); - when(predicate1.test(any())).thenReturn(false); - - assertThat(search.anyMatch(inputDoc)).isFalse(); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verifyNoMoreInteractions(predicate1, predicate2); - } - - @Test - public void testAnyMatchNothingMatchesAtFirstLevel() { - var inputDoc = parser.parse("node1; node2 {node3;};"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .addLevel(predicate2) - .build(); - when(predicate1.test(any())).thenReturn(false); - - assertThat(search.anyMatch(inputDoc)).isFalse(); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verifyNoMoreInteractions(predicate1, predicate2); - } - - @Test - public void testAnyMatchRoot() { - var inputDoc = parser.parse("node1; node2 {node3;};"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .build(); - when(predicate1.test(any())).thenReturn(true); - - assertThat(search.anyMatch(inputDoc)).isTrue(); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verifyNoMoreInteractions(predicate1); - } - - @Test - public void testAnyMatchBranch() { - var inputDoc = parser.parse("node1 {node2;}; node3 {node4 {node5;};};"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .addLevel(predicate2) - .build(); - when(predicate1.test(any())).thenReturn(true); - when(predicate2.test(any())).thenReturn(false); - when(predicate2.test(argThat(hasId("node4")))).thenReturn(true); - - assertThat(search.anyMatch(inputDoc)).isTrue(); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node3"))); - verify(predicate2, times(1)).test(argThat(hasId("node2"))); - verify(predicate2, times(1)).test(argThat(hasId("node4"))); - verifyNoMoreInteractions(predicate1, predicate2); - } - - @Test - public void testAnyMatchLeaf() { - var inputDoc = parser.parse("node1 {node2;}; node3 {node4 {node5;};};"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .addLevel(predicate2) - .addLevel(predicate3) - .build(); - when(predicate1.test(any())).thenReturn(true); - when(predicate2.test(any())).thenReturn(true); - when(predicate3.test(any())).thenReturn(true); - - assertThat(search.anyMatch(inputDoc)).isTrue(); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node3"))); - verify(predicate2, times(1)).test(argThat(hasId("node2"))); - verify(predicate2, times(1)).test(argThat(hasId("node4"))); - verify(predicate3, times(1)).test(argThat(hasId("node5"))); - verifyNoMoreInteractions(predicate1, predicate2); - } - - @Test - public void testFilterEmpty() { - var inputDoc = KDLDocument.empty(); - var search = PathedSearch.builder() - .addLevel(predicate1) - .build(); - - assertThat(search.filter(inputDoc, true)).isEqualTo(KDLDocument.empty()); - verifyNoInteractions(predicate1); - } - - @Test - public void testFilterNothingMatches() { - var inputDoc = parser.parse("node1; node2 {node3;}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .addLevel(predicate2) - .build(); - when(predicate1.test(any())).thenReturn(true); - when(predicate2.test(any())).thenReturn(false); - - assertThat(search.filter(inputDoc, true)).isEqualTo(KDLDocument.empty()); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verify(predicate2, times(1)).test(argThat(hasId("node3"))); - verifyNoMoreInteractions(predicate1, predicate2); - } - - @Test - public void testFilterEverythingMatches() { - var inputDoc = parser.parse("node1; node2 {node3;}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .addLevel(predicate2) - .build(); - when(predicate1.test(any())).thenReturn(true); - when(predicate2.test(any())).thenReturn(true); - - assertThat(search.filter(inputDoc, true)).isEqualTo(parser.parse("node2 {node3;}")); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verify(predicate2, times(1)).test(argThat(hasId("node3"))); - verifyNoMoreInteractions(predicate1, predicate2); - } - - @Test - public void testFilterRootMatches() { - var inputDoc = parser.parse("node1; node2 {node3;}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .build(); - when(predicate1.test(any())).thenReturn(true); - - assertThat(search.filter(inputDoc, true)).isEqualTo(parser.parse("node1; node2")); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verifyNoMoreInteractions(predicate1, predicate2); - } - - @Test - public void testFilterBranchMatches() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .addLevel(predicate2) - .build(); - when(predicate1.test(any())).thenReturn(true); - when(predicate2.test(any())).thenReturn(true); - - assertThat(search.filter(inputDoc, true)).isEqualTo(parser.parse("node2 {node3;}")); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verify(predicate2, times(1)).test(argThat(hasId("node3"))); - verifyNoMoreInteractions(predicate1, predicate2); - } - - @Test - public void testFilterLeafMatches() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .addLevel(predicate2) - .addLevel(predicate3) - .build(); - when(predicate1.test(any())).thenReturn(true); - when(predicate2.test(any())).thenReturn(true); - when(predicate3.test(any())).thenReturn(true); - - assertThat(search.filter(inputDoc, true)).isEqualTo(parser.parse("node2 {node3 {node4;};}")); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verify(predicate2, times(1)).test(argThat(hasId("node3"))); - verify(predicate3, times(1)).test(argThat(hasId("node4"))); - verifyNoMoreInteractions(predicate1, predicate2, predicate3); - } - - @Test - public void testFilterMultipleLeavesMatch() { - var inputDoc = parser.parse("node1; node2 {node3 {node4;};}; node5 {node6 {node7;};}"); - var search = PathedSearch.builder() - .addLevel(predicate1) - .addLevel(predicate2) - .addLevel(predicate3) - .build(); - when(predicate1.test(any())).thenReturn(true); - when(predicate2.test(any())).thenReturn(true); - when(predicate3.test(any())).thenReturn(true); - - assertThat(search.filter(inputDoc, true)).isEqualTo(parser.parse("node2 {node3 {node4;};}; node5 {node6 {node7;};}")); - verify(predicate1, times(1)).test(argThat(hasId("node1"))); - verify(predicate1, times(1)).test(argThat(hasId("node2"))); - verify(predicate1, times(1)).test(argThat(hasId("node5"))); - verify(predicate2, times(1)).test(argThat(hasId("node3"))); - verify(predicate2, times(1)).test(argThat(hasId("node6"))); - verify(predicate3, times(1)).test(argThat(hasId("node4"))); - verify(predicate3, times(1)).test(argThat(hasId("node7"))); - verifyNoMoreInteractions(predicate1, predicate2, predicate3); - } -} diff --git a/src/test/java/kdl/search/RootSearchTest.java b/src/test/java/kdl/search/RootSearchTest.java deleted file mode 100644 index 2d9c984..0000000 --- a/src/test/java/kdl/search/RootSearchTest.java +++ /dev/null @@ -1,84 +0,0 @@ -package kdl.search; - -import java.util.Optional; -import java.util.stream.Stream; -import kdl.objects.KDLDocument; -import kdl.objects.KDLNode; -import kdl.parse.KDLParser; -import kdl.print.PrintConfig; -import kdl.search.mutation.AddMutation; -import kdl.search.mutation.Mutation; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Fail.fail; - -public class RootSearchTest { - private static final KDLParser parser = new KDLParser(); - - public static Stream getCases() { - return Stream.of( - Arguments.of("", Optional.of(""), false, - Optional.of(AddMutation.builder() - .setChild(KDLDocument.empty()) - .build())), - Arguments.of("node1; node2; node3", Optional.of("node1; node2; node3"), false, - Optional.of(AddMutation.builder() - .setChild(KDLDocument.empty()) - .build())), - Arguments.of("node1; node2; node3", Optional.of("node1; node2; node3"), false, Optional.of(AddMutation.builder().build())), - Arguments.of("node1; node2", Optional.of("node1; node2; node3"), false, Optional.of(AddMutation.builder() - .setChild(KDLDocument.builder() - .addNode(KDLNode.builder() - .setIdentifier("node3") - .build()) - .build()) - .build())), - Arguments.of("", Optional.of("node1"), false, Optional.of(AddMutation.builder() - .setChild(KDLDocument.builder() - .addNode(KDLNode.builder() - .setIdentifier("node1") - .build()) - .build()) - .build())), - - Arguments.of("", Optional.of(""), false, Optional.empty()), - Arguments.of("node1", Optional.of("node1"), false, Optional.empty()), - Arguments.of("node1 10", Optional.of("node1 10"), false, Optional.empty()), - Arguments.of("node1; node2", Optional.of("node1; node2"), false, Optional.empty()), - Arguments.of("node1; node2 {node3;}", Optional.of("node1; node2 {node3;}; node3"), false, Optional.empty()), - Arguments.of("node1; node2 {node3;}", Optional.of("node1; node2; node3"), true, Optional.empty()), - Arguments.of("node1 {node2 {node3;};}", Optional.of("node1; node2; node3"), true, Optional.empty()), - Arguments.of("node1 {node2 {node3;};}", Optional.of("node1 {node2 {node3;};}; node2 {node3;}; node3"), false, Optional.empty()) - ); - } - - @ParameterizedTest(name = "{0} -> {1}") - @MethodSource("getCases") - public void test(String input, Optional expectedRaw, boolean trim, Optional mutation) { - Search search = new RootSearch(); - - var inputDoc = parser.parse(input); - var expected = expectedRaw.map(parser::parse); - - KDLDocument output = null; - try { - if (mutation.isPresent()) { - output = search.mutate(inputDoc, mutation.get()); - } else { - output = search.list(inputDoc, trim); - } - if (expected.isEmpty()) { - fail("Expected an error, but got: %s", output.toKDLPretty(PrintConfig.PRETTY_DEFAULT)); - } - } catch (Exception e) { - if (expected.isPresent()) { - throw new RuntimeException(e); - } - } - - assertThat(Optional.ofNullable(output)).isEqualTo(expected); - } -} diff --git a/src/test/java/kdl/search/mutation/AddMutationTest.java b/src/test/java/kdl/search/mutation/AddMutationTest.java deleted file mode 100644 index ba483b5..0000000 --- a/src/test/java/kdl/search/mutation/AddMutationTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package kdl.search.mutation; - -import java.util.stream.Stream; -import kdl.objects.KDLDocument; -import kdl.objects.KDLNode; -import kdl.objects.KDLValue; -import kdl.parse.KDLParser; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import static org.assertj.core.api.Assertions.assertThat; - -public class AddMutationTest { - private final KDLParser parser = new KDLParser(); - - public static Stream getCases() { - return Stream.of( - Arguments.of("node", AddMutation.builder().build(), "node"), - Arguments.of("node", AddMutation.builder().addArg(KDLValue.from(15)).build(), "node 15"), - Arguments.of("node", AddMutation.builder().addProp("key", KDLValue.from(true)).build(), "node key=true"), - Arguments.of("node", AddMutation.builder().setChild(KDLDocument.empty()).build(), "node {}"), - Arguments.of("node", AddMutation.builder() - .setChild(KDLDocument.builder() - .addNode(KDLNode.builder() - .setIdentifier("node2") - .build()) - .build()) - .build(), "node {node2;}"), - Arguments.of("node \"a\"", AddMutation.builder().addArg(KDLValue.from("a")).build(), "node \"a\" \"a\""), - Arguments.of("node \"a\"", AddMutation.builder().addArg(KDLValue.from("b")).build(), "node \"a\" \"b\""), - Arguments.of("node \"b\"", AddMutation.builder().addArg(KDLValue.from("a")).build(), "node \"b\" \"a\""), - Arguments.of("node key=10", AddMutation.builder().addProp("key2", KDLValue.from(15)).build(), "node key=10 key2=15"), - Arguments.of("node key=10", AddMutation.builder().addProp("key", KDLValue.from(15)).build(), "node key=15"), - Arguments.of("node 10 20", AddMutation.builder().addProp("key", KDLValue.from("val")).build(), "node 10 20 key=\"val\""), - Arguments.of("node", AddMutation.builder() - .setChild(KDLDocument.builder() - .addNode(KDLNode.builder().setIdentifier("node2").build()) - .build()) - .build(), "node {node2;}"), - Arguments.of("node {}", AddMutation.builder() - .setChild(KDLDocument.builder() - .addNode(KDLNode.builder().setIdentifier("node2").build()) - .build()) - .build(), "node {node2;}"), - Arguments.of("node {node2;}", AddMutation.builder() - .setChild(KDLDocument.builder() - .addNode(KDLNode.builder() - .setIdentifier("node3") - .build()) - .build()) - .build(), "node {node2; node3;}") - ); - } - - @ParameterizedTest(name = "{0} -> {2}") - @MethodSource("getCases") - public void test(String input, AddMutation mutation, String expected) { - var inputNode = parser.parse(input).getNodes().get(0); - var expectedNode = parser.parse(expected).getNodes().get(0); - - var result = mutation.apply(inputNode); - - assertThat(result).contains(expectedNode); - } -} diff --git a/src/test/java/kdl/search/mutation/SetMutationTest.java b/src/test/java/kdl/search/mutation/SetMutationTest.java deleted file mode 100644 index da3d596..0000000 --- a/src/test/java/kdl/search/mutation/SetMutationTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package kdl.search.mutation; - -import java.util.Optional; -import java.util.stream.Stream; -import kdl.objects.KDLDocument; -import kdl.objects.KDLNode; -import kdl.objects.KDLValue; -import kdl.parse.KDLParser; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import static org.assertj.core.api.Assertions.assertThat; - -public class SetMutationTest { - - public static Stream getCases() { - return Stream.of( - Arguments.of("node", SetMutation.builder().build(), "node"), - Arguments.of("node", SetMutation.builder().setIdentifier("new_node").build(), "new_node"), - Arguments.of("node", SetMutation.builder().addArg(KDLValue.from(15)).build(), "node 15"), - Arguments.of("node", SetMutation.builder().addProp("key", KDLValue.from(true)).build(), "node key=true"), - Arguments.of("node", SetMutation.builder().setChild(Optional.of(KDLDocument.empty())).build(), "node {}"), - Arguments.of("node {node2;}", SetMutation.builder().setChild(Optional.empty()).build(), "node"), - Arguments.of("node", SetMutation.builder() - .setChild(Optional.of(KDLDocument.builder() - .addNode(KDLNode.builder() - .setIdentifier("node2") - .build()) - .build())) - .build(), "node {node2;}"), - Arguments.of("node \"a\"", SetMutation.builder().addArg(KDLValue.from("a")).build(), "node \"a\""), - Arguments.of("node \"a\"", SetMutation.builder().addArg(KDLValue.from("b")).build(), "node \"b\""), - Arguments.of("node \"b\"", SetMutation.builder().addArg(KDLValue.from("a")).build(), "node \"a\""), - Arguments.of("node key=10", SetMutation.builder().addProp("key2", KDLValue.from(15)).build(), "node key2=15"), - Arguments.of("node key=10", SetMutation.builder().addProp("key", KDLValue.from(15)).build(), "node key=15"), - Arguments.of("node 10 20", SetMutation.builder().addProp("key", KDLValue.from("val")).build(), "node 10 20 key=\"val\""), - Arguments.of("node", SetMutation.builder() - .setChild(Optional.ofNullable(KDLDocument.builder() - .addNode(KDLNode.builder().setIdentifier("node2").build()) - .build())) - .build(), "node {node2;}"), - Arguments.of("node {}", SetMutation.builder() - .setChild(Optional.ofNullable(KDLDocument.builder() - .addNode(KDLNode.builder().setIdentifier("node2").build()) - .build())) - .build(), "node {node2;}"), - Arguments.of("node {node2;}", SetMutation.builder() - .setChild(Optional.ofNullable(KDLDocument.builder() - .addNode(KDLNode.builder() - .setIdentifier("node3") - .build()) - .build())) - .build(), "node {node3;}") - ); - } - - @ParameterizedTest(name = "{0} -> {2}") - @MethodSource("getCases") - public void test(String input, SetMutation mutation, String expected) { - var inputNode = parser.parse(input).getNodes().get(0); - var expectedNode = parser.parse(expected).getNodes().get(0); - - var result = mutation.apply(inputNode); - - assertThat(result).contains(expectedNode); - } - - private final KDLParser parser = new KDLParser(); -} diff --git a/src/test/java/kdl/search/mutation/SubtractMutationTest.java b/src/test/java/kdl/search/mutation/SubtractMutationTest.java deleted file mode 100644 index 3ea1efc..0000000 --- a/src/test/java/kdl/search/mutation/SubtractMutationTest.java +++ /dev/null @@ -1,59 +0,0 @@ -package kdl.search.mutation; - -import java.util.Optional; -import java.util.function.Predicate; -import java.util.stream.Stream; -import kdl.objects.KDLProperty; -import kdl.objects.KDLValue; -import kdl.parse.KDLParser; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import static org.assertj.core.api.Assertions.assertThat; - - -public class SubtractMutationTest { - - public static Stream getCases() { - return Stream.of( - Arguments.of("node \"arg\"", SubtractMutation.builder().addArg(eq("arg")).build(), Optional.of("node")), - Arguments.of("node \"arg\" \"arg\"", SubtractMutation.builder().addArg(eq("arg")).build(), Optional.of("node")), - Arguments.of("node \"arg1\" \"arg2\"", SubtractMutation.builder().addArg(eq("arg1")).build(), Optional.of("node \"arg2\"")), - Arguments.of("node1", SubtractMutation.builder().deleteChild().build(), Optional.of("node1")), - Arguments.of("node1 {}", SubtractMutation.builder().deleteChild().build(), Optional.of("node1")), - Arguments.of("node {node2;}", SubtractMutation.builder().deleteChild().build(), Optional.of("node")), - Arguments.of("node", SubtractMutation.builder().emptyChild().build(), Optional.of("node")), - Arguments.of("node {}", SubtractMutation.builder().emptyChild().build(), Optional.of("node {}")), - Arguments.of("node {node2;}", SubtractMutation.builder().emptyChild().build(), Optional.of("node {}")), - Arguments.of("node prop=\"value\"", SubtractMutation.builder().addProp(eq("prop", "value")).build(), Optional.of("node")), - Arguments.of("node prop=\"value\"", SubtractMutation.builder().addProp(eq("prop1", "value")).build(), Optional.of("node prop=\"value\"")), - Arguments.of("node prop=\"value\"", SubtractMutation.builder().addProp(eq("prop", "value1")).build(), Optional.of("node prop=\"value\"")), - Arguments.of("node", SubtractMutation.builder().build(), Optional.empty()), - Arguments.of("node 10 prop=15 {node2;}", SubtractMutation.builder().build(), Optional.empty()) - ); - } - - @ParameterizedTest(name = "{0} -> {2}") - @MethodSource("getCases") - public void test(String input, SubtractMutation mutation, Optional expected) { - var inputNode = parser.parse(input).getNodes().get(0); - var expectedNode = expected.map(ex -> parser.parse(ex).getNodes().get(0)); - - var result = mutation.apply(inputNode); - - assertThat(result).isEqualTo(expectedNode); - } - - private static Predicate eq(String key, Object val) { - var kdlValue = KDLValue.from(val); - return prop -> key.equals(prop.getKey()) && kdlValue.equals(prop.getValue()); - } - - private static Predicate> eq(Object val) { - var kdlValue = KDLValue.from(val); - return kdlValue::equals; - } - - private final KDLParser parser = new KDLParser(); -} diff --git a/src/test/java/kdl/search/predicate/ArgPredicateTest.java b/src/test/java/kdl/search/predicate/ArgPredicateTest.java deleted file mode 100644 index 1b87223..0000000 --- a/src/test/java/kdl/search/predicate/ArgPredicateTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package kdl.search.predicate; - -import kdl.objects.KDLNode; -import kdl.objects.KDLValue; -import kdl.search.predicates.ArgPredicate; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class ArgPredicateTest { - private final ArgPredicate predicate = new ArgPredicate(val -> val.isString() && val.equals(KDLValue.from("arg"))); - - @Test - public void testOneMatches() { - var node = KDLNode.builder().setIdentifier("identifier").addArg("arg").build(); - assertThat(predicate.test(node)).isTrue(); - } - - @Test - public void testSomeMatch() { - var node = KDLNode.builder().setIdentifier("identifier").addArg("arg").addArg("val").build(); - assertThat(predicate.test(node)).isTrue(); - } - - @Test - public void testMultipleMatch() { - var node = KDLNode.builder().setIdentifier("identifier").addArg("arg").addArg("arg").build(); - assertThat(predicate.test(node)).isTrue(); - } - - @Test - public void testNoneMatch() { - var node = KDLNode.builder().setIdentifier("identifier").addArg("val").build(); - assertThat(predicate.test(node)).isFalse(); - } -} diff --git a/src/test/java/kdl/search/predicate/ChildPredicateTest.java b/src/test/java/kdl/search/predicate/ChildPredicateTest.java deleted file mode 100644 index bf94e29..0000000 --- a/src/test/java/kdl/search/predicate/ChildPredicateTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package kdl.search.predicate; - -import java.util.Optional; -import kdl.objects.KDLDocument; -import kdl.objects.KDLNode; -import kdl.search.Search; -import kdl.search.predicates.ChildPredicate; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -public class ChildPredicateTest { - - @Mock - public Search search; - - @Test - public void testMissingChild() { - var predicate = ChildPredicate.empty(); - var node = KDLNode.builder().setIdentifier("node").build(); - - assertThat(predicate.test(node)).isTrue(); - } - - @Test - public void testEmptyChild() { - var predicate = ChildPredicate.empty(); - var node = KDLNode.builder().setIdentifier("node").setChild(KDLDocument.empty()).build(); - - assertThat(predicate.test(node)).isTrue(); - } - - @Test - public void testChildNotEmpty() { - var predicate = ChildPredicate.empty(); - var node = KDLNode.builder().setIdentifier("node") - .setChild(KDLDocument.builder() - .addNode(KDLNode.builder().setIdentifier("identifier").build()) - .build()) - .build(); - - assertThat(predicate.test(node)).isFalse(); - } - - @Test - public void testSearchNoChild() { - var predicate = new ChildPredicate(Optional.of(search)); - var node = KDLNode.builder().setIdentifier("node").build(); - - assertThat(predicate.test(node)).isFalse(); - verifyNoInteractions(search); - } - - @Test - public void testSearchReturnsNothing() { - var predicate = new ChildPredicate(Optional.of(search)); - var child = KDLDocument.builder() - .addNode(KDLNode.builder().setIdentifier("identifier").build()) - .build(); - var node = KDLNode.builder().setIdentifier("node") - .setChild(child) - .build(); - - when(search.anyMatch(any())).thenReturn(false); - - assertThat(predicate.test(node)).isFalse(); - verify(search, times(1)).anyMatch(eq(child)); - verifyNoMoreInteractions(search); - } - - @Test - public void testSearchReturnsSomething() { - var predicate = new ChildPredicate(Optional.of(search)); - var child = KDLDocument.builder() - .addNode(KDLNode.builder().setIdentifier("identifier").build()) - .build(); - var node = KDLNode.builder().setIdentifier("node") - .setChild(child) - .build(); - - when(search.anyMatch(any())).thenReturn(true); - - assertThat(predicate.test(node)).isTrue(); - verify(search, times(1)).anyMatch(eq(child)); - verifyNoMoreInteractions(search); - } -} diff --git a/src/test/java/kdl/search/predicate/EmptyContentPredicateTest.java b/src/test/java/kdl/search/predicate/EmptyContentPredicateTest.java deleted file mode 100644 index 158ea6c..0000000 --- a/src/test/java/kdl/search/predicate/EmptyContentPredicateTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package kdl.search.predicate; - -import kdl.objects.KDLDocument; -import kdl.objects.KDLNode; -import kdl.search.predicates.EmptyContentPredicate; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class EmptyContentPredicateTest { - private final EmptyContentPredicate predicate = new EmptyContentPredicate(); - - @Test - public void testShouldMatch() { - var node = KDLNode.builder().setIdentifier("identifier").build(); - assertThat(predicate.test(node)).isTrue(); - } - - @Test - public void testHasArgs() { - var node = KDLNode.builder() - .setIdentifier("identifier") - .addArg("o") - .build(); - - assertThat(predicate.test(node)).isFalse(); - } - - @Test - public void testHasProps() { - var node = KDLNode.builder() - .setIdentifier("identifier") - .addProp("key", "val") - .build(); - - assertThat(predicate.test(node)).isFalse(); - } - - @Test - public void testHasEmptyChild() { - var node = KDLNode.builder() - .setIdentifier("identifier") - .setChild(KDLDocument.empty()) - .build(); - assertThat(predicate.test(node)).isFalse(); - } - - @Test - public void testHasChild() { - var node = KDLNode.builder() - .setIdentifier("identifier") - .setChild(KDLDocument.builder() - .addNode(KDLNode.builder() - .setIdentifier("identifier") - .build()) - .build()) - .build(); - assertThat(predicate.test(node)).isFalse(); - } -} diff --git a/src/test/java/kdl/search/predicate/LogicalPredicatesTest.java b/src/test/java/kdl/search/predicate/LogicalPredicatesTest.java deleted file mode 100644 index ebfe4bc..0000000 --- a/src/test/java/kdl/search/predicate/LogicalPredicatesTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package kdl.search.predicate; - -import kdl.objects.KDLNode; -import kdl.search.predicates.ConjunctionPredicate; -import kdl.search.predicates.DisjunctionPredicate; -import kdl.search.predicates.NegatedPredicate; -import kdl.search.predicates.NodeContentPredicate; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -public class LogicalPredicatesTest { - private final KDLNode node = KDLNode.builder().setIdentifier("identifier").build(); - - - @Mock - public NodeContentPredicate predicate1; - - @Mock - public NodeContentPredicate predicate2; - - @Test - public void testNegation() { - final NegatedPredicate predicate = new NegatedPredicate(predicate1); - - when(predicate1.test(any())).thenReturn(false); - assertThat(predicate.test(node)).isTrue(); - - when(predicate1.test(any())).thenReturn(true); - assertThat(predicate.test(node)).isFalse(); - } - - @Test - public void testConjunction() { - final ConjunctionPredicate predicate = new ConjunctionPredicate(predicate1, predicate2); - - when(predicate1.test(any())).thenReturn(false); - when(predicate2.test(any())).thenReturn(false); - assertThat(predicate.test(node)).isFalse(); - - when(predicate1.test(any())).thenReturn(false); - when(predicate2.test(any())).thenReturn(true); - assertThat(predicate.test(node)).isFalse(); - - when(predicate1.test(any())).thenReturn(true); - when(predicate2.test(any())).thenReturn(false); - assertThat(predicate.test(node)).isFalse(); - - when(predicate1.test(any())).thenReturn(true); - when(predicate2.test(any())).thenReturn(true); - assertThat(predicate.test(node)).isTrue(); - } - - @Test - public void testDisjunction() { - final DisjunctionPredicate predicate = new DisjunctionPredicate(predicate1, predicate2); - - when(predicate1.test(any())).thenReturn(false); - when(predicate2.test(any())).thenReturn(false); - assertThat(predicate.test(node)).isFalse(); - - when(predicate1.test(any())).thenReturn(false); - when(predicate2.test(any())).thenReturn(true); - assertThat(predicate.test(node)).isTrue(); - - when(predicate1.test(any())).thenReturn(true); - assertThat(predicate.test(node)).isTrue(); - - when(predicate1.test(any())).thenReturn(true); - assertThat(predicate.test(node)).isTrue(); - } -} diff --git a/src/test/java/kdl/search/predicate/NodePredicateTest.java b/src/test/java/kdl/search/predicate/NodePredicateTest.java deleted file mode 100644 index 56c3445..0000000 --- a/src/test/java/kdl/search/predicate/NodePredicateTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package kdl.search.predicate; - -import java.util.function.Predicate; -import kdl.objects.KDLNode; -import kdl.search.predicates.NodeContentPredicate; -import kdl.search.predicates.NodePredicate; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -public class NodePredicateTest { - - @Mock - public Predicate idPredicate; - - @Mock - public NodeContentPredicate contentPredicate; - - @Test - public void test() { - final NodePredicate predicate = new NodePredicate(idPredicate, contentPredicate); - final KDLNode node = KDLNode.builder().setIdentifier("identifier").build(); - - when(idPredicate.test(any())).thenReturn(false); - when(contentPredicate.test(any())).thenReturn(false); - assertThat(predicate.test(node)).isFalse(); - - when(idPredicate.test(any())).thenReturn(true); - when(contentPredicate.test(any())).thenReturn(false); - assertThat(predicate.test(node)).isFalse(); - - when(idPredicate.test(any())).thenReturn(false); - when(contentPredicate.test(any())).thenReturn(true); - assertThat(predicate.test(node)).isFalse(); - - when(idPredicate.test(any())).thenReturn(true); - when(contentPredicate.test(any())).thenReturn(true); - assertThat(predicate.test(node)).isTrue(); - } -} diff --git a/src/test/java/kdl/search/predicate/PropPredicateTest.java b/src/test/java/kdl/search/predicate/PropPredicateTest.java deleted file mode 100644 index 89f8782..0000000 --- a/src/test/java/kdl/search/predicate/PropPredicateTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package kdl.search.predicate; - -import java.util.function.Predicate; -import kdl.objects.KDLNode; -import kdl.objects.KDLValue; -import kdl.search.predicates.PropPredicate; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -public class PropPredicateTest { - @Mock - public Predicate keyPredicate; - - @Mock - public Predicate> valuePredicate; - - - @Test - public void test() { - final PropPredicate predicate = new PropPredicate(keyPredicate, valuePredicate); - final KDLNode node = KDLNode.builder().setIdentifier("identifier") - .addProp("key", "val") - .build(); - - when(keyPredicate.test(any())).thenReturn(false); - when(valuePredicate.test(any())).thenReturn(false); - assertThat(predicate.test(node)).isFalse(); - - when(keyPredicate.test(any())).thenReturn(true); - when(valuePredicate.test(any())).thenReturn(false); - assertThat(predicate.test(node)).isFalse(); - - when(keyPredicate.test(any())).thenReturn(false); - when(valuePredicate.test(any())).thenReturn(true); - assertThat(predicate.test(node)).isFalse(); - - when(keyPredicate.test(any())).thenReturn(true); - when(valuePredicate.test(any())).thenReturn(true); - assertThat(predicate.test(node)).isTrue(); - } -} diff --git a/src/test/resources/README.md b/src/test/resources/README.md index 9abc069..0ddfea0 100644 --- a/src/test/resources/README.md +++ b/src/test/resources/README.md @@ -1,24 +1,54 @@ # Full Document Test Cases -The `input` folder contains test cases for KDL parsers. See `src/test/java/dev/hbeck/kdl/TestRoundTrip.java` -for an example of how these documents are used for kdl4j. You're encouraged to use them for your project and/or add new ones -here. The `expected_kdl` folder contains files with the same name as those in `input` with the expected output after being -run through the parser and printed out again. If there's no file in `expected_kdl` with a name corresponding to one in `input` -it indicates that parsing for that case should fail. +The `input` folder contains test cases for KDL parsers. The `expected_kdl` +folder contains files with the same name as those in `input` with the expected +output after being run through the parser and printed out again. If there's no +file in `expected_kdl` with a name corresponding to one in `input` it +indicates that parsing for that case should fail. -By necessity, the files in `expected_kdl` are not identical to their corresponding inputs. They are instead pretty-printed -using the default print configuration found in `src/main/java/dev/hbeck/kdl/print/PrintConfig.java`. This means that when -adding a test case the expected output must be structurally identical with: +## Translation Rules + +By necessity, the files in `expected_kdl` are not identical to their +corresponding inputs. They are instead pretty-printed according to the +following rules: * All comments removed * Extra empty lines removed except for a newline after the last node * All nodes should be reformatted without escaped newlines -* Node fields should be `identifier ` -* All strings/identifiers should be at the lowest level of quoting possible. `r"words"` becomes `"words"` if a value or `words` - if an identifier -* Any duplicate properties removed, with only the rightmost one remaining -* Any literal newlines or other ascii escape characters in escaped strings replaced with their escape sequences +* Node fields should be `identifier ` +* All values and all children must be in the same order as they were defined. +* Properties must be in _alphabetical order_ and separated by a single space. +* All strings must be represented as regular strings, with appropriate escapes + for invalid bare characters. That means that raw strings must be converted + to plain strings, and escaped. +* Any literal newlines or other ascii escape characters in escaped strings + replaced with their escape sequences. +* All identifiers must be unquoted unless they _must_ be quoted. That means + `"foo"` becomes `foo`, and `"foo bar"` stays that way. +* Any duplicate properties must be removed, with only the rightmost one + remaining. This also means duplicate properties must be allowed. * 4 space indents -* `E` used to indicate the exponent of all floating point literals +* All numbers must be converted to their simplest decimal representation. That + means that hex, octal, and binary must all be converted to decimals. All + floats must be represented using `E` notation, with a single digit left of + the decimal point if the float is less than 1. While parsers are required to + _consume_ different number syntaxes, they are under no obligation to + represent numbers in any particular way. + +Data may be manipulated as you wish in order to output the expected KDL. This +test suite verifies the ability to **parse**, not specific quirks about +internal representations. + +## What to do if a test fails for you + +This test suite was originally designed for a pre-1.0 version of the KDL +specification. If you encounter a failure, it's likely that the test suite +will need to be updated, rather than your parser itself. This test suite is +NOT AUTHORITATIVE. If this test suite disagrees with the KDL spec in any way, +the most desirable resolution is to send a PR to this repository to fix the +test itself. Likewise, if you think a test succeeded but should not have, +please send a PR. -Note that unlike the tests in the core KDL repo, these *DO* expect numbers to be roundtripped maintaining their radix \ No newline at end of file +If you think the disagreement is due to a genuine error or oversight in the +KDL specification, please open an issue explaining the matter and the change +will be considered for the next version of the KDL spec. diff --git a/src/test/resources/test_cases/expected_kdl/all_escapes.kdl b/src/test/resources/test_cases/expected_kdl/all_escapes.kdl index 024cda2..de0d0a0 100644 --- a/src/test/resources/test_cases/expected_kdl/all_escapes.kdl +++ b/src/test/resources/test_cases/expected_kdl/all_escapes.kdl @@ -1 +1 @@ -node "\"\\\b\f\n\r\t" +node "\"\\\b\f\n\r\t " diff --git a/src/test/resources/test_cases/expected_kdl/all_node_fields.kdl b/src/test/resources/test_cases/expected_kdl/all_node_fields.kdl index fc8a9e4..9f4ceb5 100644 --- a/src/test/resources/test_cases/expected_kdl/all_node_fields.kdl +++ b/src/test/resources/test_cases/expected_kdl/all_node_fields.kdl @@ -1,3 +1,3 @@ -node "arg" prop="val" { +node arg prop=val { inner_node } diff --git a/src/test/resources/test_cases/expected_kdl/arg_and_prop_same_name.kdl b/src/test/resources/test_cases/expected_kdl/arg_and_prop_same_name.kdl index 27d9739..ee5ace5 100644 --- a/src/test/resources/test_cases/expected_kdl/arg_and_prop_same_name.kdl +++ b/src/test/resources/test_cases/expected_kdl/arg_and_prop_same_name.kdl @@ -1 +1 @@ -node "arg" arg="val" +node arg arg=val diff --git a/src/test/resources/test_cases/expected_kdl/arg_bare.kdl b/src/test/resources/test_cases/expected_kdl/arg_bare.kdl new file mode 100644 index 0000000..2fa9785 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/arg_bare.kdl @@ -0,0 +1 @@ +node a diff --git a/src/test/resources/test_cases/expected_kdl/arg_false_type.kdl b/src/test/resources/test_cases/expected_kdl/arg_false_type.kdl index 895945d..92003d9 100644 --- a/src/test/resources/test_cases/expected_kdl/arg_false_type.kdl +++ b/src/test/resources/test_cases/expected_kdl/arg_false_type.kdl @@ -1 +1 @@ -node (type)false +node (type)#false diff --git a/src/test/resources/test_cases/expected_kdl/arg_hex_type.kdl b/src/test/resources/test_cases/expected_kdl/arg_hex_type.kdl index ec44f6c..b1a494a 100644 --- a/src/test/resources/test_cases/expected_kdl/arg_hex_type.kdl +++ b/src/test/resources/test_cases/expected_kdl/arg_hex_type.kdl @@ -1 +1 @@ -node (type)0x10 +node (type)16 diff --git a/src/test/resources/test_cases/expected_kdl/arg_null_type.kdl b/src/test/resources/test_cases/expected_kdl/arg_null_type.kdl index 476c5cd..cd66101 100644 --- a/src/test/resources/test_cases/expected_kdl/arg_null_type.kdl +++ b/src/test/resources/test_cases/expected_kdl/arg_null_type.kdl @@ -1 +1 @@ -node (type)null +node (type)#null diff --git a/src/test/resources/test_cases/expected_kdl/arg_raw_string_type.kdl b/src/test/resources/test_cases/expected_kdl/arg_raw_string_type.kdl index 2808d53..a4859b6 100644 --- a/src/test/resources/test_cases/expected_kdl/arg_raw_string_type.kdl +++ b/src/test/resources/test_cases/expected_kdl/arg_raw_string_type.kdl @@ -1 +1 @@ -node (type)"str" +node (type)str diff --git a/src/test/resources/test_cases/expected_kdl/arg_string_type.kdl b/src/test/resources/test_cases/expected_kdl/arg_string_type.kdl index 2808d53..a4859b6 100644 --- a/src/test/resources/test_cases/expected_kdl/arg_string_type.kdl +++ b/src/test/resources/test_cases/expected_kdl/arg_string_type.kdl @@ -1 +1 @@ -node (type)"str" +node (type)str diff --git a/src/test/resources/test_cases/expected_kdl/arg_true_type.kdl b/src/test/resources/test_cases/expected_kdl/arg_true_type.kdl index 6d1f9bc..20243a3 100644 --- a/src/test/resources/test_cases/expected_kdl/arg_true_type.kdl +++ b/src/test/resources/test_cases/expected_kdl/arg_true_type.kdl @@ -1 +1 @@ -node (type)true +node (type)#true diff --git a/src/test/resources/test_cases/expected_kdl/arg_type.kdl b/src/test/resources/test_cases/expected_kdl/arg_type.kdl index a0b84cf..79a093d 100644 --- a/src/test/resources/test_cases/expected_kdl/arg_type.kdl +++ b/src/test/resources/test_cases/expected_kdl/arg_type.kdl @@ -1 +1 @@ -node (type)"arg" +node (type)arg diff --git a/src/test/resources/test_cases/expected_kdl/bare_emoji.kdl b/src/test/resources/test_cases/expected_kdl/bare_emoji.kdl index 60707c8..c67d0b9 100644 --- a/src/test/resources/test_cases/expected_kdl/bare_emoji.kdl +++ b/src/test/resources/test_cases/expected_kdl/bare_emoji.kdl @@ -1 +1 @@ -😁 "happy!" +😁 happy! diff --git a/src/test/resources/test_cases/expected_kdl/bare_ident_dot.kdl b/src/test/resources/test_cases/expected_kdl/bare_ident_dot.kdl new file mode 100644 index 0000000..4ea1fa6 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/bare_ident_dot.kdl @@ -0,0 +1 @@ +node . diff --git a/src/test/resources/test_cases/expected_kdl/bare_ident_sign.kdl b/src/test/resources/test_cases/expected_kdl/bare_ident_sign.kdl new file mode 100644 index 0000000..34594d7 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/bare_ident_sign.kdl @@ -0,0 +1 @@ +node + diff --git a/src/test/resources/test_cases/expected_kdl/bare_ident_sign_dot.kdl b/src/test/resources/test_cases/expected_kdl/bare_ident_sign_dot.kdl new file mode 100644 index 0000000..a37a5c3 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/bare_ident_sign_dot.kdl @@ -0,0 +1 @@ +node +. diff --git a/src/test/resources/test_cases/expected_kdl/binary.kdl b/src/test/resources/test_cases/expected_kdl/binary.kdl index 5d111b9..d14213e 100644 --- a/src/test/resources/test_cases/expected_kdl/binary.kdl +++ b/src/test/resources/test_cases/expected_kdl/binary.kdl @@ -1 +1 @@ -node 0b10 +node 2 diff --git a/src/test/resources/test_cases/expected_kdl/binary_trailing_underscore.kdl b/src/test/resources/test_cases/expected_kdl/binary_trailing_underscore.kdl index 5d111b9..d14213e 100644 --- a/src/test/resources/test_cases/expected_kdl/binary_trailing_underscore.kdl +++ b/src/test/resources/test_cases/expected_kdl/binary_trailing_underscore.kdl @@ -1 +1 @@ -node 0b10 +node 2 diff --git a/src/test/resources/test_cases/expected_kdl/binary_underscore.kdl b/src/test/resources/test_cases/expected_kdl/binary_underscore.kdl index 5d111b9..d14213e 100644 --- a/src/test/resources/test_cases/expected_kdl/binary_underscore.kdl +++ b/src/test/resources/test_cases/expected_kdl/binary_underscore.kdl @@ -1 +1 @@ -node 0b10 +node 2 diff --git a/src/test/resources/test_cases/expected_kdl/blank_prop_type.kdl b/src/test/resources/test_cases/expected_kdl/blank_prop_type.kdl index c7b0e31..e00c6d2 100644 --- a/src/test/resources/test_cases/expected_kdl/blank_prop_type.kdl +++ b/src/test/resources/test_cases/expected_kdl/blank_prop_type.kdl @@ -1 +1 @@ -node key=("")true +node key=("")#true diff --git a/src/test/resources/test_cases/expected_kdl/block_comment.kdl b/src/test/resources/test_cases/expected_kdl/block_comment.kdl index b3a0426..1b3db2c 100644 --- a/src/test/resources/test_cases/expected_kdl/block_comment.kdl +++ b/src/test/resources/test_cases/expected_kdl/block_comment.kdl @@ -1 +1 @@ -node "arg" +node arg diff --git a/src/test/resources/test_cases/expected_kdl/block_comment_after_node.kdl b/src/test/resources/test_cases/expected_kdl/block_comment_after_node.kdl index b3a0426..1b3db2c 100644 --- a/src/test/resources/test_cases/expected_kdl/block_comment_after_node.kdl +++ b/src/test/resources/test_cases/expected_kdl/block_comment_after_node.kdl @@ -1 +1 @@ -node "arg" +node arg diff --git a/src/test/resources/test_cases/expected_kdl/bom_initial.kdl b/src/test/resources/test_cases/expected_kdl/bom_initial.kdl new file mode 100644 index 0000000..1b3db2c --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/bom_initial.kdl @@ -0,0 +1 @@ +node arg diff --git a/src/test/resources/test_cases/expected_kdl/boolean_arg.kdl b/src/test/resources/test_cases/expected_kdl/boolean_arg.kdl index 9c7928e..e0cdf1a 100644 --- a/src/test/resources/test_cases/expected_kdl/boolean_arg.kdl +++ b/src/test/resources/test_cases/expected_kdl/boolean_arg.kdl @@ -1 +1 @@ -node false true +node #false #true diff --git a/src/test/resources/test_cases/expected_kdl/boolean_prop.kdl b/src/test/resources/test_cases/expected_kdl/boolean_prop.kdl index 712b60b..f89da9b 100644 --- a/src/test/resources/test_cases/expected_kdl/boolean_prop.kdl +++ b/src/test/resources/test_cases/expected_kdl/boolean_prop.kdl @@ -1 +1 @@ -node prop1=true prop2=false +node prop1=#true prop2=#false diff --git a/src/test/resources/test_cases/expected_kdl/chevrons_in_bare_id.kdl b/src/test/resources/test_cases/expected_kdl/chevrons_in_bare_id.kdl new file mode 100644 index 0000000..58b2436 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/chevrons_in_bare_id.kdl @@ -0,0 +1 @@ +foo123foo weeee diff --git a/src/test/resources/test_cases/expected_kdl/comma_in_bare_id.kdl b/src/test/resources/test_cases/expected_kdl/comma_in_bare_id.kdl new file mode 100644 index 0000000..86c78fd --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/comma_in_bare_id.kdl @@ -0,0 +1 @@ +foo123,bar weeee diff --git a/src/test/resources/test_cases/expected_kdl/comment_after_arg_type.kdl b/src/test/resources/test_cases/expected_kdl/comment_after_arg_type.kdl new file mode 100644 index 0000000..51dcb98 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/comment_after_arg_type.kdl @@ -0,0 +1 @@ +node (type)10 diff --git a/src/test/resources/test_cases/expected_kdl/comment_after_node_type.kdl b/src/test/resources/test_cases/expected_kdl/comment_after_node_type.kdl new file mode 100644 index 0000000..c790643 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/comment_after_node_type.kdl @@ -0,0 +1 @@ +(type)node diff --git a/src/test/resources/test_cases/expected_kdl/comment_after_prop_type.kdl b/src/test/resources/test_cases/expected_kdl/comment_after_prop_type.kdl new file mode 100644 index 0000000..843551b --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/comment_after_prop_type.kdl @@ -0,0 +1 @@ +node key=(type)10 diff --git a/src/test/resources/test_cases/expected_kdl/escline_comment_node.kdl b/src/test/resources/test_cases/expected_kdl/comment_and_newline.kdl similarity index 100% rename from src/test/resources/test_cases/expected_kdl/escline_comment_node.kdl rename to src/test/resources/test_cases/expected_kdl/comment_and_newline.kdl diff --git a/src/test/resources/test_cases/expected_kdl/comment_in_arg_type.kdl b/src/test/resources/test_cases/expected_kdl/comment_in_arg_type.kdl new file mode 100644 index 0000000..51dcb98 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/comment_in_arg_type.kdl @@ -0,0 +1 @@ +node (type)10 diff --git a/src/test/resources/test_cases/expected_kdl/comment_in_node_type.kdl b/src/test/resources/test_cases/expected_kdl/comment_in_node_type.kdl new file mode 100644 index 0000000..c790643 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/comment_in_node_type.kdl @@ -0,0 +1 @@ +(type)node diff --git a/src/test/resources/test_cases/expected_kdl/comment_in_prop_type.kdl b/src/test/resources/test_cases/expected_kdl/comment_in_prop_type.kdl new file mode 100644 index 0000000..843551b --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/comment_in_prop_type.kdl @@ -0,0 +1 @@ +node key=(type)10 diff --git a/src/test/resources/test_cases/expected_kdl/commented_arg.kdl b/src/test/resources/test_cases/expected_kdl/commented_arg.kdl index 226fd56..2e98005 100644 --- a/src/test/resources/test_cases/expected_kdl/commented_arg.kdl +++ b/src/test/resources/test_cases/expected_kdl/commented_arg.kdl @@ -1 +1 @@ -node "arg2" +node arg2 diff --git a/src/test/resources/test_cases/expected_kdl/commented_child.kdl b/src/test/resources/test_cases/expected_kdl/commented_child.kdl index b3a0426..1b3db2c 100644 --- a/src/test/resources/test_cases/expected_kdl/commented_child.kdl +++ b/src/test/resources/test_cases/expected_kdl/commented_child.kdl @@ -1 +1 @@ -node "arg" +node arg diff --git a/src/test/resources/test_cases/expected_kdl/commented_prop.kdl b/src/test/resources/test_cases/expected_kdl/commented_prop.kdl index b3a0426..1b3db2c 100644 --- a/src/test/resources/test_cases/expected_kdl/commented_prop.kdl +++ b/src/test/resources/test_cases/expected_kdl/commented_prop.kdl @@ -1 +1 @@ -node "arg" +node arg diff --git a/src/test/resources/test_cases/expected_kdl/dash_dash.kdl b/src/test/resources/test_cases/expected_kdl/dash_dash.kdl new file mode 100644 index 0000000..9f6111a --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/dash_dash.kdl @@ -0,0 +1 @@ +node -- diff --git a/src/test/resources/test_cases/expected_kdl/emoji.kdl b/src/test/resources/test_cases/expected_kdl/emoji.kdl index 3ed56e2..88df78a 100644 --- a/src/test/resources/test_cases/expected_kdl/emoji.kdl +++ b/src/test/resources/test_cases/expected_kdl/emoji.kdl @@ -1 +1 @@ -node "😀" +node 😀 diff --git a/src/test/resources/test_cases/expected_kdl/empty_child.kdl b/src/test/resources/test_cases/expected_kdl/empty_child.kdl index a166b33..64f5a0a 100644 --- a/src/test/resources/test_cases/expected_kdl/empty_child.kdl +++ b/src/test/resources/test_cases/expected_kdl/empty_child.kdl @@ -1,2 +1 @@ -node { -} +node diff --git a/src/test/resources/test_cases/expected_kdl/empty_child_different_lines.kdl b/src/test/resources/test_cases/expected_kdl/empty_child_different_lines.kdl index a166b33..64f5a0a 100644 --- a/src/test/resources/test_cases/expected_kdl/empty_child_different_lines.kdl +++ b/src/test/resources/test_cases/expected_kdl/empty_child_different_lines.kdl @@ -1,2 +1 @@ -node { -} +node diff --git a/src/test/resources/test_cases/expected_kdl/empty_child_same_line.kdl b/src/test/resources/test_cases/expected_kdl/empty_child_same_line.kdl index a166b33..64f5a0a 100644 --- a/src/test/resources/test_cases/expected_kdl/empty_child_same_line.kdl +++ b/src/test/resources/test_cases/expected_kdl/empty_child_same_line.kdl @@ -1,2 +1 @@ -node { -} +node diff --git a/src/test/resources/test_cases/expected_kdl/empty_child_whitespace.kdl b/src/test/resources/test_cases/expected_kdl/empty_child_whitespace.kdl index a166b33..64f5a0a 100644 --- a/src/test/resources/test_cases/expected_kdl/empty_child_whitespace.kdl +++ b/src/test/resources/test_cases/expected_kdl/empty_child_whitespace.kdl @@ -1,2 +1 @@ -node { -} +node diff --git a/src/test/resources/test_cases/expected_kdl/empty_line_comment.kdl b/src/test/resources/test_cases/expected_kdl/empty_line_comment.kdl new file mode 100644 index 0000000..64f5a0a --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/empty_line_comment.kdl @@ -0,0 +1 @@ +node diff --git a/src/test/resources/test_cases/expected_kdl/empty_quoted_node_id.kdl b/src/test/resources/test_cases/expected_kdl/empty_quoted_node_id.kdl index ebfa893..94694bc 100644 --- a/src/test/resources/test_cases/expected_kdl/empty_quoted_node_id.kdl +++ b/src/test/resources/test_cases/expected_kdl/empty_quoted_node_id.kdl @@ -1 +1 @@ -"" "arg" +"" arg diff --git a/src/test/resources/test_cases/expected_kdl/empty_quoted_prop_key.kdl b/src/test/resources/test_cases/expected_kdl/empty_quoted_prop_key.kdl index e6e1310..e541793 100644 --- a/src/test/resources/test_cases/expected_kdl/empty_quoted_prop_key.kdl +++ b/src/test/resources/test_cases/expected_kdl/empty_quoted_prop_key.kdl @@ -1 +1 @@ -node ""="empty" +node ""=empty diff --git a/src/test/resources/test_cases/expected_kdl/eof_after_escape.kdl b/src/test/resources/test_cases/expected_kdl/eof_after_escape.kdl new file mode 100644 index 0000000..64f5a0a --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/eof_after_escape.kdl @@ -0,0 +1 @@ +node diff --git a/src/test/resources/test_cases/expected_kdl/escaped_whitespace.kdl b/src/test/resources/test_cases/expected_kdl/escaped_whitespace.kdl new file mode 100644 index 0000000..45dd408 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/escaped_whitespace.kdl @@ -0,0 +1 @@ +node "Hello\n\tWorld" "Hello\n\tWorld" "Hello\n\tWorld" "Hello\n\tWorld" "Hello\n\tWorld" diff --git a/src/test/resources/test_cases/expected_kdl/escline.kdl b/src/test/resources/test_cases/expected_kdl/escline.kdl index b3a0426..1b3db2c 100644 --- a/src/test/resources/test_cases/expected_kdl/escline.kdl +++ b/src/test/resources/test_cases/expected_kdl/escline.kdl @@ -1 +1 @@ -node "arg" +node arg diff --git a/src/test/resources/test_cases/expected_kdl/escline_line_comment.kdl b/src/test/resources/test_cases/expected_kdl/escline_line_comment.kdl index 8a5dc33..4d38bee 100644 --- a/src/test/resources/test_cases/expected_kdl/escline_line_comment.kdl +++ b/src/test/resources/test_cases/expected_kdl/escline_line_comment.kdl @@ -1 +1 @@ -node "arg" "arg2\n" +node arg arg2 diff --git a/src/test/resources/test_cases/expected_kdl/false_prefix_in_bare_id.kdl b/src/test/resources/test_cases/expected_kdl/false_prefix_in_bare_id.kdl new file mode 100644 index 0000000..cd962c4 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/false_prefix_in_bare_id.kdl @@ -0,0 +1 @@ +false_id diff --git a/src/test/resources/test_cases/expected_kdl/false_prefix_in_prop_key.kdl b/src/test/resources/test_cases/expected_kdl/false_prefix_in_prop_key.kdl new file mode 100644 index 0000000..2d29843 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/false_prefix_in_prop_key.kdl @@ -0,0 +1 @@ +node false_id=1 diff --git a/src/test/resources/test_cases/expected_kdl/floating_point_keywords.kdl b/src/test/resources/test_cases/expected_kdl/floating_point_keywords.kdl new file mode 100644 index 0000000..973a259 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/floating_point_keywords.kdl @@ -0,0 +1 @@ +floats #inf #-inf #nan diff --git a/src/test/resources/test_cases/expected_kdl/hex.kdl b/src/test/resources/test_cases/expected_kdl/hex.kdl index 6d1eba2..bcbc7ff 100644 --- a/src/test/resources/test_cases/expected_kdl/hex.kdl +++ b/src/test/resources/test_cases/expected_kdl/hex.kdl @@ -1 +1 @@ -node 0xabcdef1234567890 +node 12379813812177893520 diff --git a/src/test/resources/test_cases/expected_kdl/hex_int.kdl b/src/test/resources/test_cases/expected_kdl/hex_int.kdl index b552b7b..f8dcee1 100644 --- a/src/test/resources/test_cases/expected_kdl/hex_int.kdl +++ b/src/test/resources/test_cases/expected_kdl/hex_int.kdl @@ -1 +1 @@ -node 0xabcdef0123456789abcdef +node 207698809136909011942886895 diff --git a/src/test/resources/test_cases/expected_kdl/hex_int_underscores.kdl b/src/test/resources/test_cases/expected_kdl/hex_int_underscores.kdl index b18a9c3..78f3ce0 100644 --- a/src/test/resources/test_cases/expected_kdl/hex_int_underscores.kdl +++ b/src/test/resources/test_cases/expected_kdl/hex_int_underscores.kdl @@ -1 +1 @@ -node 0xabcdef0123 +node 737894400291 diff --git a/src/test/resources/test_cases/expected_kdl/hex_leading_zero.kdl b/src/test/resources/test_cases/expected_kdl/hex_leading_zero.kdl index c05ae7c..d20bd7d 100644 --- a/src/test/resources/test_cases/expected_kdl/hex_leading_zero.kdl +++ b/src/test/resources/test_cases/expected_kdl/hex_leading_zero.kdl @@ -1 +1 @@ -node 0x1 +node 1 diff --git a/src/test/resources/test_cases/expected_kdl/initial_slashdash.kdl b/src/test/resources/test_cases/expected_kdl/initial_slashdash.kdl new file mode 100644 index 0000000..d74a990 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/initial_slashdash.kdl @@ -0,0 +1 @@ +another-node diff --git a/src/test/resources/test_cases/expected_kdl/leading_zero_binary.kdl b/src/test/resources/test_cases/expected_kdl/leading_zero_binary.kdl index 2a38fed..d20bd7d 100644 --- a/src/test/resources/test_cases/expected_kdl/leading_zero_binary.kdl +++ b/src/test/resources/test_cases/expected_kdl/leading_zero_binary.kdl @@ -1 +1 @@ -node 0b1 +node 1 diff --git a/src/test/resources/test_cases/expected_kdl/leading_zero_oct.kdl b/src/test/resources/test_cases/expected_kdl/leading_zero_oct.kdl index 9585c83..d20bd7d 100644 --- a/src/test/resources/test_cases/expected_kdl/leading_zero_oct.kdl +++ b/src/test/resources/test_cases/expected_kdl/leading_zero_oct.kdl @@ -1 +1 @@ -node 0o1 +node 1 diff --git a/src/test/resources/test_cases/expected_kdl/multiline_comment.kdl b/src/test/resources/test_cases/expected_kdl/multiline_comment.kdl index b3a0426..1b3db2c 100644 --- a/src/test/resources/test_cases/expected_kdl/multiline_comment.kdl +++ b/src/test/resources/test_cases/expected_kdl/multiline_comment.kdl @@ -1 +1 @@ -node "arg" +node arg diff --git a/src/test/resources/test_cases/expected_kdl/multiline_nodes.kdl b/src/test/resources/test_cases/expected_kdl/multiline_nodes.kdl index bec6d05..7c27fb0 100644 --- a/src/test/resources/test_cases/expected_kdl/multiline_nodes.kdl +++ b/src/test/resources/test_cases/expected_kdl/multiline_nodes.kdl @@ -1 +1 @@ -node "arg1" "arg2" +node arg1 arg2 diff --git a/src/test/resources/test_cases/expected_kdl/multiline_raw_string.kdl b/src/test/resources/test_cases/expected_kdl/multiline_raw_string.kdl new file mode 100644 index 0000000..3c31c47 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/multiline_raw_string.kdl @@ -0,0 +1 @@ +node "hey\neveryone\nhow goes?" diff --git a/src/test/resources/test_cases/expected_kdl/multiline_raw_string_indented.kdl b/src/test/resources/test_cases/expected_kdl/multiline_raw_string_indented.kdl new file mode 100644 index 0000000..f693b84 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/multiline_raw_string_indented.kdl @@ -0,0 +1 @@ +node " hey\n everyone\n how goes?" diff --git a/src/test/resources/test_cases/expected_kdl/multiline_string.kdl b/src/test/resources/test_cases/expected_kdl/multiline_string.kdl index 021493e..3c31c47 100644 --- a/src/test/resources/test_cases/expected_kdl/multiline_string.kdl +++ b/src/test/resources/test_cases/expected_kdl/multiline_string.kdl @@ -1 +1 @@ -node " hey\neveryone\nhow goes?\n" +node "hey\neveryone\nhow goes?" diff --git a/src/test/resources/test_cases/expected_kdl/multiline_string_indented.kdl b/src/test/resources/test_cases/expected_kdl/multiline_string_indented.kdl new file mode 100644 index 0000000..f693b84 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/multiline_string_indented.kdl @@ -0,0 +1 @@ +node " hey\n everyone\n how goes?" diff --git a/src/test/resources/test_cases/expected_kdl/nested_block_comment.kdl b/src/test/resources/test_cases/expected_kdl/nested_block_comment.kdl index b3a0426..1b3db2c 100644 --- a/src/test/resources/test_cases/expected_kdl/nested_block_comment.kdl +++ b/src/test/resources/test_cases/expected_kdl/nested_block_comment.kdl @@ -1 +1 @@ -node "arg" +node arg diff --git a/src/test/resources/test_cases/expected_kdl/nested_comments.kdl b/src/test/resources/test_cases/expected_kdl/nested_comments.kdl index b3a0426..1b3db2c 100644 --- a/src/test/resources/test_cases/expected_kdl/nested_comments.kdl +++ b/src/test/resources/test_cases/expected_kdl/nested_comments.kdl @@ -1 +1 @@ -node "arg" +node arg diff --git a/src/test/resources/test_cases/expected_kdl/nested_multiline_block_comment.kdl b/src/test/resources/test_cases/expected_kdl/nested_multiline_block_comment.kdl index b3a0426..1b3db2c 100644 --- a/src/test/resources/test_cases/expected_kdl/nested_multiline_block_comment.kdl +++ b/src/test/resources/test_cases/expected_kdl/nested_multiline_block_comment.kdl @@ -1 +1 @@ -node "arg" +node arg diff --git a/src/test/resources/test_cases/expected_kdl/newlines_in_block_comment.kdl b/src/test/resources/test_cases/expected_kdl/newlines_in_block_comment.kdl index b3a0426..1b3db2c 100644 --- a/src/test/resources/test_cases/expected_kdl/newlines_in_block_comment.kdl +++ b/src/test/resources/test_cases/expected_kdl/newlines_in_block_comment.kdl @@ -1 +1 @@ -node "arg" +node arg diff --git a/src/test/resources/test_cases/expected_kdl/node_false.kdl b/src/test/resources/test_cases/expected_kdl/node_false.kdl index ef60c44..3bab782 100644 --- a/src/test/resources/test_cases/expected_kdl/node_false.kdl +++ b/src/test/resources/test_cases/expected_kdl/node_false.kdl @@ -1 +1 @@ -node false +node #false diff --git a/src/test/resources/test_cases/expected_kdl/node_true.kdl b/src/test/resources/test_cases/expected_kdl/node_true.kdl index 4b02a06..de00dcd 100644 --- a/src/test/resources/test_cases/expected_kdl/node_true.kdl +++ b/src/test/resources/test_cases/expected_kdl/node_true.kdl @@ -1 +1 @@ -node true +node #true diff --git a/src/test/resources/test_cases/expected_kdl/null_arg.kdl b/src/test/resources/test_cases/expected_kdl/null_arg.kdl index c0e6cb5..bed8dbf 100644 --- a/src/test/resources/test_cases/expected_kdl/null_arg.kdl +++ b/src/test/resources/test_cases/expected_kdl/null_arg.kdl @@ -1 +1 @@ -node null +node #null diff --git a/src/test/resources/test_cases/expected_kdl/null_prefix_in_bare_id.kdl b/src/test/resources/test_cases/expected_kdl/null_prefix_in_bare_id.kdl new file mode 100644 index 0000000..9e0cf15 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/null_prefix_in_bare_id.kdl @@ -0,0 +1 @@ +null_id diff --git a/src/test/resources/test_cases/expected_kdl/null_prefix_in_prop_key.kdl b/src/test/resources/test_cases/expected_kdl/null_prefix_in_prop_key.kdl new file mode 100644 index 0000000..1e1472b --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/null_prefix_in_prop_key.kdl @@ -0,0 +1 @@ +node null_id=1 diff --git a/src/test/resources/test_cases/expected_kdl/null_prop.kdl b/src/test/resources/test_cases/expected_kdl/null_prop.kdl index 85ef005..c463e98 100644 --- a/src/test/resources/test_cases/expected_kdl/null_prop.kdl +++ b/src/test/resources/test_cases/expected_kdl/null_prop.kdl @@ -1 +1 @@ -node prop=null +node prop=#null diff --git a/src/test/resources/test_cases/expected_kdl/octal.kdl b/src/test/resources/test_cases/expected_kdl/octal.kdl index 68bc955..225217b 100644 --- a/src/test/resources/test_cases/expected_kdl/octal.kdl +++ b/src/test/resources/test_cases/expected_kdl/octal.kdl @@ -1 +1 @@ -node 0o76543210 +node 16434824 diff --git a/src/test/resources/test_cases/expected_kdl/optional_child_semicolon.kdl b/src/test/resources/test_cases/expected_kdl/optional_child_semicolon.kdl new file mode 100644 index 0000000..25eaa7d --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/optional_child_semicolon.kdl @@ -0,0 +1,5 @@ +node { + foo + bar + baz +} diff --git a/src/test/resources/test_cases/expected_kdl/parse_all_arg_types.kdl b/src/test/resources/test_cases/expected_kdl/parse_all_arg_types.kdl index 3d1f3f7..773df95 100644 --- a/src/test/resources/test_cases/expected_kdl/parse_all_arg_types.kdl +++ b/src/test/resources/test_cases/expected_kdl/parse_all_arg_types.kdl @@ -1 +1 @@ -node 1 1.0 1.0E+10 1.0E-10 0x1 0o7 0b10 "arg" "arg\\\\" true false null +node 1 1.0 1.0E+10 1.0E-10 1 7 2 arg arg "arg\\" #true #false #null diff --git a/src/test/resources/test_cases/expected_kdl/prop_false_type.kdl b/src/test/resources/test_cases/expected_kdl/prop_false_type.kdl index 3377323..eb544ef 100644 --- a/src/test/resources/test_cases/expected_kdl/prop_false_type.kdl +++ b/src/test/resources/test_cases/expected_kdl/prop_false_type.kdl @@ -1 +1 @@ -node key=(type)false +node key=(type)#false diff --git a/src/test/resources/test_cases/expected_kdl/prop_hex_type.kdl b/src/test/resources/test_cases/expected_kdl/prop_hex_type.kdl index d819d6a..05bef6f 100644 --- a/src/test/resources/test_cases/expected_kdl/prop_hex_type.kdl +++ b/src/test/resources/test_cases/expected_kdl/prop_hex_type.kdl @@ -1 +1 @@ -node key=(type)0x10 +node key=(type)16 diff --git a/src/test/resources/test_cases/expected_kdl/prop_identifier_type.kdl b/src/test/resources/test_cases/expected_kdl/prop_identifier_type.kdl new file mode 100644 index 0000000..7df052b --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/prop_identifier_type.kdl @@ -0,0 +1 @@ +node key=(type)str diff --git a/src/test/resources/test_cases/expected_kdl/prop_null_type.kdl b/src/test/resources/test_cases/expected_kdl/prop_null_type.kdl index bafaddc..1c25b6f 100644 --- a/src/test/resources/test_cases/expected_kdl/prop_null_type.kdl +++ b/src/test/resources/test_cases/expected_kdl/prop_null_type.kdl @@ -1 +1 @@ -node key=(type)null +node key=(type)#null diff --git a/src/test/resources/test_cases/expected_kdl/prop_raw_string_type.kdl b/src/test/resources/test_cases/expected_kdl/prop_raw_string_type.kdl index 50e2d2c..7df052b 100644 --- a/src/test/resources/test_cases/expected_kdl/prop_raw_string_type.kdl +++ b/src/test/resources/test_cases/expected_kdl/prop_raw_string_type.kdl @@ -1 +1 @@ -node key=(type)"str" +node key=(type)str diff --git a/src/test/resources/test_cases/expected_kdl/prop_string_type.kdl b/src/test/resources/test_cases/expected_kdl/prop_string_type.kdl index 50e2d2c..7df052b 100644 --- a/src/test/resources/test_cases/expected_kdl/prop_string_type.kdl +++ b/src/test/resources/test_cases/expected_kdl/prop_string_type.kdl @@ -1 +1 @@ -node key=(type)"str" +node key=(type)str diff --git a/src/test/resources/test_cases/expected_kdl/prop_true_type.kdl b/src/test/resources/test_cases/expected_kdl/prop_true_type.kdl index c4eebb6..01404b8 100644 --- a/src/test/resources/test_cases/expected_kdl/prop_true_type.kdl +++ b/src/test/resources/test_cases/expected_kdl/prop_true_type.kdl @@ -1 +1 @@ -node key=(type)true +node key=(type)#true diff --git a/src/test/resources/test_cases/expected_kdl/prop_type.kdl b/src/test/resources/test_cases/expected_kdl/prop_type.kdl index c4eebb6..01404b8 100644 --- a/src/test/resources/test_cases/expected_kdl/prop_type.kdl +++ b/src/test/resources/test_cases/expected_kdl/prop_type.kdl @@ -1 +1 @@ -node key=(type)true +node key=(type)#true diff --git a/src/test/resources/test_cases/expected_kdl/question_mark_before_number.kdl b/src/test/resources/test_cases/expected_kdl/question_mark_before_number.kdl new file mode 100644 index 0000000..7745a9e --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/question_mark_before_number.kdl @@ -0,0 +1 @@ +node ?15 diff --git a/src/test/resources/test_cases/expected_kdl/quoted_prop_name.kdl b/src/test/resources/test_cases/expected_kdl/quoted_prop_name.kdl index 170a05a..8ee5e08 100644 --- a/src/test/resources/test_cases/expected_kdl/quoted_prop_name.kdl +++ b/src/test/resources/test_cases/expected_kdl/quoted_prop_name.kdl @@ -1 +1 @@ -node "0prop"="val" +node "0prop"=val diff --git a/src/test/resources/test_cases/expected_kdl/quoted_prop_type.kdl b/src/test/resources/test_cases/expected_kdl/quoted_prop_type.kdl index 0e2b920..beca5f2 100644 --- a/src/test/resources/test_cases/expected_kdl/quoted_prop_type.kdl +++ b/src/test/resources/test_cases/expected_kdl/quoted_prop_type.kdl @@ -1 +1 @@ -node key=("type/")true +node key=("type/")#true diff --git a/src/test/resources/test_cases/expected_kdl/r_node.kdl b/src/test/resources/test_cases/expected_kdl/r_node.kdl index 4a98807..282cc04 100644 --- a/src/test/resources/test_cases/expected_kdl/r_node.kdl +++ b/src/test/resources/test_cases/expected_kdl/r_node.kdl @@ -1 +1 @@ -r "arg" +r arg diff --git a/src/test/resources/test_cases/expected_kdl/raw_arg_type.kdl b/src/test/resources/test_cases/expected_kdl/raw_arg_type.kdl index 6d1f9bc..20243a3 100644 --- a/src/test/resources/test_cases/expected_kdl/raw_arg_type.kdl +++ b/src/test/resources/test_cases/expected_kdl/raw_arg_type.kdl @@ -1 +1 @@ -node (type)true +node (type)#true diff --git a/src/test/resources/test_cases/expected_kdl/raw_prop_type.kdl b/src/test/resources/test_cases/expected_kdl/raw_prop_type.kdl index c4eebb6..01404b8 100644 --- a/src/test/resources/test_cases/expected_kdl/raw_prop_type.kdl +++ b/src/test/resources/test_cases/expected_kdl/raw_prop_type.kdl @@ -1 +1 @@ -node key=(type)true +node key=(type)#true diff --git a/src/test/resources/test_cases/expected_kdl/raw_string_arg.kdl b/src/test/resources/test_cases/expected_kdl/raw_string_arg.kdl index a909993..24f8d65 100644 --- a/src/test/resources/test_cases/expected_kdl/raw_string_arg.kdl +++ b/src/test/resources/test_cases/expected_kdl/raw_string_arg.kdl @@ -1,3 +1,2 @@ -node_1 "arg\\n" -node_2 "\"arg\\n\"and stuff" -node_3 "#\"arg\\n\"#and stuff" +node_1 "\"arg\\n\"and #stuff" +node_2 "#\"arg\\n\"#and #stuff" diff --git a/src/test/resources/test_cases/expected_kdl/raw_string_newline.kdl b/src/test/resources/test_cases/expected_kdl/raw_string_newline.kdl index d738029..fd38cb0 100644 --- a/src/test/resources/test_cases/expected_kdl/raw_string_newline.kdl +++ b/src/test/resources/test_cases/expected_kdl/raw_string_newline.kdl @@ -1 +1 @@ -node "\nhello\nworld\n" +node "hello\nworld" diff --git a/src/test/resources/test_cases/expected_kdl/raw_string_prop.kdl b/src/test/resources/test_cases/expected_kdl/raw_string_prop.kdl index 0762d88..6a1b5ee 100644 --- a/src/test/resources/test_cases/expected_kdl/raw_string_prop.kdl +++ b/src/test/resources/test_cases/expected_kdl/raw_string_prop.kdl @@ -1,3 +1,2 @@ -node_1 prop="arg\\n" -node_2 prop="\"arg\"\\n" -node_3 prop="#\"arg\"#\\n" +node_1 prop="\"arg#\"\\n" +node_2 prop="#\"arg#\"#\\n" diff --git a/src/test/resources/test_cases/expected_kdl/repeated_arg.kdl b/src/test/resources/test_cases/expected_kdl/repeated_arg.kdl index 849fee0..6525757 100644 --- a/src/test/resources/test_cases/expected_kdl/repeated_arg.kdl +++ b/src/test/resources/test_cases/expected_kdl/repeated_arg.kdl @@ -1 +1 @@ -node "arg" "arg" +node arg arg diff --git a/src/test/resources/test_cases/expected_kdl/same_args.kdl b/src/test/resources/test_cases/expected_kdl/same_args.kdl deleted file mode 100644 index 6b8ae13..0000000 --- a/src/test/resources/test_cases/expected_kdl/same_args.kdl +++ /dev/null @@ -1 +0,0 @@ -node "whee" "whee" diff --git a/src/test/resources/test_cases/expected_kdl/single_arg.kdl b/src/test/resources/test_cases/expected_kdl/single_arg.kdl index b3a0426..1b3db2c 100644 --- a/src/test/resources/test_cases/expected_kdl/single_arg.kdl +++ b/src/test/resources/test_cases/expected_kdl/single_arg.kdl @@ -1 +1 @@ -node "arg" +node arg diff --git a/src/test/resources/test_cases/expected_kdl/single_prop.kdl b/src/test/resources/test_cases/expected_kdl/single_prop.kdl index a0d0062..282aa3b 100644 --- a/src/test/resources/test_cases/expected_kdl/single_prop.kdl +++ b/src/test/resources/test_cases/expected_kdl/single_prop.kdl @@ -1 +1 @@ -node prop="val" +node prop=val diff --git a/src/test/resources/test_cases/expected_kdl/slashdash_arg_after_newline_esc.kdl b/src/test/resources/test_cases/expected_kdl/slashdash_arg_after_newline_esc.kdl index 226fd56..2e98005 100644 --- a/src/test/resources/test_cases/expected_kdl/slashdash_arg_after_newline_esc.kdl +++ b/src/test/resources/test_cases/expected_kdl/slashdash_arg_after_newline_esc.kdl @@ -1 +1 @@ -node "arg2" +node arg2 diff --git a/src/test/resources/test_cases/expected_kdl/slashdash_node_in_child.kdl b/src/test/resources/test_cases/expected_kdl/slashdash_node_in_child.kdl index 56e0831..f50c4f2 100644 --- a/src/test/resources/test_cases/expected_kdl/slashdash_node_in_child.kdl +++ b/src/test/resources/test_cases/expected_kdl/slashdash_node_in_child.kdl @@ -1,2 +1 @@ -node1 { -} +node1 diff --git a/src/test/resources/test_cases/expected_kdl/slashdash_prop.kdl b/src/test/resources/test_cases/expected_kdl/slashdash_prop.kdl index b3a0426..1b3db2c 100644 --- a/src/test/resources/test_cases/expected_kdl/slashdash_prop.kdl +++ b/src/test/resources/test_cases/expected_kdl/slashdash_prop.kdl @@ -1 +1 @@ -node "arg" +node arg diff --git a/src/test/resources/test_cases/expected_kdl/slashdash_repeated_prop.kdl b/src/test/resources/test_cases/expected_kdl/slashdash_repeated_prop.kdl new file mode 100644 index 0000000..dce25a7 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/slashdash_repeated_prop.kdl @@ -0,0 +1 @@ +node arg=correct diff --git a/src/test/resources/test_cases/expected_kdl/space_after_arg_type.kdl b/src/test/resources/test_cases/expected_kdl/space_after_arg_type.kdl new file mode 100644 index 0000000..51dcb98 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/space_after_arg_type.kdl @@ -0,0 +1 @@ +node (type)10 diff --git a/src/test/resources/test_cases/expected_kdl/space_after_node_type.kdl b/src/test/resources/test_cases/expected_kdl/space_after_node_type.kdl new file mode 100644 index 0000000..c790643 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/space_after_node_type.kdl @@ -0,0 +1 @@ +(type)node diff --git a/src/test/resources/test_cases/expected_kdl/space_after_prop_type.kdl b/src/test/resources/test_cases/expected_kdl/space_after_prop_type.kdl new file mode 100644 index 0000000..eb544ef --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/space_after_prop_type.kdl @@ -0,0 +1 @@ +node key=(type)#false diff --git a/src/test/resources/test_cases/expected_kdl/space_around_prop_marker.kdl b/src/test/resources/test_cases/expected_kdl/space_around_prop_marker.kdl new file mode 100644 index 0000000..30a026f --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/space_around_prop_marker.kdl @@ -0,0 +1 @@ +node foo=bar diff --git a/src/test/resources/test_cases/expected_kdl/space_in_arg_type.kdl b/src/test/resources/test_cases/expected_kdl/space_in_arg_type.kdl new file mode 100644 index 0000000..92003d9 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/space_in_arg_type.kdl @@ -0,0 +1 @@ +node (type)#false diff --git a/src/test/resources/test_cases/expected_kdl/space_in_node_type.kdl b/src/test/resources/test_cases/expected_kdl/space_in_node_type.kdl new file mode 100644 index 0000000..c790643 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/space_in_node_type.kdl @@ -0,0 +1 @@ +(type)node diff --git a/src/test/resources/test_cases/expected_kdl/space_in_prop_type.kdl b/src/test/resources/test_cases/expected_kdl/space_in_prop_type.kdl new file mode 100644 index 0000000..eb544ef --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/space_in_prop_type.kdl @@ -0,0 +1 @@ +node key=(type)#false diff --git a/src/test/resources/test_cases/expected_kdl/string_arg.kdl b/src/test/resources/test_cases/expected_kdl/string_arg.kdl index b3a0426..1b3db2c 100644 --- a/src/test/resources/test_cases/expected_kdl/string_arg.kdl +++ b/src/test/resources/test_cases/expected_kdl/string_arg.kdl @@ -1 +1 @@ -node "arg" +node arg diff --git a/src/test/resources/test_cases/expected_kdl/string_escaped_literal_whitespace.kdl b/src/test/resources/test_cases/expected_kdl/string_escaped_literal_whitespace.kdl new file mode 100644 index 0000000..3169ad9 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/string_escaped_literal_whitespace.kdl @@ -0,0 +1 @@ +node "Hello World Stuff" diff --git a/src/test/resources/test_cases/expected_kdl/string_prop.kdl b/src/test/resources/test_cases/expected_kdl/string_prop.kdl index a0d0062..282aa3b 100644 --- a/src/test/resources/test_cases/expected_kdl/string_prop.kdl +++ b/src/test/resources/test_cases/expected_kdl/string_prop.kdl @@ -1 +1 @@ -node prop="val" +node prop=val diff --git a/src/test/resources/test_cases/expected_kdl/trailing_underscore_hex.kdl b/src/test/resources/test_cases/expected_kdl/trailing_underscore_hex.kdl index 5d6cf28..f426d4d 100644 --- a/src/test/resources/test_cases/expected_kdl/trailing_underscore_hex.kdl +++ b/src/test/resources/test_cases/expected_kdl/trailing_underscore_hex.kdl @@ -1 +1 @@ -node 0x123abc +node 1194684 diff --git a/src/test/resources/test_cases/expected_kdl/trailing_underscore_octal.kdl b/src/test/resources/test_cases/expected_kdl/trailing_underscore_octal.kdl index 0e653f9..9152a92 100644 --- a/src/test/resources/test_cases/expected_kdl/trailing_underscore_octal.kdl +++ b/src/test/resources/test_cases/expected_kdl/trailing_underscore_octal.kdl @@ -1 +1 @@ -node 0o123 +node 83 diff --git a/src/test/resources/test_cases/expected_kdl/true_prefix_in_bare_id.kdl b/src/test/resources/test_cases/expected_kdl/true_prefix_in_bare_id.kdl new file mode 100644 index 0000000..49c9d0d --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/true_prefix_in_bare_id.kdl @@ -0,0 +1 @@ +true_id diff --git a/src/test/resources/test_cases/expected_kdl/true_prefix_in_prop_key.kdl b/src/test/resources/test_cases/expected_kdl/true_prefix_in_prop_key.kdl new file mode 100644 index 0000000..2af7a1c --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/true_prefix_in_prop_key.kdl @@ -0,0 +1 @@ +node true_id=1 diff --git a/src/test/resources/test_cases/expected_kdl/underscore_before_number.kdl b/src/test/resources/test_cases/expected_kdl/underscore_before_number.kdl new file mode 100644 index 0000000..788656b --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/underscore_before_number.kdl @@ -0,0 +1 @@ +node _15 diff --git a/src/test/resources/test_cases/expected_kdl/underscore_in_octal.kdl b/src/test/resources/test_cases/expected_kdl/underscore_in_octal.kdl index 94f0c85..f4f6039 100644 --- a/src/test/resources/test_cases/expected_kdl/underscore_in_octal.kdl +++ b/src/test/resources/test_cases/expected_kdl/underscore_in_octal.kdl @@ -1 +1 @@ -node 0o1234567 +node 342391 diff --git a/src/test/resources/test_cases/expected_kdl/unicode_equals_signs.kdl b/src/test/resources/test_cases/expected_kdl/unicode_equals_signs.kdl new file mode 100644 index 0000000..4ab6443 --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/unicode_equals_signs.kdl @@ -0,0 +1 @@ +node p1=val1 p2=val2 p3=val3 diff --git a/src/test/resources/test_cases/expected_kdl/unusual_bare_id_chars_in_quoted_id.kdl b/src/test/resources/test_cases/expected_kdl/unusual_bare_id_chars_in_quoted_id.kdl index d2dcd19..8321632 100644 --- a/src/test/resources/test_cases/expected_kdl/unusual_bare_id_chars_in_quoted_id.kdl +++ b/src/test/resources/test_cases/expected_kdl/unusual_bare_id_chars_in_quoted_id.kdl @@ -1 +1 @@ -foo123~!@#$%^&*.:'|?+ "weeee" +foo123~!@$%^&*.:'|?+<>, weeee diff --git a/src/test/resources/test_cases/expected_kdl/unusual_chars_in_bare_id.kdl b/src/test/resources/test_cases/expected_kdl/unusual_chars_in_bare_id.kdl index d2dcd19..8321632 100644 --- a/src/test/resources/test_cases/expected_kdl/unusual_chars_in_bare_id.kdl +++ b/src/test/resources/test_cases/expected_kdl/unusual_chars_in_bare_id.kdl @@ -1 +1 @@ -foo123~!@#$%^&*.:'|?+ "weeee" +foo123~!@$%^&*.:'|?+<>, weeee diff --git a/src/test/resources/test_cases/expected_kdl/vertical_tab_whitespace.kdl b/src/test/resources/test_cases/expected_kdl/vertical_tab_whitespace.kdl new file mode 100644 index 0000000..1b3db2c --- /dev/null +++ b/src/test/resources/test_cases/expected_kdl/vertical_tab_whitespace.kdl @@ -0,0 +1 @@ +node arg diff --git a/src/test/resources/test_cases/input/all_escapes.kdl b/src/test/resources/test_cases/input/all_escapes.kdl index 024cda2..5c49748 100644 --- a/src/test/resources/test_cases/input/all_escapes.kdl +++ b/src/test/resources/test_cases/input/all_escapes.kdl @@ -1 +1 @@ -node "\"\\\b\f\n\r\t" +node "\"\\\b\f\n\r\t\s" diff --git a/src/test/resources/test_cases/input/all_node_fields.kdl b/src/test/resources/test_cases/input/all_node_fields.kdl index 719a8d1..9f4ceb5 100644 --- a/src/test/resources/test_cases/input/all_node_fields.kdl +++ b/src/test/resources/test_cases/input/all_node_fields.kdl @@ -1,3 +1,3 @@ -node "arg" prop="val" { - inner_node -} \ No newline at end of file +node arg prop=val { + inner_node +} diff --git a/src/test/resources/test_cases/input/arg_and_prop_same_name.kdl b/src/test/resources/test_cases/input/arg_and_prop_same_name.kdl index b830f56..ee5ace5 100644 --- a/src/test/resources/test_cases/input/arg_and_prop_same_name.kdl +++ b/src/test/resources/test_cases/input/arg_and_prop_same_name.kdl @@ -1 +1 @@ -node "arg" arg="val" \ No newline at end of file +node arg arg=val diff --git a/src/test/resources/test_cases/input/arg_bare.kdl b/src/test/resources/test_cases/input/arg_bare.kdl new file mode 100644 index 0000000..2fa9785 --- /dev/null +++ b/src/test/resources/test_cases/input/arg_bare.kdl @@ -0,0 +1 @@ +node a diff --git a/src/test/resources/test_cases/input/arg_false_type.kdl b/src/test/resources/test_cases/input/arg_false_type.kdl index 895945d..92003d9 100644 --- a/src/test/resources/test_cases/input/arg_false_type.kdl +++ b/src/test/resources/test_cases/input/arg_false_type.kdl @@ -1 +1 @@ -node (type)false +node (type)#false diff --git a/src/test/resources/test_cases/input/arg_null_type.kdl b/src/test/resources/test_cases/input/arg_null_type.kdl index 476c5cd..cd66101 100644 --- a/src/test/resources/test_cases/input/arg_null_type.kdl +++ b/src/test/resources/test_cases/input/arg_null_type.kdl @@ -1 +1 @@ -node (type)null +node (type)#null diff --git a/src/test/resources/test_cases/input/arg_raw_string_type.kdl b/src/test/resources/test_cases/input/arg_raw_string_type.kdl index 2808d53..c722312 100644 --- a/src/test/resources/test_cases/input/arg_raw_string_type.kdl +++ b/src/test/resources/test_cases/input/arg_raw_string_type.kdl @@ -1 +1 @@ -node (type)"str" +node (type)#"str"# diff --git a/src/test/resources/test_cases/input/arg_string_type.kdl b/src/test/resources/test_cases/input/arg_string_type.kdl index 1a141b2..2808d53 100644 --- a/src/test/resources/test_cases/input/arg_string_type.kdl +++ b/src/test/resources/test_cases/input/arg_string_type.kdl @@ -1 +1 @@ -node (type)"str" \ No newline at end of file +node (type)"str" diff --git a/src/test/resources/test_cases/input/arg_true_type.kdl b/src/test/resources/test_cases/input/arg_true_type.kdl index 6d1f9bc..20243a3 100644 --- a/src/test/resources/test_cases/input/arg_true_type.kdl +++ b/src/test/resources/test_cases/input/arg_true_type.kdl @@ -1 +1 @@ -node (type)true +node (type)#true diff --git a/src/test/resources/test_cases/input/arg_type.kdl b/src/test/resources/test_cases/input/arg_type.kdl index a0b84cf..79a093d 100644 --- a/src/test/resources/test_cases/input/arg_type.kdl +++ b/src/test/resources/test_cases/input/arg_type.kdl @@ -1 +1 @@ -node (type)"arg" +node (type)arg diff --git a/src/test/resources/test_cases/input/backslash_in_bare_id.kdl b/src/test/resources/test_cases/input/backslash_in_bare_id.kdl deleted file mode 100644 index 46013ce..0000000 --- a/src/test/resources/test_cases/input/backslash_in_bare_id.kdl +++ /dev/null @@ -1 +0,0 @@ -foo123\bar "weeee" \ No newline at end of file diff --git a/src/test/resources/test_cases/input/bare_arg.kdl b/src/test/resources/test_cases/input/bare_arg.kdl deleted file mode 100644 index ec2a21f..0000000 --- a/src/test/resources/test_cases/input/bare_arg.kdl +++ /dev/null @@ -1 +0,0 @@ -node a \ No newline at end of file diff --git a/src/test/resources/test_cases/input/bare_emoji.kdl b/src/test/resources/test_cases/input/bare_emoji.kdl index 60707c8..c67d0b9 100644 --- a/src/test/resources/test_cases/input/bare_emoji.kdl +++ b/src/test/resources/test_cases/input/bare_emoji.kdl @@ -1 +1 @@ -😁 "happy!" +😁 happy! diff --git a/src/test/resources/test_cases/input/bare_ident_dot.kdl b/src/test/resources/test_cases/input/bare_ident_dot.kdl new file mode 100644 index 0000000..5c32f67 --- /dev/null +++ b/src/test/resources/test_cases/input/bare_ident_dot.kdl @@ -0,0 +1 @@ +node . \ No newline at end of file diff --git a/src/test/resources/test_cases/input/bare_ident_numeric.kdl b/src/test/resources/test_cases/input/bare_ident_numeric.kdl new file mode 100644 index 0000000..053af21 --- /dev/null +++ b/src/test/resources/test_cases/input/bare_ident_numeric.kdl @@ -0,0 +1 @@ +node 0n \ No newline at end of file diff --git a/src/test/resources/test_cases/input/bare_ident_numeric_dot.kdl b/src/test/resources/test_cases/input/bare_ident_numeric_dot.kdl new file mode 100644 index 0000000..b97afcf --- /dev/null +++ b/src/test/resources/test_cases/input/bare_ident_numeric_dot.kdl @@ -0,0 +1 @@ +node .0n \ No newline at end of file diff --git a/src/test/resources/test_cases/input/bare_ident_numeric_sign.kdl b/src/test/resources/test_cases/input/bare_ident_numeric_sign.kdl new file mode 100644 index 0000000..6cadc35 --- /dev/null +++ b/src/test/resources/test_cases/input/bare_ident_numeric_sign.kdl @@ -0,0 +1 @@ +node +0n \ No newline at end of file diff --git a/src/test/resources/test_cases/input/bare_ident_sign.kdl b/src/test/resources/test_cases/input/bare_ident_sign.kdl new file mode 100644 index 0000000..b609706 --- /dev/null +++ b/src/test/resources/test_cases/input/bare_ident_sign.kdl @@ -0,0 +1 @@ +node + \ No newline at end of file diff --git a/src/test/resources/test_cases/input/bare_ident_sign_dot.kdl b/src/test/resources/test_cases/input/bare_ident_sign_dot.kdl new file mode 100644 index 0000000..d50adcf --- /dev/null +++ b/src/test/resources/test_cases/input/bare_ident_sign_dot.kdl @@ -0,0 +1 @@ +node +. \ No newline at end of file diff --git a/src/test/resources/test_cases/input/blank_prop_type.kdl b/src/test/resources/test_cases/input/blank_prop_type.kdl index 898f90d..e00c6d2 100644 --- a/src/test/resources/test_cases/input/blank_prop_type.kdl +++ b/src/test/resources/test_cases/input/blank_prop_type.kdl @@ -1 +1 @@ -node key=("")true \ No newline at end of file +node key=("")#true diff --git a/src/test/resources/test_cases/input/block_comment.kdl b/src/test/resources/test_cases/input/block_comment.kdl index e6eddb9..f6c39ac 100644 --- a/src/test/resources/test_cases/input/block_comment.kdl +++ b/src/test/resources/test_cases/input/block_comment.kdl @@ -1 +1 @@ -node /* comment */ "arg" \ No newline at end of file +node /* comment */ arg diff --git a/src/test/resources/test_cases/input/block_comment_after_node.kdl b/src/test/resources/test_cases/input/block_comment_after_node.kdl index e7777ed..071ff21 100644 --- a/src/test/resources/test_cases/input/block_comment_after_node.kdl +++ b/src/test/resources/test_cases/input/block_comment_after_node.kdl @@ -1 +1 @@ -node /* hey */ "arg" +node /* hey */ arg diff --git a/src/test/resources/test_cases/input/bom_initial.kdl b/src/test/resources/test_cases/input/bom_initial.kdl new file mode 100644 index 0000000..e52e8bf --- /dev/null +++ b/src/test/resources/test_cases/input/bom_initial.kdl @@ -0,0 +1 @@ +node arg diff --git a/src/test/resources/test_cases/input/bom_later.kdl b/src/test/resources/test_cases/input/bom_later.kdl new file mode 100644 index 0000000..6aeff8d --- /dev/null +++ b/src/test/resources/test_cases/input/bom_later.kdl @@ -0,0 +1 @@ +node arg diff --git a/src/test/resources/test_cases/input/boolean_arg.kdl b/src/test/resources/test_cases/input/boolean_arg.kdl index f099893..e0cdf1a 100644 --- a/src/test/resources/test_cases/input/boolean_arg.kdl +++ b/src/test/resources/test_cases/input/boolean_arg.kdl @@ -1 +1 @@ -node false true \ No newline at end of file +node #false #true diff --git a/src/test/resources/test_cases/input/boolean_prop.kdl b/src/test/resources/test_cases/input/boolean_prop.kdl index 61e3111..f89da9b 100644 --- a/src/test/resources/test_cases/input/boolean_prop.kdl +++ b/src/test/resources/test_cases/input/boolean_prop.kdl @@ -1 +1 @@ -node prop1=true prop2=false \ No newline at end of file +node prop1=#true prop2=#false diff --git a/src/test/resources/test_cases/input/brackets_in_bare_id.kdl b/src/test/resources/test_cases/input/brackets_in_bare_id.kdl index 6588345..ebb78d2 100644 --- a/src/test/resources/test_cases/input/brackets_in_bare_id.kdl +++ b/src/test/resources/test_cases/input/brackets_in_bare_id.kdl @@ -1 +1 @@ -foo123{bar}foo "weeee" \ No newline at end of file +foo123{bar}foo weeee diff --git a/src/test/resources/test_cases/input/chevrons_in_bare_id.kdl b/src/test/resources/test_cases/input/chevrons_in_bare_id.kdl index dbec399..58b2436 100644 --- a/src/test/resources/test_cases/input/chevrons_in_bare_id.kdl +++ b/src/test/resources/test_cases/input/chevrons_in_bare_id.kdl @@ -1 +1 @@ -foo123foo "weeee" \ No newline at end of file +foo123foo weeee diff --git a/src/test/resources/test_cases/input/comma_in_bare_id.kdl b/src/test/resources/test_cases/input/comma_in_bare_id.kdl index 8a6964f..86c78fd 100644 --- a/src/test/resources/test_cases/input/comma_in_bare_id.kdl +++ b/src/test/resources/test_cases/input/comma_in_bare_id.kdl @@ -1 +1 @@ -foo123,bar "weeee" \ No newline at end of file +foo123,bar weeee diff --git a/src/test/resources/test_cases/input/comment_after_arg_type.kdl b/src/test/resources/test_cases/input/comment_after_arg_type.kdl index f88b7c1..d493f6e 100644 --- a/src/test/resources/test_cases/input/comment_after_arg_type.kdl +++ b/src/test/resources/test_cases/input/comment_after_arg_type.kdl @@ -1 +1 @@ -node (type)/*huh*/10 +node (type)/*hey*/10 diff --git a/src/test/resources/test_cases/input/comment_after_node_type.kdl b/src/test/resources/test_cases/input/comment_after_node_type.kdl index 55ab980..a5939b4 100644 --- a/src/test/resources/test_cases/input/comment_after_node_type.kdl +++ b/src/test/resources/test_cases/input/comment_after_node_type.kdl @@ -1 +1 @@ -(type)/*huh*/node +(type)/*hey*/node diff --git a/src/test/resources/test_cases/input/comment_after_prop_type.kdl b/src/test/resources/test_cases/input/comment_after_prop_type.kdl index c9b1858..6805673 100644 --- a/src/test/resources/test_cases/input/comment_after_prop_type.kdl +++ b/src/test/resources/test_cases/input/comment_after_prop_type.kdl @@ -1 +1 @@ -node key=(type)/*huh*/10 +node key=(type)/*hey*/10 diff --git a/src/test/resources/test_cases/input/comment_and_newline.kdl b/src/test/resources/test_cases/input/comment_and_newline.kdl new file mode 100644 index 0000000..d1bb77f --- /dev/null +++ b/src/test/resources/test_cases/input/comment_and_newline.kdl @@ -0,0 +1,2 @@ +node1 // +node2 diff --git a/src/test/resources/test_cases/input/comment_in_arg_type.kdl b/src/test/resources/test_cases/input/comment_in_arg_type.kdl index 39742ac..1166a43 100644 --- a/src/test/resources/test_cases/input/comment_in_arg_type.kdl +++ b/src/test/resources/test_cases/input/comment_in_arg_type.kdl @@ -1 +1 @@ -node (type/*huh*/)10 +node (type/*hey*/)10 diff --git a/src/test/resources/test_cases/input/comment_in_node_type.kdl b/src/test/resources/test_cases/input/comment_in_node_type.kdl index 8cda2e5..7cc9b26 100644 --- a/src/test/resources/test_cases/input/comment_in_node_type.kdl +++ b/src/test/resources/test_cases/input/comment_in_node_type.kdl @@ -1 +1 @@ -(type/*huh*/)node +(type/*hey*/)node diff --git a/src/test/resources/test_cases/input/comment_in_prop_type.kdl b/src/test/resources/test_cases/input/comment_in_prop_type.kdl index 10adb3b..0587da9 100644 --- a/src/test/resources/test_cases/input/comment_in_prop_type.kdl +++ b/src/test/resources/test_cases/input/comment_in_prop_type.kdl @@ -1 +1 @@ -node key=(type/*huh*/)10 +node key=(type/*hey*/)10 diff --git a/src/test/resources/test_cases/input/commented_arg.kdl b/src/test/resources/test_cases/input/commented_arg.kdl index e389cd2..0e6157f 100644 --- a/src/test/resources/test_cases/input/commented_arg.kdl +++ b/src/test/resources/test_cases/input/commented_arg.kdl @@ -1 +1 @@ -node /- "arg1" "arg2" \ No newline at end of file +node /- arg1 arg2 diff --git a/src/test/resources/test_cases/input/commented_child.kdl b/src/test/resources/test_cases/input/commented_child.kdl index e13c479..8e873f7 100644 --- a/src/test/resources/test_cases/input/commented_child.kdl +++ b/src/test/resources/test_cases/input/commented_child.kdl @@ -1,3 +1,3 @@ -node "arg" /- { +node arg /- { inner_node -} \ No newline at end of file +} diff --git a/src/test/resources/test_cases/input/commented_node.kdl b/src/test/resources/test_cases/input/commented_node.kdl index c9e5d12..1460d67 100644 --- a/src/test/resources/test_cases/input/commented_node.kdl +++ b/src/test/resources/test_cases/input/commented_node.kdl @@ -1,2 +1,3 @@ /- node_1 node_2 +/- node_3 diff --git a/src/test/resources/test_cases/input/commented_prop.kdl b/src/test/resources/test_cases/input/commented_prop.kdl index acedc83..046fd9d 100644 --- a/src/test/resources/test_cases/input/commented_prop.kdl +++ b/src/test/resources/test_cases/input/commented_prop.kdl @@ -1 +1 @@ -node /- prop="val" "arg" \ No newline at end of file +node /- prop=val arg diff --git a/src/test/resources/test_cases/input/crlf_between_nodes.kdl b/src/test/resources/test_cases/input/crlf_between_nodes.kdl index 4ca16e5..148f7bc 100644 --- a/src/test/resources/test_cases/input/crlf_between_nodes.kdl +++ b/src/test/resources/test_cases/input/crlf_between_nodes.kdl @@ -1,2 +1,2 @@ node1 -node2 \ No newline at end of file +node2 diff --git a/src/test/resources/test_cases/input/dash_dash.kdl b/src/test/resources/test_cases/input/dash_dash.kdl index 759ddc5..9f6111a 100644 --- a/src/test/resources/test_cases/input/dash_dash.kdl +++ b/src/test/resources/test_cases/input/dash_dash.kdl @@ -1 +1 @@ -node -- \ No newline at end of file +node -- diff --git a/src/test/resources/test_cases/input/emoji.kdl b/src/test/resources/test_cases/input/emoji.kdl index 3ed56e2..88df78a 100644 --- a/src/test/resources/test_cases/input/emoji.kdl +++ b/src/test/resources/test_cases/input/emoji.kdl @@ -1 +1 @@ -node "😀" +node 😀 diff --git a/src/test/resources/test_cases/input/empty_line_comment.kdl b/src/test/resources/test_cases/input/empty_line_comment.kdl new file mode 100644 index 0000000..e62ef84 --- /dev/null +++ b/src/test/resources/test_cases/input/empty_line_comment.kdl @@ -0,0 +1,2 @@ +// +node \ No newline at end of file diff --git a/src/test/resources/test_cases/input/empty_prop_type.kdl b/src/test/resources/test_cases/input/empty_prop_type.kdl index 0515094..233480b 100644 --- a/src/test/resources/test_cases/input/empty_prop_type.kdl +++ b/src/test/resources/test_cases/input/empty_prop_type.kdl @@ -1 +1 @@ -node key=()false +node key=()#false diff --git a/src/test/resources/test_cases/input/empty_quoted_node_id.kdl b/src/test/resources/test_cases/input/empty_quoted_node_id.kdl index 2aeb594..94694bc 100644 --- a/src/test/resources/test_cases/input/empty_quoted_node_id.kdl +++ b/src/test/resources/test_cases/input/empty_quoted_node_id.kdl @@ -1 +1 @@ -"" "arg" \ No newline at end of file +"" arg diff --git a/src/test/resources/test_cases/input/empty_quoted_prop_key.kdl b/src/test/resources/test_cases/input/empty_quoted_prop_key.kdl index e6e1310..e541793 100644 --- a/src/test/resources/test_cases/input/empty_quoted_prop_key.kdl +++ b/src/test/resources/test_cases/input/empty_quoted_prop_key.kdl @@ -1 +1 @@ -node ""="empty" +node ""=empty diff --git a/src/test/resources/test_cases/input/eof_after_escape.kdl b/src/test/resources/test_cases/input/eof_after_escape.kdl new file mode 100644 index 0000000..eed8d72 --- /dev/null +++ b/src/test/resources/test_cases/input/eof_after_escape.kdl @@ -0,0 +1 @@ +node \ diff --git a/src/test/resources/test_cases/input/err_backslash_in_bare_id.kdl b/src/test/resources/test_cases/input/err_backslash_in_bare_id.kdl new file mode 100644 index 0000000..2ea1a4b --- /dev/null +++ b/src/test/resources/test_cases/input/err_backslash_in_bare_id.kdl @@ -0,0 +1 @@ +foo123\bar weeee diff --git a/src/test/resources/test_cases/input/escaped_whitespace.kdl b/src/test/resources/test_cases/input/escaped_whitespace.kdl new file mode 100644 index 0000000..797784a --- /dev/null +++ b/src/test/resources/test_cases/input/escaped_whitespace.kdl @@ -0,0 +1,15 @@ +// All of these strings are the same +node \ + "Hello\n\tWorld" \ + " + Hello + World + " \ + "Hello\n\ \tWorld" \ + "Hello\n\ + \tWorld" \ + "Hello\n\t\ + World" + +// Note that this file deliberately mixes space and newline indentation for +// test purposes diff --git a/src/test/resources/test_cases/input/escline.kdl b/src/test/resources/test_cases/input/escline.kdl index 9010e07..bcd1a1a 100644 --- a/src/test/resources/test_cases/input/escline.kdl +++ b/src/test/resources/test_cases/input/escline.kdl @@ -1,2 +1,2 @@ node \ - "arg" \ No newline at end of file + arg diff --git a/src/test/resources/test_cases/input/escline_comment_node.kdl b/src/test/resources/test_cases/input/escline_comment_node.kdl deleted file mode 100644 index 030c245..0000000 --- a/src/test/resources/test_cases/input/escline_comment_node.kdl +++ /dev/null @@ -1,3 +0,0 @@ -node1 - \// hey - node2 \ No newline at end of file diff --git a/src/test/resources/test_cases/input/escline_line_comment.kdl b/src/test/resources/test_cases/input/escline_line_comment.kdl index 31f19fd..dc81b72 100644 --- a/src/test/resources/test_cases/input/escline_line_comment.kdl +++ b/src/test/resources/test_cases/input/escline_line_comment.kdl @@ -1,4 +1,3 @@ node \ // comment - "arg" \// comment - "arg2 -" \ No newline at end of file + arg \// comment + arg2 diff --git a/src/test/resources/test_cases/input/false_prefix_in_bare_id.kdl b/src/test/resources/test_cases/input/false_prefix_in_bare_id.kdl new file mode 100644 index 0000000..cd962c4 --- /dev/null +++ b/src/test/resources/test_cases/input/false_prefix_in_bare_id.kdl @@ -0,0 +1 @@ +false_id diff --git a/src/test/resources/test_cases/input/false_prefix_in_prop_key.kdl b/src/test/resources/test_cases/input/false_prefix_in_prop_key.kdl new file mode 100644 index 0000000..2d29843 --- /dev/null +++ b/src/test/resources/test_cases/input/false_prefix_in_prop_key.kdl @@ -0,0 +1 @@ +node false_id=1 diff --git a/src/test/resources/test_cases/input/false_prop_key.kdl b/src/test/resources/test_cases/input/false_prop_key.kdl new file mode 100644 index 0000000..a032c0b --- /dev/null +++ b/src/test/resources/test_cases/input/false_prop_key.kdl @@ -0,0 +1 @@ +node false=1 diff --git a/src/test/resources/test_cases/input/floating_point_keyword_identifier_strings_error.kdl.kdl b/src/test/resources/test_cases/input/floating_point_keyword_identifier_strings_error.kdl.kdl new file mode 100644 index 0000000..e120167 --- /dev/null +++ b/src/test/resources/test_cases/input/floating_point_keyword_identifier_strings_error.kdl.kdl @@ -0,0 +1 @@ +floats inf -inf nan diff --git a/src/test/resources/test_cases/input/floating_point_keywords.kdl b/src/test/resources/test_cases/input/floating_point_keywords.kdl new file mode 100644 index 0000000..973a259 --- /dev/null +++ b/src/test/resources/test_cases/input/floating_point_keywords.kdl @@ -0,0 +1 @@ +floats #inf #-inf #nan diff --git a/src/test/resources/test_cases/input/hash_in_id.kdl b/src/test/resources/test_cases/input/hash_in_id.kdl new file mode 100644 index 0000000..e1119be --- /dev/null +++ b/src/test/resources/test_cases/input/hash_in_id.kdl @@ -0,0 +1 @@ +foo#bar weee diff --git a/src/test/resources/test_cases/input/initial_slashdash.kdl b/src/test/resources/test_cases/input/initial_slashdash.kdl new file mode 100644 index 0000000..aadeeb7 --- /dev/null +++ b/src/test/resources/test_cases/input/initial_slashdash.kdl @@ -0,0 +1,2 @@ +/-node here +another-node diff --git a/src/test/resources/test_cases/input/just_space_in_prop_type.kdl b/src/test/resources/test_cases/input/just_space_in_prop_type.kdl index a00603c..e42645f 100644 --- a/src/test/resources/test_cases/input/just_space_in_prop_type.kdl +++ b/src/test/resources/test_cases/input/just_space_in_prop_type.kdl @@ -1 +1 @@ -node key=()0x10 +node key=( )0x10 diff --git a/src/test/resources/test_cases/input/multiline_comment.kdl b/src/test/resources/test_cases/input/multiline_comment.kdl index 26485bc..5fbb80b 100644 --- a/src/test/resources/test_cases/input/multiline_comment.kdl +++ b/src/test/resources/test_cases/input/multiline_comment.kdl @@ -1,4 +1,4 @@ node /* some comments -*/ "arg" \ No newline at end of file +*/ arg diff --git a/src/test/resources/test_cases/input/multiline_nodes.kdl b/src/test/resources/test_cases/input/multiline_nodes.kdl index 3dc907e..eae83d1 100644 --- a/src/test/resources/test_cases/input/multiline_nodes.kdl +++ b/src/test/resources/test_cases/input/multiline_nodes.kdl @@ -1,3 +1,3 @@ node \ - "arg1" \// comment - "arg2" \ No newline at end of file + arg1 \// comment + arg2 diff --git a/src/test/resources/test_cases/input/multiline_raw_string.kdl b/src/test/resources/test_cases/input/multiline_raw_string.kdl new file mode 100644 index 0000000..eaa212e --- /dev/null +++ b/src/test/resources/test_cases/input/multiline_raw_string.kdl @@ -0,0 +1,5 @@ +node #" +hey +everyone +how goes? +"# diff --git a/src/test/resources/test_cases/input/multiline_raw_string_indented.kdl b/src/test/resources/test_cases/input/multiline_raw_string_indented.kdl new file mode 100644 index 0000000..67ef76d --- /dev/null +++ b/src/test/resources/test_cases/input/multiline_raw_string_indented.kdl @@ -0,0 +1,5 @@ +node #" + hey + everyone + how goes? + "# diff --git a/src/test/resources/test_cases/input/multiline_raw_string_non_matching_prefix_character_error.kdl b/src/test/resources/test_cases/input/multiline_raw_string_non_matching_prefix_character_error.kdl new file mode 100644 index 0000000..c5650e9 --- /dev/null +++ b/src/test/resources/test_cases/input/multiline_raw_string_non_matching_prefix_character_error.kdl @@ -0,0 +1,5 @@ +node #" + hey + everyone + how goes? + "# diff --git a/src/test/resources/test_cases/input/multiline_raw_string_non_matching_prefix_count_error.kdl b/src/test/resources/test_cases/input/multiline_raw_string_non_matching_prefix_count_error.kdl new file mode 100644 index 0000000..c0f4f56 --- /dev/null +++ b/src/test/resources/test_cases/input/multiline_raw_string_non_matching_prefix_count_error.kdl @@ -0,0 +1,5 @@ +node #" + hey + everyone + how goes? + "# diff --git a/src/test/resources/test_cases/input/multiline_string.kdl b/src/test/resources/test_cases/input/multiline_string.kdl index 603cddd..e3a6cc1 100644 --- a/src/test/resources/test_cases/input/multiline_string.kdl +++ b/src/test/resources/test_cases/input/multiline_string.kdl @@ -1,4 +1,5 @@ -node " hey +node " +hey everyone how goes? -" \ No newline at end of file +" diff --git a/src/test/resources/test_cases/input/multiline_string_indented.kdl b/src/test/resources/test_cases/input/multiline_string_indented.kdl new file mode 100644 index 0000000..ce9ca16 --- /dev/null +++ b/src/test/resources/test_cases/input/multiline_string_indented.kdl @@ -0,0 +1,5 @@ +node " + hey + everyone + how goes? + " diff --git a/src/test/resources/test_cases/input/multiline_string_non_matching_prefix_character_error.kdl b/src/test/resources/test_cases/input/multiline_string_non_matching_prefix_character_error.kdl new file mode 100644 index 0000000..1c2ca85 --- /dev/null +++ b/src/test/resources/test_cases/input/multiline_string_non_matching_prefix_character_error.kdl @@ -0,0 +1,5 @@ +node " + hey + everyone + how goes? + " diff --git a/src/test/resources/test_cases/input/multiline_string_non_matching_prefix_count_error.kdl b/src/test/resources/test_cases/input/multiline_string_non_matching_prefix_count_error.kdl new file mode 100644 index 0000000..86a2867 --- /dev/null +++ b/src/test/resources/test_cases/input/multiline_string_non_matching_prefix_count_error.kdl @@ -0,0 +1,5 @@ +node " + hey + everyone + how goes? + " diff --git a/src/test/resources/test_cases/input/nested_block_comment.kdl b/src/test/resources/test_cases/input/nested_block_comment.kdl index d7f765c..d9966a9 100644 --- a/src/test/resources/test_cases/input/nested_block_comment.kdl +++ b/src/test/resources/test_cases/input/nested_block_comment.kdl @@ -1 +1 @@ -node /* hi /* there */ everyone */ "arg" \ No newline at end of file +node /* hi /* there */ everyone */ arg diff --git a/src/test/resources/test_cases/input/nested_comments.kdl b/src/test/resources/test_cases/input/nested_comments.kdl index 8b3aad6..7541c39 100644 --- a/src/test/resources/test_cases/input/nested_comments.kdl +++ b/src/test/resources/test_cases/input/nested_comments.kdl @@ -1 +1 @@ -node /*/* nested */*/ "arg" \ No newline at end of file +node /*/* nested */*/ arg diff --git a/src/test/resources/test_cases/input/nested_multiline_block_comment.kdl b/src/test/resources/test_cases/input/nested_multiline_block_comment.kdl index 9d8e0ca..f1087e1 100644 --- a/src/test/resources/test_cases/input/nested_multiline_block_comment.kdl +++ b/src/test/resources/test_cases/input/nested_multiline_block_comment.kdl @@ -3,5 +3,4 @@ hey /* how's */ it going - */ "arg" - \ No newline at end of file + */ arg diff --git a/src/test/resources/test_cases/input/newlines_in_block_comment.kdl b/src/test/resources/test_cases/input/newlines_in_block_comment.kdl index a5cd2b1..690461b 100644 --- a/src/test/resources/test_cases/input/newlines_in_block_comment.kdl +++ b/src/test/resources/test_cases/input/newlines_in_block_comment.kdl @@ -1,3 +1,3 @@ node /* hey so I was thinking -about newts */ "arg" \ No newline at end of file +about newts */ arg diff --git a/src/test/resources/test_cases/input/no_integer_digit.kdl b/src/test/resources/test_cases/input/no_integer_digit.kdl new file mode 100644 index 0000000..bac8026 --- /dev/null +++ b/src/test/resources/test_cases/input/no_integer_digit.kdl @@ -0,0 +1 @@ +node .1 \ No newline at end of file diff --git a/src/test/resources/test_cases/input/no_solidus_escape.kdl b/src/test/resources/test_cases/input/no_solidus_escape.kdl new file mode 100644 index 0000000..2dbc2d1 --- /dev/null +++ b/src/test/resources/test_cases/input/no_solidus_escape.kdl @@ -0,0 +1 @@ +node "\/" diff --git a/src/test/resources/test_cases/input/node_false.kdl b/src/test/resources/test_cases/input/node_false.kdl index ef60c44..3bab782 100644 --- a/src/test/resources/test_cases/input/node_false.kdl +++ b/src/test/resources/test_cases/input/node_false.kdl @@ -1 +1 @@ -node false +node #false diff --git a/src/test/resources/test_cases/input/node_true.kdl b/src/test/resources/test_cases/input/node_true.kdl index 4b02a06..de00dcd 100644 --- a/src/test/resources/test_cases/input/node_true.kdl +++ b/src/test/resources/test_cases/input/node_true.kdl @@ -1 +1 @@ -node true +node #true diff --git a/src/test/resources/test_cases/input/null_arg.kdl b/src/test/resources/test_cases/input/null_arg.kdl index a5ce001..bed8dbf 100644 --- a/src/test/resources/test_cases/input/null_arg.kdl +++ b/src/test/resources/test_cases/input/null_arg.kdl @@ -1 +1 @@ -node null \ No newline at end of file +node #null diff --git a/src/test/resources/test_cases/input/null_prefix_in_bare_id.kdl b/src/test/resources/test_cases/input/null_prefix_in_bare_id.kdl new file mode 100644 index 0000000..9e0cf15 --- /dev/null +++ b/src/test/resources/test_cases/input/null_prefix_in_bare_id.kdl @@ -0,0 +1 @@ +null_id diff --git a/src/test/resources/test_cases/input/null_prefix_in_prop_key.kdl b/src/test/resources/test_cases/input/null_prefix_in_prop_key.kdl new file mode 100644 index 0000000..1e1472b --- /dev/null +++ b/src/test/resources/test_cases/input/null_prefix_in_prop_key.kdl @@ -0,0 +1 @@ +node null_id=1 diff --git a/src/test/resources/test_cases/input/null_prop.kdl b/src/test/resources/test_cases/input/null_prop.kdl index 847256f..c463e98 100644 --- a/src/test/resources/test_cases/input/null_prop.kdl +++ b/src/test/resources/test_cases/input/null_prop.kdl @@ -1 +1 @@ -node prop=null \ No newline at end of file +node prop=#null diff --git a/src/test/resources/test_cases/input/null_prop_key.kdl b/src/test/resources/test_cases/input/null_prop_key.kdl new file mode 100644 index 0000000..6896d42 --- /dev/null +++ b/src/test/resources/test_cases/input/null_prop_key.kdl @@ -0,0 +1 @@ +node null=1 diff --git a/src/test/resources/test_cases/input/optional_child_semicolon.kdl b/src/test/resources/test_cases/input/optional_child_semicolon.kdl new file mode 100644 index 0000000..5381491 --- /dev/null +++ b/src/test/resources/test_cases/input/optional_child_semicolon.kdl @@ -0,0 +1 @@ +node {foo;bar;baz} diff --git a/src/test/resources/test_cases/input/parens_in_bare_id.kdl b/src/test/resources/test_cases/input/parens_in_bare_id.kdl index f8b759b..ff9b439 100644 --- a/src/test/resources/test_cases/input/parens_in_bare_id.kdl +++ b/src/test/resources/test_cases/input/parens_in_bare_id.kdl @@ -1 +1 @@ -foo123(bar)foo "weeee" \ No newline at end of file +foo123(bar)foo weeee diff --git a/src/test/resources/test_cases/input/parse_all_arg_types.kdl b/src/test/resources/test_cases/input/parse_all_arg_types.kdl index 30b9072..92dffb1 100644 --- a/src/test/resources/test_cases/input/parse_all_arg_types.kdl +++ b/src/test/resources/test_cases/input/parse_all_arg_types.kdl @@ -1 +1 @@ -node 1 1.0 1.0e10 1.0e-10 0x01 0o07 0b10 "arg" r"arg\\" true false null \ No newline at end of file +node 1 1.0 1.0e10 1.0e-10 0x01 0o07 0b10 arg "arg" #"arg\"# #true #false #null diff --git a/src/test/resources/test_cases/input/prop_false_type.kdl b/src/test/resources/test_cases/input/prop_false_type.kdl index 3377323..eb544ef 100644 --- a/src/test/resources/test_cases/input/prop_false_type.kdl +++ b/src/test/resources/test_cases/input/prop_false_type.kdl @@ -1 +1 @@ -node key=(type)false +node key=(type)#false diff --git a/src/test/resources/test_cases/input/prop_identifier_type.kdl b/src/test/resources/test_cases/input/prop_identifier_type.kdl new file mode 100644 index 0000000..7df052b --- /dev/null +++ b/src/test/resources/test_cases/input/prop_identifier_type.kdl @@ -0,0 +1 @@ +node key=(type)str diff --git a/src/test/resources/test_cases/input/prop_null_type.kdl b/src/test/resources/test_cases/input/prop_null_type.kdl index bafaddc..1c25b6f 100644 --- a/src/test/resources/test_cases/input/prop_null_type.kdl +++ b/src/test/resources/test_cases/input/prop_null_type.kdl @@ -1 +1 @@ -node key=(type)null +node key=(type)#null diff --git a/src/test/resources/test_cases/input/prop_raw_string_type.kdl b/src/test/resources/test_cases/input/prop_raw_string_type.kdl index a038cfa..6822ab3 100644 --- a/src/test/resources/test_cases/input/prop_raw_string_type.kdl +++ b/src/test/resources/test_cases/input/prop_raw_string_type.kdl @@ -1 +1 @@ -node key=(type)r"str" +node key=(type)#"str"# diff --git a/src/test/resources/test_cases/input/prop_true_type.kdl b/src/test/resources/test_cases/input/prop_true_type.kdl index c4eebb6..01404b8 100644 --- a/src/test/resources/test_cases/input/prop_true_type.kdl +++ b/src/test/resources/test_cases/input/prop_true_type.kdl @@ -1 +1 @@ -node key=(type)true +node key=(type)#true diff --git a/src/test/resources/test_cases/input/prop_type.kdl b/src/test/resources/test_cases/input/prop_type.kdl index d69294f..01404b8 100644 --- a/src/test/resources/test_cases/input/prop_type.kdl +++ b/src/test/resources/test_cases/input/prop_type.kdl @@ -1 +1 @@ -node key=(type)true \ No newline at end of file +node key=(type)#true diff --git a/src/test/resources/test_cases/input/question_mark_at_start_of_int.kdl b/src/test/resources/test_cases/input/question_mark_at_start_of_int.kdl deleted file mode 100644 index ba82916..0000000 --- a/src/test/resources/test_cases/input/question_mark_at_start_of_int.kdl +++ /dev/null @@ -1 +0,0 @@ -node ?10 \ No newline at end of file diff --git a/src/test/resources/test_cases/input/question_mark_before_number.kdl b/src/test/resources/test_cases/input/question_mark_before_number.kdl index 532ef22..7745a9e 100644 --- a/src/test/resources/test_cases/input/question_mark_before_number.kdl +++ b/src/test/resources/test_cases/input/question_mark_before_number.kdl @@ -1 +1 @@ -node ?15 \ No newline at end of file +node ?15 diff --git a/src/test/resources/test_cases/input/quote_in_bare_id.kdl b/src/test/resources/test_cases/input/quote_in_bare_id.kdl index f3b0b20..0d8a664 100644 --- a/src/test/resources/test_cases/input/quote_in_bare_id.kdl +++ b/src/test/resources/test_cases/input/quote_in_bare_id.kdl @@ -1 +1 @@ -foo123"bar "weeee" \ No newline at end of file +foo123"bar weeee diff --git a/src/test/resources/test_cases/input/quoted_prop_name.kdl b/src/test/resources/test_cases/input/quoted_prop_name.kdl index 73ec6dd..8ee5e08 100644 --- a/src/test/resources/test_cases/input/quoted_prop_name.kdl +++ b/src/test/resources/test_cases/input/quoted_prop_name.kdl @@ -1 +1 @@ -node "0prop"="val" \ No newline at end of file +node "0prop"=val diff --git a/src/test/resources/test_cases/input/quoted_prop_type.kdl b/src/test/resources/test_cases/input/quoted_prop_type.kdl index 0e2b920..beca5f2 100644 --- a/src/test/resources/test_cases/input/quoted_prop_type.kdl +++ b/src/test/resources/test_cases/input/quoted_prop_type.kdl @@ -1 +1 @@ -node key=("type/")true +node key=("type/")#true diff --git a/src/test/resources/test_cases/input/raw_arg_type.kdl b/src/test/resources/test_cases/input/raw_arg_type.kdl index c5739b1..20243a3 100644 --- a/src/test/resources/test_cases/input/raw_arg_type.kdl +++ b/src/test/resources/test_cases/input/raw_arg_type.kdl @@ -1 +1 @@ -node (type)true \ No newline at end of file +node (type)#true diff --git a/src/test/resources/test_cases/input/raw_node_name.kdl b/src/test/resources/test_cases/input/raw_node_name.kdl index 0d38371..f2705c7 100644 --- a/src/test/resources/test_cases/input/raw_node_name.kdl +++ b/src/test/resources/test_cases/input/raw_node_name.kdl @@ -1 +1 @@ -r"\node" \ No newline at end of file +#"\node"# diff --git a/src/test/resources/test_cases/input/raw_prop_type.kdl b/src/test/resources/test_cases/input/raw_prop_type.kdl index d69294f..01404b8 100644 --- a/src/test/resources/test_cases/input/raw_prop_type.kdl +++ b/src/test/resources/test_cases/input/raw_prop_type.kdl @@ -1 +1 @@ -node key=(type)true \ No newline at end of file +node key=(type)#true diff --git a/src/test/resources/test_cases/input/raw_string_arg.kdl b/src/test/resources/test_cases/input/raw_string_arg.kdl index 6b7581f..05cf37e 100644 --- a/src/test/resources/test_cases/input/raw_string_arg.kdl +++ b/src/test/resources/test_cases/input/raw_string_arg.kdl @@ -1,3 +1,2 @@ -node_1 r"arg\n" -node_2 r#""arg\n"and stuff"# -node_3 r##"#"arg\n"#and stuff"## \ No newline at end of file +node_1 #""arg\n"and #stuff"# +node_2 ##"#"arg\n"#and #stuff"## diff --git a/src/test/resources/test_cases/input/raw_string_backslash.kdl b/src/test/resources/test_cases/input/raw_string_backslash.kdl index 0f7ca45..0405248 100644 --- a/src/test/resources/test_cases/input/raw_string_backslash.kdl +++ b/src/test/resources/test_cases/input/raw_string_backslash.kdl @@ -1 +1 @@ -node r"\n" +node #"\n"# diff --git a/src/test/resources/test_cases/input/raw_string_hash_no_esc.kdl b/src/test/resources/test_cases/input/raw_string_hash_no_esc.kdl index c8fa3c4..ce24c79 100644 --- a/src/test/resources/test_cases/input/raw_string_hash_no_esc.kdl +++ b/src/test/resources/test_cases/input/raw_string_hash_no_esc.kdl @@ -1 +1 @@ -node r"#" +node #"#"# diff --git a/src/test/resources/test_cases/input/raw_string_just_backslash.kdl b/src/test/resources/test_cases/input/raw_string_just_backslash.kdl index 9aefa73..f4e1cac 100644 --- a/src/test/resources/test_cases/input/raw_string_just_backslash.kdl +++ b/src/test/resources/test_cases/input/raw_string_just_backslash.kdl @@ -1 +1 @@ -node r"\" +node #"\"# diff --git a/src/test/resources/test_cases/input/raw_string_just_quote.kdl b/src/test/resources/test_cases/input/raw_string_just_quote.kdl index b8333ca..e81bf12 100644 --- a/src/test/resources/test_cases/input/raw_string_just_quote.kdl +++ b/src/test/resources/test_cases/input/raw_string_just_quote.kdl @@ -1 +1 @@ -node r#"""# +node #"""# diff --git a/src/test/resources/test_cases/input/raw_string_multiple_hash.kdl b/src/test/resources/test_cases/input/raw_string_multiple_hash.kdl index e6d054c..6317f36 100644 --- a/src/test/resources/test_cases/input/raw_string_multiple_hash.kdl +++ b/src/test/resources/test_cases/input/raw_string_multiple_hash.kdl @@ -1 +1 @@ -node r###""#"##"### +node ###""#"##"### diff --git a/src/test/resources/test_cases/input/raw_string_newline.kdl b/src/test/resources/test_cases/input/raw_string_newline.kdl index ef39d3c..0cc85c0 100644 --- a/src/test/resources/test_cases/input/raw_string_newline.kdl +++ b/src/test/resources/test_cases/input/raw_string_newline.kdl @@ -1,4 +1,4 @@ -node r" +node #" hello world -" +"# diff --git a/src/test/resources/test_cases/input/raw_string_prop.kdl b/src/test/resources/test_cases/input/raw_string_prop.kdl index a6c352a..cc59232 100644 --- a/src/test/resources/test_cases/input/raw_string_prop.kdl +++ b/src/test/resources/test_cases/input/raw_string_prop.kdl @@ -1,3 +1,2 @@ -node_1 prop=r"arg\n" -node_2 prop=r#""arg"\n"# -node_3 prop=r##"#"arg"#\n"## \ No newline at end of file +node_1 prop=#""arg#"\n"# +node_2 prop=##"#"arg#"#\n"## diff --git a/src/test/resources/test_cases/input/raw_string_quote.kdl b/src/test/resources/test_cases/input/raw_string_quote.kdl index cd7419c..004b62f 100644 --- a/src/test/resources/test_cases/input/raw_string_quote.kdl +++ b/src/test/resources/test_cases/input/raw_string_quote.kdl @@ -1 +1 @@ -node r#"a"b"# \ No newline at end of file +node #"a"b"# diff --git a/src/test/resources/test_cases/input/repeated_arg.kdl b/src/test/resources/test_cases/input/repeated_arg.kdl index beab120..6525757 100644 --- a/src/test/resources/test_cases/input/repeated_arg.kdl +++ b/src/test/resources/test_cases/input/repeated_arg.kdl @@ -1 +1 @@ -node "arg" "arg" \ No newline at end of file +node arg arg diff --git a/src/test/resources/test_cases/input/same_args.kdl b/src/test/resources/test_cases/input/same_args.kdl deleted file mode 100644 index c412de8..0000000 --- a/src/test/resources/test_cases/input/same_args.kdl +++ /dev/null @@ -1 +0,0 @@ -node "whee" "whee" \ No newline at end of file diff --git a/src/test/resources/test_cases/input/single_arg.kdl b/src/test/resources/test_cases/input/single_arg.kdl index e5161d1..1b3db2c 100644 --- a/src/test/resources/test_cases/input/single_arg.kdl +++ b/src/test/resources/test_cases/input/single_arg.kdl @@ -1 +1 @@ -node "arg" \ No newline at end of file +node arg diff --git a/src/test/resources/test_cases/input/single_prop.kdl b/src/test/resources/test_cases/input/single_prop.kdl index 4c29c14..282aa3b 100644 --- a/src/test/resources/test_cases/input/single_prop.kdl +++ b/src/test/resources/test_cases/input/single_prop.kdl @@ -1 +1 @@ -node prop="val" \ No newline at end of file +node prop=val diff --git a/src/test/resources/test_cases/input/slash_in_bare_id.kdl b/src/test/resources/test_cases/input/slash_in_bare_id.kdl index f44b5d2..d26d325 100644 --- a/src/test/resources/test_cases/input/slash_in_bare_id.kdl +++ b/src/test/resources/test_cases/input/slash_in_bare_id.kdl @@ -1 +1 @@ -foo123/bar "weeee" \ No newline at end of file +foo123/bar weeee diff --git a/src/test/resources/test_cases/input/slashdash_arg_after_newline_esc.kdl b/src/test/resources/test_cases/input/slashdash_arg_after_newline_esc.kdl index 059b3e1..5a4a9fd 100644 --- a/src/test/resources/test_cases/input/slashdash_arg_after_newline_esc.kdl +++ b/src/test/resources/test_cases/input/slashdash_arg_after_newline_esc.kdl @@ -1,2 +1,2 @@ node \ - /- "arg" "arg2" + /- arg arg2 diff --git a/src/test/resources/test_cases/input/slashdash_arg_before_newline_esc.kdl b/src/test/resources/test_cases/input/slashdash_arg_before_newline_esc.kdl index f58e4a7..70206aa 100644 --- a/src/test/resources/test_cases/input/slashdash_arg_before_newline_esc.kdl +++ b/src/test/resources/test_cases/input/slashdash_arg_before_newline_esc.kdl @@ -1,2 +1,2 @@ node /- \ - "arg" + arg diff --git a/src/test/resources/test_cases/input/slashdash_full_node.kdl b/src/test/resources/test_cases/input/slashdash_full_node.kdl index de2eb2a..4df7b55 100644 --- a/src/test/resources/test_cases/input/slashdash_full_node.kdl +++ b/src/test/resources/test_cases/input/slashdash_full_node.kdl @@ -1,2 +1,3 @@ -/- node 1.0 "a" b="b -" \ No newline at end of file +/- node 1.0 "a" b=" +b +" diff --git a/src/test/resources/test_cases/input/slashdash_prop.kdl b/src/test/resources/test_cases/input/slashdash_prop.kdl index 3d7b806..2b81f5f 100644 --- a/src/test/resources/test_cases/input/slashdash_prop.kdl +++ b/src/test/resources/test_cases/input/slashdash_prop.kdl @@ -1 +1 @@ -node /- key="value" "arg" +node /- key=value arg diff --git a/src/test/resources/test_cases/input/slashdash_raw_prop_key.kdl b/src/test/resources/test_cases/input/slashdash_raw_prop_key.kdl index c9ad5ad..9b0978b 100644 --- a/src/test/resources/test_cases/input/slashdash_raw_prop_key.kdl +++ b/src/test/resources/test_cases/input/slashdash_raw_prop_key.kdl @@ -1 +1 @@ -node /- key="value" +node /- key=value diff --git a/src/test/resources/test_cases/input/slashdash_repeated_prop.kdl b/src/test/resources/test_cases/input/slashdash_repeated_prop.kdl new file mode 100644 index 0000000..c94411a --- /dev/null +++ b/src/test/resources/test_cases/input/slashdash_repeated_prop.kdl @@ -0,0 +1 @@ +node arg=correct /- arg=wrong diff --git a/src/test/resources/test_cases/input/space_after_prop_type.kdl b/src/test/resources/test_cases/input/space_after_prop_type.kdl index a891dfd..023a75c 100644 --- a/src/test/resources/test_cases/input/space_after_prop_type.kdl +++ b/src/test/resources/test_cases/input/space_after_prop_type.kdl @@ -1 +1 @@ -node key=(type) false +node key=(type) #false diff --git a/src/test/resources/test_cases/input/space_around_prop_marker.kdl b/src/test/resources/test_cases/input/space_around_prop_marker.kdl new file mode 100644 index 0000000..52150d8 --- /dev/null +++ b/src/test/resources/test_cases/input/space_around_prop_marker.kdl @@ -0,0 +1 @@ +node foo = bar diff --git a/src/test/resources/test_cases/input/space_in_arg_type.kdl b/src/test/resources/test_cases/input/space_in_arg_type.kdl index 2f9ca24..e2fb065 100644 --- a/src/test/resources/test_cases/input/space_in_arg_type.kdl +++ b/src/test/resources/test_cases/input/space_in_arg_type.kdl @@ -1 +1 @@ -node (type )false +node (type )#false diff --git a/src/test/resources/test_cases/input/space_in_prop_type.kdl b/src/test/resources/test_cases/input/space_in_prop_type.kdl index 4e9c750..0a18c97 100644 --- a/src/test/resources/test_cases/input/space_in_prop_type.kdl +++ b/src/test/resources/test_cases/input/space_in_prop_type.kdl @@ -1 +1 @@ -node key=(type )false +node key=(type )#false diff --git a/src/test/resources/test_cases/input/square_bracket_in_bare_id.kdl b/src/test/resources/test_cases/input/square_bracket_in_bare_id.kdl index e193924..62f34e2 100644 --- a/src/test/resources/test_cases/input/square_bracket_in_bare_id.kdl +++ b/src/test/resources/test_cases/input/square_bracket_in_bare_id.kdl @@ -1 +1 @@ -foo123[bar]foo "weeee" \ No newline at end of file +foo123[bar]foo weeee diff --git a/src/test/resources/test_cases/input/string_escaped_literal_whitespace.kdl b/src/test/resources/test_cases/input/string_escaped_literal_whitespace.kdl new file mode 100644 index 0000000..1f12126 --- /dev/null +++ b/src/test/resources/test_cases/input/string_escaped_literal_whitespace.kdl @@ -0,0 +1,2 @@ +node "Hello \ +World \ Stuff" diff --git a/src/test/resources/test_cases/input/true_prefix_in_bare_id.kdl b/src/test/resources/test_cases/input/true_prefix_in_bare_id.kdl new file mode 100644 index 0000000..49c9d0d --- /dev/null +++ b/src/test/resources/test_cases/input/true_prefix_in_bare_id.kdl @@ -0,0 +1 @@ +true_id diff --git a/src/test/resources/test_cases/input/true_prefix_in_prop_key.kdl b/src/test/resources/test_cases/input/true_prefix_in_prop_key.kdl new file mode 100644 index 0000000..2af7a1c --- /dev/null +++ b/src/test/resources/test_cases/input/true_prefix_in_prop_key.kdl @@ -0,0 +1 @@ +node true_id=1 diff --git a/src/test/resources/test_cases/input/true_prop_key.kdl b/src/test/resources/test_cases/input/true_prop_key.kdl new file mode 100644 index 0000000..e88c36f --- /dev/null +++ b/src/test/resources/test_cases/input/true_prop_key.kdl @@ -0,0 +1 @@ +node true=1 diff --git a/src/test/resources/test_cases/input/unbalanced_raw_hashes.kdl b/src/test/resources/test_cases/input/unbalanced_raw_hashes.kdl index 7deb72f..d0213f2 100644 --- a/src/test/resources/test_cases/input/unbalanced_raw_hashes.kdl +++ b/src/test/resources/test_cases/input/unbalanced_raw_hashes.kdl @@ -1 +1 @@ -node r##"foo"# +node ##"foo"# diff --git a/src/test/resources/test_cases/input/underscore_at_start_of_int.kdl b/src/test/resources/test_cases/input/underscore_at_start_of_int.kdl deleted file mode 100644 index b854b60..0000000 --- a/src/test/resources/test_cases/input/underscore_at_start_of_int.kdl +++ /dev/null @@ -1 +0,0 @@ -node _15 \ No newline at end of file diff --git a/src/test/resources/test_cases/input/unicode_delete.kdl b/src/test/resources/test_cases/input/unicode_delete.kdl new file mode 100644 index 0000000..3fb52ed --- /dev/null +++ b/src/test/resources/test_cases/input/unicode_delete.kdl @@ -0,0 +1,2 @@ +// 0x007F (Delete) +node1 arg diff --git a/src/test/resources/test_cases/input/unicode_equals_signs.kdl b/src/test/resources/test_cases/input/unicode_equals_signs.kdl new file mode 100644 index 0000000..37d8e02 --- /dev/null +++ b/src/test/resources/test_cases/input/unicode_equals_signs.kdl @@ -0,0 +1,4 @@ +node \ + p1﹦val1 \ // U+FE66 + p2=val2 \ // U+FF1D + p3🟰val3 // U+1F7F0 diff --git a/src/test/resources/test_cases/input/unicode_fsi.kdl b/src/test/resources/test_cases/input/unicode_fsi.kdl new file mode 100644 index 0000000..7aece14 --- /dev/null +++ b/src/test/resources/test_cases/input/unicode_fsi.kdl @@ -0,0 +1,2 @@ +// 0x2068 +node1 ⁨arg diff --git a/src/test/resources/test_cases/input/unicode_lre.kdl b/src/test/resources/test_cases/input/unicode_lre.kdl new file mode 100644 index 0000000..33342ae --- /dev/null +++ b/src/test/resources/test_cases/input/unicode_lre.kdl @@ -0,0 +1,2 @@ +// 0x202A +node1 ‪arg diff --git a/src/test/resources/test_cases/input/unicode_lri.kdl b/src/test/resources/test_cases/input/unicode_lri.kdl new file mode 100644 index 0000000..adec826 --- /dev/null +++ b/src/test/resources/test_cases/input/unicode_lri.kdl @@ -0,0 +1,2 @@ +// 0x2066 +node1⁦arg diff --git a/src/test/resources/test_cases/input/unicode_lrm.kdl b/src/test/resources/test_cases/input/unicode_lrm.kdl new file mode 100644 index 0000000..ff37cad --- /dev/null +++ b/src/test/resources/test_cases/input/unicode_lrm.kdl @@ -0,0 +1,2 @@ +// 0x200E +node ‎arg diff --git a/src/test/resources/test_cases/input/unicode_lro.kdl b/src/test/resources/test_cases/input/unicode_lro.kdl new file mode 100644 index 0000000..b084ded --- /dev/null +++ b/src/test/resources/test_cases/input/unicode_lro.kdl @@ -0,0 +1,2 @@ +// 0x202D +node ‭arg diff --git a/src/test/resources/test_cases/input/unicode_pdf.kdl b/src/test/resources/test_cases/input/unicode_pdf.kdl new file mode 100644 index 0000000..9b94fad --- /dev/null +++ b/src/test/resources/test_cases/input/unicode_pdf.kdl @@ -0,0 +1,2 @@ +// 0x202C +node ‬arg diff --git a/src/test/resources/test_cases/input/unicode_pdi.kdl b/src/test/resources/test_cases/input/unicode_pdi.kdl new file mode 100644 index 0000000..d92d2d7 --- /dev/null +++ b/src/test/resources/test_cases/input/unicode_pdi.kdl @@ -0,0 +1,2 @@ +// 0x2069 +node ⁩arg diff --git a/src/test/resources/test_cases/input/unicode_rle.kdl b/src/test/resources/test_cases/input/unicode_rle.kdl new file mode 100644 index 0000000..3b46610 --- /dev/null +++ b/src/test/resources/test_cases/input/unicode_rle.kdl @@ -0,0 +1,2 @@ +// 0x202B +node1 ‫arg diff --git a/src/test/resources/test_cases/input/unicode_rli.kdl b/src/test/resources/test_cases/input/unicode_rli.kdl new file mode 100644 index 0000000..92902ed --- /dev/null +++ b/src/test/resources/test_cases/input/unicode_rli.kdl @@ -0,0 +1,2 @@ +// 0x2067 +node1 ⁧arg diff --git a/src/test/resources/test_cases/input/unicode_rlm.kdl b/src/test/resources/test_cases/input/unicode_rlm.kdl new file mode 100644 index 0000000..bfa63c8 --- /dev/null +++ b/src/test/resources/test_cases/input/unicode_rlm.kdl @@ -0,0 +1,2 @@ +// 0x200F +node ‏arg diff --git a/src/test/resources/test_cases/input/unicode_rlo.kdl b/src/test/resources/test_cases/input/unicode_rlo.kdl new file mode 100644 index 0000000..98c848b --- /dev/null +++ b/src/test/resources/test_cases/input/unicode_rlo.kdl @@ -0,0 +1,2 @@ +// 0x202E +node ‮arg diff --git a/src/test/resources/test_cases/input/unicode_under_0x20.kdl b/src/test/resources/test_cases/input/unicode_under_0x20.kdl new file mode 100644 index 0000000..967a87a --- /dev/null +++ b/src/test/resources/test_cases/input/unicode_under_0x20.kdl @@ -0,0 +1,2 @@ +// 0x0019 +node1 arg diff --git a/src/test/resources/test_cases/input/unusual_bare_id_chars_in_quoted_id.kdl b/src/test/resources/test_cases/input/unusual_bare_id_chars_in_quoted_id.kdl index e37de20..d3262b8 100644 --- a/src/test/resources/test_cases/input/unusual_bare_id_chars_in_quoted_id.kdl +++ b/src/test/resources/test_cases/input/unusual_bare_id_chars_in_quoted_id.kdl @@ -1 +1 @@ -"foo123~!@#$%^&*.:'|?+" "weeee" \ No newline at end of file +"foo123~!@$%^&*.:'|?+<>," weeee diff --git a/src/test/resources/test_cases/input/unusual_chars_in_bare_id.kdl b/src/test/resources/test_cases/input/unusual_chars_in_bare_id.kdl index d2dcd19..8321632 100644 --- a/src/test/resources/test_cases/input/unusual_chars_in_bare_id.kdl +++ b/src/test/resources/test_cases/input/unusual_chars_in_bare_id.kdl @@ -1 +1 @@ -foo123~!@#$%^&*.:'|?+ "weeee" +foo123~!@$%^&*.:'|?+<>, weeee diff --git a/src/test/resources/test_cases/input/vertical_tab_whitespace.kdl b/src/test/resources/test_cases/input/vertical_tab_whitespace.kdl new file mode 100644 index 0000000..507d3a0 --- /dev/null +++ b/src/test/resources/test_cases/input/vertical_tab_whitespace.kdl @@ -0,0 +1 @@ +node arg