From 499c699cd136c67714ae958e2af74dfe7746ecd2 Mon Sep 17 00:00:00 2001 From: Voomra Date: Fri, 29 Mar 2024 13:44:27 +0300 Subject: [PATCH 01/11] feat: fluent.syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Портирование кода из fluent-kotlin https://github.com/projectfluent/fluent-kotlin --- .editorconfig | 13 + .gitattributes | 3 + .gitignore | 14 + build.gradle | 4 + fluent.syntax/build.gradle | 45 + .../java/ru/di9/fluent/syntax/MathUtils.java | 7 + .../ru/di9/fluent/syntax/StringUtils.java | 38 + .../ru/di9/fluent/syntax/ast/Annotation.java | 32 + .../ru/di9/fluent/syntax/ast/Attribute.java | 28 + .../ru/di9/fluent/syntax/ast/BaseComment.java | 26 + .../ru/di9/fluent/syntax/ast/BaseNode.java | 4 + .../di9/fluent/syntax/ast/CallArgument.java | 4 + .../di9/fluent/syntax/ast/CallArguments.java | 27 + .../ru/di9/fluent/syntax/ast/Comment.java | 7 + .../java/ru/di9/fluent/syntax/ast/Entry.java | 4 + .../ru/di9/fluent/syntax/ast/Expression.java | 4 + .../fluent/syntax/ast/FunctionReference.java | 28 + .../di9/fluent/syntax/ast/GroupComment.java | 7 + .../ru/di9/fluent/syntax/ast/Identifier.java | 28 + .../fluent/syntax/ast/InsidePlaceable.java | 4 + .../java/ru/di9/fluent/syntax/ast/Junk.java | 34 + .../ru/di9/fluent/syntax/ast/Literal.java | 26 + .../ru/di9/fluent/syntax/ast/Message.java | 50 + .../fluent/syntax/ast/MessageReference.java | 35 + .../di9/fluent/syntax/ast/NamedArgument.java | 28 + .../di9/fluent/syntax/ast/NumberLiteral.java | 7 + .../ru/di9/fluent/syntax/ast/Pattern.java | 39 + .../di9/fluent/syntax/ast/PatternElement.java | 4 + .../ru/di9/fluent/syntax/ast/Placeable.java | 26 + .../ru/di9/fluent/syntax/ast/Resource.java | 30 + .../fluent/syntax/ast/ResourceComment.java | 7 + .../fluent/syntax/ast/SelectExpression.java | 29 + .../java/ru/di9/fluent/syntax/ast/Span.java | 28 + .../di9/fluent/syntax/ast/StringLiteral.java | 7 + .../ru/di9/fluent/syntax/ast/SyntaxNode.java | 29 + .../java/ru/di9/fluent/syntax/ast/Term.java | 44 + .../di9/fluent/syntax/ast/TermReference.java | 42 + .../ru/di9/fluent/syntax/ast/TextElement.java | 28 + .../ru/di9/fluent/syntax/ast/TopLevel.java | 4 + .../fluent/syntax/ast/VariableReference.java | 26 + .../ru/di9/fluent/syntax/ast/Variant.java | 30 + .../ru/di9/fluent/syntax/ast/VariantKey.java | 4 + .../ru/di9/fluent/syntax/ast/Whitespace.java | 26 + .../fluent/syntax/parser/FluentParser.java | 869 +++++ .../fluent/syntax/parser/FluentStream.java | 259 ++ .../ru/di9/fluent/syntax/parser/Indent.java | 18 + .../fluent/syntax/parser/ParseException.java | 50 + .../fluent/syntax/parser/ParserStream.java | 80 + .../fluent/syntax/processor/Processor.java | 165 + .../syntax/serializer/FluentSerializer.java | 269 ++ .../syntax/serializer/SerializeException.java | 7 + .../ru/di9/fluent/syntax/visitor/Visitor.java | 113 + .../ru/di9/fluent/syntax/MathUtilsTest.java | 15 + .../ru/di9/fluent/syntax/StringUtilsTest.java | 33 + .../di9/fluent/syntax/ast/BaseNodeTest.java | 24 + .../syntax/parser/AbstractFixturesTest.java | 66 + .../syntax/parser/FluentStreamTest.java | 158 + .../syntax/parser/ParserStreamTest.java | 181 + .../fluent/syntax/parser/ReferenceTest.java | 21 + .../fluent/syntax/parser/StructureTest.java | 11 + .../syntax/processor/ProcessorTest.java | 210 ++ .../syntax/serializer/SerializeEntryTest.java | 24 + .../serializer/SerializeResourceTest.java | 68 + .../fluent/syntax/visitor/VisitorTest.java | 80 + .../ru/di9/fluent/test/utils/AstAssert.java | 49 + .../test/utils/BaseNodeJsonSerializer.java | 43 + .../ru/di9/fluent/test/utils/FileUtils.java | 26 + .../test/utils/NullIgnoreComparator.java | 32 + .../java/ru/di9/fluent/test/utils/Tuple3.java | 4 + .../reference_fixtures/.gitattributes | 2 + .../resources/reference_fixtures/any_char.ftl | 8 + .../reference_fixtures/any_char.json | 148 + .../resources/reference_fixtures/astral.ftl | 20 + .../resources/reference_fixtures/astral.json | 414 +++ .../reference_fixtures/call_expressions.ftl | 120 + .../reference_fixtures/call_expressions.json | 2898 +++++++++++++++++ .../reference_fixtures/callee_expressions.ftl | 46 + .../callee_expressions.json | 706 ++++ .../resources/reference_fixtures/comments.ftl | 20 + .../reference_fixtures/comments.json | 219 ++ .../test/resources/reference_fixtures/cr.ftl | 1 + .../test/resources/reference_fixtures/cr.json | 19 + .../resources/reference_fixtures/crlf.ftl | 14 + .../resources/reference_fixtures/crlf.json | 76 + .../reference_fixtures/eof_comment.ftl | 3 + .../reference_fixtures/eof_comment.json | 28 + .../reference_fixtures/eof_empty.ftl | 0 .../reference_fixtures/eof_empty.json | 9 + .../resources/reference_fixtures/eof_id.ftl | 3 + .../resources/reference_fixtures/eof_id.json | 43 + .../reference_fixtures/eof_id_equals.ftl | 3 + .../reference_fixtures/eof_id_equals.json | 43 + .../resources/reference_fixtures/eof_junk.ftl | 3 + .../reference_fixtures/eof_junk.json | 41 + .../reference_fixtures/eof_value.ftl | 3 + .../reference_fixtures/eof_value.json | 57 + .../reference_fixtures/escaped_characters.ftl | 37 + .../escaped_characters.json | 937 ++++++ .../resources/reference_fixtures/junk.ftl | 21 + .../resources/reference_fixtures/junk.json | 233 ++ .../reference_fixtures/leading_dots.ftl | 76 + .../reference_fixtures/leading_dots.json | 1079 ++++++ .../literal_expressions.ftl | 3 + .../literal_expressions.json | 148 + .../reference_fixtures/member_expressions.ftl | 19 + .../member_expressions.json | 280 ++ .../resources/reference_fixtures/messages.ftl | 49 + .../reference_fixtures/messages.json | 998 ++++++ .../reference_fixtures/mixed_entries.ftl | 24 + .../reference_fixtures/mixed_entries.json | 315 ++ .../reference_fixtures/multiline_values.ftl | 60 + .../reference_fixtures/multiline_values.json | 696 ++++ .../resources/reference_fixtures/numbers.ftl | 38 + .../resources/reference_fixtures/numbers.json | 1197 +++++++ .../resources/reference_fixtures/obsolete.ftl | 29 + .../reference_fixtures/obsolete.json | 177 + .../reference_fixtures/placeables.ftl | 15 + .../reference_fixtures/placeables.json | 300 ++ .../reference_expressions.ftl | 30 + .../reference_expressions.json | 450 +++ .../reference_fixtures/select_expressions.ftl | 64 + .../select_expressions.json | 362 ++ .../reference_fixtures/select_indent.ftl | 96 + .../reference_fixtures/select_indent.json | 1573 +++++++++ .../reference_fixtures/sparse_entries.ftl | 39 + .../reference_fixtures/sparse_entries.json | 348 ++ .../reference_fixtures/special_chars.ftl | 14 + .../reference_fixtures/special_chars.json | 214 ++ .../test/resources/reference_fixtures/tab.ftl | 14 + .../resources/reference_fixtures/tab.json | 190 ++ .../reference_fixtures/term_parameters.ftl | 8 + .../reference_fixtures/term_parameters.json | 452 +++ .../resources/reference_fixtures/terms.ftl | 29 + .../resources/reference_fixtures/terms.json | 446 +++ .../reference_fixtures/variables.ftl | 17 + .../reference_fixtures/variables.json | 334 ++ .../reference_fixtures/variant_keys.ftl | 37 + .../reference_fixtures/variant_keys.json | 513 +++ .../whitespace_in_value.ftl | 10 + .../whitespace_in_value.json | 56 + .../reference_fixtures/zero_length.ftl | 0 .../reference_fixtures/zero_length.json | 9 + .../test/resources/serialized/attribute.ftl | 2 + .../serialized/backslash_in_text_element.ftl | 1 + .../serialized/block_multiline_message.ftl | 3 + .../resources/serialized/call_expression.ftl | 1 + ...call_expression_with_message_reference.ftl | 1 + ...l_expression_with_named_number_literal.ftl | 1 + ...l_expression_with_named_string_literal.ftl | 1 + .../call_expression_with_number_literal.ftl | 1 + ...on_with_positional_and_named_arguments.ftl | 1 + .../call_expression_with_string_literal.ftl | 1 + ...ll_expression_with_two_named_arguments.ftl | 1 + ...pression_with_two_positional_arguments.ftl | 1 + ...all_expression_with_variable_reference.ftl | 1 + ...escaped_special_char_in_string_literal.ftl | 1 + .../resources/serialized/group_comment.ftl | 8 + .../inline_multiline_message.exp.txt | 3 + .../serialized/inline_multiline_message.ftl | 2 + .../message_attribute_reference.ftl | 1 + .../resources/serialized/message_comment.ftl | 3 + .../serialized/message_reference.ftl | 1 + .../serialized/message_with_whitespace.ftl | 9 + .../serialized/message_without_eol.exp.txt | 1 + .../serialized/message_without_eol.ftl | 1 + .../serialized/multiline_attribute.ftl | 4 + .../multiline_starting_inline.exp.txt | 3 + .../serialized/multiline_starting_inline.ftl | 2 + ...ne_starting_inline_with_a_special_char.ftl | 2 + .../multiline_value_and_attributes.ftl | 5 + .../serialized/multiline_variant.ftl | 6 + ...ine_variant_with_first_line_inline.exp.txt | 6 + ...ltiline_variant_with_first_line_inline.ftl | 5 + .../serialized/multiline_with_placeable.ftl | 3 + .../resources/serialized/nested_placeable.ftl | 1 + .../serialized/nested_select_expression.ftl | 7 + .../resources/serialized/number_literal.ftl | 1 + .../resources/serialized/resource_comment.ftl | 4 + .../serialized/select_expression.ftl | 5 + .../select_expression_in_block_pattern.ftl | 5 + ...elect_expression_in_inline_pattern.exp.txt | 5 + .../select_expression_in_inline_pattern.ftl | 4 + ...e_pattern_starting_with_a_special_char.ftl | 4 + ...select_expression_in_multiline_pattern.ftl | 6 + .../serialized/selector_number_literal.ftl | 4 + .../serialized/selector_string_literal.ftl | 4 + .../selector_term_attribute_reference.ftl | 4 + .../selector_variable_reference.ftl | 4 + .../serialized/standalone_comment.ftl | 5 + .../resources/serialized/string_literal.ftl | 1 + .../src/test/resources/serialized/term.ftl | 1 + .../resources/serialized/term_reference.ftl | 1 + .../serialized/term_reference_call.ftl | 1 + .../resources/serialized/two_attribute.ftl | 3 + .../serialized/two_simple_messages.ftl | 2 + .../serialized/unicode_escape_sequence.ftl | 1 + .../serialized/value_and_attributes.ftl | 3 + .../serialized/variable_reference.ftl | 1 + .../serialized/variant_key_number.ftl | 4 + .../structure_fixtures/.gitattributes | 1 + .../attribute_expression_with_wrong_attr.ftl | 2 + .../attribute_expression_with_wrong_attr.json | 58 + .../attribute_of_private_as_placeable.ftl | 1 + .../attribute_of_private_as_placeable.json | 32 + .../attribute_of_public_as_selector.ftl | 5 + .../attribute_of_public_as_selector.json | 32 + .../attribute_starts_from_nl.ftl | 2 + .../attribute_starts_from_nl.json | 85 + .../attribute_with_empty_pattern.ftl | 17 + .../attribute_with_empty_pattern.json | 120 + .../attribute_without_equal_sign.ftl | 2 + .../attribute_without_equal_sign.json | 34 + .../structure_fixtures/blank_lines.ftl | 27 + .../structure_fixtures/blank_lines.json | 271 ++ .../structure_fixtures/broken_number.ftl | 5 + .../structure_fixtures/broken_number.json | 130 + .../call_expression_errors.ftl | 3 + .../call_expression_errors.json | 76 + .../structure_fixtures/comment_with_eof.ftl | 1 + .../structure_fixtures/comment_with_eof.json | 19 + .../resources/structure_fixtures/crlf.ftl | 14 + .../resources/structure_fixtures/crlf.json | 187 ++ .../structure_fixtures/dash_at_eof.ftl | 8 + .../structure_fixtures/dash_at_eof.json | 43 + .../structure_fixtures/elements_indent.ftl | 6 + .../structure_fixtures/elements_indent.json | 196 ++ .../structure_fixtures/empty_resource.ftl | 0 .../structure_fixtures/empty_resource.json | 9 + .../empty_resource_with_ws.ftl | 2 + .../empty_resource_with_ws.json | 9 + .../structure_fixtures/escape_sequences.ftl | 24 + .../structure_fixtures/escape_sequences.json | 673 ++++ .../expressions_call_args.ftl | 2 + .../expressions_call_args.json | 127 + .../resources/structure_fixtures/indent.ftl | 10 + .../resources/structure_fixtures/indent.json | 205 ++ .../resources/structure_fixtures/junk.ftl | 23 + .../resources/structure_fixtures/junk.json | 238 ++ .../structure_fixtures/leading_dots.ftl | 64 + .../structure_fixtures/leading_dots.json | 934 ++++++ .../leading_empty_lines.ftl | 3 + .../leading_empty_lines.json | 48 + .../leading_empty_lines_with_ws.ftl | 5 + .../leading_empty_lines_with_ws.json | 48 + .../message_reference_as_selector.ftl | 4 + .../message_reference_as_selector.json | 32 + .../message_with_empty_multiline_pattern.ftl | 14 + .../message_with_empty_multiline_pattern.json | 67 + .../message_with_empty_pattern.ftl | 18 + .../message_with_empty_pattern.json | 205 ++ .../structure_fixtures/multiline-comment.ftl | 6 + .../structure_fixtures/multiline-comment.json | 57 + .../structure_fixtures/multiline_pattern.ftl | 20 + .../structure_fixtures/multiline_pattern.json | 250 ++ .../structure_fixtures/multiline_string.ftl | 3 + .../structure_fixtures/multiline_string.json | 32 + .../multiline_with_non_empty_first_line.ftl | 2 + .../multiline_with_non_empty_first_line.json | 48 + .../multiline_with_placeables.ftl | 3 + .../multiline_with_placeables.json | 83 + .../non_id_attribute_name.ftl | 2 + .../non_id_attribute_name.json | 34 + .../structure_fixtures/placeable_at_eol.ftl | 9 + .../structure_fixtures/placeable_at_eol.json | 211 ++ .../placeable_at_line_extremes.ftl | 8 + .../placeable_at_line_extremes.json | 193 ++ .../placeable_in_placeable.ftl | 12 + .../placeable_in_placeable.json | 245 ++ .../placeable_without_close_bracket.ftl | 1 + .../placeable_without_close_bracket.json | 34 + .../structure_fixtures/resource_comment.ftl | 2 + .../structure_fixtures/resource_comment.json | 19 + .../resource_comment_trailing_line.ftl | 3 + .../resource_comment_trailing_line.json | 19 + .../second_attribute_starts_from_nl.ftl | 3 + .../second_attribute_starts_from_nl.json | 121 + .../select_expression_with_two_selectors.ftl | 1 + .../select_expression_with_two_selectors.json | 34 + .../select_expression_without_arrow.ftl | 1 + .../select_expression_without_arrow.json | 34 + .../select_expression_without_variants.ftl | 6 + .../select_expression_without_variants.json | 78 + .../structure_fixtures/select_expressions.ftl | 9 + .../select_expressions.json | 72 + .../structure_fixtures/simple_message.ftl | 1 + .../structure_fixtures/simple_message.json | 48 + .../structure_fixtures/single_char_id.ftl | 2 + .../structure_fixtures/single_char_id.json | 85 + .../structure_fixtures/sparse-messages.ftl | 45 + .../structure_fixtures/sparse-messages.json | 457 +++ .../structure_fixtures/standalone_comment.ftl | 3 + .../standalone_comment.json | 57 + .../standalone_identifier.ftl | 2 + .../standalone_identifier.json | 43 + .../resources/structure_fixtures/term.ftl | 24 + .../resources/structure_fixtures/term.json | 742 +++++ .../term_with_empty_pattern.ftl | 6 + .../term_with_empty_pattern.json | 82 + .../resources/structure_fixtures/unclosed.ftl | 12 + .../structure_fixtures/unclosed.json | 154 + .../unclosed_empty_placeable_error.ftl | 3 + .../unclosed_empty_placeable_error.json | 94 + .../unknown_entry_start.ftl | 2 + .../unknown_entry_start.json | 32 + .../variant_ends_abruptly.ftl | 3 + .../variant_ends_abruptly.json | 43 + .../structure_fixtures/variant_keys.ftl | 61 + .../structure_fixtures/variant_keys.json | 500 +++ .../variant_starts_from_nl.ftl | 3 + .../variant_starts_from_nl.json | 111 + .../variant_with_digit_key.ftl | 3 + .../variant_with_digit_key.json | 111 + .../variant_with_empty_pattern.ftl | 9 + .../variant_with_empty_pattern.json | 133 + .../variant_with_leading_space_in_name.ftl | 3 + .../variant_with_leading_space_in_name.json | 111 + .../variant_with_symbol_with_space.ftl | 3 + .../variant_with_symbol_with_space.json | 34 + .../variants_with_two_defaults.ftl | 4 + .../variants_with_two_defaults.json | 32 + .../structure_fixtures/whitespace_leading.ftl | 8 + .../whitespace_leading.json | 212 ++ .../whitespace_trailing.ftl | 8 + .../whitespace_trailing.json | 230 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 249 ++ gradlew.bat | 92 + settings.gradle | 3 + 329 files changed, 30594 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 build.gradle create mode 100644 fluent.syntax/build.gradle create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/MathUtils.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/StringUtils.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Annotation.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Attribute.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/BaseComment.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/BaseNode.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/CallArgument.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/CallArguments.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Comment.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Entry.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Expression.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/FunctionReference.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/GroupComment.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Identifier.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/InsidePlaceable.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Junk.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Literal.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Message.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/MessageReference.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/NamedArgument.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/NumberLiteral.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Pattern.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/PatternElement.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Placeable.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Resource.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/ResourceComment.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/SelectExpression.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Span.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/StringLiteral.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/SyntaxNode.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Term.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/TermReference.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/TextElement.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/TopLevel.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/VariableReference.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Variant.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/VariantKey.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Whitespace.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentParser.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentStream.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/Indent.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/ParseException.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/ParserStream.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/processor/Processor.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/serializer/FluentSerializer.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/serializer/SerializeException.java create mode 100644 fluent.syntax/src/main/java/ru/di9/fluent/syntax/visitor/Visitor.java create mode 100644 fluent.syntax/src/test/java/ru/di9/fluent/syntax/MathUtilsTest.java create mode 100644 fluent.syntax/src/test/java/ru/di9/fluent/syntax/StringUtilsTest.java create mode 100644 fluent.syntax/src/test/java/ru/di9/fluent/syntax/ast/BaseNodeTest.java create mode 100644 fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/AbstractFixturesTest.java create mode 100644 fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/FluentStreamTest.java create mode 100644 fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/ParserStreamTest.java create mode 100644 fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/ReferenceTest.java create mode 100644 fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/StructureTest.java create mode 100644 fluent.syntax/src/test/java/ru/di9/fluent/syntax/processor/ProcessorTest.java create mode 100644 fluent.syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeEntryTest.java create mode 100644 fluent.syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeResourceTest.java create mode 100644 fluent.syntax/src/test/java/ru/di9/fluent/syntax/visitor/VisitorTest.java create mode 100644 fluent.syntax/src/test/java/ru/di9/fluent/test/utils/AstAssert.java create mode 100644 fluent.syntax/src/test/java/ru/di9/fluent/test/utils/BaseNodeJsonSerializer.java create mode 100644 fluent.syntax/src/test/java/ru/di9/fluent/test/utils/FileUtils.java create mode 100644 fluent.syntax/src/test/java/ru/di9/fluent/test/utils/NullIgnoreComparator.java create mode 100644 fluent.syntax/src/test/java/ru/di9/fluent/test/utils/Tuple3.java create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/.gitattributes create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/any_char.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/any_char.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/astral.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/astral.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/call_expressions.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/call_expressions.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/callee_expressions.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/callee_expressions.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/comments.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/comments.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/cr.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/cr.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/crlf.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/crlf.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/eof_comment.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/eof_comment.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/eof_empty.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/eof_empty.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/eof_id.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/eof_id.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/eof_id_equals.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/eof_id_equals.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/eof_junk.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/eof_junk.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/eof_value.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/eof_value.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/escaped_characters.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/escaped_characters.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/junk.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/junk.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/leading_dots.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/leading_dots.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/literal_expressions.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/literal_expressions.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/member_expressions.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/member_expressions.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/messages.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/messages.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/mixed_entries.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/mixed_entries.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/multiline_values.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/multiline_values.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/numbers.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/numbers.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/obsolete.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/obsolete.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/placeables.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/placeables.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/reference_expressions.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/reference_expressions.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/select_expressions.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/select_expressions.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/select_indent.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/select_indent.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/sparse_entries.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/sparse_entries.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/special_chars.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/special_chars.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/tab.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/tab.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/term_parameters.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/term_parameters.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/terms.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/terms.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/variables.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/variables.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/variant_keys.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/variant_keys.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/whitespace_in_value.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/whitespace_in_value.json create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/zero_length.ftl create mode 100644 fluent.syntax/src/test/resources/reference_fixtures/zero_length.json create mode 100644 fluent.syntax/src/test/resources/serialized/attribute.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/backslash_in_text_element.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/block_multiline_message.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/call_expression.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/call_expression_with_message_reference.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/call_expression_with_named_number_literal.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/call_expression_with_named_string_literal.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/call_expression_with_number_literal.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/call_expression_with_positional_and_named_arguments.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/call_expression_with_string_literal.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/call_expression_with_two_named_arguments.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/call_expression_with_two_positional_arguments.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/call_expression_with_variable_reference.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/escaped_special_char_in_string_literal.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/group_comment.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/inline_multiline_message.exp.txt create mode 100644 fluent.syntax/src/test/resources/serialized/inline_multiline_message.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/message_attribute_reference.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/message_comment.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/message_reference.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/message_with_whitespace.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/message_without_eol.exp.txt create mode 100644 fluent.syntax/src/test/resources/serialized/message_without_eol.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/multiline_attribute.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/multiline_starting_inline.exp.txt create mode 100644 fluent.syntax/src/test/resources/serialized/multiline_starting_inline.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/multiline_starting_inline_with_a_special_char.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/multiline_value_and_attributes.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/multiline_variant.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/multiline_variant_with_first_line_inline.exp.txt create mode 100644 fluent.syntax/src/test/resources/serialized/multiline_variant_with_first_line_inline.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/multiline_with_placeable.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/nested_placeable.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/nested_select_expression.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/number_literal.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/resource_comment.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/select_expression.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/select_expression_in_block_pattern.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/select_expression_in_inline_pattern.exp.txt create mode 100644 fluent.syntax/src/test/resources/serialized/select_expression_in_inline_pattern.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/select_expression_in_inline_pattern_starting_with_a_special_char.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/select_expression_in_multiline_pattern.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/selector_number_literal.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/selector_string_literal.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/selector_term_attribute_reference.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/selector_variable_reference.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/standalone_comment.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/string_literal.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/term.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/term_reference.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/term_reference_call.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/two_attribute.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/two_simple_messages.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/unicode_escape_sequence.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/value_and_attributes.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/variable_reference.ftl create mode 100644 fluent.syntax/src/test/resources/serialized/variant_key_number.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/.gitattributes create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/attribute_expression_with_wrong_attr.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/attribute_expression_with_wrong_attr.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/attribute_of_private_as_placeable.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/attribute_of_private_as_placeable.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/attribute_of_public_as_selector.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/attribute_of_public_as_selector.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/attribute_starts_from_nl.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/attribute_starts_from_nl.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/attribute_with_empty_pattern.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/attribute_with_empty_pattern.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/attribute_without_equal_sign.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/attribute_without_equal_sign.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/blank_lines.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/blank_lines.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/broken_number.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/broken_number.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/call_expression_errors.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/call_expression_errors.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/comment_with_eof.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/comment_with_eof.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/crlf.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/crlf.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/dash_at_eof.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/dash_at_eof.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/elements_indent.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/elements_indent.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/empty_resource.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/empty_resource.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/empty_resource_with_ws.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/empty_resource_with_ws.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/escape_sequences.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/escape_sequences.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/expressions_call_args.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/expressions_call_args.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/indent.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/indent.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/junk.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/junk.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/leading_dots.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/leading_dots.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines_with_ws.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines_with_ws.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/message_reference_as_selector.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/message_reference_as_selector.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_multiline_pattern.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_multiline_pattern.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_pattern.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_pattern.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/multiline-comment.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/multiline-comment.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/multiline_pattern.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/multiline_pattern.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/multiline_string.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/multiline_string.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/multiline_with_non_empty_first_line.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/multiline_with_non_empty_first_line.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/multiline_with_placeables.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/multiline_with_placeables.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/non_id_attribute_name.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/non_id_attribute_name.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/placeable_at_eol.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/placeable_at_eol.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/placeable_at_line_extremes.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/placeable_at_line_extremes.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/placeable_in_placeable.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/placeable_in_placeable.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/placeable_without_close_bracket.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/placeable_without_close_bracket.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/resource_comment.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/resource_comment.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/resource_comment_trailing_line.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/resource_comment_trailing_line.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/second_attribute_starts_from_nl.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/second_attribute_starts_from_nl.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/select_expression_with_two_selectors.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/select_expression_with_two_selectors.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_arrow.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_arrow.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_variants.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_variants.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/select_expressions.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/select_expressions.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/simple_message.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/simple_message.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/single_char_id.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/single_char_id.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/sparse-messages.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/sparse-messages.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/standalone_comment.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/standalone_comment.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/standalone_identifier.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/standalone_identifier.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/term.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/term.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/term_with_empty_pattern.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/term_with_empty_pattern.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/unclosed.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/unclosed.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/unclosed_empty_placeable_error.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/unclosed_empty_placeable_error.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/unknown_entry_start.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/unknown_entry_start.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/variant_ends_abruptly.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/variant_ends_abruptly.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/variant_keys.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/variant_keys.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/variant_starts_from_nl.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/variant_starts_from_nl.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/variant_with_digit_key.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/variant_with_digit_key.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/variant_with_empty_pattern.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/variant_with_empty_pattern.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/variant_with_leading_space_in_name.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/variant_with_leading_space_in_name.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/variant_with_symbol_with_space.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/variant_with_symbol_with_space.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/variants_with_two_defaults.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/variants_with_two_defaults.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/whitespace_leading.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/whitespace_leading.json create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/whitespace_trailing.ftl create mode 100644 fluent.syntax/src/test/resources/structure_fixtures/whitespace_trailing.json create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5e5d609 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +max_line_length = 120 +tab_width = 4 + +[*.json] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5fecd3c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +gradlew text eol=lf +gradlew.bat text eol=crlf +gradle/wrapper/gradle-wrapper.properties text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..251bb62 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Gradle +.gradle/ +build/ + +# Java +hs_err_pid*.log + +# JetBrains IDEA +.idea/ +!.idea/codeStyles/codeStyleConfig.xml +!.idea/codeStyles/Project.xml +!.idea/codeInsightSettings.xml +!.idea/.gitignore +*.iml diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..6db8473 --- /dev/null +++ b/build.gradle @@ -0,0 +1,4 @@ +wrapper { + gradleVersion = "8.6" + distributionType = Wrapper.DistributionType.BIN +} diff --git a/fluent.syntax/build.gradle b/fluent.syntax/build.gradle new file mode 100644 index 0000000..3329a63 --- /dev/null +++ b/fluent.syntax/build.gradle @@ -0,0 +1,45 @@ +plugins { + id "java" +} + +compileJava { + targetCompatibility = sourceCompatibility = JavaVersion.VERSION_17 + options.encoding = "UTF-8" +} + +group = "ru.di9.fluent" +version = "1.0-SNAPSHOT" + +repositories { + mavenLocal() + mavenCentral() +} + +ext { + assertjVersion = "3.24.2" + gsonVersion = "2.9.1" + joorVersion = "0.9.15" + jsonAssertVersion = "1.5.1" + junitVersion = "5.9.2" + lombokVersion = "1.18.30" +} + +dependencies { + annotationProcessor("org.projectlombok:lombok:$lombokVersion") + compileOnly("org.projectlombok:lombok:$lombokVersion") + + testImplementation(platform("org.junit:junit-bom:$junitVersion")) + testImplementation("org.junit.jupiter:junit-jupiter") + + testAnnotationProcessor("org.projectlombok:lombok:$lombokVersion") + testCompileOnly("org.projectlombok:lombok:$lombokVersion") + testImplementation("org.assertj:assertj-core:$assertjVersion") + testImplementation("com.google.code.gson:gson:$gsonVersion") + testImplementation("org.jooq:joor:$joorVersion") + testImplementation("org.skyscreamer:jsonassert:$jsonAssertVersion") +} + +test { + useJUnitPlatform() +} + diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/MathUtils.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/MathUtils.java new file mode 100644 index 0000000..3ae1de1 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/MathUtils.java @@ -0,0 +1,7 @@ +package ru.di9.fluent.syntax; + +public interface MathUtils { + static int clamp(int value, int min, int max) { + return Math.max(min, Math.min(value, max)); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/StringUtils.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/StringUtils.java new file mode 100644 index 0000000..ced45ca --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/StringUtils.java @@ -0,0 +1,38 @@ +package ru.di9.fluent.syntax; + +import java.util.Optional; + +public interface StringUtils { + + static Optional getCharAt(String string, int index) { + if (string == null || string.isEmpty()) { + return Optional.empty(); + } + + if (index >= 0 && index <= string.length() - 1) { + return Optional.of(string.charAt(index)); + } else { + return Optional.empty(); + } + } + + static boolean inRange_az(int cc) { + return (97 <= cc && cc <= 122); // a-z + } + + static boolean inRange_AZ(int cc) { + return (65 <= cc && cc <= 90); // A-Z + } + + static boolean inRange_af(int cc) { + return (97 <= cc && cc <= 102); // a-f + } + + static boolean inRange_AF(int cc) { + return (65 <= cc && cc <= 70); // A-F + } + + static boolean inRange_09(int cc) { + return (48 <= cc && cc <= 57); // 0-9 + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Annotation.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Annotation.java new file mode 100644 index 0000000..af4b370 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Annotation.java @@ -0,0 +1,32 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@RequiredArgsConstructor +@Getter +public class Annotation extends SyntaxNode { + private final List arguments = new ArrayList<>(); + private final String code; + private final String message; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Annotation that = (Annotation) o; + return Objects.equals(code, that.code) && + Objects.equals(message, that.message) && + Objects.equals(arguments, that.arguments); + } + + @Override + public int hashCode() { + return Objects.hash(code, message, arguments); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Attribute.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Attribute.java new file mode 100644 index 0000000..e4ecb28 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Attribute.java @@ -0,0 +1,28 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Objects; + +@RequiredArgsConstructor +@Getter +public class Attribute extends SyntaxNode { + private final Identifier id; + private final Pattern value; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Attribute attribute = (Attribute) o; + return Objects.equals(id, attribute.id) && + Objects.equals(value, attribute.value); + } + + @Override + public int hashCode() { + return Objects.hash(id, value); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/BaseComment.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/BaseComment.java new file mode 100644 index 0000000..a1db089 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/BaseComment.java @@ -0,0 +1,26 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Objects; + +@RequiredArgsConstructor +@Getter +public abstract class BaseComment extends Entry { + private final String content; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + BaseComment that = (BaseComment) o; + return Objects.equals(content, that.content); + } + + @Override + public int hashCode() { + return Objects.hash(content); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/BaseNode.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/BaseNode.java new file mode 100644 index 0000000..0c93053 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/BaseNode.java @@ -0,0 +1,4 @@ +package ru.di9.fluent.syntax.ast; + +public abstract class BaseNode { +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/CallArgument.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/CallArgument.java new file mode 100644 index 0000000..099f7dd --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/CallArgument.java @@ -0,0 +1,4 @@ +package ru.di9.fluent.syntax.ast; + +public interface CallArgument { +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/CallArguments.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/CallArguments.java new file mode 100644 index 0000000..939f311 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/CallArguments.java @@ -0,0 +1,27 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@Getter +public class CallArguments extends SyntaxNode { + private final List positional = new ArrayList<>(); + private final List named = new ArrayList<>(); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + CallArguments that = (CallArguments) o; + return Objects.equals(positional, that.positional) && Objects.equals(named, that.named); + } + + @Override + public int hashCode() { + return Objects.hash(positional, named); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Comment.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Comment.java new file mode 100644 index 0000000..f2fa20f --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Comment.java @@ -0,0 +1,7 @@ +package ru.di9.fluent.syntax.ast; + +public class Comment extends BaseComment { + public Comment(String content) { + super(content); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Entry.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Entry.java new file mode 100644 index 0000000..229fd31 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Entry.java @@ -0,0 +1,4 @@ +package ru.di9.fluent.syntax.ast; + +public class Entry extends TopLevel { +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Expression.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Expression.java new file mode 100644 index 0000000..7e0b168 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Expression.java @@ -0,0 +1,4 @@ +package ru.di9.fluent.syntax.ast; + +public abstract class Expression extends SyntaxNode implements CallArgument, InsidePlaceable { +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/FunctionReference.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/FunctionReference.java new file mode 100644 index 0000000..7106c8f --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/FunctionReference.java @@ -0,0 +1,28 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Objects; + +@RequiredArgsConstructor +@Getter +public class FunctionReference extends Expression { + private final Identifier id; + private final CallArguments arguments; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + FunctionReference that = (FunctionReference) o; + return Objects.equals(id, that.id) && + Objects.equals(arguments, that.arguments); + } + + @Override + public int hashCode() { + return Objects.hash(id, arguments); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/GroupComment.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/GroupComment.java new file mode 100644 index 0000000..9221608 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/GroupComment.java @@ -0,0 +1,7 @@ +package ru.di9.fluent.syntax.ast; + +public class GroupComment extends BaseComment { + public GroupComment(String content) { + super(content); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Identifier.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Identifier.java new file mode 100644 index 0000000..52c0d2b --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Identifier.java @@ -0,0 +1,28 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +import java.util.Objects; + +@RequiredArgsConstructor +@Getter +@ToString(of = {"name"}) +public class Identifier extends SyntaxNode implements VariantKey { + private final String name; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Identifier that = (Identifier) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/InsidePlaceable.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/InsidePlaceable.java new file mode 100644 index 0000000..9e62672 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/InsidePlaceable.java @@ -0,0 +1,4 @@ +package ru.di9.fluent.syntax.ast; + +public interface InsidePlaceable { +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Junk.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Junk.java new file mode 100644 index 0000000..ede3526 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Junk.java @@ -0,0 +1,34 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@RequiredArgsConstructor +@Getter +public class Junk extends TopLevel { + private final List annotations = new ArrayList<>(); + private final String content; + + public void addAnnotation(Annotation annotation) { + this.getAnnotations().add(annotation); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Junk junk = (Junk) o; + return Objects.equals(content, junk.content) && + Objects.equals(annotations, junk.annotations); + } + + @Override + public int hashCode() { + return Objects.hash(content, annotations); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Literal.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Literal.java new file mode 100644 index 0000000..f4db023 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Literal.java @@ -0,0 +1,26 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Objects; + +@RequiredArgsConstructor +@Getter +public abstract class Literal extends Expression { + private final String value; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Literal literal = (Literal) o; + return Objects.equals(value, literal.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Message.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Message.java new file mode 100644 index 0000000..5e313e3 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Message.java @@ -0,0 +1,50 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +@RequiredArgsConstructor +public class Message extends Entry { + + @Getter + private final List attributes = new ArrayList<>(); + + @Getter + private final Identifier id; + + private final Pattern value; + + @Setter + private Comment comment; + + public Optional getComment() { + return Optional.ofNullable(comment); + } + + public Optional getValue() { + return Optional.ofNullable(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Message message = (Message) o; + return Objects.equals(id, message.id) && + Objects.equals(value, message.value) && + Objects.equals(attributes, message.attributes) && + Objects.equals(comment, message.comment); + } + + @Override + public int hashCode() { + return Objects.hash(id, value, attributes, comment); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/MessageReference.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/MessageReference.java new file mode 100644 index 0000000..7cae88e --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/MessageReference.java @@ -0,0 +1,35 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Objects; +import java.util.Optional; + +@RequiredArgsConstructor +public class MessageReference extends Expression { + + @Getter + private final Identifier id; + + private final Identifier attribute; + + public Optional getAttribute() { + return Optional.ofNullable(attribute); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + MessageReference that = (MessageReference) o; + return Objects.equals(id, that.id) && + Objects.equals(attribute, that.attribute); + } + + @Override + public int hashCode() { + return Objects.hash(id, attribute); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/NamedArgument.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/NamedArgument.java new file mode 100644 index 0000000..84ce6bf --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/NamedArgument.java @@ -0,0 +1,28 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Objects; + +@RequiredArgsConstructor +@Getter +public class NamedArgument extends SyntaxNode implements CallArgument { + private final Identifier name; + private final Literal value; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + NamedArgument that = (NamedArgument) o; + return Objects.equals(name, that.name) && + Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(name, value); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/NumberLiteral.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/NumberLiteral.java new file mode 100644 index 0000000..db23c97 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/NumberLiteral.java @@ -0,0 +1,7 @@ +package ru.di9.fluent.syntax.ast; + +public class NumberLiteral extends Literal implements VariantKey { + public NumberLiteral(String value) { + super(value); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Pattern.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Pattern.java new file mode 100644 index 0000000..074efef --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Pattern.java @@ -0,0 +1,39 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +@Getter +public class Pattern extends SyntaxNode { + private final List elements; + + public Pattern() { + this.elements = new ArrayList<>(); + } + + public Pattern(PatternElement... elements) { + this.elements = new ArrayList<>(Arrays.asList(elements)); + } + + public Pattern(List elements) { + this.elements = elements; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + var that = (Pattern) o; + return Objects.equals(this.elements, that.elements); + } + + @Override + public int hashCode() { + return Objects.hash(elements); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/PatternElement.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/PatternElement.java new file mode 100644 index 0000000..afd4717 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/PatternElement.java @@ -0,0 +1,4 @@ +package ru.di9.fluent.syntax.ast; + +public abstract class PatternElement extends SyntaxNode { +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Placeable.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Placeable.java new file mode 100644 index 0000000..56555a3 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Placeable.java @@ -0,0 +1,26 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Objects; + +@RequiredArgsConstructor +@Getter +public class Placeable extends PatternElement implements InsidePlaceable { + private final InsidePlaceable expression; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Placeable placeable = (Placeable) o; + return Objects.equals(expression, placeable.expression); + } + + @Override + public int hashCode() { + return Objects.hash(expression); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Resource.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Resource.java new file mode 100644 index 0000000..33acd2a --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Resource.java @@ -0,0 +1,30 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@Getter +public class Resource extends SyntaxNode { + private final List body = new ArrayList<>(); + + public Resource(List children) { + this.body.addAll(children); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Resource resource = (Resource) o; + return Objects.equals(body, resource.body); + } + + @Override + public int hashCode() { + return Objects.hash(body); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/ResourceComment.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/ResourceComment.java new file mode 100644 index 0000000..0463c46 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/ResourceComment.java @@ -0,0 +1,7 @@ +package ru.di9.fluent.syntax.ast; + +public class ResourceComment extends BaseComment { + public ResourceComment(String content) { + super(content); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/SelectExpression.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/SelectExpression.java new file mode 100644 index 0000000..1f44539 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/SelectExpression.java @@ -0,0 +1,29 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.List; +import java.util.Objects; + +@RequiredArgsConstructor +@Getter +public class SelectExpression extends Expression { + private final Expression selector; + private final List variants; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + SelectExpression that = (SelectExpression) o; + return Objects.equals(selector, that.selector) && + Objects.equals(variants, that.variants); + } + + @Override + public int hashCode() { + return Objects.hash(selector, variants); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Span.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Span.java new file mode 100644 index 0000000..98d05a7 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Span.java @@ -0,0 +1,28 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.util.Objects; + +@AllArgsConstructor +@Getter +@Setter +public class Span extends BaseNode { + private int start; + private int end; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Span span = (Span) o; + return start == span.start && end == span.end; + } + + @Override + public int hashCode() { + return Objects.hash(start, end); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/StringLiteral.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/StringLiteral.java new file mode 100644 index 0000000..16f0826 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/StringLiteral.java @@ -0,0 +1,7 @@ +package ru.di9.fluent.syntax.ast; + +public class StringLiteral extends Literal { + public StringLiteral(String value) { + super(value); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/SyntaxNode.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/SyntaxNode.java new file mode 100644 index 0000000..2384584 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/SyntaxNode.java @@ -0,0 +1,29 @@ +package ru.di9.fluent.syntax.ast; + +import java.util.Objects; +import java.util.Optional; + +public abstract class SyntaxNode extends BaseNode { + private Span span; + + public Optional getSpan() { + return Optional.ofNullable(span); + } + + public void addSpan(int start, int end) { + span = new Span(start, end); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SyntaxNode that = (SyntaxNode) o; + return Objects.equals(span, that.span); + } + + @Override + public int hashCode() { + return Objects.hash(span); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Term.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Term.java new file mode 100644 index 0000000..fae0fb2 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Term.java @@ -0,0 +1,44 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +@RequiredArgsConstructor +public class Term extends Entry { + + @Getter + private final List attributes = new ArrayList<>(); + + @Getter + private final Identifier id; + + @Getter + private final Pattern value; + + @Setter + private Comment comment; + + public Optional getComment() { + return Optional.ofNullable(comment); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Term term = (Term) o; + return Objects.equals(id, term.id) && Objects.equals(value, term.value) && Objects.equals(attributes, term.attributes) && Objects.equals(comment, term.comment); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), id, value, attributes, comment); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/TermReference.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/TermReference.java new file mode 100644 index 0000000..6076c17 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/TermReference.java @@ -0,0 +1,42 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Objects; +import java.util.Optional; + +@RequiredArgsConstructor +public class TermReference extends Expression { + + @Getter + private final Identifier id; + + private final Identifier attribute; + + private final CallArguments arguments; + + public Optional getAttribute() { + return Optional.ofNullable(attribute); + } + + public Optional getArguments() { + return Optional.ofNullable(arguments); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + TermReference that = (TermReference) o; + return Objects.equals(id, that.id) && + Objects.equals(attribute, that.attribute) && + Objects.equals(arguments, that.arguments); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), id, attribute, arguments); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/TextElement.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/TextElement.java new file mode 100644 index 0000000..038ae59 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/TextElement.java @@ -0,0 +1,28 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.util.Objects; + +@AllArgsConstructor +@Getter +@Setter +public class TextElement extends PatternElement { + private String value; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + TextElement that = (TextElement) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), value); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/TopLevel.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/TopLevel.java new file mode 100644 index 0000000..9c71da4 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/TopLevel.java @@ -0,0 +1,4 @@ +package ru.di9.fluent.syntax.ast; + +public abstract class TopLevel extends SyntaxNode { +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/VariableReference.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/VariableReference.java new file mode 100644 index 0000000..18d4d3e --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/VariableReference.java @@ -0,0 +1,26 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Objects; + +@RequiredArgsConstructor +@Getter +public class VariableReference extends Expression { + private final Identifier id; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + VariableReference that = (VariableReference) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), id); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Variant.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Variant.java new file mode 100644 index 0000000..d8d8d88 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Variant.java @@ -0,0 +1,30 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Objects; + +@RequiredArgsConstructor +@Getter +public class Variant extends SyntaxNode { + private final VariantKey key; + private final Pattern value; + private final boolean isDefault; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Variant variant = (Variant) o; + return Objects.equals(key, variant.key) && + Objects.equals(value, variant.value) && + Objects.equals(isDefault, variant.isDefault); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), key, value, isDefault); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/VariantKey.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/VariantKey.java new file mode 100644 index 0000000..cc51ee9 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/VariantKey.java @@ -0,0 +1,4 @@ +package ru.di9.fluent.syntax.ast; + +public interface VariantKey { +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Whitespace.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Whitespace.java new file mode 100644 index 0000000..e963957 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Whitespace.java @@ -0,0 +1,26 @@ +package ru.di9.fluent.syntax.ast; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Objects; + +@RequiredArgsConstructor +@Getter +public class Whitespace extends TopLevel { + private final String content; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Whitespace that = (Whitespace) o; + return Objects.equals(content, that.content); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), content); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentParser.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentParser.java new file mode 100644 index 0000000..e74d9f1 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentParser.java @@ -0,0 +1,869 @@ +package ru.di9.fluent.syntax.parser; + +import ru.di9.fluent.syntax.ast.*; + +import java.util.*; +import java.util.function.Predicate; + +import static ru.di9.fluent.syntax.StringUtils.inRange_09; +import static ru.di9.fluent.syntax.parser.FluentStream.*; +import static ru.di9.fluent.syntax.parser.ParseException.ErrorCode.*; + +public class FluentParser { + public boolean withSpans = false; + public boolean withJunkAnnotations = true; + + public Resource parse(String source) { + var ps = new FluentStream(source); + int spanStart = ps.index; + List entries = new ArrayList<>(); + Comment lastComment = null; + var blankLines = ps.skipBlankBlock(); + if (!blankLines.isEmpty()) { + entries.add(new Whitespace(blankLines)); + } + + while (ps.currentChar().isPresent()) { + var entry = getEntryOrJunk(ps); + blankLines = ps.skipBlankBlock(); + + // Regular Comments require special logic. Comments may be attached to + // Messages or Terms if they are followed immediately by them. However + // they should parse as standalone when they're followed by Junk. + // Consequently, we only attach Comments once we know that the Message + // or the Term parsed successfully. + if (entry instanceof Comment comment && blankLines.isEmpty() && ps.currentChar().isPresent()) { + // Stash the comment and decide what to do with it in the next pass. + lastComment = comment; + continue; + } + + if (lastComment != null) { + if (entry instanceof Message message) { + message.setComment(lastComment); + if (withSpans && message.getSpan().isPresent() && lastComment.getSpan().isPresent()) { + message.getSpan().get().setStart(lastComment.getSpan().get().getStart()); + } + } else if (entry instanceof Term term) { + term.setComment(lastComment); + if (withSpans && term.getSpan().isPresent() && lastComment.getSpan().isPresent()) { + term.getSpan().get().setStart(lastComment.getSpan().get().getStart()); + } + } else { + entries.add(lastComment); + } + + // In either case, the stashed comment has been dealt with; clear it. + lastComment = null; + } + + // No special logic for other types of entries. + entries.add(entry); + if (!blankLines.isEmpty()) { + entries.add(new Whitespace(blankLines)); + } + } + + var resource = new Resource(entries); + if (withSpans) { + resource.addSpan(spanStart, ps.index); + } + + return resource; + } + + /// PRIVATE //////////////////////////////////////////////////////////////////////////////////////////////////////// + + private TopLevel getEntryOrJunk(FluentStream ps) { + int entryStartPos = ps.index; + + try { + var entry = getEntry(ps); + ps.expectLineEnd(); + return entry; + } catch (ParseException e) { + int errorIndex = ps.index; + ps.skipToNextEntryStart(entryStartPos); + int nextEntryStart = ps.index; + if (nextEntryStart < errorIndex) { + // The position of the error must be inside of the Junk's span. + errorIndex = nextEntryStart; + } + + // Create a Junk instance + var slice = ps.string.substring(entryStartPos, nextEntryStart); + var junk = new Junk(slice); + if (withSpans) { + junk.addSpan(entryStartPos, nextEntryStart); + } + if (withJunkAnnotations) { + var annot = new Annotation(e.getCode().name(), e.getMessage()); + annot.getArguments().addAll(Arrays.asList(e.getArgs())); + annot.addSpan(errorIndex, errorIndex); + junk.addAnnotation(annot); + } + return junk; + } + } + + private Entry getEntry(FluentStream ps) { + var currentChar = ps.currentChar(); + + if (currentChar.filter(v -> v == '#').isPresent()) { + return getComment(ps); + } + + if (currentChar.filter(v -> v == '-').isPresent()) { + return getTerm(ps); + } + + if (ps.isIdentifierStart()) { + return getMessage(ps); + } + + throw new ParseException(E0002); + } + + private Message getMessage(FluentStream ps) { + int spanStart = ps.index; + var id = getIdentifier(ps); + + ps.skipBlankInline(); + ps.expectChar('='); + + var value = maybeGetPattern(ps); + var attrs = getAttributes(ps); + + if (value == null && attrs.isEmpty()) { + throw new ParseException(E0005, id.getName()); + } + + var msg = new Message(id, value); + msg.getAttributes().addAll(attrs); + if (withSpans) { + msg.addSpan(spanStart, ps.index); + } + return msg; + } + + private Term getTerm(FluentStream ps) { + int spanStart = ps.index; + + ps.expectChar('-'); + var id = getIdentifier(ps); + + ps.skipBlankInline(); + ps.expectChar('='); + + var value = maybeGetPattern(ps); + if (value == null) { + throw new ParseException(E0006, id.getName()); + } + + var attrs = getAttributes(ps); + var term = new Term(id, value); + term.getAttributes().addAll(attrs); + + if (withSpans) { + term.addSpan(spanStart, ps.index); + } + + return term; + } + + private Collection getAttributes(FluentStream ps) { + List attrs = new ArrayList<>(); + ps.peekBlank(); + while (ps.isAttributeStart()) { + ps.skipToPeek(); + var attr = getAttribute(ps); + attrs.add(attr); + ps.peekBlank(); + } + return attrs; + } + + private Attribute getAttribute(FluentStream ps) { + int spanStart = ps.index; + ps.expectChar('.'); + + var key = getIdentifier(ps); + + ps.skipBlankInline(); + ps.expectChar('='); + + var value = maybeGetPattern(ps); + if (value == null) { + throw new ParseException(E0012); + } + + var attribute = new Attribute(key, value); + if (withSpans) { + attribute.addSpan(spanStart, ps.index); + } + + return attribute; + } + + // maybeGetPattern distinguishes between patterns which start on the same line + // as the identifier (a.k.a. inline signleline patterns and inline multiline + // patterns) and patterns which start on a new line (a.k.a. block multiline + // patterns). The distinction is important for the dedentation logic: the + // indent of the first line of a block pattern must be taken into account when + // calculating the maximum common indent. + private Pattern maybeGetPattern(FluentStream ps) { + ps.peekBlankInline(); + if (ps.isValueStart()) { + ps.skipToPeek(); + return getPattern(ps, false); + } + + ps.peekBlankBlock(); + if (ps.isValueContinuation()) { + ps.skipToPeek(); + return getPattern(ps, true); + } + + return null; + } + + private Pattern getPattern(FluentStream ps, boolean isBlock) { + int spanStart = ps.index; + List elements = new ArrayList<>(); + int commonIndentLength = Integer.MAX_VALUE; + if (isBlock) { + // A block pattern is a pattern which starts on a new line. Store and + // measure the indent of this first line for the dedentation logic. + var blankStart = ps.index; + var firstIndent = ps.skipBlankInline(); + elements.add(getIndent(ps, firstIndent, blankStart)); + commonIndentLength = firstIndent.length(); + } + + Optional opt; + elements: + while ((opt = ps.currentChar()).isPresent()) { + var ch = opt.get(); + switch (ch) { + case EOL -> { + var blankStart = ps.index; + var blankLines = ps.peekBlankBlock(); + if (ps.isValueContinuation()) { + ps.skipToPeek(); + var indent = ps.skipBlankInline(); + commonIndentLength = Math.min(commonIndentLength, indent.length()); + elements.add(getIndent(ps, blankLines + indent, blankStart)); + continue; + } + + // The end condition for getPattern's while loop is a newline + // which is not followed by a valid pattern continuation. + ps.resetPeek(); + break elements; + } + case '{' -> elements.add(getPlaceable(ps)); + case '}' -> throw new ParseException(E0027); + default -> elements.add(getTextElement(ps)); + } + } + + var dedented = dedent(elements, commonIndentLength); + var pattern = new Pattern(dedented); + if (withSpans) { + pattern.addSpan(spanStart, ps.index); + } + + return pattern; + } + + // Dedent a list of elements by removing the maximum common indent from the + // beginning of text lines. The common indent is calculated in getPattern. + private List dedent(Collection elements, int commonIndent) { + ArrayList trimmed = new ArrayList<>(); + + for (var element : elements) { + if (element instanceof Placeable pl) { + trimmed.add(pl); + continue; + } + + if (element instanceof Indent ind) { + // Strip common indent. + ind.setValue(ind.getValue().substring(0, (ind.getValue().length() - commonIndent))); + if (ind.getValue().isEmpty()) { + continue; + } + } + + if (!trimmed.isEmpty()) { + var prev = trimmed.get(trimmed.size() - 1); + if (prev instanceof TextElement prevTE) { + // Join adjacent TextElements by replacing them with their sum. + String newVal; + if (element instanceof TextElement elmTE) { + newVal = elmTE.getValue(); + } else if (element instanceof Indent elmInd) { + newVal = elmInd.getValue(); + } else { + throw new IllegalStateException("Unexpected PatternElement type"); + } + + var sum = new TextElement(prevTE.getValue() + newVal); + if (withSpans && prevTE.getSpan().isPresent() && element.getSpan().isPresent()) { + sum.addSpan(prevTE.getSpan().get().getStart(), element.getSpan().get().getEnd()); + } + + trimmed.set(trimmed.size() - 1, sum); + continue; + } + } + + if (element instanceof Indent elmInd) { + // If the indent hasn't been merged into a preceding TextElement, + // convert it into a new TextElement. + var textElement = new TextElement(elmInd.getValue()); + if (withSpans && elmInd.getSpan().isPresent()) { + textElement.addSpan(elmInd.getSpan().get().getStart(), elmInd.getSpan().get().getEnd()); + } + + trimmed.add(textElement); + continue; + } + + // The element is a TextElement or a Placeable + trimmed.add(element); + } + + // Trim trailing whitespace from the Pattern. + var lastElement = trimmed.get(trimmed.size() - 1); + if (lastElement instanceof TextElement lastElmTE) { + lastElmTE.setValue(TRAILING_WS_RE.matcher(lastElmTE.getValue()).replaceAll("")); + if (lastElmTE.getValue().isEmpty()) { + trimmed.remove(trimmed.size() - 1); + } + } + + return trimmed; + } + + private TextElement getTextElement(FluentStream ps) { + var buffer = new StringBuilder(); + int spanStart = ps.index; + + Optional opt; + while ((opt = ps.currentChar()).isPresent()) { + char ch = opt.get(); + + if (ch == '{' || ch == '}' || ch == EOL) { + var textElement = new TextElement(buffer.toString()); + if (withSpans) { + textElement.addSpan(spanStart, ps.index); + } + return textElement; + } + + buffer.append(ch); + ps.next(); + } + + var textElement = new TextElement(buffer.toString()); + if (withSpans) { + textElement.addSpan(spanStart, ps.index); + } + + return textElement; + } + + private PatternElement getPlaceable(FluentStream ps) { + var spanStart = ps.index; + ps.expectChar('{'); + ps.skipBlank(); + + SyntaxNode expression; + if (ps.currentChar().filter(v -> v == '{').isPresent()) { + var child = getPlaceable(ps); + ps.skipBlank(); + expression = child; + } else { + expression = getExpression(ps); + } + + ps.expectChar('}'); + + var placeable = new Placeable((InsidePlaceable) expression); + if (withSpans) { + placeable.addSpan(spanStart, ps.index); + } + + return placeable; + } + + private Expression getExpression(FluentStream ps) { + int spanStart = ps.index; + + var selector = getInlineExpression(ps); + ps.skipBlank(); + + if (ps.currentChar().filter(v -> v == '-').isPresent()) { + if (ps.peek().filter(v -> v != '>').isPresent()) { + ps.resetPeek(); + return selector; + } + + // Validate selector expression according to + // abstract.js in the Fluent specification + + if (selector instanceof MessageReference mr) { + throw new ParseException(mr.getAttribute().isEmpty() ? E0016 : E0018); + } else if (selector instanceof TermReference tr && tr.getAttribute().isEmpty()) { + throw new ParseException(E0017); + } + + ps.next(); + ps.next(); + + ps.skipBlankInline(); + ps.expectLineEnd(); + + var variants = getVariants(ps); + + var selectExpression = new SelectExpression(selector, variants); + if (withSpans) { + selectExpression.addSpan(spanStart, ps.index); + } + + return selectExpression; + } + + if (selector instanceof TermReference tr && tr.getAttribute().isPresent()) { + throw new ParseException(E0019); + } + + return selector; + } + + private List getVariants(FluentStream ps) { + List variants = new ArrayList<>(); + var hasDefault = false; + + ps.skipBlank(); + while (ps.isVariantStart()) { + var variant = getVariant(ps, hasDefault); + + if (variant.isDefault()) { + hasDefault = true; + } + + variants.add(variant); + ps.expectLineEnd(); + ps.skipBlank(); + } + + if (variants.isEmpty()) { + throw new ParseException(E0011); + } + + if (!hasDefault) { + throw new ParseException(E0010); + } + + return variants; + } + + private Variant getVariant(FluentStream ps, boolean hasDefault) { + int spanStart = ps.index; + var defaultIndex = false; + + if (ps.currentChar().filter(v -> v == '*').isPresent()) { + if (hasDefault) { + throw new ParseException(E0015); + } + ps.next(); + defaultIndex = true; + } + + ps.expectChar('['); + + ps.skipBlank(); + + var key = getVariantKey(ps); + + ps.skipBlank(); + ps.expectChar(']'); + + //val value = this.maybeGetPattern(ps) ?: throw ParseError("E0012") + var value = maybeGetPattern(ps); + if (value == null) { + throw new ParseException(E0012); + } + + var variant = new Variant(key, value, defaultIndex); + if (withSpans) { + variant.addSpan(spanStart, ps.index); + } + + return variant; + } + + private VariantKey getVariantKey(FluentStream ps) { + int cc = ps.currentChar().orElseThrow(() -> new ParseException(E0013)); + if (inRange_09(cc) || cc == 45 /*-*/) { + return getNumber(ps); + } + + return getIdentifier(ps); + } + + private Expression getInlineExpression(FluentStream ps) { + var spanStart = ps.index; + + if (ps.isNumberStart()) { + return getNumber(ps); + } + + if (ps.currentChar().filter(v -> v == '"').isPresent()) { + return getString(ps); + } + + if (ps.currentChar().filter(v -> v == '$').isPresent()) { + ps.next(); + var id = getIdentifier(ps); + + var variableReference = new VariableReference(id); + if (withSpans) { + variableReference.addSpan(spanStart, ps.index); + } + + return variableReference; + } + + if (ps.currentChar().filter(v -> v == '-').isPresent()) { + ps.next(); + var id = getIdentifier(ps); + + Identifier attr = null; + if (ps.currentChar().filter(v -> v == '.').isPresent()) { + ps.next(); + attr = getIdentifier(ps); + } + + CallArguments args = null; + ps.peekBlank(); + if (ps.currentPeek().filter(v -> v == '(').isPresent()) { + ps.skipToPeek(); + args = getCallArguments(ps); + } + + var termReference = new TermReference(id, attr, args); + if (withSpans) { + termReference.addSpan(spanStart, ps.index); + } + + return termReference; + } + + if (ps.isIdentifierStart()) { + var id = getIdentifier(ps); + ps.peekBlank(); + + if (ps.currentPeek().filter(v -> v == '(').isPresent()) { + // It's a Function. Ensure it's all upper-case. + if (!VALID_FUNCTION_NAME.matcher(id.getName()).matches()) { + throw new ParseException(E0008); + } + + ps.skipToPeek(); + var args = getCallArguments(ps); + var functionReference = new FunctionReference(id, args); + if (withSpans) { + functionReference.addSpan(spanStart, ps.index); + } + return functionReference; + } + + Identifier attr = null; + if (ps.currentChar().filter(v -> v == '.').isPresent()) { + ps.next(); + attr = this.getIdentifier(ps); + } + + var messageReference = new MessageReference(id, attr); + if (withSpans) { + messageReference.addSpan(spanStart, ps.index); + } + + return messageReference; + } + + throw new ParseException(E0028); + } + + private CallArguments getCallArguments(FluentStream ps) { + var spanStart = ps.index; + List positional = new ArrayList<>(); + List named = new ArrayList<>(); + Set argumentNames = new HashSet<>(); + + ps.expectChar('('); + ps.skipBlank(); + + while (true) { + if (ps.currentChar().filter(v -> v == ')').isPresent()) { + break; + } + + var arg = getCallArgument(ps); + if (arg instanceof NamedArgument na) { + if (argumentNames.contains(na.getName().getName())) { + throw new ParseException(E0022); + } + named.add(na); + argumentNames.add(na.getName().getName()); + } else if (arg instanceof Expression exp) { + if (!argumentNames.isEmpty()) { + throw new ParseException(E0021); + } + positional.add(exp); + } + + ps.skipBlank(); + + if (ps.currentChar().filter(v -> v == ',').isPresent()) { + ps.next(); + ps.skipBlank(); + continue; + } + + break; + } + + ps.expectChar(')'); + var args = new CallArguments(); + args.getPositional().addAll(positional); + args.getNamed().addAll(named); + if (withSpans) { + args.addSpan(spanStart, ps.index); + } + return args; + } + + private CallArgument getCallArgument(FluentStream ps) { + var spanStart = ps.index; + var exp = getInlineExpression(ps); + + ps.skipBlank(); + + if (ps.currentChar().filter(v -> v != ':').isPresent()) { + return exp; + } + + if (exp instanceof MessageReference mr && mr.getAttribute().isEmpty()) { + ps.next(); + ps.skipBlank(); + + var value = getLiteral(ps); + + var namedArgument = new NamedArgument(mr.getId(), value); + if (withSpans) { + namedArgument.addSpan(spanStart, ps.index); + } + + return namedArgument; + } + + throw new ParseException(E0009); + } + + private Literal getLiteral(FluentStream ps) { + if (ps.isNumberStart()) { + return getNumber(ps); + } + + if (ps.currentChar().filter(v -> v == '"').isPresent()) { + return getString(ps); + } + + throw new ParseException(E0014); + } + + private StringLiteral getString(FluentStream ps) { + var spanStart = ps.index; + + ps.expectChar('"'); + var value = new StringBuilder(); + + Predicate filter = x -> x != '"' && x != EOL; + Optional opt; + while ((opt = ps.takeChar(filter)).isPresent()) { + var ch = opt.get(); + value.append(ch == '\\' ? getEscapeSequence(ps) : ch); + } + + if (ps.currentChar().filter(v -> v == EOL).isPresent()) { + throw new ParseException(E0020); + } + + ps.expectChar('"'); + + var stringLiteral = new StringLiteral(value.toString()); + if (withSpans) { + stringLiteral.addSpan(spanStart, ps.index); + } + + return stringLiteral; + } + + private String getEscapeSequence(FluentStream ps) { + var nextOpt = ps.currentChar(); + if (nextOpt.isEmpty()) { + throw new ParseException(E0025, (Character) null); + } + + var next = nextOpt.get(); + return switch (next) { + case '\\', '"' -> { + ps.next(); + yield "\\" + next; + } + case 'u' -> getUnicodeEscapeSequence(ps, next, 4); + case 'U' -> getUnicodeEscapeSequence(ps, next, 6); + default -> throw new ParseException(E0025, next); + }; + } + + private String getUnicodeEscapeSequence(FluentStream ps, Character u, int digits) { + ps.expectChar(u); + + var sequence = new StringBuilder(); + for (int i = 0; i < digits; i++) { + var opt = ps.takeHexDigit(); + if (opt.isEmpty()) { + throw new ParseException(E0026, "\\%s%s%s".formatted(u, sequence, ps.currentChar().orElseThrow())); + } + sequence.append(opt.get()); + } + + return "\\%s%s".formatted(u, sequence); + } + + private NumberLiteral getNumber(FluentStream ps) { + var spanStart = ps.index; + var value = new StringBuilder(); + + if (ps.currentChar().filter(v -> v == '-').isPresent()) { + ps.next(); + value.append("-"); + } + value.append(getDigits(ps)); + + if (ps.currentChar().filter(v -> v == '.').isPresent()) { + ps.next(); + value.append(".").append(getDigits(ps)); + } + + var numberLiteral = new NumberLiteral(value.toString()); + if (withSpans) { + numberLiteral.addSpan(spanStart, ps.index); + } + + return numberLiteral; + } + + private String getDigits(FluentStream ps) { + var num = new StringBuilder(); + + Optional opt; + while ((opt = ps.takeDigit()).isPresent()) { + num.append(opt.get()); + } + + if (num.isEmpty()) { + throw new ParseException(E0004, "0-9"); + } + + return num.toString(); + } + + // Create a token representing an indent. It's not part of the AST and it will + // be trimmed and merged into adjacent TextElements, or turned into a new + // TextElement, if it's surrounded by two Placeables. + private PatternElement getIndent(FluentStream ps, String value, int start) { + return new Indent(value, start, ps.index); + } + + private Identifier getIdentifier(FluentStream ps) { + int spanStart = ps.index; + + var name = new StringBuilder().append(ps.takeIDStart()); + Optional opt; + while ((opt = ps.takeIDChar()).isPresent()) { + name.append(opt.get()); + } + + var identifier = new Identifier(name.toString()); + if (withSpans) { + identifier.addSpan(spanStart, ps.index); + } + + return identifier; + } + + private BaseComment getComment(FluentStream ps) { + int spanStart = ps.index; + + final int ANY = -1; + final int COMMENT = 0; + final int GROUP_COMMENT = 1; + final int RESOURCE_COMMENT = 2; + + var level = ANY; + var content = new StringBuilder(); + + while (true) { + var i = -1; + + int thisLevel; + if (level == ANY) { + thisLevel = RESOURCE_COMMENT; + } else { + thisLevel = level; + } + + while (ps.currentChar().filter(v -> v =='#').isPresent() && i < thisLevel) { + ps.next(); + i++; + } + + if (level == ANY) { + level = i; + } + + if (ps.currentChar().filter(v -> v == EOL).isEmpty()) { + ps.expectChar(' '); + Optional opt; + while ((opt = ps.takeChar(v -> v != EOL)).isPresent()) { + content.append(opt.get()); + } + } + + if (ps.isNextLineComment(level)) { + ps.currentChar().ifPresent(content::append); + ps.next(); + } else { + break; + } + } + + BaseComment comment = switch (level) { + case COMMENT -> new Comment(content.toString()); + case GROUP_COMMENT -> new GroupComment(content.toString()); + default -> new ResourceComment(content.toString()); + }; + + if (withSpans) { + comment.addSpan(spanStart, ps.index); + } + + return comment; + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentStream.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentStream.java new file mode 100644 index 0000000..0ef0994 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentStream.java @@ -0,0 +1,259 @@ +package ru.di9.fluent.syntax.parser; + +import ru.di9.fluent.syntax.MathUtils; + +import java.util.Optional; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +import static ru.di9.fluent.syntax.StringUtils.*; +import static ru.di9.fluent.syntax.parser.ParseException.ErrorCode.E0003; +import static ru.di9.fluent.syntax.parser.ParseException.ErrorCode.E0004; + +public class FluentStream extends ParserStream { + public static final char EOL = '\n'; + public static final String SPECIAL_LINE_START_CHARS = "}.[*"; + public static final Pattern VALID_FUNCTION_NAME = Pattern.compile("^[A-Z][A-Z0-9_-]*$"); + public static final Pattern TRAILING_WS_RE = Pattern.compile("[ \t\n\r]+$"); + + public FluentStream(String string) { + super(string); + } + + public String peekBlankInline() { + int start = index + peekOffset; + while (currentPeek().filter(v -> v == ' ').isPresent()) { + peek(); + } + return string.substring(start, (index + peekOffset)); + } + + public String skipBlankInline() { + var blank = peekBlankInline(); + skipToPeek(); + return blank; + } + + public String peekBlankBlock() { + var blank = new StringBuilder(); + while (true) { + var lineStart = peekOffset; + peekBlankInline(); + var currentPeek = currentPeek(); + if (currentPeek.filter(v -> v == EOL).isPresent()) { + blank.append(EOL); + peek(); + continue; + } + if (currentPeek.isEmpty()/*EOF*/) { + // Treat the blank line at EOF as a blank block. + return blank.toString(); + } + // Any other char; reset to column 1 on this line. + resetPeek(lineStart); + return blank.toString(); + } + } + + public String skipBlankBlock() { + var blank = peekBlankBlock(); + skipToPeek(); + return blank; + } + + public void peekBlank() { + while (currentPeek().filter(v -> v == ' ' || v == EOL).isPresent()) { + peek(); + } + } + + public void skipBlank() { + peekBlank(); + skipToPeek(); + } + + public void expectChar(char ch) { + currentChar().filter(v -> v == ch) + .orElseThrow(() -> new ParseException(E0003, ch)); + next(); + } + + public void expectLineEnd() { + var opt = currentChar(); + + if (opt.isEmpty()/*EOF*/) { + // EOF is a valid line end in Fluent. + return; + } + + if (opt.get() == EOL) { + next(); + return; + } + + // Unicode Character 'SYMBOL FOR NEWLINE' (U+2424) + //noinspection UnnecessaryUnicodeEscape + throw new ParseException(E0003, "\u2424"); + } + + public Optional takeChar(Predicate f) { + var ch = currentChar().filter(f); + ch.ifPresent(x -> next()); + return ch; + } + + public boolean isIdentifierStart() { + return currentPeek().map(this::isCharIdStart).orElse(false); + } + + public boolean isNumberStart() { + var opt = currentChar(); + if (opt.filter(v -> v == '-').isPresent()) { + opt = peek(); + } + + boolean result = opt.map(ch -> inRange_09((int) ch)).orElse(false); + resetPeek(); + return result; + } + + public boolean isValueStart() { + // Inline Patterns may start with any char. + return currentPeek().filter(v -> v != EOL).isPresent(); + } + + public boolean isValueContinuation() { + var column1 = peekOffset; + peekBlankInline(); + + if (currentPeek().filter(v -> v == '{').isPresent()) { + resetPeek(column1); + return true; + } + + if (peekOffset - column1 == 0) { + return false; + } + + if (currentPeek().map(this::isCharPatternContinuation).orElse(false)) { + resetPeek(column1); + return true; + } + + return false; + } + + /** + * @param level
    + *
  • -1 - any + *
  • 0 - comment + *
  • 1 - group comment + *
  • 2 - resource comment + */ + public boolean isNextLineComment(int level) { + if (currentChar().filter(v -> v != EOL).isPresent()) { + return false; + } + + int lvl = MathUtils.clamp(level, -1, 2); + var i = 0; + while (i <= lvl || (lvl == -1 && i < 3)) { + if (peek().filter(v -> v != '#').isPresent()) { + if (i <= lvl && lvl != -1) { + resetPeek(); + return false; + } + break; + } + i++; + } + + // The first char after #, ## or ###. + boolean result = peek().filter(ch -> ch == ' ' || ch == EOL).isPresent(); + resetPeek(); + return result; + } + + public boolean isVariantStart() { + var currentPeekOffset = peekOffset; + if (currentPeek().filter(v -> v == '*').isPresent()) { + peek(); + } + + boolean result = currentPeek().filter(v -> v == '[').isPresent(); + resetPeek(currentPeekOffset); + return result; + } + + public boolean isAttributeStart() { + return currentPeek().filter(v -> v == '.').isPresent(); + } + + public void skipToNextEntryStart(int junkStart) { + var lastNewline = string.lastIndexOf(EOL, index); + if (junkStart < lastNewline) { + // Last seen newline is _after_ the junk start. It's safe to rewind + // without the risk of resuming at the same broken entry. + index = lastNewline; + } + + while (currentChar().isPresent()) { + // We're only interested in beginnings of line. + if (currentChar().filter(v -> v != EOL).isPresent()) { + next(); + continue; + } + + // Break if the first char in this line looks like an entry start. + var firstOpt = next(); + if (firstOpt.isEmpty()/*EOF*/) { + continue; + } + + char first = firstOpt.get(); + if (isCharIdStart(first) || first == '-' || first == '#') { + break; + } + } + } + + public char takeIDStart() { + if (currentChar().map(this::isCharIdStart).orElse(false)) { + var ret = currentChar(); + if (ret.isPresent()) { + next(); + return ret.get(); + } + } + + throw new ParseException(E0004, "a-zA-Z"); + } + + public Optional takeIDChar() { + return takeChar(ch -> { + int cc = ch; + return inRange_az(cc) || inRange_AZ(cc) || inRange_09(cc) || cc == 95 /*_*/ || cc == 45 /*-*/; + }); + } + + public Optional takeDigit() { + return takeChar(ch -> inRange_09((int) ch)); + } + + public Optional takeHexDigit() { + return takeChar(ch -> { + int cc = ch; + return inRange_09(cc) || inRange_AF(cc) || inRange_af(cc); + }); + } + + /// PRIVATE //////////////////////////////////////////////////////////////////////////////////////////////////////// + + private boolean isCharIdStart(char ch) { + return inRange_az(ch) || inRange_AZ(ch); + } + + private boolean isCharPatternContinuation(char ch) { + return SPECIAL_LINE_START_CHARS.indexOf(ch) < 0; + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/Indent.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/Indent.java new file mode 100644 index 0000000..0d7dd88 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/Indent.java @@ -0,0 +1,18 @@ +package ru.di9.fluent.syntax.parser; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import ru.di9.fluent.syntax.ast.PatternElement; + +@AllArgsConstructor +@Getter +@Setter +public class Indent extends PatternElement { + private String value; + + public Indent(String value, int start, int end) { + this(value); + this.addSpan(start, end); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/ParseException.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/ParseException.java new file mode 100644 index 0000000..0214a7a --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/ParseException.java @@ -0,0 +1,50 @@ +package ru.di9.fluent.syntax.parser; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +public class ParseException extends RuntimeException { + private final ErrorCode code; + private final Object[] args; + + public ParseException(ErrorCode code, Object... args) { + super(code.getMessage().formatted(args)); + this.code = code; + this.args = args; + } + + @RequiredArgsConstructor + @Getter + public enum ErrorCode { + E0001("Generic Error"), + E0002("Expected an entry start"), + E0003("Expected token: \"%s\""), + E0004("Expected a character from range: \"%s\""), + E0005("Expected message \"%s\" to have a value or attributes"), + E0006("Expected term \"-%s\" to have a value"), + E0007("Keyword cannot end with a whitespace"), + E0008("The callee has to be an upper-case identifier or a term"), + E0009("The argument name has to be a simple identifier"), + E0010("Expected one of the variants to be marked as default (*)"), + E0011("Expected at least one variant after \"->\""), + E0012("Expected value"), + E0013("Expected variant key"), + E0014("Expected literal"), + E0015("Only one variant can be marked as default (*)"), + E0016("Message references cannot be used as selectors"), + E0017("Terms cannot be used as selectors"), + E0018("Attributes of messages cannot be used as selectors"), + E0019("Attributes of terms cannot be used as placeables"), + E0020("Unterminated string expression"), + E0021("Positional arguments must not follow named arguments"), + E0022("Named arguments must be unique"), + E0024("Cannot access variants of a message."), + E0025("Unknown escape sequence: \\%s."), + E0026("Invalid Unicode escape sequence: %s."), + E0027("Unbalanced closing brace in TextElement."), + E0028("Expected an inline expression"); + + private final String message; + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/ParserStream.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/ParserStream.java new file mode 100644 index 0000000..7c218ed --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/ParserStream.java @@ -0,0 +1,80 @@ +package ru.di9.fluent.syntax.parser; + +import java.util.Optional; + +import static ru.di9.fluent.syntax.StringUtils.getCharAt; + +public class ParserStream { + + protected int index = 0; + protected int peekOffset = 0; + + protected String string; + + public ParserStream(String string) { + this.string = string; + } + + public Optional currentChar() { + return charAt(index); + } + + public Optional currentPeek() { + return charAt(index + peekOffset); + } + + public Optional next() { + peekOffset = 0; + if (index >= string.length()) { + return Optional.empty(); + } + + // Skip over the CRLF as if it was a single character. + getCharAt(string, index) + .filter(v -> v == '\r') + .flatMap(x -> getCharAt(string, index + 1).filter(v -> v == '\n')) + .ifPresent(x -> index++); + + index++; + return getCharAt(string, index); + } + + public Optional peek() { + // Skip over the CRLF as if it was a single character. + getCharAt(string, index + peekOffset) + .filter(v -> v == '\r') + .flatMap(x -> getCharAt(string, index + peekOffset + 1).filter(v -> v == '\n')) + .ifPresent(x -> peekOffset++); + + peekOffset++; + return getCharAt(string, index + peekOffset); + } + + public void resetPeek() { + resetPeek(0); + } + + public void resetPeek(int offset) { + this.peekOffset = offset; + } + + public void skipToPeek() { + this.index += this.peekOffset; + this.peekOffset = 0; + } + + /// PRIVATE //////////////////////////////////////////////////////////////////////////////////////////////////////// + + private Optional charAt(int offset) { + // When the cursor is at CRLF, return LF but don't move the cursor. + // The cursor still points to the EOL position, which in this case is the + // beginning of the compound CRLF sequence. This ensures slices of + // [inclusive, exclusive) continue to work properly. + + var ch = getCharAt(string, offset); + var opt = ch.filter(v -> v == '\r') + .flatMap(x -> getCharAt(string, offset + 1).filter(v -> v == '\n')); + + return opt.isPresent() ? opt : ch; + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/processor/Processor.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/processor/Processor.java new file mode 100644 index 0000000..1df973f --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/processor/Processor.java @@ -0,0 +1,165 @@ +package ru.di9.fluent.syntax.processor; + +import ru.di9.fluent.syntax.ast.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; + +public class Processor { + private static final java.util.regex.Pattern SPECIAL + = java.util.regex.Pattern.compile("\\\\(([\\\\\"])|(u[0-9a-fA-F]{4})|(U[0-90a-fA-F]{6}))"); + + public Pattern unescapeLiteralsToText(Pattern pattern) { + var result = new Pattern(); + textFromLiterals(pattern, result.getElements()); + return result; + } + + public Pattern escapeTextToLiterals(Pattern pattern) { + var result = new Pattern(); + literalsFromText(pattern, result.getElements()); + return result; + } + + private void textFromLiterals(Pattern pattern, Collection collection) { + TextElement lastText = null; + for (PatternElement element : pattern.getElements()) { + if (element instanceof TextElement txtElm) { + if (lastText == null) { + lastText = new TextElement(txtElm.getValue()); + } else { + lastText.setValue(lastText.getValue() + txtElm.getValue()); + } + } else if (element instanceof Placeable plcElm) { + InsidePlaceable expression = plcElm.getExpression(); + if (expression instanceof StringLiteral strExp) { + String content = strExp.getValue(); + { + Matcher matcher = SPECIAL.matcher(content); + var sb = new StringBuilder(); + while (matcher.find()) { + String replacement = unescape(matcher.toMatchResult()).replace("\\", "\\\\"); + matcher.appendReplacement(sb, replacement); + } + matcher.appendTail(sb); + content = sb.toString(); + } + if (lastText == null) { + lastText = new TextElement(""); + } + lastText.setValue(lastText.getValue() + content); + } else if (expression instanceof SelectExpression selExp) { + List processedVariants = new ArrayList<>(); + for (Variant variant : selExp.getVariants()) { + processedVariants.add(new Variant(variant.getKey(), unescapeLiteralsToText(variant.getValue()), variant.isDefault())); + } + var processedSelect = new SelectExpression(selExp.getSelector(), processedVariants); + var placeable = new Placeable(processedSelect); + + if (lastText != null) { + collection.add(lastText); + lastText = null; + } + collection.add(placeable); + } else { + if (lastText != null) { + collection.add(lastText); + lastText = null; + } + collection.add(plcElm); + } + } + } + if (lastText != null) { + collection.add(lastText); + } + } + + private void literalsFromText(Pattern pattern, Collection collection) { + for (PatternElement element : pattern.getElements()) { + if (element instanceof TextElement txtElm) { + if (txtElm.getValue().startsWith(" ") || txtElm.getValue().startsWith("\n")) { + collection.add(new Placeable(new StringLiteral(""))); + } + + int startIndex = 0; + for (int i = 0; i < txtElm.getValue().length(); i++) { + char ch = txtElm.getValue().charAt(i); + if (ch == '{' || ch == '}') { + String before = txtElm.getValue().substring(startIndex, i); + if (!before.isEmpty()) { + collection.add(new TextElement(before)); + } + collection.add(new Placeable(new StringLiteral(String.valueOf(ch)))); + startIndex = i + 1; + } else if (ch == '[' || ch == '*' || ch == '.') { + if (i > 0 && txtElm.getValue().charAt(i - 1) == '\n') { + String before = txtElm.getValue().substring(startIndex, i); + collection.add(new TextElement(before)); + collection.add(new Placeable(new StringLiteral(String.valueOf(ch)))); + startIndex = i + 1; + } + } + } + + if ((txtElm.getValue().length() - 1) > startIndex) { + collection.add(new TextElement(txtElm.getValue().substring(startIndex))); + } + + if (txtElm.getValue().endsWith(" ") || txtElm.getValue().endsWith("\n")) { + collection.add(new Placeable(new StringLiteral(""))); + } + } else if (element instanceof Placeable plcElm) { + if (plcElm.getExpression() instanceof SelectExpression selExp) { + List rawVariants = new ArrayList<>(); + for (Variant variant : selExp.getVariants()) { + rawVariants.add(new Variant(variant.getKey(), escapeTextToLiterals(variant.getValue()), variant.isDefault())); + } + collection.add(new Placeable(new SelectExpression(selExp.getSelector(), rawVariants))); + } else { + collection.add(element); + } + } + } + } + + private static String unescape(MatchResult matchResult) { + List groupValues = new ArrayList<>(); + for (int i = 2; i < matchResult.groupCount() + 1; i++) { + groupValues.add(matchResult.group(i) != null ? matchResult.group(i) : ""); + } + Iterator matches = groupValues.iterator(); + String simple = matches.next(); + if (!simple.isEmpty()) { + return simple; + } + + String uni4 = matches.next(); + if (!uni4.isEmpty()) { + int codepoint = Integer.valueOf(uni4.substring(1), 16); + if (Character.isBmpCodePoint(codepoint)) { + char chr = (char) codepoint; + if (!Character.isSurrogate(chr)) { + return Character.toString(chr); + } + } + } + + String uni6 = matches.next(); + if (!uni6.isEmpty()) { + int codepoint = Integer.valueOf(uni6.substring(1), 16); + if (Character.isValidCodePoint(codepoint)) { + char chr = (char) codepoint; + if (!Character.isSurrogate(chr)) { + return String.valueOf(Character.highSurrogate(codepoint)) + Character.lowSurrogate(codepoint); + } + } + } + + return "\uFFFD"; // � + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/serializer/FluentSerializer.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/serializer/FluentSerializer.java new file mode 100644 index 0000000..1eb463e --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/serializer/FluentSerializer.java @@ -0,0 +1,269 @@ +package ru.di9.fluent.syntax.serializer; + +import ru.di9.fluent.syntax.ast.*; + +import java.util.Arrays; +import java.util.Objects; +import java.util.stream.Collectors; + +public class FluentSerializer { + public boolean withJunk = false; + + public String serialize(Resource resource) { + return resource.getBody() + .stream() + .map(this::serialize) + .collect(Collectors.joining()); + } + + public String serialize(TopLevel topLevel) { + if (topLevel instanceof Entry entry) { + return serializeEntry(entry); + } else if (topLevel instanceof Whitespace whitespace) { + return whitespace.getContent(); + } else if (topLevel instanceof Junk junk) { + return withJunk ? junk.getContent() : ""; + } else { + throw new SerializeException("Unknown top-level entry type '%s'".formatted(topLevel.getClass())); + } + } + + /// PRIVATE //////////////////////////////////////////////////////////////////////////////////////////////////////// + + private String serializeEntry(Entry entry) { + if (entry instanceof Message message) { + return serializeMessage(message); + } else if (entry instanceof Term term) { + return serializeTerm(term); + } else if (entry instanceof Comment comment) { + return serializeComment(comment, "#"); + } else if (entry instanceof GroupComment comment) { + return serializeComment(comment, "##"); + } else if (entry instanceof ResourceComment comment) { + return serializeComment(comment, "###"); + } else { + throw new SerializeException("Unknown entry type '%s'".formatted(entry.getClass())); + } + } + + private String serializeMessage(Message message) { + var builder = new StringBuilder(); + + message.getComment().ifPresent(comment -> + builder.append(serializeComment(comment, "#"))); + + builder.append(message.getId().getName()).append(" ="); + + message.getValue().ifPresent(value -> + builder.append(serializePattern(value))); + + for (Attribute attribute : message.getAttributes()) { + builder.append(serializeAttribute(attribute)); + } + + return builder.append('\n').toString(); + } + + private String serializeTerm(Term term) { + var builder = new StringBuilder(); + + term.getComment().ifPresent(comment -> + builder.append(serializeComment(comment, "#"))); + + builder + .append('-').append(term.getId().getName()).append(" =") + .append(serializePattern(term.getValue())); + + for (Attribute attribute : term.getAttributes()) { + builder.append(serializeAttribute(attribute)); + } + + return builder.append('\n').toString(); + } + + private String serializeComment(BaseComment comment, String prefix) { + return Arrays.stream(comment.getContent().split("\n")) + .map(line -> { + if (line.isEmpty()) { + return prefix + "\n"; + } else { + return prefix + " " + line + "\n"; + } + }) + .collect(Collectors.joining()); + } + + private String serializePattern(Pattern pattern) { + var builder = new StringBuilder(); + + String content = pattern.getElements() + .stream() + .map(this::serializeElement) + .filter(Objects::nonNull) + .map(string -> string.replaceAll("\n", "\n ")) + .collect(Collectors.joining()); + + if (shouldStartOnNewLine(pattern)) { + builder.append("\n "); + } else { + builder.append(' '); + } + + return builder.append(content).toString(); + } + + private String serializeAttribute(Attribute attribute) { + String value = serializePattern(attribute.getValue()).replaceAll("\n", "\n "); + return "\n ." + attribute.getId().getName() + " =" + value; + } + + private String serializeElement(PatternElement patternElement) { + if (patternElement instanceof TextElement textElement) { + return textElement.getValue(); + } else if (patternElement instanceof Placeable placeable) { + return serializePlaceable(placeable); + } else { + throw new SerializeException("Unknown element type: '%s".formatted(patternElement.getClass())); + } + } + + private String serializePlaceable(Placeable placeable) { + InsidePlaceable expression = placeable.getExpression(); + if (expression instanceof Placeable placeable1) { + return "{" + serializePlaceable(placeable1) + "}"; + } else if (expression instanceof SelectExpression selectExpression) { + return "{ " + serializeExpression(selectExpression) + "}"; + } else if (expression instanceof Expression expression1) { + return "{ " + serializeExpression(expression1) + " }"; + } else { + throw new SerializeException("Unknown placeable type '%s'".formatted(expression.getClass())); + } + } + + private String serializeExpression(Expression expression) { + var builder = new StringBuilder(); + + if (expression instanceof StringLiteral stringLiteral) { + builder.append('"').append(stringLiteral.getValue()).append('"'); + } else if (expression instanceof NumberLiteral numberLiteral) { + builder.append(numberLiteral.getValue()); + } else if (expression instanceof VariableReference variableReference) { + builder.append('$').append(variableReference.getId().getName()); + } else if (expression instanceof TermReference termReference) { + builder.append('-').append(termReference.getId().getName()); + + termReference.getAttribute().ifPresent(attribute -> + builder.append('.').append(attribute.getName())); + + termReference.getArguments().ifPresent(arguments -> + builder.append(serializeCallArguments(arguments))); + } else if (expression instanceof MessageReference messageReference) { + builder.append(messageReference.getId().getName()); + + messageReference.getAttribute().ifPresent(attribute -> + builder.append('.').append(attribute.getName())); + } else if (expression instanceof FunctionReference functionReference) { + builder + .append(functionReference.getId().getName()) + .append(serializeCallArguments(functionReference.getArguments())); + } else if (expression instanceof SelectExpression selectExpression) { + builder.append(serializeExpression(selectExpression.getSelector())).append(" ->"); + for (Variant variant : selectExpression.getVariants()) { + builder.append(serializeVariant(variant)); + } + builder.append('\n'); + } else { + throw new SerializeException("Unknown expression type '%s".formatted(expression.getClass())); + } + + return builder.toString(); + } + + private String serializeCallArguments(CallArguments callArguments) { + boolean hasPositional = !callArguments.getPositional().isEmpty(); + boolean hasNamed = !callArguments.getNamed().isEmpty(); + var builder = new StringBuilder("("); + + if (hasPositional) { + var positional = callArguments.getPositional() + .stream() + .map(this::serializeExpression) + .collect(Collectors.joining(", ")); + builder.append(positional); + } + + if (hasNamed) { + var named = callArguments.getNamed() + .stream() + .map(this::serializeNamedArgument) + .collect(Collectors.joining(", ")); + + if (hasPositional) { + builder.append(", "); + } + + builder.append(named); + } + + return builder.append(')').toString(); + } + + private String serializeVariant(Variant variant) { + var key = serializeVariantKey(variant.getKey()); + var value = serializePattern(variant.getValue()).replaceAll("\n", "\n "); + var builder = new StringBuilder("\n "); + + if (variant.isDefault()) { + builder.append('*'); + } else { + builder.append(' '); + } + + return builder.append('[').append(key).append(']').append(value).toString(); + } + + private String serializeNamedArgument(NamedArgument namedArgument) { + return namedArgument.getName().getName() + ": " + serializeExpression(namedArgument.getValue()); + } + + private String serializeVariantKey(VariantKey variantKey) { + if (variantKey instanceof Identifier identifier) { + return identifier.getName(); + } else if (variantKey instanceof NumberLiteral numberLiteral) { + return numberLiteral.getValue(); + } else { + throw new SerializeException("Unknown variant key type '%s'".formatted(variantKey.getClass())); + } + } + + private boolean shouldStartOnNewLine(Pattern pattern) { + boolean isMultiline = pattern.getElements().stream().anyMatch(it -> isSelectExpr(it) || includesLine(it)); + if (isMultiline) { + if (!pattern.getElements().isEmpty()) { + var firstElement = pattern.getElements().get(0); + if (firstElement instanceof TextElement te) { + if (!te.getValue().isEmpty()) { + char firstChar = te.getValue().charAt(0); + // Due to the indentation requirement the following characters may not appear + // as the first character on a new line. + return firstChar != '[' && firstChar != '.' && firstChar != '*'; + } + } + } + + return true; + } + + return false; + } + + private boolean isSelectExpr(PatternElement patternElement) { + return patternElement instanceof Placeable pl + && pl.getExpression() instanceof SelectExpression; + } + + private boolean includesLine(PatternElement patternElement) { + return patternElement instanceof TextElement te + && te.getValue().contains("\n"); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/serializer/SerializeException.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/serializer/SerializeException.java new file mode 100644 index 0000000..3c1d715 --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/serializer/SerializeException.java @@ -0,0 +1,7 @@ +package ru.di9.fluent.syntax.serializer; + +public class SerializeException extends RuntimeException { + public SerializeException(String message) { + super(message); + } +} diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/visitor/Visitor.java b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/visitor/Visitor.java new file mode 100644 index 0000000..18defeb --- /dev/null +++ b/fluent.syntax/src/main/java/ru/di9/fluent/syntax/visitor/Visitor.java @@ -0,0 +1,113 @@ +package ru.di9.fluent.syntax.visitor; + +import ru.di9.fluent.syntax.ast.BaseNode; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; + +public class Visitor { + private final Map handlers = new HashMap<>(); + + public Visitor() { + Method[] methods = this.getClass().getDeclaredMethods(); + for (Method method : methods) { + if (method.getName().startsWith("visit")) { + handlers.put(method.getName().substring("visit".length()), method); + } + } + } + + public void visit(BaseNode node) { + String cName = node.getClass().getSimpleName(); + Method handler = handlers.get(cName); + if (handler != null) { + try { + handler.invoke(this, node); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } else { + genericVisit(node); + } + } + + public void genericVisit(BaseNode node) { + Iterator itr = childrenOf(node); + while (itr.hasNext()) { + Pair pair = itr.next(); + Object value = pair.invokeResult(); + + if (value instanceof Optional opt) { + value = opt.orElse(null); + } + + if (value instanceof BaseNode baseNode) { + this.visit(baseNode); + } else if (value instanceof Collection collection) { + for (Object it : collection) { + if (it instanceof BaseNode baseNode) { + this.visit(baseNode); + } + } + } + } + } + + record Pair(String name, Object invokeResult){} + + static Iterator childrenOf(BaseNode node) { + final Method[] methods = node.getClass().getMethods(); + Arrays.sort(methods, Comparator.comparing(Method::getName)); + + return new Iterator<>() { + private int idx = 0; + private Method nextMethod; + + @Override + public boolean hasNext() { + if (nextMethod != null) return true; + else if (idx >= methods.length) return false; + return (nextMethod = loadNext()) != null; + } + + @Override + public Pair next() { + if (idx >= methods.length) { + throw new NoSuchElementException(); + } else if (nextMethod == null) { + if ((nextMethod = loadNext()) == null) { + throw new NoSuchElementException(); + } + } + + Object invokeResult; + try { + invokeResult = nextMethod.invoke(node); + } catch (InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException(e); + } + + var pair = new Pair(nextMethod.getName(), invokeResult); + nextMethod = null; + return pair; + } + + private Method loadNext() { + do { + Method method = methods[idx++]; + if (Modifier.isPublic(method.getModifiers()) + && method.getName().startsWith("get") + && method.getParameterCount() == 0 + && !method.getName().equals("getClass")) { + + return method; + } + } while (idx < methods.length); + + return null; + } + }; + } +} diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/MathUtilsTest.java b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/MathUtilsTest.java new file mode 100644 index 0000000..7eb0c5f --- /dev/null +++ b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/MathUtilsTest.java @@ -0,0 +1,15 @@ +package ru.di9.fluent.syntax; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class MathUtilsTest { + + @Test + void clamp() { + assertThat(MathUtils.clamp(7, 1, 10)).isEqualTo(7); + assertThat(MathUtils.clamp(0, 1, 10)).isEqualTo(1); + assertThat(MathUtils.clamp(11, 1, 10)).isEqualTo(10); + } +} diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/StringUtilsTest.java b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/StringUtilsTest.java new file mode 100644 index 0000000..d4ac741 --- /dev/null +++ b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/StringUtilsTest.java @@ -0,0 +1,33 @@ +package ru.di9.fluent.syntax; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +class StringUtilsTest { + + @Test + void testGetChar() { + String string = "abcd"; + Optional chr = StringUtils.getCharAt(string, 0); + + Assertions.assertThat(chr) + .hasValue('a'); + + chr = StringUtils.getCharAt(string, 4); + + Assertions.assertThat(chr) + .isEmpty(); + + chr = StringUtils.getCharAt(null, 0); + + Assertions.assertThat(chr) + .isEmpty(); + + chr = StringUtils.getCharAt("", 0); + + Assertions.assertThat(chr) + .isEmpty(); + } +} diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/ast/BaseNodeTest.java b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/ast/BaseNodeTest.java new file mode 100644 index 0000000..b6d3755 --- /dev/null +++ b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/ast/BaseNodeTest.java @@ -0,0 +1,24 @@ +package ru.di9.fluent.syntax.ast; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +class BaseNodeTest { + + @Test + void testEquals() { + var m1 = new Message(new Identifier("test-id"), new Pattern(new TextElement("localized"))); + var m11 = new Message(new Identifier("test-id"), new Pattern(new TextElement("localized"))); + var m2 = new Message(new Identifier("test-id"), new Pattern(new TextElement("different"))); + + assertEquals(m1, m11); + assertNotEquals(m1, m2); + assertEquals(m1.getId(), m2.getId()); + assertNotEquals(m1.getValue(), m2.getValue()); + //Шта? + assertNotEquals(m1, null); + assertNotEquals(null, m1); + } +} diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/AbstractFixturesTest.java b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/AbstractFixturesTest.java new file mode 100644 index 0000000..3ae538d --- /dev/null +++ b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/AbstractFixturesTest.java @@ -0,0 +1,66 @@ +package ru.di9.fluent.syntax.parser; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; +import ru.di9.fluent.test.utils.AstAssert; +import ru.di9.fluent.test.utils.Tuple3; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import static ru.di9.fluent.test.utils.FileUtils.getExt; +import static ru.di9.fluent.test.utils.FileUtils.getName; + +public abstract class AbstractFixturesTest { + + @TestFactory + public Iterable fixturesTest() throws IOException { + List tests = new ArrayList<>(); + + try (Stream walker = Files.walk(getFixturesPath())) { + walker + .filter(path -> path.toFile().isFile()) + .filter(path -> getExt(path.toFile().getName()).equals("ftl")) + .map(path -> { + Path parent = path.getParent(); + String name = getName(path.getFileName().toString()); + + try { + return new Tuple3( + name, + Files.readString(parent.resolve(name + ".ftl")), + Files.readString(parent.resolve(name + ".json")) + ); + } catch (Exception e) { + throw new RuntimeException(e); + } + }) + .map(tuple -> DynamicTest.dynamicTest(tuple.value1(), () -> { + var parser = new FluentParser(); + parser.withSpans = isWithSpans(tuple.value1()); + parser.withJunkAnnotations = isWithJunkAnnotations(tuple.value1()); + var resource = parser.parse(tuple.value2()); + + AstAssert.assertThat(resource) + .isEqualAstJson(tuple.value3()); + })) + .forEachOrdered(tests::add); + } + + return tests; + } + + protected abstract Path getFixturesPath(); + + protected boolean isWithSpans(String testName) { + return true; + } + + protected boolean isWithJunkAnnotations(String testName) { + return true; + } +} diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/FluentStreamTest.java b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/FluentStreamTest.java new file mode 100644 index 0000000..b4e39ef --- /dev/null +++ b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/FluentStreamTest.java @@ -0,0 +1,158 @@ +package ru.di9.fluent.syntax.parser; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; +import static ru.di9.fluent.syntax.parser.ParseException.ErrorCode.E0003; + +class FluentStreamTest { + + @Test + void testPeekBlankInline() { + var ps = new FluentStream(" "); + assertThat(ps.peekBlankInline()).isEqualTo(" "); + + ps = new FluentStream(" | "); + assertThat(ps.peekBlankInline()).isEqualTo(" "); + } + + @Test + void testPeekBlankBlock() { + var ps = new FluentStream(" \n \n "); + assertThat(ps.peekBlankBlock()).isEqualTo("\n\n"); + + ps = new FluentStream(" \n . "); + assertThat(ps.peekBlankBlock()).isEqualTo("\n"); + } + + @Test + void testSkipBlankBlock() { + var ps = new FluentStream(" \n \n "); + assertThat(ps.skipBlankBlock()).isEqualTo("\n\n"); + assertThat(ps.index).isEqualTo(12); + assertThat(ps.peekOffset).isEqualTo(0); + } + + @Test + void testExpectLineEnd() { + var ps1 = new FluentStream(""); + assertThatNoException().isThrownBy(ps1::expectLineEnd); + + var ps2 = new FluentStream("\n"); + assertThatNoException().isThrownBy(ps2::expectLineEnd); + + var ps3 = new FluentStream(" "); + var catchException = catchThrowableOfType(ps3::expectLineEnd, ParseException.class); + assertThat(catchException.getCode()).isEqualTo(E0003); + } + + @Test + void testExpectChar() { + var ps = new FluentStream("z"); + assertThatNoException().isThrownBy(() -> ps.expectChar('z')); + var catchException = catchThrowableOfType(() -> ps.expectChar('a'), ParseException.class); + assertThat(catchException.getCode()).isEqualTo(E0003); + } + + @Test + void testTakeChar() { + var ps = new FluentStream("abc"); + + assertThat(ps.currentChar()).hasValue('a'); + assertThat(ps.takeChar(c -> c == 'a')).hasValue('a'); + assertThat(ps.currentChar()).hasValue('b'); + assertThat(ps.takeChar(c -> c == 'c')).isEmpty(); + assertThat(ps.currentChar()).hasValue('b'); + } + + @Test + void testIsNextLineComment() { + var ps = new FluentStream("# 123"); + for (int i = 0; i < 5; i++) { + ps.next(); + } + assertThat(ps.isNextLineComment(0)).isFalse(); + + var ps1 = new FluentStream("# 123\n# 456"); + for (int i = 0; i < 5; i++) { + ps1.next(); + } + assertThat(ps1.isNextLineComment(0)).isTrue(); + + var ps2 = new FluentStream("# 123\nkey = value"); + for (int i = 0; i < 5; i++) { + ps2.next(); + } + assertThat(ps2.isNextLineComment(0)).isFalse(); + + var ps3 = new FluentStream("# 123"); + assertThat(ps3.isNextLineComment(0)).isFalse(); + } + + @Test + void testSkipBlankInline() { + var ps = new FluentStream(" \n123"); + + assertThat(ps.skipBlankInline()).isEqualTo(" "); + assertThat(ps.index).isEqualTo(4); + assertThat(ps.peekOffset).isEqualTo(0); + assertThat(ps.currentChar()).hasValue('\n'); + } + + @Test + void testPeekBlank() { + var ps = new FluentStream(" 123"); + + assertThat(ps.currentChar()).hasValue(' '); + assertThat(ps.currentPeek()).hasValue(' '); + + ps.peekBlank(); + + assertThat(ps.currentChar()).hasValue(' '); + assertThat(ps.currentPeek()).hasValue('1'); + } + + @Test + void testSkipBlank() { + var ps = new FluentStream(" 123"); + + assertThat(ps.currentChar()).hasValue(' '); + assertThat(ps.currentPeek()).hasValue(' '); + + ps.skipBlank(); + + assertThat(ps.currentChar()).hasValue('1'); + assertThat(ps.currentPeek()).hasValue('1'); + } + + @Test + void testIsNumberStart() { + var ps = new FluentStream("123"); + assertThat(ps.isNumberStart()).isTrue(); + + var ps1 = new FluentStream("-123"); + assertThat(ps1.isNumberStart()).isTrue(); + + var ps2 = new FluentStream("a123"); + assertThat(ps2.isNumberStart()).isFalse(); + } + + @Test + void testIsIdentifierStart() { + var ps = new FluentStream("foo = Bar"); + assertThat(ps.isIdentifierStart()).isTrue(); + + var ps1 = new FluentStream("# foo = Bar"); + assertThat(ps1.isIdentifierStart()).isFalse(); + } + + @Test + void testIsValueStart() { + var ps = new FluentStream("foo = Bar\n"); + assertThat(ps.isValueStart()).isTrue(); + for (int i = 0; i < 9; i++) { + ps.next(); + } + assertThat(ps.isValueStart()).isFalse(); + } +} diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/ParserStreamTest.java b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/ParserStreamTest.java new file mode 100644 index 0000000..d8cda76 --- /dev/null +++ b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/ParserStreamTest.java @@ -0,0 +1,181 @@ +package ru.di9.fluent.syntax.parser; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class ParserStreamTest { + ParserStream ps; + + @BeforeEach + void setUp() { + ps = new ParserStream("abcd"); + } + + @Test + void next() { + assertThat(ps.currentChar()).hasValue('a'); + assertThat(ps.index).isEqualTo(0); + + assertThat(ps.next()).hasValue('b'); + assertThat(ps.currentChar()).hasValue('b'); + assertThat(ps.index).isEqualTo(1); + + assertThat(ps.next()).hasValue('c'); + assertThat(ps.currentChar()).hasValue('c'); + assertThat(ps.index).isEqualTo(2); + + assertThat(ps.next()).hasValue('d'); + assertThat(ps.currentChar()).hasValue('d'); + assertThat(ps.index).isEqualTo(3); + + assertThat(ps.next()).isEmpty(); + assertThat(ps.currentChar()).isEmpty(); + assertThat(ps.index).isEqualTo(4); + } + + @Test + void peek() { + assertThat(ps.currentPeek()).hasValue('a'); + assertThat(ps.peekOffset).isEqualTo(0); + + assertThat(ps.peek()).hasValue('b'); + assertThat(ps.currentPeek()).hasValue('b'); + assertThat(ps.peekOffset).isEqualTo(1); + + assertThat(ps.peek()).hasValue('c'); + assertThat(ps.currentPeek()).hasValue('c'); + assertThat(ps.peekOffset).isEqualTo(2); + + assertThat(ps.peek()).hasValue('d'); + assertThat(ps.currentPeek()).hasValue('d'); + assertThat(ps.peekOffset).isEqualTo(3); + + assertThat(ps.peek()).isEmpty(); + assertThat(ps.currentPeek()).isEmpty(); + assertThat(ps.peekOffset).isEqualTo(4); + } + + @Test + void peekAndNext() { + assertThat(ps.peek()).hasValue('b'); + assertThat(ps.peekOffset).isEqualTo(1); + assertThat(ps.index).isEqualTo(0); + + assertThat(ps.next()).hasValue('b'); + assertThat(ps.peekOffset).isEqualTo(0); + assertThat(ps.index).isEqualTo(1); + + assertThat(ps.peek()).hasValue('c'); + assertThat(ps.peekOffset).isEqualTo(1); + assertThat(ps.index).isEqualTo(1); + + assertThat(ps.next()).hasValue('c'); + assertThat(ps.peekOffset).isEqualTo(0); + assertThat(ps.index).isEqualTo(2); + assertThat(ps.currentChar()).hasValue('c'); + assertThat(ps.currentPeek()).hasValue('c'); + + assertThat(ps.peek()).hasValue('d'); + assertThat(ps.peekOffset).isEqualTo(1); + assertThat(ps.index).isEqualTo(2); + + assertThat(ps.next()).hasValue('d'); + assertThat(ps.peekOffset).isEqualTo(0); + assertThat(ps.index).isEqualTo(3); + assertThat(ps.currentChar()).hasValue('d'); + assertThat(ps.currentPeek()).hasValue('d'); + + assertThat(ps.peek()).isEmpty(); + assertThat(ps.peekOffset).isEqualTo(1); + assertThat(ps.index).isEqualTo(3); + assertThat(ps.currentChar()).hasValue('d'); + assertThat(ps.currentPeek()).isEmpty(); + + assertThat(ps.peek()).isEmpty(); + assertThat(ps.peekOffset).isEqualTo(2); + assertThat(ps.index).isEqualTo(3); + + assertThat(ps.next()).isEmpty(); + assertThat(ps.peekOffset).isEqualTo(0); + assertThat(ps.index).isEqualTo(4); + } + + @Test + void skipAndPeek() { + ps.peek(); + ps.peek(); + + ps.skipToPeek(); + + assertThat(ps.currentChar()).hasValue('c'); + assertThat(ps.currentPeek()).hasValue('c'); + assertThat(ps.peekOffset).isEqualTo(0); + assertThat(ps.index).isEqualTo(2); + + ps.peek(); + + assertThat(ps.currentChar()).hasValue('c'); + assertThat(ps.currentPeek()).hasValue('d'); + assertThat(ps.peekOffset).isEqualTo(1); + assertThat(ps.index).isEqualTo(2); + + ps.next(); + + assertThat(ps.currentChar()).hasValue('d'); + assertThat(ps.currentPeek()).hasValue('d'); + assertThat(ps.peekOffset).isEqualTo(0); + assertThat(ps.index).isEqualTo(3); + } + + @Test + void resetPeek() { + ps.next(); + ps.peek(); + ps.peek(); + ps.resetPeek(); + + assertThat(ps.currentChar()).hasValue('b'); + assertThat(ps.currentPeek()).hasValue('b'); + assertThat(ps.peekOffset).isEqualTo(0); + assertThat(ps.index).isEqualTo(1); + + ps.peek(); + + assertThat(ps.currentChar()).hasValue('b'); + assertThat(ps.currentPeek()).hasValue('c'); + assertThat(ps.peekOffset).isEqualTo(1); + assertThat(ps.index).isEqualTo(1); + + ps.peek(); + ps.peek(); + ps.peek(); + ps.resetPeek(); + + assertThat(ps.currentChar()).hasValue('b'); + assertThat(ps.currentPeek()).hasValue('b'); + assertThat(ps.peekOffset).isEqualTo(0); + assertThat(ps.index).isEqualTo(1); + + assertThat(ps.peek()).hasValue('c'); + assertThat(ps.currentChar()).hasValue('b'); + assertThat(ps.currentPeek()).hasValue('c'); + assertThat(ps.peekOffset).isEqualTo(1); + assertThat(ps.index).isEqualTo(1); + + assertThat(ps.peek()).hasValue('d'); + assertThat(ps.peek()).isEmpty(); + } + + @Test + void indexOutOfRange() { + ps.next(); + ps.next(); + ps.next(); + ps.next(); + + assertThat(ps.index).isGreaterThanOrEqualTo(ps.string.length()); + assertThat(ps.next()).isEmpty(); + } +} diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/ReferenceTest.java b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/ReferenceTest.java new file mode 100644 index 0000000..19de371 --- /dev/null +++ b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/ReferenceTest.java @@ -0,0 +1,21 @@ +package ru.di9.fluent.syntax.parser; + +import java.nio.file.Path; + +public class ReferenceTest extends AbstractFixturesTest { + + @Override + protected Path getFixturesPath() { + return Path.of("src", "test", "resources", "reference_fixtures"); + } + + @Override + protected boolean isWithSpans(String testName) { + return !testName.equals("crlf") && !testName.equals("select_expressions"); + } + + @Override + protected boolean isWithJunkAnnotations(String testName) { + return !testName.equals("crlf") && !testName.equals("select_expressions"); + } +} diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/StructureTest.java b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/StructureTest.java new file mode 100644 index 0000000..9e25a60 --- /dev/null +++ b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/StructureTest.java @@ -0,0 +1,11 @@ +package ru.di9.fluent.syntax.parser; + +import java.nio.file.Path; + +public class StructureTest extends AbstractFixturesTest { + + @Override + protected Path getFixturesPath() { + return Path.of("src", "test", "resources", "structure_fixtures"); + } +} diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/processor/ProcessorTest.java b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/processor/ProcessorTest.java new file mode 100644 index 0000000..1c74812 --- /dev/null +++ b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/processor/ProcessorTest.java @@ -0,0 +1,210 @@ +package ru.di9.fluent.syntax.processor; + +import org.junit.jupiter.api.Test; +import ru.di9.fluent.syntax.ast.*; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ProcessorTest { + + @Test + void toProcessedPattern() { + var processor = new Processor(); + + var pattern = new Pattern(); + pattern.getElements().clear(); + pattern.getElements().add(new TextElement("Hi")); + assertEquals(pattern, processor.unescapeLiteralsToText(pattern)); + + pattern.getElements().clear(); + pattern.getElements().addAll(List.of( + new Placeable(new StringLiteral("\\\\")), + new TextElement(" "), + new Placeable(new StringLiteral("\\\"")) + )); + assertEquals( + new Pattern(new TextElement("\\ \"")), + processor.unescapeLiteralsToText(pattern) + ); + + pattern.getElements().clear(); + pattern.getElements().addAll(List.of( + new TextElement("Foo "), + new Placeable(new StringLiteral("Bar")) + )); + assertEquals( + new Pattern(new TextElement("Foo Bar")), + processor.unescapeLiteralsToText(pattern) + ); + assertEquals( + new Pattern( + new TextElement("Foo "), + new Placeable(new StringLiteral("Bar")) + ), + pattern + ); + + pattern.getElements().clear(); + pattern.getElements().addAll(List.of( + new TextElement("Hi,"), + new Placeable(new StringLiteral("\\u0020")), + new TextElement("there") + )); + assertEquals( + new Pattern(new TextElement("Hi, there")), + processor.unescapeLiteralsToText(pattern) + ); + + pattern.getElements().clear(); + pattern.getElements().addAll(List.of( + new TextElement("Emoji: "), + new Placeable(new StringLiteral("\\U01f602")) + )); + assertEquals( + new Pattern(new TextElement("Emoji: \uD83D\uDE02")), + processor.unescapeLiteralsToText(pattern) + ); + assertEquals( + new Pattern(new TextElement("Emoji: 😂")), + processor.unescapeLiteralsToText(pattern) + ); + + pattern.getElements().clear(); + pattern.getElements().addAll(List.of( + new TextElement("Illegal escape sequence: "), + new Placeable(new StringLiteral("\\ud800")) + )); + assertEquals( + new Pattern(new TextElement("Illegal escape sequence: �")), + processor.unescapeLiteralsToText(pattern) + ); + + pattern.getElements().clear(); + pattern.getElements().addAll(List.of( + new TextElement("Illegal escape sequence: "), + new Placeable(new StringLiteral("\\U00d800")) + )); + assertEquals( + new Pattern(new TextElement("Illegal escape sequence: �")), + processor.unescapeLiteralsToText(pattern) + ); + + pattern.getElements().clear(); + pattern.getElements().addAll(List.of( + new TextElement("Hi, "), + new Placeable(new StringLiteral("{")), + new TextElement(" there") + )); + assertEquals( + new Pattern(new TextElement("Hi, { there")), + processor.unescapeLiteralsToText(pattern) + ); + + pattern.getElements().clear(); + pattern.getElements().addAll(List.of( + new TextElement("Foo\n"), + new Placeable(new StringLiteral(".")), + new TextElement("bar") + )); + assertEquals( + new Pattern(new TextElement("Foo\n.bar")), + processor.unescapeLiteralsToText(pattern) + ); + + pattern.getElements().clear(); + pattern.getElements().addAll(List.of( + new TextElement("Foo "), + new Placeable( + new SelectExpression( + new NumberLiteral("1"), + List.of( + new Variant( + new Identifier("other"), + new Pattern( + new TextElement("bar "), + new Placeable( + new StringLiteral("{-_-}") + ) + ), + true + ) + ) + ) + ), + new TextElement(" baz") + )); + assertEquals( + new Pattern( + new TextElement("Foo "), + new Placeable( + new SelectExpression( + new NumberLiteral("1"), + List.of( + new Variant( + new Identifier("other"), + new Pattern(new TextElement("bar {-_-}")), + true) + ) + ) + ), + new TextElement(" baz") + + ), + processor.unescapeLiteralsToText(pattern) + ); + } + + @Test + void toRawPattern() { + var processor = new Processor(); + + var pattern = new Pattern(); + pattern.getElements().clear(); + pattern.getElements().add(new TextElement("Hi")); + assertEquals(pattern, processor.escapeTextToLiterals(pattern)); + + pattern.getElements().clear(); + pattern.getElements().add(new TextElement("\\ \"")); + assertEquals(pattern, processor.escapeTextToLiterals(pattern)); + + pattern.getElements().clear(); + pattern.getElements().add(new TextElement("Hi, {-_-}")); + assertEquals( + new Pattern( + new TextElement("Hi, "), + new Placeable(new StringLiteral("{")), + new TextElement("-_-"), + new Placeable(new StringLiteral("}")) + ), + processor.escapeTextToLiterals(pattern) + ); + + pattern.getElements().clear(); + pattern.getElements().add(new TextElement("Foo\nbar")); + assertEquals(pattern, processor.escapeTextToLiterals(pattern)); + + pattern.getElements().clear(); + pattern.getElements().add(new TextElement("Foo\n*bar")); + assertEquals( + new Pattern( + new TextElement("Foo\n"), + new Placeable(new StringLiteral("*")), + new TextElement("bar") + ), + processor.escapeTextToLiterals(pattern) + ); + + pattern.getElements().clear(); + pattern.getElements().add(new TextElement("\nFoo\nbar ")); + assertEquals( + new Pattern( + new Placeable(new StringLiteral("")), + new TextElement("\nFoo\nbar "), + new Placeable(new StringLiteral("")) + ), + processor.escapeTextToLiterals(pattern) + ); + } +} diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeEntryTest.java b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeEntryTest.java new file mode 100644 index 0000000..85568c8 --- /dev/null +++ b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeEntryTest.java @@ -0,0 +1,24 @@ +package ru.di9.fluent.syntax.serializer; + +import org.junit.jupiter.api.Test; +import ru.di9.fluent.syntax.parser.FluentParser; + +import static org.assertj.core.api.Assertions.assertThat; + +class SerializeEntryTest { + + @Test + void test() { + String input = """ + # Attached comment + key = Value"""; + + var parser = new FluentParser(); + var topLevel = parser.parse(input).getBody().get(0); + + var serializer = new FluentSerializer(); + var serialized = serializer.serialize(topLevel).trim(); + + assertThat(serialized).isEqualTo(input); + } +} diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeResourceTest.java b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeResourceTest.java new file mode 100644 index 0000000..0a8dead --- /dev/null +++ b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeResourceTest.java @@ -0,0 +1,68 @@ +package ru.di9.fluent.syntax.serializer; + +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; +import ru.di9.fluent.syntax.ast.Resource; +import ru.di9.fluent.syntax.parser.FluentParser; +import ru.di9.fluent.test.utils.Tuple3; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static ru.di9.fluent.test.utils.FileUtils.getExt; +import static ru.di9.fluent.test.utils.FileUtils.getName; + +class SerializeResourceTest { + + @TestFactory + Iterable test() throws IOException { + SortedSet tests = new TreeSet<>(Comparator.comparing(DynamicNode::getDisplayName)); + + try (Stream walker = Files.walk(Path.of("src", "test", "resources", "serialized"))) { + walker + .filter(path -> path.toFile().isFile()) + .filter(path -> getExt(path).equals("ftl")) + .map(path -> { + Path parent = path.getParent(); + String name = getName(path); + + try { + Path ftlSourcePath = parent.resolve(name + ".ftl"); + Path expSourcePath = parent.resolve(name + ".exp.txt"); + + String ftlSource = Files.readString(ftlSourcePath); + String expSource; + + if (Files.exists(expSourcePath)) { + expSource = Files.readString(expSourcePath); + } else { + expSource = ftlSource; + } + + return new Tuple3(name, ftlSource, expSource); + } catch (Exception e) { + throw new RuntimeException(e); + } + }) + .map(tuple -> DynamicTest.dynamicTest(tuple.value1(), () -> { + FluentParser parser = new FluentParser(); + Resource resource = parser.parse(tuple.value2()); + + FluentSerializer serializer = new FluentSerializer(); + String serialized = serializer.serialize(resource); + + assertThat(serialized).isEqualTo(tuple.value3()); + })) + .forEach(tests::add); + } + + return tests; + } +} diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/visitor/VisitorTest.java b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/visitor/VisitorTest.java new file mode 100644 index 0000000..7c243c0 --- /dev/null +++ b/fluent.syntax/src/test/java/ru/di9/fluent/syntax/visitor/VisitorTest.java @@ -0,0 +1,80 @@ +package ru.di9.fluent.syntax.visitor; + +import lombok.Getter; +import lombok.Setter; +import org.junit.jupiter.api.Test; +import ru.di9.fluent.syntax.ast.Identifier; +import ru.di9.fluent.syntax.ast.Pattern; +import ru.di9.fluent.syntax.ast.TextElement; +import ru.di9.fluent.syntax.ast.Variant; +import ru.di9.fluent.syntax.parser.FluentParser; + +import java.util.Iterator; +import java.util.List; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.regex.Matcher; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import static org.junit.jupiter.api.Assertions.*; + +class VisitorTest { + FluentParser parser = new FluentParser(); + + @Test + void test_basics() { + var visitor = new TestableVisitor(); + var source = """ + msg = foo {$var -> + *[other] bar + } baz"""; + var res = parser.parse(source); + visitor.visit(res); + assertEquals(3, visitor.wordCount); + assertEquals(2, visitor.patternCount); + assertEquals(1, visitor.variantCount); + } + + @Test + void test_childrenOf() { + var variant = new Variant(new Identifier("other"), new Pattern(), true); + Iterator itr = Visitor.childrenOf(variant); + + Spliterator spliterator = Spliterators.spliteratorUnknownSize(itr, Spliterator.ORDERED); + Stream stream = StreamSupport.stream(spliterator, false); + + assertEquals( + List.of("getKey", "getSpan", "getValue"), + stream.map(Visitor.Pair::name).sorted().toList() + ); + } + + @Getter + @Setter + @SuppressWarnings("unused") + static class TestableVisitor extends Visitor { + private static final java.util.regex.Pattern WORDS = java.util.regex.Pattern.compile("\\w+"); + + private int patternCount = 0; + private int variantCount = 0; + private int wordCount = 0; + + public void visitPattern(Pattern node) { + super.genericVisit(node); + patternCount++; + } + + public void visitVariant(Variant node) { + super.genericVisit(node); + variantCount++; + } + + public void visitTextElement(TextElement node) { + Matcher matcher = WORDS.matcher(node.getValue()); + while (matcher.find()) { + wordCount++; + } + } + } +} diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/AstAssert.java b/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/AstAssert.java new file mode 100644 index 0000000..ead430c --- /dev/null +++ b/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/AstAssert.java @@ -0,0 +1,49 @@ +package ru.di9.fluent.test.utils; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.assertj.core.api.AbstractAssert; +import org.json.JSONException; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; +import ru.di9.fluent.syntax.ast.BaseNode; + +import java.io.PrintStream; + +@SuppressWarnings("UnusedReturnValue") +public class AstAssert extends AbstractAssert { + private final Gson gson; + private final NullIgnoreComparator nullIgnoreComparator; + + protected AstAssert(BaseNode actualNode) { + super(actualNode, AstAssert.class); + gson = new GsonBuilder() + .setPrettyPrinting() + .registerTypeHierarchyAdapter(BaseNode.class, new BaseNodeJsonSerializer()) + .create(); + nullIgnoreComparator = new NullIgnoreComparator(JSONCompareMode.STRICT); + } + + @SuppressWarnings("unused") + public AstAssert printAstJson(PrintStream printStream) { + var json = gson.toJson(actual); + printStream.println(json); + return this; + } + + public AstAssert isEqualAstJson(String astJson) { + var actualJson = gson.toJson(actual); + + try { + JSONAssert.assertEquals(astJson, actualJson, nullIgnoreComparator); + } catch (JSONException e) { + failWithMessage(e.getMessage()); + } + + return this; + } + + public static AstAssert assertThat(BaseNode baseNode) { + return new AstAssert(baseNode); + } +} diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/BaseNodeJsonSerializer.java b/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/BaseNodeJsonSerializer.java new file mode 100644 index 0000000..c2f3857 --- /dev/null +++ b/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/BaseNodeJsonSerializer.java @@ -0,0 +1,43 @@ +package ru.di9.fluent.test.utils; + +import com.google.gson.*; +import org.joor.Reflect; +import ru.di9.fluent.syntax.ast.BaseNode; +import ru.di9.fluent.syntax.ast.Whitespace; + +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Map; + +public class BaseNodeJsonSerializer implements JsonSerializer { + + @Override + public JsonElement serialize(BaseNode baseNode, Type type, JsonSerializationContext ctx) { + var root = new JsonObject(); + root.add("type", new JsonPrimitive(baseNode.getClass().getSimpleName())); + + var fields = Reflect.on(baseNode).fields(); + + for (Map.Entry field : fields.entrySet()) { + String name = field.getKey().equalsIgnoreCase("isDefault") ? "default" : field.getKey(); + Object value = field.getValue().get(); + + if (value instanceof Collection collection) { + var jsonArray = new JsonArray(); + + for (Object item : collection) { + if (item instanceof Whitespace) { + continue; + } + jsonArray.add(ctx.serialize(item)); + } + + root.add(name, jsonArray); + } else { + root.add(name, ctx.serialize(value)); + } + } + + return root; + } +} diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/FileUtils.java b/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/FileUtils.java new file mode 100644 index 0000000..8a3e7ea --- /dev/null +++ b/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/FileUtils.java @@ -0,0 +1,26 @@ +package ru.di9.fluent.test.utils; + +import java.nio.file.Path; + +public interface FileUtils { + + static String getExt(String fileName) { + var idx = fileName.lastIndexOf('.'); + if (idx < 0) return ""; + return fileName.substring(idx + 1); + } + + static String getName(String fileName) { + var idx = fileName.lastIndexOf('.'); + if (idx < 0) return fileName; + return fileName.substring(0, idx); + } + + static String getName(Path pathToFile) { + return getName(pathToFile.getFileName().toString()); + } + + static String getExt(Path pathToFile) { + return getExt(pathToFile.getFileName().toString()); + } +} diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/NullIgnoreComparator.java b/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/NullIgnoreComparator.java new file mode 100644 index 0000000..a9461f2 --- /dev/null +++ b/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/NullIgnoreComparator.java @@ -0,0 +1,32 @@ +package ru.di9.fluent.test.utils; + +import org.json.JSONException; +import org.json.JSONObject; +import org.skyscreamer.jsonassert.JSONCompareMode; +import org.skyscreamer.jsonassert.JSONCompareResult; +import org.skyscreamer.jsonassert.comparator.DefaultComparator; + +import java.util.Set; + +import static org.skyscreamer.jsonassert.comparator.JSONCompareUtil.getKeys; +import static org.skyscreamer.jsonassert.comparator.JSONCompareUtil.qualify; + +public class NullIgnoreComparator extends DefaultComparator { + public NullIgnoreComparator(JSONCompareMode mode) { + super(mode); + } + + @Override + protected void checkJsonObjectKeysExpectedInActual(String prefix, JSONObject expected, JSONObject actual, JSONCompareResult result) throws JSONException { + Set expectedKeys = getKeys(expected); + for (String key : expectedKeys) { + Object expectedValue = expected.get(key); + if (actual.has(key)) { + Object actualValue = actual.get(key); + compareValues(qualify(prefix, key), expectedValue, actualValue, result); + } else if (!expectedValue.equals(JSONObject.NULL)) { + result.missing(prefix, key); + } + } + } +} diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/Tuple3.java b/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/Tuple3.java new file mode 100644 index 0000000..77a2ade --- /dev/null +++ b/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/Tuple3.java @@ -0,0 +1,4 @@ +package ru.di9.fluent.test.utils; + +public record Tuple3(String value1, String value2, String value3) { +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/.gitattributes b/fluent.syntax/src/test/resources/reference_fixtures/.gitattributes new file mode 100644 index 0000000..a11ffd3 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/.gitattributes @@ -0,0 +1,2 @@ +crlf.ftl eol=crlf +cr.ftl eol=cr diff --git a/fluent.syntax/src/test/resources/reference_fixtures/any_char.ftl b/fluent.syntax/src/test/resources/reference_fixtures/any_char.ftl new file mode 100644 index 0000000..6966a0d --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/any_char.ftl @@ -0,0 +1,8 @@ +# ↓ BEL, U+0007 +control0 = abcdef + +# ↓ DEL, U+007F +delete = abcdef + +# ↓ BPM, U+0082 +control1 = abc‚def diff --git a/fluent.syntax/src/test/resources/reference_fixtures/any_char.json b/fluent.syntax/src/test/resources/reference_fixtures/any_char.json new file mode 100644 index 0000000..e1eb3cc --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/any_char.json @@ -0,0 +1,148 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "control0", + "span": { + "type": "Span", + "start": 28, + "end": 36 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "abc\u0007def", + "span": { + "type": "Span", + "start": 39, + "end": 46 + } + } + ], + "span": { + "type": "Span", + "start": 39, + "end": 46 + } + }, + "attributes": [], + "comment": { + "content": " ↓ BEL, U+0007", + "type": "Comment", + "span": { + "type": "Span", + "start": 0, + "end": 27 + } + }, + "span": { + "type": "Span", + "start": 0, + "end": 46 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "delete", + "span": { + "type": "Span", + "start": 74, + "end": 80 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "abcdef", + "span": { + "type": "Span", + "start": 83, + "end": 90 + } + } + ], + "span": { + "type": "Span", + "start": 83, + "end": 90 + } + }, + "attributes": [], + "comment": { + "content": " ↓ DEL, U+007F", + "type": "Comment", + "span": { + "type": "Span", + "start": 48, + "end": 73 + } + }, + "span": { + "type": "Span", + "start": 48, + "end": 90 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "control1", + "span": { + "type": "Span", + "start": 120, + "end": 128 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "abc‚def", + "span": { + "type": "Span", + "start": 131, + "end": 138 + } + } + ], + "span": { + "type": "Span", + "start": 131, + "end": 138 + } + }, + "attributes": [], + "comment": { + "content": " ↓ BPM, U+0082", + "type": "Comment", + "span": { + "type": "Span", + "start": 92, + "end": 119 + } + }, + "span": { + "type": "Span", + "start": 92, + "end": 138 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 139 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/astral.ftl b/fluent.syntax/src/test/resources/reference_fixtures/astral.ftl new file mode 100644 index 0000000..b77e32e --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/astral.ftl @@ -0,0 +1,20 @@ +face-with-tears-of-joy = 😂 +tetragram-for-centre = 𝌆 + +surrogates-in-text = \uD83D\uDE02 +surrogates-in-string = {"\uD83D\uDE02"} +surrogates-in-adjacent-strings = {"\uD83D"}{"\uDE02"} + +emoji-in-text = A face 😂 with tears of joy. +emoji-in-string = {"A face 😂 with tears of joy."} + +# ERROR Invalid identifier +err-😂 = Value + +# ERROR Invalid expression +err-invalid-expression = { 😂 } + +# ERROR Invalid variant key +err-invalid-variant-key = { $sel -> + *[😂] Value +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/astral.json b/fluent.syntax/src/test/resources/reference_fixtures/astral.json new file mode 100644 index 0000000..6e50f16 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/astral.json @@ -0,0 +1,414 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "face-with-tears-of-joy", + "span": { + "type": "Span", + "start": 0, + "end": 22 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "😂", + "span": { + "type": "Span", + "start": 25, + "end": 27 + } + } + ], + "span": { + "type": "Span", + "start": 25, + "end": 27 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 27 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "tetragram-for-centre", + "span": { + "type": "Span", + "start": 28, + "end": 48 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "𝌆", + "span": { + "type": "Span", + "start": 51, + "end": 53 + } + } + ], + "span": { + "type": "Span", + "start": 51, + "end": 53 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 28, + "end": 53 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "surrogates-in-text", + "span": { + "type": "Span", + "start": 55, + "end": 73 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "\\uD83D\\uDE02", + "span": { + "type": "Span", + "start": 76, + "end": 88 + } + } + ], + "span": { + "type": "Span", + "start": 76, + "end": 88 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 55, + "end": 88 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "surrogates-in-string", + "span": { + "type": "Span", + "start": 89, + "end": 109 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "\\uD83D\\uDE02", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 113, + "end": 127 + } + }, + "span": { + "type": "Span", + "start": 112, + "end": 128 + } + } + ], + "span": { + "type": "Span", + "start": 112, + "end": 128 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 89, + "end": 128 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "surrogates-in-adjacent-strings", + "span": { + "type": "Span", + "start": 129, + "end": 159 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "\\uD83D", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 163, + "end": 171 + } + }, + "span": { + "type": "Span", + "start": 162, + "end": 172 + } + }, + { + "type": "Placeable", + "expression": { + "value": "\\uDE02", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 173, + "end": 181 + } + }, + "span": { + "type": "Span", + "start": 172, + "end": 182 + } + } + ], + "span": { + "type": "Span", + "start": 162, + "end": 182 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 129, + "end": 182 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "emoji-in-text", + "span": { + "type": "Span", + "start": 184, + "end": 197 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "A face 😂 with tears of joy.", + "span": { + "type": "Span", + "start": 200, + "end": 228 + } + } + ], + "span": { + "type": "Span", + "start": 200, + "end": 228 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 184, + "end": 228 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "emoji-in-string", + "span": { + "type": "Span", + "start": 229, + "end": 244 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "A face 😂 with tears of joy.", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 248, + "end": 278 + } + }, + "span": { + "type": "Span", + "start": 247, + "end": 279 + } + } + ], + "span": { + "type": "Span", + "start": 247, + "end": 279 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 229, + "end": 279 + } + }, + { + "content": "ERROR Invalid identifier", + "type": "Comment", + "span": { + "type": "Span", + "start": 281, + "end": 307 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 312, + "end": 312 + } + } + ], + "content": "err-😂 = Value\n\n", + "span": { + "type": "Span", + "start": 308, + "end": 324 + } + }, + { + "content": "ERROR Invalid expression", + "type": "Comment", + "span": { + "type": "Span", + "start": 324, + "end": 350 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0028", + "arguments": [], + "message": "Expected an inline expression", + "span": { + "type": "Span", + "start": 378, + "end": 378 + } + } + ], + "content": "err-invalid-expression = { 😂 }\n\n", + "span": { + "type": "Span", + "start": 351, + "end": 384 + } + }, + { + "content": "ERROR Invalid variant key", + "type": "Comment", + "span": { + "type": "Span", + "start": 384, + "end": 411 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 454, + "end": 454 + } + } + ], + "content": "err-invalid-variant-key = { $sel ->\n *[😂] Value\n}\n", + "span": { + "type": "Span", + "start": 412, + "end": 466 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 466 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/call_expressions.ftl b/fluent.syntax/src/test/resources/reference_fixtures/call_expressions.ftl new file mode 100644 index 0000000..77c2188 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/call_expressions.ftl @@ -0,0 +1,120 @@ +## Function names + +valid-func-name-01 = {FUN1()} +valid-func-name-02 = {FUN_FUN()} +valid-func-name-03 = {FUN-FUN()} + +# JUNK 0 is not a valid Identifier start +invalid-func-name-01 = {0FUN()} +# JUNK Function names may not be lowercase +invalid-func-name-02 = {fun()} +# JUNK Function names may not contain lowercase character +invalid-func-name-03 = {Fun()} +# JUNK ? is not a valid Identifier character +invalid-func-name-04 = {FUN?()} + +## Arguments + +positional-args = {FUN(1, "a", msg)} +named-args = {FUN(x: 1, y: "Y")} +dense-named-args = {FUN(x:1, y:"Y")} +mixed-args = {FUN(1, "a", msg, x: 1, y: "Y")} + +# ERROR Positional arg must not follow keyword args +shuffled-args = {FUN(1, x: 1, "a", y: "Y", msg)} + +# ERROR Named arguments must be unique +duplicate-named-args = {FUN(x: 1, x: "X")} + + +## Whitespace around arguments + +sparse-inline-call = {FUN ( "a" , msg, x: 1 )} +empty-inline-call = {FUN( )} +multiline-call = {FUN( + "a", + msg, + x: 1 + )} +sparse-multiline-call = {FUN + ( + + "a" , + msg + , x: 1 + )} +empty-multiline-call = {FUN( + + )} + + +unindented-arg-number = {FUN( +1)} + +unindented-arg-string = {FUN( +"a")} + +unindented-arg-msg-ref = {FUN( +msg)} + +unindented-arg-term-ref = {FUN( +-msg)} + +unindented-arg-var-ref = {FUN( +$var)} + +unindented-arg-call = {FUN( +OTHER())} + +unindented-named-arg = {FUN( +x:1)} + +unindented-closing-paren = {FUN( + x +)} + + + +## Optional trailing comma + +one-argument = {FUN(1,)} +many-arguments = {FUN(1, 2, 3,)} +inline-sparse-args = {FUN( 1, 2, 3, )} +mulitline-args = {FUN( + 1, + 2, + )} +mulitline-sparse-args = {FUN( + + 1 + , + 2 + , + )} + + +## Syntax errors for trailing comma + +one-argument = {FUN(1,,)} +missing-arg = {FUN(,)} +missing-sparse-arg = {FUN( , )} + + +## Whitespace in named arguments + +sparse-named-arg = {FUN( + x : 1, + y : 2, + z + : + 3 + )} + + +unindented-colon = {FUN( + x +:1)} + +unindented-value = {FUN( + x: +1)} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/call_expressions.json b/fluent.syntax/src/test/resources/reference_fixtures/call_expressions.json new file mode 100644 index 0000000..5a4a74f --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/call_expressions.json @@ -0,0 +1,2898 @@ +{ + "type": "Resource", + "body": [ + { + "content": "Function names", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 0, + "end": 17 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "valid-func-name-01", + "span": { + "type": "Span", + "start": 19, + "end": 37 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN1", + "span": { + "type": "Span", + "start": 41, + "end": 45 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [], + "span": { + "type": "Span", + "start": 45, + "end": 47 + } + }, + "span": { + "type": "Span", + "start": 41, + "end": 47 + } + }, + "span": { + "type": "Span", + "start": 40, + "end": 48 + } + } + ], + "span": { + "type": "Span", + "start": 40, + "end": 48 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 19, + "end": 48 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "valid-func-name-02", + "span": { + "type": "Span", + "start": 49, + "end": 67 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN_FUN", + "span": { + "type": "Span", + "start": 71, + "end": 78 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [], + "span": { + "type": "Span", + "start": 78, + "end": 80 + } + }, + "span": { + "type": "Span", + "start": 71, + "end": 80 + } + }, + "span": { + "type": "Span", + "start": 70, + "end": 81 + } + } + ], + "span": { + "type": "Span", + "start": 70, + "end": 81 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 49, + "end": 81 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "valid-func-name-03", + "span": { + "type": "Span", + "start": 82, + "end": 100 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN-FUN", + "span": { + "type": "Span", + "start": 104, + "end": 111 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [], + "span": { + "type": "Span", + "start": 111, + "end": 113 + } + }, + "span": { + "type": "Span", + "start": 104, + "end": 113 + } + }, + "span": { + "type": "Span", + "start": 103, + "end": 114 + } + } + ], + "span": { + "type": "Span", + "start": 103, + "end": 114 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 82, + "end": 114 + } + }, + { + "content": "JUNK 0 is not a valid Identifier start", + "type": "Comment", + "span": { + "type": "Span", + "start": 116, + "end": 156 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 182, + "end": 182 + } + } + ], + "content": "invalid-func-name-01 = {0FUN()}\n", + "span": { + "type": "Span", + "start": 157, + "end": 189 + } + }, + { + "content": "JUNK Function names may not be lowercase", + "type": "Comment", + "span": { + "type": "Span", + "start": 189, + "end": 231 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0008", + "arguments": [], + "message": "The callee has to be an upper-case identifier or a term", + "span": { + "type": "Span", + "start": 259, + "end": 259 + } + } + ], + "content": "invalid-func-name-02 = {fun()}\n", + "span": { + "type": "Span", + "start": 232, + "end": 263 + } + }, + { + "content": "JUNK Function names may not contain lowercase character", + "type": "Comment", + "span": { + "type": "Span", + "start": 263, + "end": 320 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0008", + "arguments": [], + "message": "The callee has to be an upper-case identifier or a term", + "span": { + "type": "Span", + "start": 348, + "end": 348 + } + } + ], + "content": "invalid-func-name-03 = {Fun()}\n", + "span": { + "type": "Span", + "start": 321, + "end": 352 + } + }, + { + "content": "JUNK ? is not a valid Identifier character", + "type": "Comment", + "span": { + "type": "Span", + "start": 352, + "end": 396 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 424, + "end": 424 + } + } + ], + "content": "invalid-func-name-04 = {FUN?()}\n\n", + "span": { + "type": "Span", + "start": 397, + "end": 430 + } + }, + { + "content": "Arguments", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 430, + "end": 442 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "positional-args", + "span": { + "type": "Span", + "start": 444, + "end": 459 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 463, + "end": 466 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [ + { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 467, + "end": 468 + } + }, + { + "value": "a", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 470, + "end": 473 + } + }, + { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "msg", + "span": { + "type": "Span", + "start": 475, + "end": 478 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 475, + "end": 478 + } + } + ], + "named": [], + "span": { + "type": "Span", + "start": 466, + "end": 479 + } + }, + "span": { + "type": "Span", + "start": 463, + "end": 479 + } + }, + "span": { + "type": "Span", + "start": 462, + "end": 480 + } + } + ], + "span": { + "type": "Span", + "start": 462, + "end": 480 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 444, + "end": 480 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "named-args", + "span": { + "type": "Span", + "start": 481, + "end": 491 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 495, + "end": 498 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [ + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "x", + "span": { + "type": "Span", + "start": 499, + "end": 500 + } + }, + "value": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 502, + "end": 503 + } + }, + "span": { + "type": "Span", + "start": 499, + "end": 503 + } + }, + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "y", + "span": { + "type": "Span", + "start": 505, + "end": 506 + } + }, + "value": { + "value": "Y", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 508, + "end": 511 + } + }, + "span": { + "type": "Span", + "start": 505, + "end": 511 + } + } + ], + "span": { + "type": "Span", + "start": 498, + "end": 512 + } + }, + "span": { + "type": "Span", + "start": 495, + "end": 512 + } + }, + "span": { + "type": "Span", + "start": 494, + "end": 513 + } + } + ], + "span": { + "type": "Span", + "start": 494, + "end": 513 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 481, + "end": 513 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "dense-named-args", + "span": { + "type": "Span", + "start": 514, + "end": 530 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 534, + "end": 537 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [ + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "x", + "span": { + "type": "Span", + "start": 538, + "end": 539 + } + }, + "value": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 540, + "end": 541 + } + }, + "span": { + "type": "Span", + "start": 538, + "end": 541 + } + }, + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "y", + "span": { + "type": "Span", + "start": 543, + "end": 544 + } + }, + "value": { + "value": "Y", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 545, + "end": 548 + } + }, + "span": { + "type": "Span", + "start": 543, + "end": 548 + } + } + ], + "span": { + "type": "Span", + "start": 537, + "end": 549 + } + }, + "span": { + "type": "Span", + "start": 534, + "end": 549 + } + }, + "span": { + "type": "Span", + "start": 533, + "end": 550 + } + } + ], + "span": { + "type": "Span", + "start": 533, + "end": 550 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 514, + "end": 550 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "mixed-args", + "span": { + "type": "Span", + "start": 551, + "end": 561 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 565, + "end": 568 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [ + { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 569, + "end": 570 + } + }, + { + "value": "a", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 572, + "end": 575 + } + }, + { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "msg", + "span": { + "type": "Span", + "start": 577, + "end": 580 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 577, + "end": 580 + } + } + ], + "named": [ + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "x", + "span": { + "type": "Span", + "start": 582, + "end": 583 + } + }, + "value": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 585, + "end": 586 + } + }, + "span": { + "type": "Span", + "start": 582, + "end": 586 + } + }, + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "y", + "span": { + "type": "Span", + "start": 588, + "end": 589 + } + }, + "value": { + "value": "Y", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 591, + "end": 594 + } + }, + "span": { + "type": "Span", + "start": 588, + "end": 594 + } + } + ], + "span": { + "type": "Span", + "start": 568, + "end": 595 + } + }, + "span": { + "type": "Span", + "start": 565, + "end": 595 + } + }, + "span": { + "type": "Span", + "start": 564, + "end": 596 + } + } + ], + "span": { + "type": "Span", + "start": 564, + "end": 596 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 551, + "end": 596 + } + }, + { + "content": "ERROR Positional arg must not follow keyword args", + "type": "Comment", + "span": { + "type": "Span", + "start": 598, + "end": 649 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0021", + "arguments": [], + "message": "Positional arguments must not follow named arguments", + "span": { + "type": "Span", + "start": 683, + "end": 683 + } + } + ], + "content": "shuffled-args = {FUN(1, x: 1, \"a\", y: \"Y\", msg)}\n\n", + "span": { + "type": "Span", + "start": 650, + "end": 700 + } + }, + { + "content": "ERROR Named arguments must be unique", + "type": "Comment", + "span": { + "type": "Span", + "start": 700, + "end": 738 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0022", + "arguments": [], + "message": "Named arguments must be unique", + "span": { + "type": "Span", + "start": 779, + "end": 779 + } + } + ], + "content": "duplicate-named-args = {FUN(x: 1, x: \"X\")}\n\n\n", + "span": { + "type": "Span", + "start": 739, + "end": 784 + } + }, + { + "content": "Whitespace around arguments", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 784, + "end": 814 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "sparse-inline-call", + "span": { + "type": "Span", + "start": 816, + "end": 834 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 838, + "end": 841 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [ + { + "value": "a", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 849, + "end": 852 + } + }, + { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "msg", + "span": { + "type": "Span", + "start": 856, + "end": 859 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 856, + "end": 859 + } + } + ], + "named": [ + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "x", + "span": { + "type": "Span", + "start": 863, + "end": 864 + } + }, + "value": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 866, + "end": 867 + } + }, + "span": { + "type": "Span", + "start": 863, + "end": 867 + } + } + ], + "span": { + "type": "Span", + "start": 846, + "end": 871 + } + }, + "span": { + "type": "Span", + "start": 838, + "end": 871 + } + }, + "span": { + "type": "Span", + "start": 837, + "end": 872 + } + } + ], + "span": { + "type": "Span", + "start": 837, + "end": 872 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 816, + "end": 872 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "empty-inline-call", + "span": { + "type": "Span", + "start": 873, + "end": 890 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 894, + "end": 897 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [], + "span": { + "type": "Span", + "start": 897, + "end": 901 + } + }, + "span": { + "type": "Span", + "start": 894, + "end": 901 + } + }, + "span": { + "type": "Span", + "start": 893, + "end": 902 + } + } + ], + "span": { + "type": "Span", + "start": 893, + "end": 902 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 873, + "end": 902 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "multiline-call", + "span": { + "type": "Span", + "start": 903, + "end": 917 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 921, + "end": 924 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [ + { + "value": "a", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 934, + "end": 937 + } + }, + { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "msg", + "span": { + "type": "Span", + "start": 947, + "end": 950 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 947, + "end": 950 + } + } + ], + "named": [ + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "x", + "span": { + "type": "Span", + "start": 960, + "end": 961 + } + }, + "value": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 963, + "end": 964 + } + }, + "span": { + "type": "Span", + "start": 960, + "end": 964 + } + } + ], + "span": { + "type": "Span", + "start": 924, + "end": 970 + } + }, + "span": { + "type": "Span", + "start": 921, + "end": 970 + } + }, + "span": { + "type": "Span", + "start": 920, + "end": 971 + } + } + ], + "span": { + "type": "Span", + "start": 920, + "end": 971 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 903, + "end": 971 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "sparse-multiline-call", + "span": { + "type": "Span", + "start": 972, + "end": 993 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 997, + "end": 1000 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [ + { + "value": "a", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 1016, + "end": 1019 + } + }, + { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "msg", + "span": { + "type": "Span", + "start": 1033, + "end": 1036 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 1033, + "end": 1036 + } + } + ], + "named": [ + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "x", + "span": { + "type": "Span", + "start": 1047, + "end": 1048 + } + }, + "value": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 1050, + "end": 1051 + } + }, + "span": { + "type": "Span", + "start": 1047, + "end": 1051 + } + } + ], + "span": { + "type": "Span", + "start": 1005, + "end": 1057 + } + }, + "span": { + "type": "Span", + "start": 997, + "end": 1057 + } + }, + "span": { + "type": "Span", + "start": 996, + "end": 1058 + } + } + ], + "span": { + "type": "Span", + "start": 996, + "end": 1058 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 972, + "end": 1058 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "empty-multiline-call", + "span": { + "type": "Span", + "start": 1059, + "end": 1079 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 1083, + "end": 1086 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [], + "span": { + "type": "Span", + "start": 1086, + "end": 1094 + } + }, + "span": { + "type": "Span", + "start": 1083, + "end": 1094 + } + }, + "span": { + "type": "Span", + "start": 1082, + "end": 1095 + } + } + ], + "span": { + "type": "Span", + "start": 1082, + "end": 1095 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1059, + "end": 1095 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-arg-number", + "span": { + "type": "Span", + "start": 1098, + "end": 1119 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 1123, + "end": 1126 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [ + { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 1128, + "end": 1129 + } + } + ], + "named": [], + "span": { + "type": "Span", + "start": 1126, + "end": 1130 + } + }, + "span": { + "type": "Span", + "start": 1123, + "end": 1130 + } + }, + "span": { + "type": "Span", + "start": 1122, + "end": 1131 + } + } + ], + "span": { + "type": "Span", + "start": 1122, + "end": 1131 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1098, + "end": 1131 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-arg-string", + "span": { + "type": "Span", + "start": 1133, + "end": 1154 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 1158, + "end": 1161 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [ + { + "value": "a", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 1163, + "end": 1166 + } + } + ], + "named": [], + "span": { + "type": "Span", + "start": 1161, + "end": 1167 + } + }, + "span": { + "type": "Span", + "start": 1158, + "end": 1167 + } + }, + "span": { + "type": "Span", + "start": 1157, + "end": 1168 + } + } + ], + "span": { + "type": "Span", + "start": 1157, + "end": 1168 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1133, + "end": 1168 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-arg-msg-ref", + "span": { + "type": "Span", + "start": 1170, + "end": 1192 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 1196, + "end": 1199 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [ + { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "msg", + "span": { + "type": "Span", + "start": 1201, + "end": 1204 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 1201, + "end": 1204 + } + } + ], + "named": [], + "span": { + "type": "Span", + "start": 1199, + "end": 1205 + } + }, + "span": { + "type": "Span", + "start": 1196, + "end": 1205 + } + }, + "span": { + "type": "Span", + "start": 1195, + "end": 1206 + } + } + ], + "span": { + "type": "Span", + "start": 1195, + "end": 1206 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1170, + "end": 1206 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-arg-term-ref", + "span": { + "type": "Span", + "start": 1208, + "end": 1231 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 1235, + "end": 1238 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [ + { + "type": "TermReference", + "id": { + "type": "Identifier", + "name": "msg", + "span": { + "type": "Span", + "start": 1241, + "end": 1244 + } + }, + "attribute": null, + "arguments": null, + "span": { + "type": "Span", + "start": 1240, + "end": 1244 + } + } + ], + "named": [], + "span": { + "type": "Span", + "start": 1238, + "end": 1245 + } + }, + "span": { + "type": "Span", + "start": 1235, + "end": 1245 + } + }, + "span": { + "type": "Span", + "start": 1234, + "end": 1246 + } + } + ], + "span": { + "type": "Span", + "start": 1234, + "end": 1246 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1208, + "end": 1246 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-arg-var-ref", + "span": { + "type": "Span", + "start": 1248, + "end": 1270 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 1274, + "end": 1277 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [ + { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "var", + "span": { + "type": "Span", + "start": 1280, + "end": 1283 + } + }, + "span": { + "type": "Span", + "start": 1279, + "end": 1283 + } + } + ], + "named": [], + "span": { + "type": "Span", + "start": 1277, + "end": 1284 + } + }, + "span": { + "type": "Span", + "start": 1274, + "end": 1284 + } + }, + "span": { + "type": "Span", + "start": 1273, + "end": 1285 + } + } + ], + "span": { + "type": "Span", + "start": 1273, + "end": 1285 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1248, + "end": 1285 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-arg-call", + "span": { + "type": "Span", + "start": 1287, + "end": 1306 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 1310, + "end": 1313 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [ + { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "OTHER", + "span": { + "type": "Span", + "start": 1315, + "end": 1320 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [], + "span": { + "type": "Span", + "start": 1320, + "end": 1322 + } + }, + "span": { + "type": "Span", + "start": 1315, + "end": 1322 + } + } + ], + "named": [], + "span": { + "type": "Span", + "start": 1313, + "end": 1323 + } + }, + "span": { + "type": "Span", + "start": 1310, + "end": 1323 + } + }, + "span": { + "type": "Span", + "start": 1309, + "end": 1324 + } + } + ], + "span": { + "type": "Span", + "start": 1309, + "end": 1324 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1287, + "end": 1324 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-named-arg", + "span": { + "type": "Span", + "start": 1326, + "end": 1346 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 1350, + "end": 1353 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [ + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "x", + "span": { + "type": "Span", + "start": 1355, + "end": 1356 + } + }, + "value": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 1357, + "end": 1358 + } + }, + "span": { + "type": "Span", + "start": 1355, + "end": 1358 + } + } + ], + "span": { + "type": "Span", + "start": 1353, + "end": 1359 + } + }, + "span": { + "type": "Span", + "start": 1350, + "end": 1359 + } + }, + "span": { + "type": "Span", + "start": 1349, + "end": 1360 + } + } + ], + "span": { + "type": "Span", + "start": 1349, + "end": 1360 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1326, + "end": 1360 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-closing-paren", + "span": { + "type": "Span", + "start": 1362, + "end": 1386 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 1390, + "end": 1393 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [ + { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "x", + "span": { + "type": "Span", + "start": 1399, + "end": 1400 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 1399, + "end": 1400 + } + } + ], + "named": [], + "span": { + "type": "Span", + "start": 1393, + "end": 1402 + } + }, + "span": { + "type": "Span", + "start": 1390, + "end": 1402 + } + }, + "span": { + "type": "Span", + "start": 1389, + "end": 1403 + } + } + ], + "span": { + "type": "Span", + "start": 1389, + "end": 1403 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1362, + "end": 1403 + } + }, + { + "content": "Optional trailing comma", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 1407, + "end": 1433 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "one-argument", + "span": { + "type": "Span", + "start": 1435, + "end": 1447 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 1451, + "end": 1454 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [ + { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 1455, + "end": 1456 + } + } + ], + "named": [], + "span": { + "type": "Span", + "start": 1454, + "end": 1458 + } + }, + "span": { + "type": "Span", + "start": 1451, + "end": 1458 + } + }, + "span": { + "type": "Span", + "start": 1450, + "end": 1459 + } + } + ], + "span": { + "type": "Span", + "start": 1450, + "end": 1459 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1435, + "end": 1459 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "many-arguments", + "span": { + "type": "Span", + "start": 1460, + "end": 1474 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 1478, + "end": 1481 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [ + { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 1482, + "end": 1483 + } + }, + { + "value": "2", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 1485, + "end": 1486 + } + }, + { + "value": "3", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 1488, + "end": 1489 + } + } + ], + "named": [], + "span": { + "type": "Span", + "start": 1481, + "end": 1491 + } + }, + "span": { + "type": "Span", + "start": 1478, + "end": 1491 + } + }, + "span": { + "type": "Span", + "start": 1477, + "end": 1492 + } + } + ], + "span": { + "type": "Span", + "start": 1477, + "end": 1492 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1460, + "end": 1492 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "inline-sparse-args", + "span": { + "type": "Span", + "start": 1493, + "end": 1511 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 1515, + "end": 1518 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [ + { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 1521, + "end": 1522 + } + }, + { + "value": "2", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 1525, + "end": 1526 + } + }, + { + "value": "3", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 1529, + "end": 1530 + } + } + ], + "named": [], + "span": { + "type": "Span", + "start": 1518, + "end": 1534 + } + }, + "span": { + "type": "Span", + "start": 1515, + "end": 1534 + } + }, + "span": { + "type": "Span", + "start": 1514, + "end": 1535 + } + } + ], + "span": { + "type": "Span", + "start": 1514, + "end": 1535 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1493, + "end": 1535 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "mulitline-args", + "span": { + "type": "Span", + "start": 1536, + "end": 1550 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 1554, + "end": 1557 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [ + { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 1567, + "end": 1568 + } + }, + { + "value": "2", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 1578, + "end": 1579 + } + } + ], + "named": [], + "span": { + "type": "Span", + "start": 1557, + "end": 1586 + } + }, + "span": { + "type": "Span", + "start": 1554, + "end": 1586 + } + }, + "span": { + "type": "Span", + "start": 1553, + "end": 1587 + } + } + ], + "span": { + "type": "Span", + "start": 1553, + "end": 1587 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1536, + "end": 1587 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "mulitline-sparse-args", + "span": { + "type": "Span", + "start": 1588, + "end": 1609 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 1613, + "end": 1616 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [ + { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 1627, + "end": 1628 + } + }, + { + "value": "2", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 1647, + "end": 1648 + } + } + ], + "named": [], + "span": { + "type": "Span", + "start": 1616, + "end": 1667 + } + }, + "span": { + "type": "Span", + "start": 1613, + "end": 1667 + } + }, + "span": { + "type": "Span", + "start": 1612, + "end": 1668 + } + } + ], + "span": { + "type": "Span", + "start": 1612, + "end": 1668 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1588, + "end": 1668 + } + }, + { + "content": "Syntax errors for trailing comma", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 1671, + "end": 1706 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0028", + "arguments": [], + "message": "Expected an inline expression", + "span": { + "type": "Span", + "start": 1730, + "end": 1730 + } + } + ], + "content": "one-argument = {FUN(1,,)}\n", + "span": { + "type": "Span", + "start": 1708, + "end": 1734 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0028", + "arguments": [], + "message": "Expected an inline expression", + "span": { + "type": "Span", + "start": 1753, + "end": 1753 + } + } + ], + "content": "missing-arg = {FUN(,)}\n", + "span": { + "type": "Span", + "start": 1734, + "end": 1757 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0028", + "arguments": [], + "message": "Expected an inline expression", + "span": { + "type": "Span", + "start": 1786, + "end": 1786 + } + } + ], + "content": "missing-sparse-arg = {FUN( , )}\n\n\n", + "span": { + "type": "Span", + "start": 1757, + "end": 1795 + } + }, + { + "content": "Whitespace in named arguments", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 1795, + "end": 1827 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "sparse-named-arg", + "span": { + "type": "Span", + "start": 1829, + "end": 1845 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 1849, + "end": 1852 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [ + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "x", + "span": { + "type": "Span", + "start": 1862, + "end": 1863 + } + }, + "value": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 1870, + "end": 1871 + } + }, + "span": { + "type": "Span", + "start": 1862, + "end": 1871 + } + }, + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "y", + "span": { + "type": "Span", + "start": 1881, + "end": 1882 + } + }, + "value": { + "value": "2", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 1889, + "end": 1890 + } + }, + "span": { + "type": "Span", + "start": 1881, + "end": 1890 + } + }, + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "z", + "span": { + "type": "Span", + "start": 1900, + "end": 1901 + } + }, + "value": { + "value": "3", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 1920, + "end": 1921 + } + }, + "span": { + "type": "Span", + "start": 1900, + "end": 1921 + } + } + ], + "span": { + "type": "Span", + "start": 1852, + "end": 1927 + } + }, + "span": { + "type": "Span", + "start": 1849, + "end": 1927 + } + }, + "span": { + "type": "Span", + "start": 1848, + "end": 1928 + } + } + ], + "span": { + "type": "Span", + "start": 1848, + "end": 1928 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1829, + "end": 1928 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-colon", + "span": { + "type": "Span", + "start": 1931, + "end": 1947 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 1951, + "end": 1954 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [ + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "x", + "span": { + "type": "Span", + "start": 1964, + "end": 1965 + } + }, + "value": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 1967, + "end": 1968 + } + }, + "span": { + "type": "Span", + "start": 1964, + "end": 1968 + } + } + ], + "span": { + "type": "Span", + "start": 1954, + "end": 1969 + } + }, + "span": { + "type": "Span", + "start": 1951, + "end": 1969 + } + }, + "span": { + "type": "Span", + "start": 1950, + "end": 1970 + } + } + ], + "span": { + "type": "Span", + "start": 1950, + "end": 1970 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1931, + "end": 1970 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-value", + "span": { + "type": "Span", + "start": 1972, + "end": 1988 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 1992, + "end": 1995 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [ + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "x", + "span": { + "type": "Span", + "start": 2005, + "end": 2006 + } + }, + "value": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 2008, + "end": 2009 + } + }, + "span": { + "type": "Span", + "start": 2005, + "end": 2009 + } + } + ], + "span": { + "type": "Span", + "start": 1995, + "end": 2010 + } + }, + "span": { + "type": "Span", + "start": 1992, + "end": 2010 + } + }, + "span": { + "type": "Span", + "start": 1991, + "end": 2011 + } + } + ], + "span": { + "type": "Span", + "start": 1991, + "end": 2011 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1972, + "end": 2011 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 2012 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/callee_expressions.ftl b/fluent.syntax/src/test/resources/reference_fixtures/callee_expressions.ftl new file mode 100644 index 0000000..637a2e4 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/callee_expressions.ftl @@ -0,0 +1,46 @@ +## Callees in placeables. + +function-callee-placeable = {FUNCTION()} +term-callee-placeable = {-term()} + +# ERROR Messages cannot be parameterized. +message-callee-placeable = {message()} +# ERROR Equivalent to a MessageReference callee. +mixed-case-callee-placeable = {Function()} +# ERROR Message attributes cannot be parameterized. +message-attr-callee-placeable = {message.attr()} +# ERROR Term attributes may not be used in Placeables. +term-attr-callee-placeable = {-term.attr()} +# ERROR Variables cannot be parameterized. +variable-callee-placeable = {$variable()} + + +## Callees in selectors. + +function-callee-selector = {FUNCTION() -> + *[key] Value +} +term-attr-callee-selector = {-term.attr() -> + *[key] Value +} + +# ERROR Messages cannot be parameterized. +message-callee-selector = {message() -> + *[key] Value +} +# ERROR Equivalent to a MessageReference callee. +mixed-case-callee-selector = {Function() -> + *[key] Value +} +# ERROR Message attributes cannot be parameterized. +message-attr-callee-selector = {message.attr() -> + *[key] Value +} +# ERROR Term values may not be used as selectors. +term-callee-selector = {-term() -> + *[key] Value +} +# ERROR Variables cannot be parameterized. +variable-callee-selector = {$variable() -> + *[key] Value +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/callee_expressions.json b/fluent.syntax/src/test/resources/reference_fixtures/callee_expressions.json new file mode 100644 index 0000000..d0be718 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/callee_expressions.json @@ -0,0 +1,706 @@ +{ + "type": "Resource", + "body": [ + { + "content": "Callees in placeables.", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 0, + "end": 25 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "function-callee-placeable", + "span": { + "type": "Span", + "start": 27, + "end": 52 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUNCTION", + "span": { + "type": "Span", + "start": 56, + "end": 64 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [], + "span": { + "type": "Span", + "start": 64, + "end": 66 + } + }, + "span": { + "type": "Span", + "start": 56, + "end": 66 + } + }, + "span": { + "type": "Span", + "start": 55, + "end": 67 + } + } + ], + "span": { + "type": "Span", + "start": 55, + "end": 67 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 27, + "end": 67 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "term-callee-placeable", + "span": { + "type": "Span", + "start": 68, + "end": 89 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "TermReference", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 94, + "end": 98 + } + }, + "attribute": null, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [], + "span": { + "type": "Span", + "start": 98, + "end": 100 + } + }, + "span": { + "type": "Span", + "start": 93, + "end": 100 + } + }, + "span": { + "type": "Span", + "start": 92, + "end": 101 + } + } + ], + "span": { + "type": "Span", + "start": 92, + "end": 101 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 68, + "end": 101 + } + }, + { + "content": "ERROR Messages cannot be parameterized.", + "type": "Comment", + "span": { + "type": "Span", + "start": 103, + "end": 144 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0008", + "arguments": [], + "message": "The callee has to be an upper-case identifier or a term", + "span": { + "type": "Span", + "start": 180, + "end": 180 + } + } + ], + "content": "message-callee-placeable = {message()}\n", + "span": { + "type": "Span", + "start": 145, + "end": 184 + } + }, + { + "content": "ERROR Equivalent to a MessageReference callee.", + "type": "Comment", + "span": { + "type": "Span", + "start": 184, + "end": 232 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0008", + "arguments": [], + "message": "The callee has to be an upper-case identifier or a term", + "span": { + "type": "Span", + "start": 272, + "end": 272 + } + } + ], + "content": "mixed-case-callee-placeable = {Function()}\n", + "span": { + "type": "Span", + "start": 233, + "end": 276 + } + }, + { + "content": "ERROR Message attributes cannot be parameterized.", + "type": "Comment", + "span": { + "type": "Span", + "start": 276, + "end": 327 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 373, + "end": 373 + } + } + ], + "content": "message-attr-callee-placeable = {message.attr()}\n", + "span": { + "type": "Span", + "start": 328, + "end": 377 + } + }, + { + "content": "ERROR Term attributes may not be used in Placeables.", + "type": "Comment", + "span": { + "type": "Span", + "start": 377, + "end": 431 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0019", + "arguments": [], + "message": "Attributes of terms cannot be used as placeables", + "span": { + "type": "Span", + "start": 474, + "end": 474 + } + } + ], + "content": "term-attr-callee-placeable = {-term.attr()}\n", + "span": { + "type": "Span", + "start": 432, + "end": 476 + } + }, + { + "content": "ERROR Variables cannot be parameterized.", + "type": "Comment", + "span": { + "type": "Span", + "start": 476, + "end": 518 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 557, + "end": 557 + } + } + ], + "content": "variable-callee-placeable = {$variable()}\n\n\n", + "span": { + "type": "Span", + "start": 519, + "end": 563 + } + }, + { + "content": "Callees in selectors.", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 563, + "end": 587 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "function-callee-selector", + "span": { + "type": "Span", + "start": 589, + "end": 613 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FUNCTION", + "span": { + "type": "Span", + "start": 617, + "end": 625 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [], + "span": { + "type": "Span", + "start": 625, + "end": 627 + } + }, + "span": { + "type": "Span", + "start": 617, + "end": 627 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 636, + "end": 639 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 641, + "end": 646 + } + } + ], + "span": { + "type": "Span", + "start": 641, + "end": 646 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 634, + "end": 646 + } + } + ], + "span": { + "type": "Span", + "start": 617, + "end": 647 + } + }, + "span": { + "type": "Span", + "start": 616, + "end": 648 + } + } + ], + "span": { + "type": "Span", + "start": 616, + "end": 648 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 589, + "end": 648 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "term-attr-callee-selector", + "span": { + "type": "Span", + "start": 649, + "end": 674 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "TermReference", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 679, + "end": 683 + } + }, + "attribute": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 684, + "end": 688 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [], + "span": { + "type": "Span", + "start": 688, + "end": 690 + } + }, + "span": { + "type": "Span", + "start": 678, + "end": 690 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 699, + "end": 702 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 704, + "end": 709 + } + } + ], + "span": { + "type": "Span", + "start": 704, + "end": 709 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 697, + "end": 709 + } + } + ], + "span": { + "type": "Span", + "start": 678, + "end": 710 + } + }, + "span": { + "type": "Span", + "start": 677, + "end": 711 + } + } + ], + "span": { + "type": "Span", + "start": 677, + "end": 711 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 649, + "end": 711 + } + }, + { + "content": "ERROR Messages cannot be parameterized.", + "type": "Comment", + "span": { + "type": "Span", + "start": 713, + "end": 754 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0008", + "arguments": [], + "message": "The callee has to be an upper-case identifier or a term", + "span": { + "type": "Span", + "start": 789, + "end": 789 + } + } + ], + "content": "message-callee-selector = {message() ->\n *[key] Value\n}\n", + "span": { + "type": "Span", + "start": 755, + "end": 813 + } + }, + { + "content": "ERROR Equivalent to a MessageReference callee.", + "type": "Comment", + "span": { + "type": "Span", + "start": 813, + "end": 861 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0008", + "arguments": [], + "message": "The callee has to be an upper-case identifier or a term", + "span": { + "type": "Span", + "start": 900, + "end": 900 + } + } + ], + "content": "mixed-case-callee-selector = {Function() ->\n *[key] Value\n}\n", + "span": { + "type": "Span", + "start": 862, + "end": 924 + } + }, + { + "content": "ERROR Message attributes cannot be parameterized.", + "type": "Comment", + "span": { + "type": "Span", + "start": 924, + "end": 975 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 1020, + "end": 1020 + } + } + ], + "content": "message-attr-callee-selector = {message.attr() ->\n *[key] Value\n}\n", + "span": { + "type": "Span", + "start": 976, + "end": 1044 + } + }, + { + "content": "ERROR Term values may not be used as selectors.", + "type": "Comment", + "span": { + "type": "Span", + "start": 1044, + "end": 1093 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0017", + "arguments": [], + "message": "Terms cannot be used as selectors", + "span": { + "type": "Span", + "start": 1126, + "end": 1126 + } + } + ], + "content": "term-callee-selector = {-term() ->\n *[key] Value\n}\n", + "span": { + "type": "Span", + "start": 1094, + "end": 1147 + } + }, + { + "content": "ERROR Variables cannot be parameterized.", + "type": "Comment", + "span": { + "type": "Span", + "start": 1147, + "end": 1189 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 1227, + "end": 1227 + } + } + ], + "content": "variable-callee-selector = {$variable() ->\n *[key] Value\n}\n", + "span": { + "type": "Span", + "start": 1190, + "end": 1251 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 1251 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/comments.ftl b/fluent.syntax/src/test/resources/reference_fixtures/comments.ftl new file mode 100644 index 0000000..d356693 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/comments.ftl @@ -0,0 +1,20 @@ +# Standalone Comment + +# Message Comment +foo = Foo + +# Term Comment +# with a blank last line. +# +-term = Term + +# Another standalone +# +# with indent +## Group Comment +### Resource Comment + +# Errors +#error +##error +###error diff --git a/fluent.syntax/src/test/resources/reference_fixtures/comments.json b/fluent.syntax/src/test/resources/reference_fixtures/comments.json new file mode 100644 index 0000000..8b43c11 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/comments.json @@ -0,0 +1,219 @@ +{ + "type": "Resource", + "body": [ + { + "content": "Standalone Comment", + "type": "Comment", + "span": { + "type": "Span", + "start": 0, + "end": 20 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "foo", + "span": { + "type": "Span", + "start": 40, + "end": 43 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Foo", + "span": { + "type": "Span", + "start": 46, + "end": 49 + } + } + ], + "span": { + "type": "Span", + "start": 46, + "end": 49 + } + }, + "attributes": [], + "comment": { + "content": "Message Comment", + "type": "Comment", + "span": { + "type": "Span", + "start": 22, + "end": 39 + } + }, + "span": { + "type": "Span", + "start": 22, + "end": 49 + } + }, + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 95, + "end": 99 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Term", + "span": { + "type": "Span", + "start": 102, + "end": 106 + } + } + ], + "span": { + "type": "Span", + "start": 102, + "end": 106 + } + }, + "attributes": [], + "comment": { + "content": "Term Comment\nwith a blank last line.\n", + "type": "Comment", + "span": { + "type": "Span", + "start": 51, + "end": 93 + } + }, + "span": { + "type": "Span", + "start": 51, + "end": 106 + } + }, + { + "content": "Another standalone\n\n with indent", + "type": "Comment", + "span": { + "type": "Span", + "start": 108, + "end": 150 + } + }, + { + "content": "Group Comment", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 151, + "end": 167 + } + }, + { + "content": "Resource Comment", + "type": "ResourceComment", + "span": { + "type": "Span", + "start": 168, + "end": 188 + } + }, + { + "content": "Errors", + "type": "Comment", + "span": { + "type": "Span", + "start": 190, + "end": 198 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + " " + ], + "message": "Expected token: \" \"", + "span": { + "type": "Span", + "start": 200, + "end": 200 + } + } + ], + "content": "#error\n", + "span": { + "type": "Span", + "start": 199, + "end": 206 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + " " + ], + "message": "Expected token: \" \"", + "span": { + "type": "Span", + "start": 208, + "end": 208 + } + } + ], + "content": "##error\n", + "span": { + "type": "Span", + "start": 206, + "end": 214 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + " " + ], + "message": "Expected token: \" \"", + "span": { + "type": "Span", + "start": 217, + "end": 217 + } + } + ], + "content": "###error\n", + "span": { + "type": "Span", + "start": 214, + "end": 223 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 223 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/cr.ftl b/fluent.syntax/src/test/resources/reference_fixtures/cr.ftl new file mode 100644 index 0000000..549c662 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/cr.ftl @@ -0,0 +1 @@ +### This entire file uses CR as EOL. err01 = Value 01 err02 = Value 02 err03 = Value 03 Continued .title = Title err04 = { "str err05 = { $sel -> } \ No newline at end of file diff --git a/fluent.syntax/src/test/resources/reference_fixtures/cr.json b/fluent.syntax/src/test/resources/reference_fixtures/cr.json new file mode 100644 index 0000000..7c78c02 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/cr.json @@ -0,0 +1,19 @@ +{ + "type": "Resource", + "body": [ + { + "type": "ResourceComment", + "content": "This entire file uses CR as EOL.\r\rerr01 = Value 01\rerr02 = Value 02\r\rerr03 =\r\r Value 03\r Continued\r\r .title = Title\r\rerr04 = { \"str\r\rerr05 = { $sel -> }\r", + "span": { + "type": "Span", + "start": 0, + "end": 166 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 166 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/crlf.ftl b/fluent.syntax/src/test/resources/reference_fixtures/crlf.ftl new file mode 100644 index 0000000..df3a02c --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/crlf.ftl @@ -0,0 +1,14 @@ + +key01 = Value 01 +key02 = + + Value 02 + Continued + + .title = Title + +# ERROR Unclosed StringLiteral +err03 = { "str + +# ERROR Missing newline after ->. +err04 = { $sel -> } diff --git a/fluent.syntax/src/test/resources/reference_fixtures/crlf.json b/fluent.syntax/src/test/resources/reference_fixtures/crlf.json new file mode 100644 index 0000000..23e3784 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/crlf.json @@ -0,0 +1,76 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 01" + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key02" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 02\nContinued" + } + ] + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "title" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Title" + } + ] + } + } + ], + "comment": null + }, + { + "type": "Comment", + "content": "ERROR Unclosed StringLiteral" + }, + { + "type": "Junk", + "annotations": [], + "content": "err03 = { \"str\r\n\r\n" + }, + { + "type": "Comment", + "content": "ERROR Missing newline after ->." + }, + { + "type": "Junk", + "annotations": [], + "content": "err04 = { $sel -> }\r\n" + } + ] +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_comment.ftl b/fluent.syntax/src/test/resources/reference_fixtures/eof_comment.ftl new file mode 100644 index 0000000..cdeafd9 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/eof_comment.ftl @@ -0,0 +1,3 @@ +### NOTE: Disable final newline insertion when editing this file. + +# No EOL \ No newline at end of file diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_comment.json b/fluent.syntax/src/test/resources/reference_fixtures/eof_comment.json new file mode 100644 index 0000000..31fe68f --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/eof_comment.json @@ -0,0 +1,28 @@ +{ + "type": "Resource", + "body": [ + { + "content": "NOTE: Disable final newline insertion when editing this file.", + "type": "ResourceComment", + "span": { + "type": "Span", + "start": 0, + "end": 65 + } + }, + { + "content": "No EOL", + "type": "Comment", + "span": { + "type": "Span", + "start": 67, + "end": 75 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 75 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_empty.ftl b/fluent.syntax/src/test/resources/reference_fixtures/eof_empty.ftl new file mode 100644 index 0000000..e69de29 diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_empty.json b/fluent.syntax/src/test/resources/reference_fixtures/eof_empty.json new file mode 100644 index 0000000..603a5bb --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/eof_empty.json @@ -0,0 +1,9 @@ +{ + "type": "Resource", + "body": [], + "span": { + "type": "Span", + "start": 0, + "end": 0 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_id.ftl b/fluent.syntax/src/test/resources/reference_fixtures/eof_id.ftl new file mode 100644 index 0000000..63fa86d --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/eof_id.ftl @@ -0,0 +1,3 @@ +### NOTE: Disable final newline insertion when editing this file. + +message-id \ No newline at end of file diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_id.json b/fluent.syntax/src/test/resources/reference_fixtures/eof_id.json new file mode 100644 index 0000000..60099e3 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/eof_id.json @@ -0,0 +1,43 @@ +{ + "type": "Resource", + "body": [ + { + "content": "NOTE: Disable final newline insertion when editing this file.", + "type": "ResourceComment", + "span": { + "type": "Span", + "start": 0, + "end": 65 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 77, + "end": 77 + } + } + ], + "content": "message-id", + "span": { + "type": "Span", + "start": 67, + "end": 77 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 77 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_id_equals.ftl b/fluent.syntax/src/test/resources/reference_fixtures/eof_id_equals.ftl new file mode 100644 index 0000000..7d0d953 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/eof_id_equals.ftl @@ -0,0 +1,3 @@ +### NOTE: Disable final newline insertion when editing this file. + +message-id = \ No newline at end of file diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_id_equals.json b/fluent.syntax/src/test/resources/reference_fixtures/eof_id_equals.json new file mode 100644 index 0000000..7087419 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/eof_id_equals.json @@ -0,0 +1,43 @@ +{ + "type": "Resource", + "body": [ + { + "content": "NOTE: Disable final newline insertion when editing this file.", + "type": "ResourceComment", + "span": { + "type": "Span", + "start": 0, + "end": 65 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0005", + "arguments": [ + "message-id" + ], + "message": "Expected message \"message-id\" to have a value or attributes", + "span": { + "type": "Span", + "start": 79, + "end": 79 + } + } + ], + "content": "message-id =", + "span": { + "type": "Span", + "start": 67, + "end": 79 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 79 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_junk.ftl b/fluent.syntax/src/test/resources/reference_fixtures/eof_junk.ftl new file mode 100644 index 0000000..dbafd3a --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/eof_junk.ftl @@ -0,0 +1,3 @@ +### NOTE: Disable final newline insertion when editing this file. + +000 \ No newline at end of file diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_junk.json b/fluent.syntax/src/test/resources/reference_fixtures/eof_junk.json new file mode 100644 index 0000000..be2e25d --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/eof_junk.json @@ -0,0 +1,41 @@ +{ + "type": "Resource", + "body": [ + { + "content": "NOTE: Disable final newline insertion when editing this file.", + "type": "ResourceComment", + "span": { + "type": "Span", + "start": 0, + "end": 65 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0002", + "arguments": [], + "message": "Expected an entry start", + "span": { + "type": "Span", + "start": 67, + "end": 67 + } + } + ], + "content": "000", + "span": { + "type": "Span", + "start": 67, + "end": 70 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 70 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_value.ftl b/fluent.syntax/src/test/resources/reference_fixtures/eof_value.ftl new file mode 100644 index 0000000..0d255c5 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/eof_value.ftl @@ -0,0 +1,3 @@ +### NOTE: Disable final newline insertion when editing this file. + +no-eol = No EOL \ No newline at end of file diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_value.json b/fluent.syntax/src/test/resources/reference_fixtures/eof_value.json new file mode 100644 index 0000000..cdb30d8 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/eof_value.json @@ -0,0 +1,57 @@ +{ + "type": "Resource", + "body": [ + { + "content": "NOTE: Disable final newline insertion when editing this file.", + "type": "ResourceComment", + "span": { + "type": "Span", + "start": 0, + "end": 65 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "no-eol", + "span": { + "type": "Span", + "start": 67, + "end": 73 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "No EOL", + "span": { + "type": "Span", + "start": 76, + "end": 82 + } + } + ], + "span": { + "type": "Span", + "start": 76, + "end": 82 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 67, + "end": 82 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 82 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/escaped_characters.ftl b/fluent.syntax/src/test/resources/reference_fixtures/escaped_characters.ftl new file mode 100644 index 0000000..fccf257 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/escaped_characters.ftl @@ -0,0 +1,37 @@ +## Literal text +text-backslash-one = Value with \ a backslash +text-backslash-two = Value with \\ two backslashes +text-backslash-brace = Value with \{placeable} +text-backslash-u = \u0041 +text-backslash-backslash-u = \\u0041 + +## String literals +quote-in-string = {"\""} +backslash-in-string = {"\\"} +# ERROR Mismatched quote +mismatched-quote = {"\\""} +# ERROR Unknown escape +unknown-escape = {"\x"} +# ERROR Multiline literal +invalid-multiline-literal = {" + "} + +## Unicode escapes +string-unicode-4digits = {"\u0041"} +escape-unicode-4digits = {"\\u0041"} +string-unicode-6digits = {"\U01F602"} +escape-unicode-6digits = {"\\U01F602"} + +# OK The trailing "00" is part of the literal value. +string-too-many-4digits = {"\u004100"} +# OK The trailing "00" is part of the literal value. +string-too-many-6digits = {"\U01F60200"} + +# ERROR Too few hex digits after \u. +string-too-few-4digits = {"\u41"} +# ERROR Too few hex digits after \U. +string-too-few-6digits = {"\U1F602"} + +## Literal braces +brace-open = An opening {"{"} brace. +brace-close = A closing {"}"} brace. diff --git a/fluent.syntax/src/test/resources/reference_fixtures/escaped_characters.json b/fluent.syntax/src/test/resources/reference_fixtures/escaped_characters.json new file mode 100644 index 0000000..1f1dac3 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/escaped_characters.json @@ -0,0 +1,937 @@ +{ + "type": "Resource", + "body": [ + { + "content": "Literal text", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 0, + "end": 15 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "text-backslash-one", + "span": { + "type": "Span", + "start": 16, + "end": 34 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value with \\ a backslash", + "span": { + "type": "Span", + "start": 37, + "end": 61 + } + } + ], + "span": { + "type": "Span", + "start": 37, + "end": 61 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 16, + "end": 61 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "text-backslash-two", + "span": { + "type": "Span", + "start": 62, + "end": 80 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value with \\\\ two backslashes", + "span": { + "type": "Span", + "start": 83, + "end": 112 + } + } + ], + "span": { + "type": "Span", + "start": 83, + "end": 112 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 62, + "end": 112 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "text-backslash-brace", + "span": { + "type": "Span", + "start": 113, + "end": 133 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value with \\", + "span": { + "type": "Span", + "start": 136, + "end": 148 + } + }, + { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "placeable", + "span": { + "type": "Span", + "start": 149, + "end": 158 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 149, + "end": 158 + } + }, + "span": { + "type": "Span", + "start": 148, + "end": 159 + } + } + ], + "span": { + "type": "Span", + "start": 136, + "end": 159 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 113, + "end": 159 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "text-backslash-u", + "span": { + "type": "Span", + "start": 160, + "end": 176 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "\\u0041", + "span": { + "type": "Span", + "start": 179, + "end": 185 + } + } + ], + "span": { + "type": "Span", + "start": 179, + "end": 185 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 160, + "end": 185 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "text-backslash-backslash-u", + "span": { + "type": "Span", + "start": 186, + "end": 212 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "\\\\u0041", + "span": { + "type": "Span", + "start": 215, + "end": 222 + } + } + ], + "span": { + "type": "Span", + "start": 215, + "end": 222 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 186, + "end": 222 + } + }, + { + "content": "String literals", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 224, + "end": 242 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "quote-in-string", + "span": { + "type": "Span", + "start": 243, + "end": 258 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "\\\"", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 262, + "end": 266 + } + }, + "span": { + "type": "Span", + "start": 261, + "end": 267 + } + } + ], + "span": { + "type": "Span", + "start": 261, + "end": 267 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 243, + "end": 267 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "backslash-in-string", + "span": { + "type": "Span", + "start": 268, + "end": 287 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "\\\\", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 291, + "end": 295 + } + }, + "span": { + "type": "Span", + "start": 290, + "end": 296 + } + } + ], + "span": { + "type": "Span", + "start": 290, + "end": 296 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 268, + "end": 296 + } + }, + { + "content": "ERROR Mismatched quote", + "type": "Comment", + "span": { + "type": "Span", + "start": 297, + "end": 321 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 346, + "end": 346 + } + } + ], + "content": "mismatched-quote = {\"\\\\\"\"}\n", + "span": { + "type": "Span", + "start": 322, + "end": 349 + } + }, + { + "content": "ERROR Unknown escape", + "type": "Comment", + "span": { + "type": "Span", + "start": 349, + "end": 371 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0025", + "arguments": [ + "x" + ], + "message": "Unknown escape sequence: \\x.", + "span": { + "type": "Span", + "start": 392, + "end": 392 + } + } + ], + "content": "unknown-escape = {\"\\x\"}\n", + "span": { + "type": "Span", + "start": 372, + "end": 396 + } + }, + { + "content": "ERROR Multiline literal", + "type": "Comment", + "span": { + "type": "Span", + "start": 396, + "end": 421 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0020", + "arguments": [], + "message": "Unterminated string expression", + "span": { + "type": "Span", + "start": 452, + "end": 452 + } + } + ], + "content": "invalid-multiline-literal = {\"\n \"}\n\n", + "span": { + "type": "Span", + "start": 422, + "end": 458 + } + }, + { + "content": "Unicode escapes", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 458, + "end": 476 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "string-unicode-4digits", + "span": { + "type": "Span", + "start": 477, + "end": 499 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "\\u0041", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 503, + "end": 511 + } + }, + "span": { + "type": "Span", + "start": 502, + "end": 512 + } + } + ], + "span": { + "type": "Span", + "start": 502, + "end": 512 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 477, + "end": 512 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "escape-unicode-4digits", + "span": { + "type": "Span", + "start": 513, + "end": 535 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "\\\\u0041", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 539, + "end": 548 + } + }, + "span": { + "type": "Span", + "start": 538, + "end": 549 + } + } + ], + "span": { + "type": "Span", + "start": 538, + "end": 549 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 513, + "end": 549 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "string-unicode-6digits", + "span": { + "type": "Span", + "start": 550, + "end": 572 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "\\U01F602", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 576, + "end": 586 + } + }, + "span": { + "type": "Span", + "start": 575, + "end": 587 + } + } + ], + "span": { + "type": "Span", + "start": 575, + "end": 587 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 550, + "end": 587 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "escape-unicode-6digits", + "span": { + "type": "Span", + "start": 588, + "end": 610 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "\\\\U01F602", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 614, + "end": 625 + } + }, + "span": { + "type": "Span", + "start": 613, + "end": 626 + } + } + ], + "span": { + "type": "Span", + "start": 613, + "end": 626 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 588, + "end": 626 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "string-too-many-4digits", + "span": { + "type": "Span", + "start": 681, + "end": 704 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "\\u004100", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 708, + "end": 718 + } + }, + "span": { + "type": "Span", + "start": 707, + "end": 719 + } + } + ], + "span": { + "type": "Span", + "start": 707, + "end": 719 + } + }, + "attributes": [], + "comment": { + "content": "OK The trailing \"00\" is part of the literal value.", + "type": "Comment", + "span": { + "type": "Span", + "start": 628, + "end": 680 + } + }, + "span": { + "type": "Span", + "start": 628, + "end": 719 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "string-too-many-6digits", + "span": { + "type": "Span", + "start": 773, + "end": 796 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "\\U01F60200", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 800, + "end": 812 + } + }, + "span": { + "type": "Span", + "start": 799, + "end": 813 + } + } + ], + "span": { + "type": "Span", + "start": 799, + "end": 813 + } + }, + "attributes": [], + "comment": { + "content": "OK The trailing \"00\" is part of the literal value.", + "type": "Comment", + "span": { + "type": "Span", + "start": 720, + "end": 772 + } + }, + "span": { + "type": "Span", + "start": 720, + "end": 813 + } + }, + { + "content": "ERROR Too few hex digits after \\u.", + "type": "Comment", + "span": { + "type": "Span", + "start": 815, + "end": 851 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0026", + "arguments": [ + "\\u41\"" + ], + "message": "Invalid Unicode escape sequence: \\u41\".", + "span": { + "type": "Span", + "start": 883, + "end": 883 + } + } + ], + "content": "string-too-few-4digits = {\"\\u41\"}\n", + "span": { + "type": "Span", + "start": 852, + "end": 886 + } + }, + { + "content": "ERROR Too few hex digits after \\U.", + "type": "Comment", + "span": { + "type": "Span", + "start": 886, + "end": 922 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0026", + "arguments": [ + "\\U1F602\"" + ], + "message": "Invalid Unicode escape sequence: \\U1F602\".", + "span": { + "type": "Span", + "start": 957, + "end": 957 + } + } + ], + "content": "string-too-few-6digits = {\"\\U1F602\"}\n\n", + "span": { + "type": "Span", + "start": 923, + "end": 961 + } + }, + { + "content": "Literal braces", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 961, + "end": 978 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "brace-open", + "span": { + "type": "Span", + "start": 979, + "end": 989 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "An opening ", + "span": { + "type": "Span", + "start": 992, + "end": 1003 + } + }, + { + "type": "Placeable", + "expression": { + "value": "{", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 1004, + "end": 1007 + } + }, + "span": { + "type": "Span", + "start": 1003, + "end": 1008 + } + }, + { + "type": "TextElement", + "value": " brace.", + "span": { + "type": "Span", + "start": 1008, + "end": 1015 + } + } + ], + "span": { + "type": "Span", + "start": 992, + "end": 1015 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 979, + "end": 1015 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "brace-close", + "span": { + "type": "Span", + "start": 1016, + "end": 1027 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "A closing ", + "span": { + "type": "Span", + "start": 1030, + "end": 1040 + } + }, + { + "type": "Placeable", + "expression": { + "value": "}", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 1041, + "end": 1044 + } + }, + "span": { + "type": "Span", + "start": 1040, + "end": 1045 + } + }, + { + "type": "TextElement", + "value": " brace.", + "span": { + "type": "Span", + "start": 1045, + "end": 1052 + } + } + ], + "span": { + "type": "Span", + "start": 1030, + "end": 1052 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1016, + "end": 1052 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 1053 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/junk.ftl b/fluent.syntax/src/test/resources/reference_fixtures/junk.ftl new file mode 100644 index 0000000..b0b0c5f --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/junk.ftl @@ -0,0 +1,21 @@ +## Two adjacent Junks. +err01 = {1x} +err02 = {2x} + +# A single Junk. +err03 = {1x +2 + +# A single Junk. +ą=Invalid identifier +ć=Another one + +# The COMMENT ends this junk. +err04 = { +# COMMENT + +# The COMMENT ends this junk. +# The closing brace is a separate Junk. +err04 = { +# COMMENT +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/junk.json b/fluent.syntax/src/test/resources/reference_fixtures/junk.json new file mode 100644 index 0000000..efeb1db --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/junk.json @@ -0,0 +1,233 @@ +{ + "type": "Resource", + "body": [ + { + "content": "Two adjacent Junks.", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 0, + "end": 22 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 33, + "end": 33 + } + } + ], + "content": "err01 = {1x}\n", + "span": { + "type": "Span", + "start": 23, + "end": 36 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 46, + "end": 46 + } + } + ], + "content": "err02 = {2x}\n\n", + "span": { + "type": "Span", + "start": 36, + "end": 50 + } + }, + { + "content": "A single Junk.", + "type": "Comment", + "span": { + "type": "Span", + "start": 50, + "end": 66 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 77, + "end": 77 + } + } + ], + "content": "err03 = {1x\n2\n\n", + "span": { + "type": "Span", + "start": 67, + "end": 82 + } + }, + { + "content": "A single Junk.", + "type": "Comment", + "span": { + "type": "Span", + "start": 82, + "end": 98 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0002", + "arguments": [], + "message": "Expected an entry start", + "span": { + "type": "Span", + "start": 99, + "end": 99 + } + } + ], + "content": "ą=Invalid identifier\nć=Another one\n\n", + "span": { + "type": "Span", + "start": 99, + "end": 135 + } + }, + { + "content": "The COMMENT ends this junk.", + "type": "Comment", + "span": { + "type": "Span", + "start": 135, + "end": 164 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0028", + "arguments": [], + "message": "Expected an inline expression", + "span": { + "type": "Span", + "start": 175, + "end": 175 + } + } + ], + "content": "err04 = {\n", + "span": { + "type": "Span", + "start": 165, + "end": 175 + } + }, + { + "content": "COMMENT", + "type": "Comment", + "span": { + "type": "Span", + "start": 175, + "end": 184 + } + }, + { + "content": "The COMMENT ends this junk.\nThe closing brace is a separate Junk.", + "type": "Comment", + "span": { + "type": "Span", + "start": 186, + "end": 255 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0028", + "arguments": [], + "message": "Expected an inline expression", + "span": { + "type": "Span", + "start": 266, + "end": 266 + } + } + ], + "content": "err04 = {\n", + "span": { + "type": "Span", + "start": 256, + "end": 266 + } + }, + { + "content": "COMMENT", + "type": "Comment", + "span": { + "type": "Span", + "start": 266, + "end": 275 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0002", + "arguments": [], + "message": "Expected an entry start", + "span": { + "type": "Span", + "start": 276, + "end": 276 + } + } + ], + "content": "}\n", + "span": { + "type": "Span", + "start": 276, + "end": 278 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 278 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/leading_dots.ftl b/fluent.syntax/src/test/resources/reference_fixtures/leading_dots.ftl new file mode 100644 index 0000000..0b9d669 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/leading_dots.ftl @@ -0,0 +1,76 @@ +key01 = .Value +key02 = …Value +key03 = {"."}Value +key04 = + {"."}Value + +key05 = Value + {"."}Continued + +key06 = .Value + {"."}Continued + +# MESSAGE (value = "Value", attributes = []) +# JUNK (attr .Continued" must have a value) +key07 = Value + .Continued + +# JUNK (attr .Value must have a value) +key08 = + .Value + +# JUNK (attr .Value must have a value) +key09 = + .Value + Continued + +key10 = + .Value = which is an attribute + Continued + +key11 = + {"."}Value = which looks like an attribute + Continued + +key12 = + .accesskey = + A + +key13 = + .attribute = .Value + +key14 = + .attribute = + {"."}Value + +key15 = + { 1 -> + [one] .Value + *[other] + {"."}Value + } + +# JUNK (variant must have a value) +key16 = + { 1 -> + *[one] + .Value + } + +# JUNK (unclosed placeable) +key17 = + { 1 -> + *[one] Value + .Continued + } + +# JUNK (attr .Value must have a value) +key18 = +.Value + +key19 = +.attribute = Value + Continued + +key20 = +{"."}Value diff --git a/fluent.syntax/src/test/resources/reference_fixtures/leading_dots.json b/fluent.syntax/src/test/resources/reference_fixtures/leading_dots.json new file mode 100644 index 0000000..15b50a3 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/leading_dots.json @@ -0,0 +1,1079 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01", + "span": { + "type": "Span", + "start": 0, + "end": 5 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".Value", + "span": { + "type": "Span", + "start": 8, + "end": 14 + } + } + ], + "span": { + "type": "Span", + "start": 8, + "end": 14 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 14 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key02", + "span": { + "type": "Span", + "start": 15, + "end": 20 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "…Value", + "span": { + "type": "Span", + "start": 23, + "end": 29 + } + } + ], + "span": { + "type": "Span", + "start": 23, + "end": 29 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 15, + "end": 29 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key03", + "span": { + "type": "Span", + "start": 30, + "end": 35 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": ".", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 39, + "end": 42 + } + }, + "span": { + "type": "Span", + "start": 38, + "end": 43 + } + }, + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 43, + "end": 48 + } + } + ], + "span": { + "type": "Span", + "start": 38, + "end": 48 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 30, + "end": 48 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key04", + "span": { + "type": "Span", + "start": 49, + "end": 54 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": ".", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 62, + "end": 65 + } + }, + "span": { + "type": "Span", + "start": 61, + "end": 66 + } + }, + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 66, + "end": 71 + } + } + ], + "span": { + "type": "Span", + "start": 57, + "end": 71 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 49, + "end": 71 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key05", + "span": { + "type": "Span", + "start": 73, + "end": 78 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value\n", + "span": { + "type": "Span", + "start": 81, + "end": 91 + } + }, + { + "type": "Placeable", + "expression": { + "value": ".", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 92, + "end": 95 + } + }, + "span": { + "type": "Span", + "start": 91, + "end": 96 + } + }, + { + "type": "TextElement", + "value": "Continued", + "span": { + "type": "Span", + "start": 96, + "end": 105 + } + } + ], + "span": { + "type": "Span", + "start": 81, + "end": 105 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 73, + "end": 105 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key06", + "span": { + "type": "Span", + "start": 107, + "end": 112 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".Value\n", + "span": { + "type": "Span", + "start": 115, + "end": 126 + } + }, + { + "type": "Placeable", + "expression": { + "value": ".", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 127, + "end": 130 + } + }, + "span": { + "type": "Span", + "start": 126, + "end": 131 + } + }, + { + "type": "TextElement", + "value": "Continued", + "span": { + "type": "Span", + "start": 131, + "end": 140 + } + } + ], + "span": { + "type": "Span", + "start": 115, + "end": 140 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 107, + "end": 140 + } + }, + { + "content": "MESSAGE (value = \"Value\", attributes = [])\nJUNK (attr .Continued\" must have a value)", + "type": "Comment", + "span": { + "type": "Span", + "start": 142, + "end": 230 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 259, + "end": 259 + } + } + ], + "content": "key07 = Value\n .Continued\n\n", + "span": { + "type": "Span", + "start": 231, + "end": 261 + } + }, + { + "content": "JUNK (attr .Value must have a value)", + "type": "Comment", + "span": { + "type": "Span", + "start": 261, + "end": 299 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 318, + "end": 318 + } + } + ], + "content": "key08 =\n .Value\n\n", + "span": { + "type": "Span", + "start": 300, + "end": 320 + } + }, + { + "content": "JUNK (attr .Value must have a value)", + "type": "Comment", + "span": { + "type": "Span", + "start": 320, + "end": 358 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 377, + "end": 377 + } + } + ], + "content": "key09 =\n .Value\n Continued\n\n", + "span": { + "type": "Span", + "start": 359, + "end": 393 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key10", + "span": { + "type": "Span", + "start": 393, + "end": 398 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "Value", + "span": { + "type": "Span", + "start": 406, + "end": 411 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "which is an attribute\nContinued", + "span": { + "type": "Span", + "start": 414, + "end": 449 + } + } + ], + "span": { + "type": "Span", + "start": 414, + "end": 449 + } + }, + "span": { + "type": "Span", + "start": 405, + "end": 449 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 393, + "end": 449 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key11", + "span": { + "type": "Span", + "start": 451, + "end": 456 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": ".", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 464, + "end": 467 + } + }, + "span": { + "type": "Span", + "start": 463, + "end": 468 + } + }, + { + "type": "TextElement", + "value": "Value = which looks like an attribute\nContinued", + "span": { + "type": "Span", + "start": 468, + "end": 519 + } + } + ], + "span": { + "type": "Span", + "start": 459, + "end": 519 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 451, + "end": 519 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key12", + "span": { + "type": "Span", + "start": 521, + "end": 526 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "accesskey", + "span": { + "type": "Span", + "start": 534, + "end": 543 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "A", + "span": { + "type": "Span", + "start": 550, + "end": 551 + } + } + ], + "span": { + "type": "Span", + "start": 546, + "end": 551 + } + }, + "span": { + "type": "Span", + "start": 533, + "end": 551 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 521, + "end": 551 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key13", + "span": { + "type": "Span", + "start": 553, + "end": 558 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attribute", + "span": { + "type": "Span", + "start": 566, + "end": 575 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".Value", + "span": { + "type": "Span", + "start": 578, + "end": 584 + } + } + ], + "span": { + "type": "Span", + "start": 578, + "end": 584 + } + }, + "span": { + "type": "Span", + "start": 565, + "end": 584 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 553, + "end": 584 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key14", + "span": { + "type": "Span", + "start": 586, + "end": 591 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attribute", + "span": { + "type": "Span", + "start": 599, + "end": 608 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": ".", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 621, + "end": 624 + } + }, + "span": { + "type": "Span", + "start": 620, + "end": 625 + } + }, + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 625, + "end": 630 + } + } + ], + "span": { + "type": "Span", + "start": 611, + "end": 630 + } + }, + "span": { + "type": "Span", + "start": 598, + "end": 630 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 586, + "end": 630 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key15", + "span": { + "type": "Span", + "start": 632, + "end": 637 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 646, + "end": 647 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "one", + "span": { + "type": "Span", + "start": 660, + "end": 663 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".Value", + "span": { + "type": "Span", + "start": 665, + "end": 671 + } + } + ], + "span": { + "type": "Span", + "start": 665, + "end": 671 + } + }, + "default": false, + "span": { + "type": "Span", + "start": 659, + "end": 671 + } + }, + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "other", + "span": { + "type": "Span", + "start": 681, + "end": 686 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": ".", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 701, + "end": 704 + } + }, + "span": { + "type": "Span", + "start": 700, + "end": 705 + } + }, + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 705, + "end": 710 + } + } + ], + "span": { + "type": "Span", + "start": 688, + "end": 710 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 679, + "end": 710 + } + } + ], + "span": { + "type": "Span", + "start": 646, + "end": 715 + } + }, + "span": { + "type": "Span", + "start": 644, + "end": 716 + } + } + ], + "span": { + "type": "Span", + "start": 640, + "end": 716 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 632, + "end": 716 + } + }, + { + "content": "JUNK (variant must have a value)", + "type": "Comment", + "span": { + "type": "Span", + "start": 718, + "end": 752 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0012", + "arguments": [], + "message": "Expected value", + "span": { + "type": "Span", + "start": 785, + "end": 785 + } + } + ], + "content": "key16 =\n { 1 ->\n *[one]\n .Value\n }\n\n", + "span": { + "type": "Span", + "start": 753, + "end": 811 + } + }, + { + "content": "JUNK (unclosed placeable)", + "type": "Comment", + "span": { + "type": "Span", + "start": 811, + "end": 838 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 889, + "end": 889 + } + } + ], + "content": "key17 =\n { 1 ->\n *[one] Value\n .Continued\n }\n\n", + "span": { + "type": "Span", + "start": 839, + "end": 907 + } + }, + { + "content": "JUNK (attr .Value must have a value)", + "type": "Comment", + "span": { + "type": "Span", + "start": 907, + "end": 945 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 960, + "end": 960 + } + } + ], + "content": "key18 =\n.Value\n\n", + "span": { + "type": "Span", + "start": 946, + "end": 962 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key19", + "span": { + "type": "Span", + "start": 962, + "end": 967 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attribute", + "span": { + "type": "Span", + "start": 971, + "end": 980 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value\nContinued", + "span": { + "type": "Span", + "start": 983, + "end": 1002 + } + } + ], + "span": { + "type": "Span", + "start": 983, + "end": 1002 + } + }, + "span": { + "type": "Span", + "start": 970, + "end": 1002 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 962, + "end": 1002 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key20", + "span": { + "type": "Span", + "start": 1004, + "end": 1009 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": ".", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 1013, + "end": 1016 + } + }, + "span": { + "type": "Span", + "start": 1012, + "end": 1017 + } + }, + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 1017, + "end": 1022 + } + } + ], + "span": { + "type": "Span", + "start": 1012, + "end": 1022 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 1004, + "end": 1022 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 1023 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/literal_expressions.ftl b/fluent.syntax/src/test/resources/reference_fixtures/literal_expressions.ftl new file mode 100644 index 0000000..937b8a1 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/literal_expressions.ftl @@ -0,0 +1,3 @@ +string-expression = {"abc"} +number-expression = {123} +number-expression = {-3.14} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/literal_expressions.json b/fluent.syntax/src/test/resources/reference_fixtures/literal_expressions.json new file mode 100644 index 0000000..9af47d2 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/literal_expressions.json @@ -0,0 +1,148 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "string-expression", + "span": { + "type": "Span", + "start": 0, + "end": 17 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "abc", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 21, + "end": 26 + } + }, + "span": { + "type": "Span", + "start": 20, + "end": 27 + } + } + ], + "span": { + "type": "Span", + "start": 20, + "end": 27 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 27 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "number-expression", + "span": { + "type": "Span", + "start": 28, + "end": 45 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "123", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 49, + "end": 52 + } + }, + "span": { + "type": "Span", + "start": 48, + "end": 53 + } + } + ], + "span": { + "type": "Span", + "start": 48, + "end": 53 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 28, + "end": 53 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "number-expression", + "span": { + "type": "Span", + "start": 54, + "end": 71 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "-3.14", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 75, + "end": 80 + } + }, + "span": { + "type": "Span", + "start": 74, + "end": 81 + } + } + ], + "span": { + "type": "Span", + "start": 74, + "end": 81 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 54, + "end": 81 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 82 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/member_expressions.ftl b/fluent.syntax/src/test/resources/reference_fixtures/member_expressions.ftl new file mode 100644 index 0000000..db4c8f4 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/member_expressions.ftl @@ -0,0 +1,19 @@ +## Member expressions in placeables. + +# OK Message attributes may be interpolated in values. +message-attribute-expression-placeable = {msg.attr} + +# ERROR Term attributes may not be used for interpolation. +term-attribute-expression-placeable = {-term.attr} + + +## Member expressions in selectors. + +# OK Term attributes may be used as selectors. +term-attribute-expression-selector = {-term.attr -> + *[key] Value +} +# ERROR Message attributes may not be used as selectors. +message-attribute-expression-selector = {msg.attr -> + *[key] Value +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/member_expressions.json b/fluent.syntax/src/test/resources/reference_fixtures/member_expressions.json new file mode 100644 index 0000000..e08b9b1 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/member_expressions.json @@ -0,0 +1,280 @@ +{ + "type": "Resource", + "body": [ + { + "content": "Member expressions in placeables.", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 0, + "end": 36 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "message-attribute-expression-placeable", + "span": { + "type": "Span", + "start": 93, + "end": 131 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "msg", + "span": { + "type": "Span", + "start": 135, + "end": 138 + } + }, + "attribute": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 139, + "end": 143 + } + }, + "span": { + "type": "Span", + "start": 135, + "end": 143 + } + }, + "span": { + "type": "Span", + "start": 134, + "end": 144 + } + } + ], + "span": { + "type": "Span", + "start": 134, + "end": 144 + } + }, + "attributes": [], + "comment": { + "content": "OK Message attributes may be interpolated in values.", + "type": "Comment", + "span": { + "type": "Span", + "start": 38, + "end": 92 + } + }, + "span": { + "type": "Span", + "start": 38, + "end": 144 + } + }, + { + "content": "ERROR Term attributes may not be used for interpolation.", + "type": "Comment", + "span": { + "type": "Span", + "start": 146, + "end": 204 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0019", + "arguments": [], + "message": "Attributes of terms cannot be used as placeables", + "span": { + "type": "Span", + "start": 254, + "end": 254 + } + } + ], + "content": "term-attribute-expression-placeable = {-term.attr}\n\n\n", + "span": { + "type": "Span", + "start": 205, + "end": 258 + } + }, + { + "content": "Member expressions in selectors.", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 258, + "end": 293 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "term-attribute-expression-selector", + "span": { + "type": "Span", + "start": 342, + "end": 376 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "TermReference", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 381, + "end": 385 + } + }, + "attribute": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 386, + "end": 390 + } + }, + "arguments": null, + "span": { + "type": "Span", + "start": 380, + "end": 390 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 399, + "end": 402 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 404, + "end": 409 + } + } + ], + "span": { + "type": "Span", + "start": 404, + "end": 409 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 397, + "end": 409 + } + } + ], + "span": { + "type": "Span", + "start": 380, + "end": 410 + } + }, + "span": { + "type": "Span", + "start": 379, + "end": 411 + } + } + ], + "span": { + "type": "Span", + "start": 379, + "end": 411 + } + }, + "attributes": [], + "comment": { + "content": "OK Term attributes may be used as selectors.", + "type": "Comment", + "span": { + "type": "Span", + "start": 295, + "end": 341 + } + }, + "span": { + "type": "Span", + "start": 295, + "end": 411 + } + }, + { + "content": "ERROR Message attributes may not be used as selectors.", + "type": "Comment", + "span": { + "type": "Span", + "start": 412, + "end": 468 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0018", + "arguments": [], + "message": "Attributes of messages cannot be used as selectors", + "span": { + "type": "Span", + "start": 519, + "end": 519 + } + } + ], + "content": "message-attribute-expression-selector = {msg.attr ->\n *[key] Value\n}\n", + "span": { + "type": "Span", + "start": 469, + "end": 540 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 540 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/messages.ftl b/fluent.syntax/src/test/resources/reference_fixtures/messages.ftl new file mode 100644 index 0000000..dbc616e --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/messages.ftl @@ -0,0 +1,49 @@ +key01 = Value + +key02 = Value + .attr = Attribute + +key02 = Value + .attr1 = Attribute 1 + .attr2 = Attribute 2 + +key03 = + .attr = Attribute + +key04 = + .attr1 = Attribute 1 + .attr2 = Attribute 2 + +# < whitespace > +key05 = + .attr1 = Attribute 1 + +no-whitespace=Value + .attr1=Attribute 1 + +extra-whitespace = Value + .attr1 = Attribute 1 + +key06 = {""} + +# JUNK Missing value +key07 = + +# JUNK Missing = +key08 + +KEY09 = Value 09 + +key-10 = Value 10 +key_11 = Value 11 +key-12- = Value 12 +key_13_ = Value 13 + +# JUNK Invalid id +0err-14 = Value 14 + +# JUNK Invalid id +err-15? = Value 15 + +# JUNK Invalid id +err-ąę-16 = Value 16 diff --git a/fluent.syntax/src/test/resources/reference_fixtures/messages.json b/fluent.syntax/src/test/resources/reference_fixtures/messages.json new file mode 100644 index 0000000..bb1dd06 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/messages.json @@ -0,0 +1,998 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01", + "span": { + "type": "Span", + "start": 0, + "end": 5 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 8, + "end": 13 + } + } + ], + "span": { + "type": "Span", + "start": 8, + "end": 13 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 13 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key02", + "span": { + "type": "Span", + "start": 15, + "end": 20 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 23, + "end": 28 + } + } + ], + "span": { + "type": "Span", + "start": 23, + "end": 28 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 34, + "end": 38 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attribute", + "span": { + "type": "Span", + "start": 41, + "end": 50 + } + } + ], + "span": { + "type": "Span", + "start": 41, + "end": 50 + } + }, + "span": { + "type": "Span", + "start": 33, + "end": 50 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 15, + "end": 50 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key02", + "span": { + "type": "Span", + "start": 52, + "end": 57 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 60, + "end": 65 + } + } + ], + "span": { + "type": "Span", + "start": 60, + "end": 65 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr1", + "span": { + "type": "Span", + "start": 71, + "end": 76 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attribute 1", + "span": { + "type": "Span", + "start": 79, + "end": 90 + } + } + ], + "span": { + "type": "Span", + "start": 79, + "end": 90 + } + }, + "span": { + "type": "Span", + "start": 70, + "end": 90 + } + }, + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr2", + "span": { + "type": "Span", + "start": 96, + "end": 101 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attribute 2", + "span": { + "type": "Span", + "start": 104, + "end": 115 + } + } + ], + "span": { + "type": "Span", + "start": 104, + "end": 115 + } + }, + "span": { + "type": "Span", + "start": 95, + "end": 115 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 52, + "end": 115 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key03", + "span": { + "type": "Span", + "start": 117, + "end": 122 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 130, + "end": 134 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attribute", + "span": { + "type": "Span", + "start": 137, + "end": 146 + } + } + ], + "span": { + "type": "Span", + "start": 137, + "end": 146 + } + }, + "span": { + "type": "Span", + "start": 129, + "end": 146 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 117, + "end": 146 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key04", + "span": { + "type": "Span", + "start": 148, + "end": 153 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr1", + "span": { + "type": "Span", + "start": 161, + "end": 166 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attribute 1", + "span": { + "type": "Span", + "start": 169, + "end": 180 + } + } + ], + "span": { + "type": "Span", + "start": 169, + "end": 180 + } + }, + "span": { + "type": "Span", + "start": 160, + "end": 180 + } + }, + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr2", + "span": { + "type": "Span", + "start": 186, + "end": 191 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attribute 2", + "span": { + "type": "Span", + "start": 194, + "end": 205 + } + } + ], + "span": { + "type": "Span", + "start": 194, + "end": 205 + } + }, + "span": { + "type": "Span", + "start": 185, + "end": 205 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 148, + "end": 205 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key05", + "span": { + "type": "Span", + "start": 231, + "end": 236 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr1", + "span": { + "type": "Span", + "start": 260, + "end": 265 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attribute 1", + "span": { + "type": "Span", + "start": 268, + "end": 279 + } + } + ], + "span": { + "type": "Span", + "start": 268, + "end": 279 + } + }, + "span": { + "type": "Span", + "start": 259, + "end": 279 + } + } + ], + "comment": { + "content": " < whitespace >", + "type": "Comment", + "span": { + "type": "Span", + "start": 207, + "end": 230 + } + }, + "span": { + "type": "Span", + "start": 207, + "end": 279 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "no-whitespace", + "span": { + "type": "Span", + "start": 281, + "end": 294 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 295, + "end": 300 + } + } + ], + "span": { + "type": "Span", + "start": 295, + "end": 300 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr1", + "span": { + "type": "Span", + "start": 306, + "end": 311 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attribute 1", + "span": { + "type": "Span", + "start": 312, + "end": 323 + } + } + ], + "span": { + "type": "Span", + "start": 312, + "end": 323 + } + }, + "span": { + "type": "Span", + "start": 305, + "end": 323 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 281, + "end": 323 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "extra-whitespace", + "span": { + "type": "Span", + "start": 325, + "end": 341 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 348, + "end": 353 + } + } + ], + "span": { + "type": "Span", + "start": 348, + "end": 353 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr1", + "span": { + "type": "Span", + "start": 359, + "end": 364 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attribute 1", + "span": { + "type": "Span", + "start": 374, + "end": 385 + } + } + ], + "span": { + "type": "Span", + "start": 374, + "end": 385 + } + }, + "span": { + "type": "Span", + "start": 358, + "end": 385 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 325, + "end": 385 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key06", + "span": { + "type": "Span", + "start": 387, + "end": 392 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 396, + "end": 398 + } + }, + "span": { + "type": "Span", + "start": 395, + "end": 399 + } + } + ], + "span": { + "type": "Span", + "start": 395, + "end": 399 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 387, + "end": 399 + } + }, + { + "content": "JUNK Missing value", + "type": "Comment", + "span": { + "type": "Span", + "start": 401, + "end": 421 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0005", + "arguments": [ + "key07" + ], + "message": "Expected message \"key07\" to have a value or attributes", + "span": { + "type": "Span", + "start": 429, + "end": 429 + } + } + ], + "content": "key07 =\n\n", + "span": { + "type": "Span", + "start": 422, + "end": 431 + } + }, + { + "content": "JUNK Missing =", + "type": "Comment", + "span": { + "type": "Span", + "start": 431, + "end": 447 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 453, + "end": 453 + } + } + ], + "content": "key08\n\n", + "span": { + "type": "Span", + "start": 448, + "end": 455 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "KEY09", + "span": { + "type": "Span", + "start": 455, + "end": 460 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 09", + "span": { + "type": "Span", + "start": 463, + "end": 471 + } + } + ], + "span": { + "type": "Span", + "start": 463, + "end": 471 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 455, + "end": 471 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key-10", + "span": { + "type": "Span", + "start": 473, + "end": 479 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 10", + "span": { + "type": "Span", + "start": 482, + "end": 490 + } + } + ], + "span": { + "type": "Span", + "start": 482, + "end": 490 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 473, + "end": 490 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key_11", + "span": { + "type": "Span", + "start": 491, + "end": 497 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 11", + "span": { + "type": "Span", + "start": 500, + "end": 508 + } + } + ], + "span": { + "type": "Span", + "start": 500, + "end": 508 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 491, + "end": 508 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key-12-", + "span": { + "type": "Span", + "start": 509, + "end": 516 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 12", + "span": { + "type": "Span", + "start": 519, + "end": 527 + } + } + ], + "span": { + "type": "Span", + "start": 519, + "end": 527 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 509, + "end": 527 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key_13_", + "span": { + "type": "Span", + "start": 528, + "end": 535 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 13", + "span": { + "type": "Span", + "start": 538, + "end": 546 + } + } + ], + "span": { + "type": "Span", + "start": 538, + "end": 546 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 528, + "end": 546 + } + }, + { + "content": "JUNK Invalid id", + "type": "Comment", + "span": { + "type": "Span", + "start": 548, + "end": 565 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0002", + "arguments": [], + "message": "Expected an entry start", + "span": { + "type": "Span", + "start": 566, + "end": 566 + } + } + ], + "content": "0err-14 = Value 14\n\n", + "span": { + "type": "Span", + "start": 566, + "end": 586 + } + }, + { + "content": "JUNK Invalid id", + "type": "Comment", + "span": { + "type": "Span", + "start": 586, + "end": 603 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 610, + "end": 610 + } + } + ], + "content": "err-15? = Value 15\n\n", + "span": { + "type": "Span", + "start": 604, + "end": 624 + } + }, + { + "content": "JUNK Invalid id", + "type": "Comment", + "span": { + "type": "Span", + "start": 624, + "end": 641 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 646, + "end": 646 + } + } + ], + "content": "err-ąę-16 = Value 16\n", + "span": { + "type": "Span", + "start": 642, + "end": 663 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 663 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/mixed_entries.ftl b/fluent.syntax/src/test/resources/reference_fixtures/mixed_entries.ftl new file mode 100644 index 0000000..99cc023 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/mixed_entries.ftl @@ -0,0 +1,24 @@ +# License Comment + +### Resource Comment + +-brand-name = Aurora + +## Group Comment + +key01 = + .attr = Attribute + +ą=Invalid identifier +ć=Another one + +# Message Comment +key02 = Value + +# Standalone Comment + .attr = Dangling attribute + +# There are 5 spaces on the line between key03 and key04. +key03 = Value 03 + +key04 = Value 04 diff --git a/fluent.syntax/src/test/resources/reference_fixtures/mixed_entries.json b/fluent.syntax/src/test/resources/reference_fixtures/mixed_entries.json new file mode 100644 index 0000000..d56ee5b --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/mixed_entries.json @@ -0,0 +1,315 @@ +{ + "type": "Resource", + "body": [ + { + "content": "License Comment", + "type": "Comment", + "span": { + "type": "Span", + "start": 0, + "end": 17 + } + }, + { + "content": "Resource Comment", + "type": "ResourceComment", + "span": { + "type": "Span", + "start": 19, + "end": 39 + } + }, + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "brand-name", + "span": { + "type": "Span", + "start": 42, + "end": 52 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Aurora", + "span": { + "type": "Span", + "start": 55, + "end": 61 + } + } + ], + "span": { + "type": "Span", + "start": 55, + "end": 61 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 41, + "end": 61 + } + }, + { + "content": "Group Comment", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 63, + "end": 79 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01", + "span": { + "type": "Span", + "start": 81, + "end": 86 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 94, + "end": 98 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attribute", + "span": { + "type": "Span", + "start": 101, + "end": 110 + } + } + ], + "span": { + "type": "Span", + "start": 101, + "end": 110 + } + }, + "span": { + "type": "Span", + "start": 93, + "end": 110 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 81, + "end": 110 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0002", + "arguments": [], + "message": "Expected an entry start", + "span": { + "type": "Span", + "start": 112, + "end": 112 + } + } + ], + "content": "ą=Invalid identifier\nć=Another one\n\n", + "span": { + "type": "Span", + "start": 112, + "end": 148 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key02", + "span": { + "type": "Span", + "start": 166, + "end": 171 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 174, + "end": 179 + } + } + ], + "span": { + "type": "Span", + "start": 174, + "end": 179 + } + }, + "attributes": [], + "comment": { + "content": "Message Comment", + "type": "Comment", + "span": { + "type": "Span", + "start": 148, + "end": 165 + } + }, + "span": { + "type": "Span", + "start": 148, + "end": 179 + } + }, + { + "content": "Standalone Comment", + "type": "Comment", + "span": { + "type": "Span", + "start": 181, + "end": 201 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0002", + "arguments": [], + "message": "Expected an entry start", + "span": { + "type": "Span", + "start": 202, + "end": 202 + } + } + ], + "content": " .attr = Dangling attribute\n\n", + "span": { + "type": "Span", + "start": 202, + "end": 234 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key03", + "span": { + "type": "Span", + "start": 292, + "end": 297 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 03", + "span": { + "type": "Span", + "start": 300, + "end": 308 + } + } + ], + "span": { + "type": "Span", + "start": 300, + "end": 308 + } + }, + "attributes": [], + "comment": { + "content": "There are 5 spaces on the line between key03 and key04.", + "type": "Comment", + "span": { + "type": "Span", + "start": 234, + "end": 291 + } + }, + "span": { + "type": "Span", + "start": 234, + "end": 308 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key04", + "span": { + "type": "Span", + "start": 315, + "end": 320 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 04", + "span": { + "type": "Span", + "start": 323, + "end": 331 + } + } + ], + "span": { + "type": "Span", + "start": 323, + "end": 331 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 315, + "end": 331 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 332 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/multiline_values.ftl b/fluent.syntax/src/test/resources/reference_fixtures/multiline_values.ftl new file mode 100644 index 0000000..e3739bb --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/multiline_values.ftl @@ -0,0 +1,60 @@ +key01 = A multiline value + continued on the next line + + and also down here. + +key02 = + A multiline value starting + on a new line. + +key03 = + .attr = A multiline attribute value + continued on the next line + + and also down here. + +key04 = + .attr = + A multiline attribute value + staring on a new line + +key05 = + + A multiline value with non-standard + + indentation. + +key06 = + A multiline value with {"placeables"} + {"at"} the beginning and the end + {"of lines"}{"."} + +key07 = + {"A multiline value"} starting and ending {"with a placeable"} + +key08 = Leading and trailing whitespace. + +key09 = zero + three + two + one + zero + +key10 = + two + zero + four + +key11 = + + + two + zero + +key12 = +{"."} + four + +key13 = + four +{"."} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/multiline_values.json b/fluent.syntax/src/test/resources/reference_fixtures/multiline_values.json new file mode 100644 index 0000000..08f65d8 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/multiline_values.json @@ -0,0 +1,696 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01", + "span": { + "type": "Span", + "start": 0, + "end": 5 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "A multiline value\ncontinued on the next line\n\nand also down here.", + "span": { + "type": "Span", + "start": 8, + "end": 81 + } + } + ], + "span": { + "type": "Span", + "start": 8, + "end": 81 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 81 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key02", + "span": { + "type": "Span", + "start": 83, + "end": 88 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "A multiline value starting\non a new line.", + "span": { + "type": "Span", + "start": 95, + "end": 140 + } + } + ], + "span": { + "type": "Span", + "start": 91, + "end": 140 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 83, + "end": 140 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key03", + "span": { + "type": "Span", + "start": 142, + "end": 147 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 155, + "end": 159 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "A multiline attribute value\ncontinued on the next line\n\nand also down here.", + "span": { + "type": "Span", + "start": 162, + "end": 253 + } + } + ], + "span": { + "type": "Span", + "start": 162, + "end": 253 + } + }, + "span": { + "type": "Span", + "start": 154, + "end": 253 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 142, + "end": 253 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key04", + "span": { + "type": "Span", + "start": 255, + "end": 260 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 268, + "end": 272 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "A multiline attribute value\nstaring on a new line", + "span": { + "type": "Span", + "start": 283, + "end": 340 + } + } + ], + "span": { + "type": "Span", + "start": 275, + "end": 340 + } + }, + "span": { + "type": "Span", + "start": 267, + "end": 340 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 255, + "end": 340 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key05", + "span": { + "type": "Span", + "start": 342, + "end": 347 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "A multiline value with non-standard\n\n indentation.", + "span": { + "type": "Span", + "start": 352, + "end": 406 + } + } + ], + "span": { + "type": "Span", + "start": 351, + "end": 406 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 342, + "end": 406 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key06", + "span": { + "type": "Span", + "start": 408, + "end": 413 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "A multiline value with ", + "span": { + "type": "Span", + "start": 420, + "end": 443 + } + }, + { + "type": "Placeable", + "expression": { + "value": "placeables", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 444, + "end": 456 + } + }, + "span": { + "type": "Span", + "start": 443, + "end": 457 + } + }, + { + "type": "TextElement", + "value": "\n", + "span": { + "type": "Span", + "start": 457, + "end": 462 + } + }, + { + "type": "Placeable", + "expression": { + "value": "at", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 463, + "end": 467 + } + }, + "span": { + "type": "Span", + "start": 462, + "end": 468 + } + }, + { + "type": "TextElement", + "value": " the beginning and the end\n", + "span": { + "type": "Span", + "start": 468, + "end": 499 + } + }, + { + "type": "Placeable", + "expression": { + "value": "of lines", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 500, + "end": 510 + } + }, + "span": { + "type": "Span", + "start": 499, + "end": 511 + } + }, + { + "type": "Placeable", + "expression": { + "value": ".", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 512, + "end": 515 + } + }, + "span": { + "type": "Span", + "start": 511, + "end": 516 + } + } + ], + "span": { + "type": "Span", + "start": 416, + "end": 516 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 408, + "end": 516 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key07", + "span": { + "type": "Span", + "start": 518, + "end": 523 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "A multiline value", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 531, + "end": 550 + } + }, + "span": { + "type": "Span", + "start": 530, + "end": 551 + } + }, + { + "type": "TextElement", + "value": " starting and ending ", + "span": { + "type": "Span", + "start": 551, + "end": 572 + } + }, + { + "type": "Placeable", + "expression": { + "value": "with a placeable", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 573, + "end": 591 + } + }, + "span": { + "type": "Span", + "start": 572, + "end": 592 + } + } + ], + "span": { + "type": "Span", + "start": 526, + "end": 592 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 518, + "end": 592 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key08", + "span": { + "type": "Span", + "start": 594, + "end": 599 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Leading and trailing whitespace.", + "span": { + "type": "Span", + "start": 606, + "end": 643 + } + } + ], + "span": { + "type": "Span", + "start": 606, + "end": 643 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 594, + "end": 643 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key09", + "span": { + "type": "Span", + "start": 645, + "end": 650 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "zero\n three\n two\n one\nzero", + "span": { + "type": "Span", + "start": 653, + "end": 690 + } + } + ], + "span": { + "type": "Span", + "start": 653, + "end": 690 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 645, + "end": 690 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key10", + "span": { + "type": "Span", + "start": 692, + "end": 697 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": " two\nzero\n four", + "span": { + "type": "Span", + "start": 700, + "end": 731 + } + } + ], + "span": { + "type": "Span", + "start": 700, + "end": 731 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 692, + "end": 731 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key11", + "span": { + "type": "Span", + "start": 733, + "end": 738 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": " two\nzero", + "span": { + "type": "Span", + "start": 743, + "end": 761 + } + } + ], + "span": { + "type": "Span", + "start": 743, + "end": 761 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 733, + "end": 761 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key12", + "span": { + "type": "Span", + "start": 763, + "end": 768 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": ".", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 772, + "end": 775 + } + }, + "span": { + "type": "Span", + "start": 771, + "end": 776 + } + }, + { + "type": "TextElement", + "value": "\n four", + "span": { + "type": "Span", + "start": 776, + "end": 785 + } + } + ], + "span": { + "type": "Span", + "start": 771, + "end": 785 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 763, + "end": 785 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key13", + "span": { + "type": "Span", + "start": 787, + "end": 792 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": " four\n", + "span": { + "type": "Span", + "start": 795, + "end": 804 + } + }, + { + "type": "Placeable", + "expression": { + "value": ".", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 805, + "end": 808 + } + }, + "span": { + "type": "Span", + "start": 804, + "end": 809 + } + } + ], + "span": { + "type": "Span", + "start": 795, + "end": 809 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 787, + "end": 809 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 810 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/numbers.ftl b/fluent.syntax/src/test/resources/reference_fixtures/numbers.ftl new file mode 100644 index 0000000..ed70068 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/numbers.ftl @@ -0,0 +1,38 @@ +int-zero = {0} +int-positive = {1} +int-negative = {-1} +int-negative-zero = {-0} + +int-positive-padded = {01} +int-negative-padded = {-01} +int-zero-padded = {00} +int-negative-zero-padded = {-00} + +float-zero = {0.0} +float-positive = {0.01} +float-positive-one = {1.03} +float-positive-without-fraction = {1.000} + +float-negative = {-0.01} +float-negative-one = {-1.03} +float-negative-zero = {-0.0} +float-negative-without-fraction = {-1.000} + +float-positive-padded-left = {01.03} +float-positive-padded-right = {1.0300} +float-positive-padded-both = {01.0300} + +float-negative-padded-left = {-01.03} +float-negative-padded-right = {-1.0300} +float-negative-padded-both = {-01.0300} + + +## ERRORS + +err01 = {1.} +err02 = {.02} +err03 = {1.02.03} +err04 = {1. 02} +err05 = {1 .02} +err06 = {- 1} +err07 = {1,02} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/numbers.json b/fluent.syntax/src/test/resources/reference_fixtures/numbers.json new file mode 100644 index 0000000..8e3a7d1 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/numbers.json @@ -0,0 +1,1197 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "int-zero", + "span": { + "type": "Span", + "start": 0, + "end": 8 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "0", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 12, + "end": 13 + } + }, + "span": { + "type": "Span", + "start": 11, + "end": 14 + } + } + ], + "span": { + "type": "Span", + "start": 11, + "end": 14 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 14 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "int-positive", + "span": { + "type": "Span", + "start": 15, + "end": 27 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 31, + "end": 32 + } + }, + "span": { + "type": "Span", + "start": 30, + "end": 33 + } + } + ], + "span": { + "type": "Span", + "start": 30, + "end": 33 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 15, + "end": 33 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "int-negative", + "span": { + "type": "Span", + "start": 34, + "end": 46 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "-1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 50, + "end": 52 + } + }, + "span": { + "type": "Span", + "start": 49, + "end": 53 + } + } + ], + "span": { + "type": "Span", + "start": 49, + "end": 53 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 34, + "end": 53 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "int-negative-zero", + "span": { + "type": "Span", + "start": 54, + "end": 71 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "-0", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 75, + "end": 77 + } + }, + "span": { + "type": "Span", + "start": 74, + "end": 78 + } + } + ], + "span": { + "type": "Span", + "start": 74, + "end": 78 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 54, + "end": 78 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "int-positive-padded", + "span": { + "type": "Span", + "start": 80, + "end": 99 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "01", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 103, + "end": 105 + } + }, + "span": { + "type": "Span", + "start": 102, + "end": 106 + } + } + ], + "span": { + "type": "Span", + "start": 102, + "end": 106 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 80, + "end": 106 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "int-negative-padded", + "span": { + "type": "Span", + "start": 107, + "end": 126 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "-01", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 130, + "end": 133 + } + }, + "span": { + "type": "Span", + "start": 129, + "end": 134 + } + } + ], + "span": { + "type": "Span", + "start": 129, + "end": 134 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 107, + "end": 134 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "int-zero-padded", + "span": { + "type": "Span", + "start": 135, + "end": 150 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "00", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 154, + "end": 156 + } + }, + "span": { + "type": "Span", + "start": 153, + "end": 157 + } + } + ], + "span": { + "type": "Span", + "start": 153, + "end": 157 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 135, + "end": 157 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "int-negative-zero-padded", + "span": { + "type": "Span", + "start": 158, + "end": 182 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "-00", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 186, + "end": 189 + } + }, + "span": { + "type": "Span", + "start": 185, + "end": 190 + } + } + ], + "span": { + "type": "Span", + "start": 185, + "end": 190 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 158, + "end": 190 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "float-zero", + "span": { + "type": "Span", + "start": 192, + "end": 202 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "0.0", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 206, + "end": 209 + } + }, + "span": { + "type": "Span", + "start": 205, + "end": 210 + } + } + ], + "span": { + "type": "Span", + "start": 205, + "end": 210 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 192, + "end": 210 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "float-positive", + "span": { + "type": "Span", + "start": 211, + "end": 225 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "0.01", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 229, + "end": 233 + } + }, + "span": { + "type": "Span", + "start": 228, + "end": 234 + } + } + ], + "span": { + "type": "Span", + "start": 228, + "end": 234 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 211, + "end": 234 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "float-positive-one", + "span": { + "type": "Span", + "start": 235, + "end": 253 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "1.03", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 257, + "end": 261 + } + }, + "span": { + "type": "Span", + "start": 256, + "end": 262 + } + } + ], + "span": { + "type": "Span", + "start": 256, + "end": 262 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 235, + "end": 262 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "float-positive-without-fraction", + "span": { + "type": "Span", + "start": 263, + "end": 294 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "1.000", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 298, + "end": 303 + } + }, + "span": { + "type": "Span", + "start": 297, + "end": 304 + } + } + ], + "span": { + "type": "Span", + "start": 297, + "end": 304 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 263, + "end": 304 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "float-negative", + "span": { + "type": "Span", + "start": 306, + "end": 320 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "-0.01", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 324, + "end": 329 + } + }, + "span": { + "type": "Span", + "start": 323, + "end": 330 + } + } + ], + "span": { + "type": "Span", + "start": 323, + "end": 330 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 306, + "end": 330 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "float-negative-one", + "span": { + "type": "Span", + "start": 331, + "end": 349 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "-1.03", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 353, + "end": 358 + } + }, + "span": { + "type": "Span", + "start": 352, + "end": 359 + } + } + ], + "span": { + "type": "Span", + "start": 352, + "end": 359 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 331, + "end": 359 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "float-negative-zero", + "span": { + "type": "Span", + "start": 360, + "end": 379 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "-0.0", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 383, + "end": 387 + } + }, + "span": { + "type": "Span", + "start": 382, + "end": 388 + } + } + ], + "span": { + "type": "Span", + "start": 382, + "end": 388 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 360, + "end": 388 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "float-negative-without-fraction", + "span": { + "type": "Span", + "start": 389, + "end": 420 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "-1.000", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 424, + "end": 430 + } + }, + "span": { + "type": "Span", + "start": 423, + "end": 431 + } + } + ], + "span": { + "type": "Span", + "start": 423, + "end": 431 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 389, + "end": 431 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "float-positive-padded-left", + "span": { + "type": "Span", + "start": 433, + "end": 459 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "01.03", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 463, + "end": 468 + } + }, + "span": { + "type": "Span", + "start": 462, + "end": 469 + } + } + ], + "span": { + "type": "Span", + "start": 462, + "end": 469 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 433, + "end": 469 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "float-positive-padded-right", + "span": { + "type": "Span", + "start": 470, + "end": 497 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "1.0300", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 501, + "end": 507 + } + }, + "span": { + "type": "Span", + "start": 500, + "end": 508 + } + } + ], + "span": { + "type": "Span", + "start": 500, + "end": 508 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 470, + "end": 508 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "float-positive-padded-both", + "span": { + "type": "Span", + "start": 509, + "end": 535 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "01.0300", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 539, + "end": 546 + } + }, + "span": { + "type": "Span", + "start": 538, + "end": 547 + } + } + ], + "span": { + "type": "Span", + "start": 538, + "end": 547 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 509, + "end": 547 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "float-negative-padded-left", + "span": { + "type": "Span", + "start": 549, + "end": 575 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "-01.03", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 579, + "end": 585 + } + }, + "span": { + "type": "Span", + "start": 578, + "end": 586 + } + } + ], + "span": { + "type": "Span", + "start": 578, + "end": 586 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 549, + "end": 586 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "float-negative-padded-right", + "span": { + "type": "Span", + "start": 587, + "end": 614 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "-1.0300", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 618, + "end": 625 + } + }, + "span": { + "type": "Span", + "start": 617, + "end": 626 + } + } + ], + "span": { + "type": "Span", + "start": 617, + "end": 626 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 587, + "end": 626 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "float-negative-padded-both", + "span": { + "type": "Span", + "start": 627, + "end": 653 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "-01.0300", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 657, + "end": 665 + } + }, + "span": { + "type": "Span", + "start": 656, + "end": 666 + } + } + ], + "span": { + "type": "Span", + "start": 656, + "end": 666 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 627, + "end": 666 + } + }, + { + "content": "ERRORS", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 669, + "end": 678 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "0-9" + ], + "message": "Expected a character from range: \"0-9\"", + "span": { + "type": "Span", + "start": 691, + "end": 691 + } + } + ], + "content": "err01 = {1.}\n", + "span": { + "type": "Span", + "start": 680, + "end": 693 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0028", + "arguments": [], + "message": "Expected an inline expression", + "span": { + "type": "Span", + "start": 702, + "end": 702 + } + } + ], + "content": "err02 = {.02}\n", + "span": { + "type": "Span", + "start": 693, + "end": 707 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 720, + "end": 720 + } + } + ], + "content": "err03 = {1.02.03}\n", + "span": { + "type": "Span", + "start": 707, + "end": 725 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "0-9" + ], + "message": "Expected a character from range: \"0-9\"", + "span": { + "type": "Span", + "start": 736, + "end": 736 + } + } + ], + "content": "err04 = {1. 02}\n", + "span": { + "type": "Span", + "start": 725, + "end": 741 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 752, + "end": 752 + } + } + ], + "content": "err05 = {1 .02}\n", + "span": { + "type": "Span", + "start": 741, + "end": 757 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 767, + "end": 767 + } + } + ], + "content": "err06 = {- 1}\n", + "span": { + "type": "Span", + "start": 757, + "end": 771 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 781, + "end": 781 + } + } + ], + "content": "err07 = {1,02}\n", + "span": { + "type": "Span", + "start": 771, + "end": 786 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 786 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/obsolete.ftl b/fluent.syntax/src/test/resources/reference_fixtures/obsolete.ftl new file mode 100644 index 0000000..e0869e3 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/obsolete.ftl @@ -0,0 +1,29 @@ +### The syntax in this file has been discontinued. It is no longer part of the +### Fluent specification and should not be implemented nor used. We're keeping +### these fixtures around to protect against accidental syntax reuse. + + +## Variant lists. + +message-variant-list = + { + *[key] Value + } + +-term-variant-list = + { + *[key] Value + } + + +## Variant expressions. + +message-variant-expression-placeable = {msg[case]} +message-variant-expression-selector = {msg[case] -> + *[key] Value +} + +term-variant-expression-placeable = {-term[case]} +term-variant-expression-selector = {-term[case] -> + *[key] Value +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/obsolete.json b/fluent.syntax/src/test/resources/reference_fixtures/obsolete.json new file mode 100644 index 0000000..879e4be --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/obsolete.json @@ -0,0 +1,177 @@ +{ + "type": "Resource", + "body": [ + { + "content": "The syntax in this file has been discontinued. It is no longer part of the\nFluent specification and should not be implemented nor used. We're keeping\nthese fixtures around to protect against accidental syntax reuse.", + "type": "ResourceComment", + "span": { + "type": "Span", + "start": 0, + "end": 227 + } + }, + { + "content": "Variant lists.", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 230, + "end": 247 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0028", + "arguments": [], + "message": "Expected an inline expression", + "span": { + "type": "Span", + "start": 285, + "end": 285 + } + } + ], + "content": "message-variant-list =\n {\n *[key] Value\n }\n\n", + "span": { + "type": "Span", + "start": 249, + "end": 305 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0028", + "arguments": [], + "message": "Expected an inline expression", + "span": { + "type": "Span", + "start": 339, + "end": 339 + } + } + ], + "content": "-term-variant-list =\n {\n *[key] Value\n }\n\n\n", + "span": { + "type": "Span", + "start": 305, + "end": 360 + } + }, + { + "content": "Variant expressions.", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 360, + "end": 383 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 428, + "end": 428 + } + } + ], + "content": "message-variant-expression-placeable = {msg[case]}\n", + "span": { + "type": "Span", + "start": 385, + "end": 436 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 478, + "end": 478 + } + } + ], + "content": "message-variant-expression-selector = {msg[case] ->\n *[key] Value\n}\n\n", + "span": { + "type": "Span", + "start": 436, + "end": 507 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 549, + "end": 549 + } + } + ], + "content": "term-variant-expression-placeable = {-term[case]}\n", + "span": { + "type": "Span", + "start": 507, + "end": 557 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 598, + "end": 598 + } + } + ], + "content": "term-variant-expression-selector = {-term[case] ->\n *[key] Value\n}\n", + "span": { + "type": "Span", + "start": 557, + "end": 626 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 626 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/placeables.ftl b/fluent.syntax/src/test/resources/reference_fixtures/placeables.ftl new file mode 100644 index 0000000..7a1b280 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/placeables.ftl @@ -0,0 +1,15 @@ +nested-placeable = {{{1}}} +padded-placeable = { 1 } +sparse-placeable = { { 1 } } + +# ERROR Unmatched opening brace +unmatched-open1 = { 1 + +# ERROR Unmatched opening brace +unmatched-open2 = {{ 1 } + +# ERROR Unmatched closing brace +unmatched-close1 = 1 } + +# ERROR Unmatched closing brace +unmatched-close2 = { 1 }} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/placeables.json b/fluent.syntax/src/test/resources/reference_fixtures/placeables.json new file mode 100644 index 0000000..83c76c0 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/placeables.json @@ -0,0 +1,300 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "nested-placeable", + "span": { + "type": "Span", + "start": 0, + "end": 16 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "Placeable", + "expression": { + "type": "Placeable", + "expression": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 22, + "end": 23 + } + }, + "span": { + "type": "Span", + "start": 21, + "end": 24 + } + }, + "span": { + "type": "Span", + "start": 20, + "end": 25 + } + }, + "span": { + "type": "Span", + "start": 19, + "end": 26 + } + } + ], + "span": { + "type": "Span", + "start": 19, + "end": 26 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 26 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "padded-placeable", + "span": { + "type": "Span", + "start": 27, + "end": 43 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 49, + "end": 50 + } + }, + "span": { + "type": "Span", + "start": 46, + "end": 53 + } + } + ], + "span": { + "type": "Span", + "start": 46, + "end": 53 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 27, + "end": 53 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "sparse-placeable", + "span": { + "type": "Span", + "start": 54, + "end": 70 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "Placeable", + "expression": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 77, + "end": 78 + } + }, + "span": { + "type": "Span", + "start": 75, + "end": 80 + } + }, + "span": { + "type": "Span", + "start": 73, + "end": 82 + } + } + ], + "span": { + "type": "Span", + "start": 73, + "end": 82 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 54, + "end": 82 + } + }, + { + "content": "ERROR Unmatched opening brace", + "type": "Comment", + "span": { + "type": "Span", + "start": 84, + "end": 115 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 139, + "end": 139 + } + } + ], + "content": "unmatched-open1 = { 1\n\n", + "span": { + "type": "Span", + "start": 116, + "end": 139 + } + }, + { + "content": "ERROR Unmatched opening brace", + "type": "Comment", + "span": { + "type": "Span", + "start": 139, + "end": 170 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 197, + "end": 197 + } + } + ], + "content": "unmatched-open2 = {{ 1 }\n\n", + "span": { + "type": "Span", + "start": 171, + "end": 197 + } + }, + { + "content": "ERROR Unmatched closing brace", + "type": "Comment", + "span": { + "type": "Span", + "start": 197, + "end": 228 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0027", + "arguments": [], + "message": "Unbalanced closing brace in TextElement.", + "span": { + "type": "Span", + "start": 250, + "end": 250 + } + } + ], + "content": "unmatched-close1 = 1 }\n\n", + "span": { + "type": "Span", + "start": 229, + "end": 253 + } + }, + { + "content": "ERROR Unmatched closing brace", + "type": "Comment", + "span": { + "type": "Span", + "start": 253, + "end": 284 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0027", + "arguments": [], + "message": "Unbalanced closing brace in TextElement.", + "span": { + "type": "Span", + "start": 309, + "end": 309 + } + } + ], + "content": "unmatched-close2 = { 1 }}\n", + "span": { + "type": "Span", + "start": 285, + "end": 311 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 311 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/reference_expressions.ftl b/fluent.syntax/src/test/resources/reference_fixtures/reference_expressions.ftl new file mode 100644 index 0000000..5b03334 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/reference_expressions.ftl @@ -0,0 +1,30 @@ +## Reference expressions in placeables. + +message-reference-placeable = {msg} +term-reference-placeable = {-term} +variable-reference-placeable = {$var} + +# Function references are invalid outside of call expressions. +# This parses as a valid MessageReference. +function-reference-placeable = {FUN} + + +## Reference expressions in selectors. + +variable-reference-selector = {$var -> + *[key] Value +} + +# ERROR Message values may not be used as selectors. +message-reference-selector = {msg -> + *[key] Value +} +# ERROR Term values may not be used as selectors. +term-reference-selector = {-term -> + *[key] Value +} +# ERROR Function references are invalid outside of call expressions, and this +# parses as a MessageReference which isn't a valid selector. +function-expression-selector = {FUN -> + *[key] Value +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/reference_expressions.json b/fluent.syntax/src/test/resources/reference_fixtures/reference_expressions.json new file mode 100644 index 0000000..fde8377 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/reference_expressions.json @@ -0,0 +1,450 @@ +{ + "type": "Resource", + "body": [ + { + "content": "Reference expressions in placeables.", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 0, + "end": 39 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "message-reference-placeable", + "span": { + "type": "Span", + "start": 41, + "end": 68 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "msg", + "span": { + "type": "Span", + "start": 72, + "end": 75 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 72, + "end": 75 + } + }, + "span": { + "type": "Span", + "start": 71, + "end": 76 + } + } + ], + "span": { + "type": "Span", + "start": 71, + "end": 76 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 41, + "end": 76 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "term-reference-placeable", + "span": { + "type": "Span", + "start": 77, + "end": 101 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "TermReference", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 106, + "end": 110 + } + }, + "attribute": null, + "arguments": null, + "span": { + "type": "Span", + "start": 105, + "end": 110 + } + }, + "span": { + "type": "Span", + "start": 104, + "end": 111 + } + } + ], + "span": { + "type": "Span", + "start": 104, + "end": 111 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 77, + "end": 111 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "variable-reference-placeable", + "span": { + "type": "Span", + "start": 112, + "end": 140 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "var", + "span": { + "type": "Span", + "start": 145, + "end": 148 + } + }, + "span": { + "type": "Span", + "start": 144, + "end": 148 + } + }, + "span": { + "type": "Span", + "start": 143, + "end": 149 + } + } + ], + "span": { + "type": "Span", + "start": 143, + "end": 149 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 112, + "end": 149 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "function-reference-placeable", + "span": { + "type": "Span", + "start": 257, + "end": 285 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "FUN", + "span": { + "type": "Span", + "start": 289, + "end": 292 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 289, + "end": 292 + } + }, + "span": { + "type": "Span", + "start": 288, + "end": 293 + } + } + ], + "span": { + "type": "Span", + "start": 288, + "end": 293 + } + }, + "attributes": [], + "comment": { + "content": "Function references are invalid outside of call expressions.\nThis parses as a valid MessageReference.", + "type": "Comment", + "span": { + "type": "Span", + "start": 151, + "end": 256 + } + }, + "span": { + "type": "Span", + "start": 151, + "end": 293 + } + }, + { + "content": "Reference expressions in selectors.", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 296, + "end": 334 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "variable-reference-selector", + "span": { + "type": "Span", + "start": 336, + "end": 363 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "var", + "span": { + "type": "Span", + "start": 368, + "end": 371 + } + }, + "span": { + "type": "Span", + "start": 367, + "end": 371 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 380, + "end": 383 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 385, + "end": 390 + } + } + ], + "span": { + "type": "Span", + "start": 385, + "end": 390 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 378, + "end": 390 + } + } + ], + "span": { + "type": "Span", + "start": 367, + "end": 391 + } + }, + "span": { + "type": "Span", + "start": 366, + "end": 392 + } + } + ], + "span": { + "type": "Span", + "start": 366, + "end": 392 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 336, + "end": 392 + } + }, + { + "content": "ERROR Message values may not be used as selectors.", + "type": "Comment", + "span": { + "type": "Span", + "start": 394, + "end": 446 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0016", + "arguments": [], + "message": "Message references cannot be used as selectors", + "span": { + "type": "Span", + "start": 481, + "end": 481 + } + } + ], + "content": "message-reference-selector = {msg ->\n *[key] Value\n}\n", + "span": { + "type": "Span", + "start": 447, + "end": 502 + } + }, + { + "content": "ERROR Term values may not be used as selectors.", + "type": "Comment", + "span": { + "type": "Span", + "start": 502, + "end": 551 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0017", + "arguments": [], + "message": "Terms cannot be used as selectors", + "span": { + "type": "Span", + "start": 585, + "end": 585 + } + } + ], + "content": "term-reference-selector = {-term ->\n *[key] Value\n}\n", + "span": { + "type": "Span", + "start": 552, + "end": 606 + } + }, + { + "content": "ERROR Function references are invalid outside of call expressions, and this\nparses as a MessageReference which isn't a valid selector.", + "type": "Comment", + "span": { + "type": "Span", + "start": 606, + "end": 744 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0016", + "arguments": [], + "message": "Message references cannot be used as selectors", + "span": { + "type": "Span", + "start": 781, + "end": 781 + } + } + ], + "content": "function-expression-selector = {FUN ->\n *[key] Value\n}\n", + "span": { + "type": "Span", + "start": 745, + "end": 802 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 802 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/select_expressions.ftl b/fluent.syntax/src/test/resources/reference_fixtures/select_expressions.ftl new file mode 100644 index 0000000..603a122 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/select_expressions.ftl @@ -0,0 +1,64 @@ +new-messages = + { BUILTIN() -> + [0] Zero + *[other] {""}Other + } + +valid-selector-term-attribute = + { -term.case -> + *[key] value + } + +# ERROR Term values are not valid selectors +invalid-selector-term-value = + { -term -> + *[key] value + } + +# ERROR CallExpressions on Terms are similar to TermReferences +invalid-selector-term-variant = + { -term(case: "nominative") -> + *[key] value + } + +# ERROR Nested expressions are not valid selectors +invalid-selector-nested-expression = + { { 3 } -> + *[key] default + } + +# ERROR Select expressions are not valid selectors +invalid-selector-select-expression = + { { $sel -> + *[key] value + } -> + *[key] default + } + +empty-variant = + { $sel -> + *[key] {""} + } + +reduced-whitespace = + {FOO()-> + *[key] {""} + } + +nested-select = + { $sel -> + *[one] { $sel -> + *[two] Value + } + } + +# ERROR Missing selector +missing-selector = + { + *[key] Value + } + +# ERROR Missing line end after variant list +missing-line-end = + { $sel -> + *[key] Value} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/select_expressions.json b/fluent.syntax/src/test/resources/reference_fixtures/select_expressions.json new file mode 100644 index 0000000..38c237a --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/select_expressions.json @@ -0,0 +1,362 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "new-messages" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "BUILTIN" + }, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [] + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "value": "0", + "type": "NumberLiteral" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Zero" + } + ] + }, + "default": false + }, + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "other" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "", + "type": "StringLiteral" + } + }, + { + "type": "TextElement", + "value": "Other" + } + ] + }, + "default": true + } + ] + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "valid-selector-term-attribute" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "TermReference", + "id": { + "type": "Identifier", + "name": "term" + }, + "attribute": { + "type": "Identifier", + "name": "case" + }, + "arguments": null + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "value" + } + ] + }, + "default": true + } + ] + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Comment", + "content": "ERROR Term values are not valid selectors" + }, + { + "type": "Junk", + "annotations": [], + "content": "invalid-selector-term-value =\n { -term ->\n *[key] value\n }\n\n" + }, + { + "type": "Comment", + "content": "ERROR CallExpressions on Terms are similar to TermReferences" + }, + { + "type": "Junk", + "annotations": [], + "content": "invalid-selector-term-variant =\n { -term(case: \"nominative\") ->\n *[key] value\n }\n\n" + }, + { + "type": "Comment", + "content": "ERROR Nested expressions are not valid selectors" + }, + { + "type": "Junk", + "annotations": [], + "content": "invalid-selector-nested-expression =\n { { 3 } ->\n *[key] default\n }\n\n" + }, + { + "type": "Comment", + "content": "ERROR Select expressions are not valid selectors" + }, + { + "type": "Junk", + "annotations": [], + "content": "invalid-selector-select-expression =\n { { $sel ->\n *[key] value\n } ->\n *[key] default\n }\n\n" + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "empty-variant" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "sel" + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "", + "type": "StringLiteral" + } + } + ] + }, + "default": true + } + ] + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "reduced-whitespace" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FOO" + }, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [] + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "", + "type": "StringLiteral" + } + } + ] + }, + "default": true + } + ] + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "nested-select" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "sel" + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "one" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "sel" + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "two" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value" + } + ] + }, + "default": true + } + ] + } + } + ] + }, + "default": true + } + ] + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Comment", + "content": "ERROR Missing selector" + }, + { + "type": "Junk", + "annotations": [], + "content": "missing-selector =\n {\n *[key] Value\n }\n\n" + }, + { + "type": "Comment", + "content": "ERROR Missing line end after variant list" + }, + { + "type": "Junk", + "annotations": [], + "content": "missing-line-end =\n { $sel ->\n *[key] Value}\n" + } + ] +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/select_indent.ftl b/fluent.syntax/src/test/resources/reference_fixtures/select_indent.ftl new file mode 100644 index 0000000..7455e93 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/select_indent.ftl @@ -0,0 +1,96 @@ +select-1tbs-inline = { $selector -> + *[key] Value +} + +select-1tbs-newline = { +$selector -> + *[key] Value +} + +select-1tbs-indent = { + $selector -> + *[key] Value +} + +select-allman-inline = +{ $selector -> + *[key] Value + [other] Other +} + +select-allman-newline = +{ +$selector -> + *[key] Value +} + +select-allman-indent = +{ + $selector -> + *[key] Value +} + +select-gnu-inline = + { $selector -> + *[key] Value + } + +select-gnu-newline = + { +$selector -> + *[key] Value + } + +select-gnu-indent = + { + $selector -> + *[key] Value + } + +select-no-indent = +{ +$selector -> +*[key] Value +[other] Other +} + +select-no-indent-multiline = +{ +$selector -> +*[key] Value + Continued +[other] + Other + Multiline +} + +# ERROR (Multiline text must be indented) +select-no-indent-multiline = { $selector -> + *[key] Value +Continued without indent. +} + +select-flat = +{ +$selector +-> +*[ +key +] Value +[ +other +] Other +} + +# Each line ends with 5 spaces. +select-flat-with-trailing-spaces = +{ +$selector +-> +*[ +key +] Value +[ +other +] Other +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/select_indent.json b/fluent.syntax/src/test/resources/reference_fixtures/select_indent.json new file mode 100644 index 0000000..92b67ff --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/select_indent.json @@ -0,0 +1,1573 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-1tbs-inline", + "span": { + "type": "Span", + "start": 0, + "end": 18 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector", + "span": { + "type": "Span", + "start": 24, + "end": 32 + } + }, + "span": { + "type": "Span", + "start": 23, + "end": 32 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 41, + "end": 44 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 46, + "end": 51 + } + } + ], + "span": { + "type": "Span", + "start": 46, + "end": 51 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 39, + "end": 51 + } + } + ], + "span": { + "type": "Span", + "start": 23, + "end": 52 + } + }, + "span": { + "type": "Span", + "start": 21, + "end": 53 + } + } + ], + "span": { + "type": "Span", + "start": 21, + "end": 53 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 53 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-1tbs-newline", + "span": { + "type": "Span", + "start": 55, + "end": 74 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector", + "span": { + "type": "Span", + "start": 80, + "end": 88 + } + }, + "span": { + "type": "Span", + "start": 79, + "end": 88 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 97, + "end": 100 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 102, + "end": 107 + } + } + ], + "span": { + "type": "Span", + "start": 102, + "end": 107 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 95, + "end": 107 + } + } + ], + "span": { + "type": "Span", + "start": 79, + "end": 108 + } + }, + "span": { + "type": "Span", + "start": 77, + "end": 109 + } + } + ], + "span": { + "type": "Span", + "start": 77, + "end": 109 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 55, + "end": 109 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-1tbs-indent", + "span": { + "type": "Span", + "start": 111, + "end": 129 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector", + "span": { + "type": "Span", + "start": 139, + "end": 147 + } + }, + "span": { + "type": "Span", + "start": 138, + "end": 147 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 156, + "end": 159 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 161, + "end": 166 + } + } + ], + "span": { + "type": "Span", + "start": 161, + "end": 166 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 154, + "end": 166 + } + } + ], + "span": { + "type": "Span", + "start": 138, + "end": 167 + } + }, + "span": { + "type": "Span", + "start": 132, + "end": 168 + } + } + ], + "span": { + "type": "Span", + "start": 132, + "end": 168 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 111, + "end": 168 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-allman-inline", + "span": { + "type": "Span", + "start": 170, + "end": 190 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector", + "span": { + "type": "Span", + "start": 196, + "end": 204 + } + }, + "span": { + "type": "Span", + "start": 195, + "end": 204 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 213, + "end": 216 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 218, + "end": 223 + } + } + ], + "span": { + "type": "Span", + "start": 218, + "end": 223 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 211, + "end": 223 + } + }, + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "other", + "span": { + "type": "Span", + "start": 229, + "end": 234 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Other", + "span": { + "type": "Span", + "start": 236, + "end": 241 + } + } + ], + "span": { + "type": "Span", + "start": 236, + "end": 241 + } + }, + "default": false, + "span": { + "type": "Span", + "start": 228, + "end": 241 + } + } + ], + "span": { + "type": "Span", + "start": 195, + "end": 242 + } + }, + "span": { + "type": "Span", + "start": 193, + "end": 243 + } + } + ], + "span": { + "type": "Span", + "start": 193, + "end": 243 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 170, + "end": 243 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-allman-newline", + "span": { + "type": "Span", + "start": 245, + "end": 266 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector", + "span": { + "type": "Span", + "start": 272, + "end": 280 + } + }, + "span": { + "type": "Span", + "start": 271, + "end": 280 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 289, + "end": 292 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 294, + "end": 299 + } + } + ], + "span": { + "type": "Span", + "start": 294, + "end": 299 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 287, + "end": 299 + } + } + ], + "span": { + "type": "Span", + "start": 271, + "end": 300 + } + }, + "span": { + "type": "Span", + "start": 269, + "end": 301 + } + } + ], + "span": { + "type": "Span", + "start": 269, + "end": 301 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 245, + "end": 301 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-allman-indent", + "span": { + "type": "Span", + "start": 303, + "end": 323 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector", + "span": { + "type": "Span", + "start": 333, + "end": 341 + } + }, + "span": { + "type": "Span", + "start": 332, + "end": 341 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 350, + "end": 353 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 355, + "end": 360 + } + } + ], + "span": { + "type": "Span", + "start": 355, + "end": 360 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 348, + "end": 360 + } + } + ], + "span": { + "type": "Span", + "start": 332, + "end": 361 + } + }, + "span": { + "type": "Span", + "start": 326, + "end": 362 + } + } + ], + "span": { + "type": "Span", + "start": 326, + "end": 362 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 303, + "end": 362 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-gnu-inline", + "span": { + "type": "Span", + "start": 364, + "end": 381 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector", + "span": { + "type": "Span", + "start": 390, + "end": 398 + } + }, + "span": { + "type": "Span", + "start": 389, + "end": 398 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 410, + "end": 413 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 415, + "end": 420 + } + } + ], + "span": { + "type": "Span", + "start": 415, + "end": 420 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 408, + "end": 420 + } + } + ], + "span": { + "type": "Span", + "start": 389, + "end": 424 + } + }, + "span": { + "type": "Span", + "start": 387, + "end": 425 + } + } + ], + "span": { + "type": "Span", + "start": 384, + "end": 425 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 364, + "end": 425 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-gnu-newline", + "span": { + "type": "Span", + "start": 427, + "end": 445 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector", + "span": { + "type": "Span", + "start": 454, + "end": 462 + } + }, + "span": { + "type": "Span", + "start": 453, + "end": 462 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 474, + "end": 477 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 479, + "end": 484 + } + } + ], + "span": { + "type": "Span", + "start": 479, + "end": 484 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 472, + "end": 484 + } + } + ], + "span": { + "type": "Span", + "start": 453, + "end": 488 + } + }, + "span": { + "type": "Span", + "start": 451, + "end": 489 + } + } + ], + "span": { + "type": "Span", + "start": 448, + "end": 489 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 427, + "end": 489 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-gnu-indent", + "span": { + "type": "Span", + "start": 491, + "end": 508 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector", + "span": { + "type": "Span", + "start": 524, + "end": 532 + } + }, + "span": { + "type": "Span", + "start": 523, + "end": 532 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 544, + "end": 547 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 549, + "end": 554 + } + } + ], + "span": { + "type": "Span", + "start": 549, + "end": 554 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 542, + "end": 554 + } + } + ], + "span": { + "type": "Span", + "start": 523, + "end": 558 + } + }, + "span": { + "type": "Span", + "start": 514, + "end": 559 + } + } + ], + "span": { + "type": "Span", + "start": 511, + "end": 559 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 491, + "end": 559 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-no-indent", + "span": { + "type": "Span", + "start": 561, + "end": 577 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector", + "span": { + "type": "Span", + "start": 583, + "end": 591 + } + }, + "span": { + "type": "Span", + "start": 582, + "end": 591 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 597, + "end": 600 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 602, + "end": 607 + } + } + ], + "span": { + "type": "Span", + "start": 602, + "end": 607 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 595, + "end": 607 + } + }, + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "other", + "span": { + "type": "Span", + "start": 609, + "end": 614 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Other", + "span": { + "type": "Span", + "start": 616, + "end": 621 + } + } + ], + "span": { + "type": "Span", + "start": 616, + "end": 621 + } + }, + "default": false, + "span": { + "type": "Span", + "start": 608, + "end": 621 + } + } + ], + "span": { + "type": "Span", + "start": 582, + "end": 622 + } + }, + "span": { + "type": "Span", + "start": 580, + "end": 623 + } + } + ], + "span": { + "type": "Span", + "start": 580, + "end": 623 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 561, + "end": 623 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-no-indent-multiline", + "span": { + "type": "Span", + "start": 625, + "end": 651 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector", + "span": { + "type": "Span", + "start": 657, + "end": 665 + } + }, + "span": { + "type": "Span", + "start": 656, + "end": 665 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 671, + "end": 674 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value\nContinued", + "span": { + "type": "Span", + "start": 676, + "end": 698 + } + } + ], + "span": { + "type": "Span", + "start": 676, + "end": 698 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 669, + "end": 698 + } + }, + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "other", + "span": { + "type": "Span", + "start": 700, + "end": 705 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Other\nMultiline", + "span": { + "type": "Span", + "start": 711, + "end": 730 + } + } + ], + "span": { + "type": "Span", + "start": 707, + "end": 730 + } + }, + "default": false, + "span": { + "type": "Span", + "start": 699, + "end": 730 + } + } + ], + "span": { + "type": "Span", + "start": 656, + "end": 731 + } + }, + "span": { + "type": "Span", + "start": 654, + "end": 732 + } + } + ], + "span": { + "type": "Span", + "start": 654, + "end": 732 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 625, + "end": 732 + } + }, + { + "content": "ERROR (Multiline text must be indented)", + "type": "Comment", + "span": { + "type": "Span", + "start": 734, + "end": 775 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 836, + "end": 836 + } + } + ], + "content": "select-no-indent-multiline = { $selector ->\n *[key] Value\n", + "span": { + "type": "Span", + "start": 776, + "end": 836 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 846, + "end": 846 + } + } + ], + "content": "Continued without indent.\n}\n\n", + "span": { + "type": "Span", + "start": 836, + "end": 865 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-flat", + "span": { + "type": "Span", + "start": 865, + "end": 876 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector", + "span": { + "type": "Span", + "start": 882, + "end": 890 + } + }, + "span": { + "type": "Span", + "start": 881, + "end": 890 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 897, + "end": 900 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 903, + "end": 908 + } + } + ], + "span": { + "type": "Span", + "start": 903, + "end": 908 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 894, + "end": 908 + } + }, + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "other", + "span": { + "type": "Span", + "start": 911, + "end": 916 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Other", + "span": { + "type": "Span", + "start": 919, + "end": 924 + } + } + ], + "span": { + "type": "Span", + "start": 919, + "end": 924 + } + }, + "default": false, + "span": { + "type": "Span", + "start": 909, + "end": 924 + } + } + ], + "span": { + "type": "Span", + "start": 881, + "end": 925 + } + }, + "span": { + "type": "Span", + "start": 879, + "end": 926 + } + } + ], + "span": { + "type": "Span", + "start": 879, + "end": 926 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 865, + "end": 926 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-flat-with-trailing-spaces", + "span": { + "type": "Span", + "start": 960, + "end": 992 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector", + "span": { + "type": "Span", + "start": 1003, + "end": 1011 + } + }, + "span": { + "type": "Span", + "start": 1002, + "end": 1011 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 1033, + "end": 1036 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 1044, + "end": 1054 + } + } + ], + "span": { + "type": "Span", + "start": 1044, + "end": 1054 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 1025, + "end": 1054 + } + }, + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "other", + "span": { + "type": "Span", + "start": 1062, + "end": 1067 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Other", + "span": { + "type": "Span", + "start": 1075, + "end": 1085 + } + } + ], + "span": { + "type": "Span", + "start": 1075, + "end": 1085 + } + }, + "default": false, + "span": { + "type": "Span", + "start": 1055, + "end": 1085 + } + } + ], + "span": { + "type": "Span", + "start": 1002, + "end": 1086 + } + }, + "span": { + "type": "Span", + "start": 995, + "end": 1087 + } + } + ], + "span": { + "type": "Span", + "start": 995, + "end": 1092 + } + }, + "attributes": [], + "comment": { + "content": "Each line ends with 5 spaces.", + "type": "Comment", + "span": { + "type": "Span", + "start": 928, + "end": 959 + } + }, + "span": { + "type": "Span", + "start": 928, + "end": 1092 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 1093 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/sparse_entries.ftl b/fluent.syntax/src/test/resources/reference_fixtures/sparse_entries.ftl new file mode 100644 index 0000000..67920b2 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/sparse_entries.ftl @@ -0,0 +1,39 @@ +key01 = + + + Value + +key02 = + + + .attr = Attribute + + +key03 = + Value + Continued + + + Over multiple + Lines + + + + .attr = Attribute + + +key05 = Value + +key06 = { 1 -> + + + [one] One + + + + + *[two] Two + + + + } diff --git a/fluent.syntax/src/test/resources/reference_fixtures/sparse_entries.json b/fluent.syntax/src/test/resources/reference_fixtures/sparse_entries.json new file mode 100644 index 0000000..0adcb94 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/sparse_entries.json @@ -0,0 +1,348 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01", + "span": { + "type": "Span", + "start": 0, + "end": 5 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 14, + "end": 19 + } + } + ], + "span": { + "type": "Span", + "start": 10, + "end": 19 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 19 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key02", + "span": { + "type": "Span", + "start": 21, + "end": 26 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 36, + "end": 40 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attribute", + "span": { + "type": "Span", + "start": 43, + "end": 52 + } + } + ], + "span": { + "type": "Span", + "start": 43, + "end": 52 + } + }, + "span": { + "type": "Span", + "start": 35, + "end": 52 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 21, + "end": 52 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key03", + "span": { + "type": "Span", + "start": 55, + "end": 60 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value\nContinued\n\n\nOver multiple\nLines", + "span": { + "type": "Span", + "start": 67, + "end": 116 + } + } + ], + "span": { + "type": "Span", + "start": 63, + "end": 116 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 125, + "end": 129 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attribute", + "span": { + "type": "Span", + "start": 132, + "end": 141 + } + } + ], + "span": { + "type": "Span", + "start": 132, + "end": 141 + } + }, + "span": { + "type": "Span", + "start": 124, + "end": 141 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 55, + "end": 141 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key05", + "span": { + "type": "Span", + "start": 144, + "end": 149 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 172, + "end": 177 + } + } + ], + "span": { + "type": "Span", + "start": 172, + "end": 177 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 144, + "end": 177 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key06", + "span": { + "type": "Span", + "start": 179, + "end": 184 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 189, + "end": 190 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "one", + "span": { + "type": "Span", + "start": 206, + "end": 209 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "One", + "span": { + "type": "Span", + "start": 211, + "end": 214 + } + } + ], + "span": { + "type": "Span", + "start": 211, + "end": 214 + } + }, + "default": false, + "span": { + "type": "Span", + "start": 205, + "end": 214 + } + }, + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "two", + "span": { + "type": "Span", + "start": 229, + "end": 232 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Two", + "span": { + "type": "Span", + "start": 234, + "end": 237 + } + } + ], + "span": { + "type": "Span", + "start": 234, + "end": 237 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 227, + "end": 237 + } + } + ], + "span": { + "type": "Span", + "start": 189, + "end": 245 + } + }, + "span": { + "type": "Span", + "start": 187, + "end": 246 + } + } + ], + "span": { + "type": "Span", + "start": 187, + "end": 246 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 179, + "end": 246 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 247 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/special_chars.ftl b/fluent.syntax/src/test/resources/reference_fixtures/special_chars.ftl new file mode 100644 index 0000000..5224bad --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/special_chars.ftl @@ -0,0 +1,14 @@ +## OK + +bracket-inline = [Value] +dot-inline = .Value +star-inline = *Value + +## ERRORS + +bracket-newline = + [Value] +dot-newline = + .Value +star-newline = + *Value diff --git a/fluent.syntax/src/test/resources/reference_fixtures/special_chars.json b/fluent.syntax/src/test/resources/reference_fixtures/special_chars.json new file mode 100644 index 0000000..d637d18 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/special_chars.json @@ -0,0 +1,214 @@ +{ + "type": "Resource", + "body": [ + { + "content": "OK", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 0, + "end": 5 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "bracket-inline", + "span": { + "type": "Span", + "start": 7, + "end": 21 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "[Value]", + "span": { + "type": "Span", + "start": 24, + "end": 31 + } + } + ], + "span": { + "type": "Span", + "start": 24, + "end": 31 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 7, + "end": 31 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "dot-inline", + "span": { + "type": "Span", + "start": 32, + "end": 42 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".Value", + "span": { + "type": "Span", + "start": 45, + "end": 51 + } + } + ], + "span": { + "type": "Span", + "start": 45, + "end": 51 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 32, + "end": 51 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "star-inline", + "span": { + "type": "Span", + "start": 52, + "end": 63 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "*Value", + "span": { + "type": "Span", + "start": 66, + "end": 72 + } + } + ], + "span": { + "type": "Span", + "start": 66, + "end": 72 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 52, + "end": 72 + } + }, + { + "content": "ERRORS", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 74, + "end": 83 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0005", + "arguments": [ + "bracket-newline" + ], + "message": "Expected message \"bracket-newline\" to have a value or attributes", + "span": { + "type": "Span", + "start": 102, + "end": 102 + } + } + ], + "content": "bracket-newline =\n [Value]\n", + "span": { + "type": "Span", + "start": 85, + "end": 115 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 139, + "end": 139 + } + } + ], + "content": "dot-newline =\n .Value\n", + "span": { + "type": "Span", + "start": 115, + "end": 140 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0005", + "arguments": [ + "star-newline" + ], + "message": "Expected message \"star-newline\" to have a value or attributes", + "span": { + "type": "Span", + "start": 154, + "end": 154 + } + } + ], + "content": "star-newline =\n *Value\n", + "span": { + "type": "Span", + "start": 140, + "end": 166 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 166 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/tab.ftl b/fluent.syntax/src/test/resources/reference_fixtures/tab.ftl new file mode 100644 index 0000000..4b23ad8 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/tab.ftl @@ -0,0 +1,14 @@ +# OK (tab after = is part of the value) +key01 = Value 01 + +# Error (tab before =) +key02 = Value 02 + +# Error (tab is not a valid indent) +key03 = + This line isn't properly indented. + +# Partial Error (tab is not a valid indent) +key04 = + This line is indented by 4 spaces, + whereas this line by 1 tab. diff --git a/fluent.syntax/src/test/resources/reference_fixtures/tab.json b/fluent.syntax/src/test/resources/reference_fixtures/tab.json new file mode 100644 index 0000000..7314802 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/tab.json @@ -0,0 +1,190 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01", + "span": { + "type": "Span", + "start": 40, + "end": 45 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "\tValue 01", + "span": { + "type": "Span", + "start": 47, + "end": 56 + } + } + ], + "span": { + "type": "Span", + "start": 47, + "end": 56 + } + }, + "attributes": [], + "comment": { + "content": "OK (tab after = is part of the value)", + "type": "Comment", + "span": { + "type": "Span", + "start": 0, + "end": 39 + } + }, + "span": { + "type": "Span", + "start": 0, + "end": 56 + } + }, + { + "content": "Error (tab before =)", + "type": "Comment", + "span": { + "type": "Span", + "start": 58, + "end": 80 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 86, + "end": 86 + } + } + ], + "content": "key02\t= Value 02\n\n", + "span": { + "type": "Span", + "start": 81, + "end": 99 + } + }, + { + "content": "Error (tab is not a valid indent)", + "type": "Comment", + "span": { + "type": "Span", + "start": 99, + "end": 134 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0005", + "arguments": [ + "key03" + ], + "message": "Expected message \"key03\" to have a value or attributes", + "span": { + "type": "Span", + "start": 142, + "end": 142 + } + } + ], + "content": "key03 =\n\tThis line isn't properly indented.\n\n", + "span": { + "type": "Span", + "start": 135, + "end": 180 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key04", + "span": { + "type": "Span", + "start": 224, + "end": 229 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "This line is indented by 4 spaces,", + "span": { + "type": "Span", + "start": 236, + "end": 270 + } + } + ], + "span": { + "type": "Span", + "start": 232, + "end": 270 + } + }, + "attributes": [], + "comment": { + "content": "Partial Error (tab is not a valid indent)", + "type": "Comment", + "span": { + "type": "Span", + "start": 180, + "end": 223 + } + }, + "span": { + "type": "Span", + "start": 180, + "end": 270 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0002", + "arguments": [], + "message": "Expected an entry start", + "span": { + "type": "Span", + "start": 271, + "end": 271 + } + } + ], + "content": "\twhereas this line by 1 tab.\n", + "span": { + "type": "Span", + "start": 271, + "end": 300 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 300 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/term_parameters.ftl b/fluent.syntax/src/test/resources/reference_fixtures/term_parameters.ftl new file mode 100644 index 0000000..600c12c --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/term_parameters.ftl @@ -0,0 +1,8 @@ +-term = { $arg -> + *[key] Value +} + +key01 = { -term } +key02 = { -term () } +key03 = { -term(arg: 1) } +key04 = { -term("positional", narg1: 1, narg2: 2) } diff --git a/fluent.syntax/src/test/resources/reference_fixtures/term_parameters.json b/fluent.syntax/src/test/resources/reference_fixtures/term_parameters.json new file mode 100644 index 0000000..304fa1a --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/term_parameters.json @@ -0,0 +1,452 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 1, + "end": 5 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "arg", + "span": { + "type": "Span", + "start": 11, + "end": 14 + } + }, + "span": { + "type": "Span", + "start": 10, + "end": 14 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 23, + "end": 26 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 28, + "end": 33 + } + } + ], + "span": { + "type": "Span", + "start": 28, + "end": 33 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 21, + "end": 33 + } + } + ], + "span": { + "type": "Span", + "start": 10, + "end": 34 + } + }, + "span": { + "type": "Span", + "start": 8, + "end": 35 + } + } + ], + "span": { + "type": "Span", + "start": 8, + "end": 35 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 35 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01", + "span": { + "type": "Span", + "start": 37, + "end": 42 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "TermReference", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 48, + "end": 52 + } + }, + "attribute": null, + "arguments": null, + "span": { + "type": "Span", + "start": 47, + "end": 52 + } + }, + "span": { + "type": "Span", + "start": 45, + "end": 54 + } + } + ], + "span": { + "type": "Span", + "start": 45, + "end": 54 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 37, + "end": 54 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key02", + "span": { + "type": "Span", + "start": 55, + "end": 60 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "TermReference", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 66, + "end": 70 + } + }, + "attribute": null, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [], + "span": { + "type": "Span", + "start": 71, + "end": 73 + } + }, + "span": { + "type": "Span", + "start": 65, + "end": 73 + } + }, + "span": { + "type": "Span", + "start": 63, + "end": 75 + } + } + ], + "span": { + "type": "Span", + "start": 63, + "end": 75 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 55, + "end": 75 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key03", + "span": { + "type": "Span", + "start": 76, + "end": 81 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "TermReference", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 87, + "end": 91 + } + }, + "attribute": null, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [ + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "arg", + "span": { + "type": "Span", + "start": 92, + "end": 95 + } + }, + "value": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 97, + "end": 98 + } + }, + "span": { + "type": "Span", + "start": 92, + "end": 98 + } + } + ], + "span": { + "type": "Span", + "start": 91, + "end": 99 + } + }, + "span": { + "type": "Span", + "start": 86, + "end": 99 + } + }, + "span": { + "type": "Span", + "start": 84, + "end": 101 + } + } + ], + "span": { + "type": "Span", + "start": 84, + "end": 101 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 76, + "end": 101 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key04", + "span": { + "type": "Span", + "start": 102, + "end": 107 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "TermReference", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 113, + "end": 117 + } + }, + "attribute": null, + "arguments": { + "type": "CallArguments", + "positional": [ + { + "value": "positional", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 118, + "end": 130 + } + } + ], + "named": [ + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "narg1", + "span": { + "type": "Span", + "start": 132, + "end": 137 + } + }, + "value": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 139, + "end": 140 + } + }, + "span": { + "type": "Span", + "start": 132, + "end": 140 + } + }, + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "narg2", + "span": { + "type": "Span", + "start": 142, + "end": 147 + } + }, + "value": { + "value": "2", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 149, + "end": 150 + } + }, + "span": { + "type": "Span", + "start": 142, + "end": 150 + } + } + ], + "span": { + "type": "Span", + "start": 117, + "end": 151 + } + }, + "span": { + "type": "Span", + "start": 112, + "end": 151 + } + }, + "span": { + "type": "Span", + "start": 110, + "end": 153 + } + } + ], + "span": { + "type": "Span", + "start": 110, + "end": 153 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 102, + "end": 153 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 154 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/terms.ftl b/fluent.syntax/src/test/resources/reference_fixtures/terms.ftl new file mode 100644 index 0000000..893188d --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/terms.ftl @@ -0,0 +1,29 @@ +-term01 = Value + .attr = Attribute + +-term02 = {""} + +# JUNK Missing value +-term03 = + .attr = Attribute + +# JUNK Missing value +# < whitespace > +-term04 = + .attr1 = Attribute 1 + +# JUNK Missing value +-term05 = + +# JUNK Missing value +# < whitespace > +-term06 = + +# JUNK Missing = +-term07 + +-term08=Value + .attr=Attribute + +-term09 = Value + .attr = Attribute diff --git a/fluent.syntax/src/test/resources/reference_fixtures/terms.json b/fluent.syntax/src/test/resources/reference_fixtures/terms.json new file mode 100644 index 0000000..bc028d7 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/terms.json @@ -0,0 +1,446 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "term01", + "span": { + "type": "Span", + "start": 1, + "end": 7 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 10, + "end": 15 + } + } + ], + "span": { + "type": "Span", + "start": 10, + "end": 15 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 21, + "end": 25 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attribute", + "span": { + "type": "Span", + "start": 28, + "end": 37 + } + } + ], + "span": { + "type": "Span", + "start": 28, + "end": 37 + } + }, + "span": { + "type": "Span", + "start": 20, + "end": 37 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 37 + } + }, + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "term02", + "span": { + "type": "Span", + "start": 40, + "end": 46 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 50, + "end": 52 + } + }, + "span": { + "type": "Span", + "start": 49, + "end": 53 + } + } + ], + "span": { + "type": "Span", + "start": 49, + "end": 53 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 39, + "end": 53 + } + }, + { + "content": "JUNK Missing value", + "type": "Comment", + "span": { + "type": "Span", + "start": 55, + "end": 75 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0006", + "arguments": [ + "term03" + ], + "message": "Expected term \"-term03\" to have a value", + "span": { + "type": "Span", + "start": 85, + "end": 85 + } + } + ], + "content": "-term03 =\n .attr = Attribute\n\n", + "span": { + "type": "Span", + "start": 76, + "end": 109 + } + }, + { + "content": "JUNK Missing value\n < whitespace >", + "type": "Comment", + "span": { + "type": "Span", + "start": 109, + "end": 155 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0006", + "arguments": [ + "term04" + ], + "message": "Expected term \"-term04\" to have a value", + "span": { + "type": "Span", + "start": 165, + "end": 165 + } + } + ], + "content": "-term04 = \n .attr1 = Attribute 1\n\n", + "span": { + "type": "Span", + "start": 156, + "end": 208 + } + }, + { + "content": "JUNK Missing value", + "type": "Comment", + "span": { + "type": "Span", + "start": 208, + "end": 228 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0006", + "arguments": [ + "term05" + ], + "message": "Expected term \"-term05\" to have a value", + "span": { + "type": "Span", + "start": 238, + "end": 238 + } + } + ], + "content": "-term05 =\n\n", + "span": { + "type": "Span", + "start": 229, + "end": 240 + } + }, + { + "content": "JUNK Missing value\n < whitespace >", + "type": "Comment", + "span": { + "type": "Span", + "start": 240, + "end": 286 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0006", + "arguments": [ + "term06" + ], + "message": "Expected term \"-term06\" to have a value", + "span": { + "type": "Span", + "start": 296, + "end": 296 + } + } + ], + "content": "-term06 = \n\n", + "span": { + "type": "Span", + "start": 287, + "end": 314 + } + }, + { + "content": "JUNK Missing =", + "type": "Comment", + "span": { + "type": "Span", + "start": 314, + "end": 330 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 338, + "end": 338 + } + } + ], + "content": "-term07\n\n", + "span": { + "type": "Span", + "start": 331, + "end": 340 + } + }, + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "term08", + "span": { + "type": "Span", + "start": 341, + "end": 347 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 348, + "end": 353 + } + } + ], + "span": { + "type": "Span", + "start": 348, + "end": 353 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 359, + "end": 363 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attribute", + "span": { + "type": "Span", + "start": 364, + "end": 373 + } + } + ], + "span": { + "type": "Span", + "start": 364, + "end": 373 + } + }, + "span": { + "type": "Span", + "start": 358, + "end": 373 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 340, + "end": 373 + } + }, + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "term09", + "span": { + "type": "Span", + "start": 376, + "end": 382 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 388, + "end": 393 + } + } + ], + "span": { + "type": "Span", + "start": 388, + "end": 393 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 399, + "end": 403 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attribute", + "span": { + "type": "Span", + "start": 409, + "end": 418 + } + } + ], + "span": { + "type": "Span", + "start": 409, + "end": 418 + } + }, + "span": { + "type": "Span", + "start": 398, + "end": 418 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 375, + "end": 418 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 419 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/variables.ftl b/fluent.syntax/src/test/resources/reference_fixtures/variables.ftl new file mode 100644 index 0000000..6c34369 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/variables.ftl @@ -0,0 +1,17 @@ +key01 = {$var} +key02 = { $var } +key03 = { + $var +} +key04 = { +$var} + + +## Errors + +# ERROR Missing variable identifier +err01 = {$} +# ERROR Double $$ +err02 = {$$var} +# ERROR Invalid first char of the identifier +err03 = {$-var} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/variables.json b/fluent.syntax/src/test/resources/reference_fixtures/variables.json new file mode 100644 index 0000000..b597a39 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/variables.json @@ -0,0 +1,334 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01", + "span": { + "type": "Span", + "start": 0, + "end": 5 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "var", + "span": { + "type": "Span", + "start": 10, + "end": 13 + } + }, + "span": { + "type": "Span", + "start": 9, + "end": 13 + } + }, + "span": { + "type": "Span", + "start": 8, + "end": 14 + } + } + ], + "span": { + "type": "Span", + "start": 8, + "end": 14 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 14 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key02", + "span": { + "type": "Span", + "start": 15, + "end": 20 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "var", + "span": { + "type": "Span", + "start": 28, + "end": 31 + } + }, + "span": { + "type": "Span", + "start": 27, + "end": 31 + } + }, + "span": { + "type": "Span", + "start": 23, + "end": 35 + } + } + ], + "span": { + "type": "Span", + "start": 23, + "end": 35 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 15, + "end": 35 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key03", + "span": { + "type": "Span", + "start": 36, + "end": 41 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "var", + "span": { + "type": "Span", + "start": 51, + "end": 54 + } + }, + "span": { + "type": "Span", + "start": 50, + "end": 54 + } + }, + "span": { + "type": "Span", + "start": 44, + "end": 56 + } + } + ], + "span": { + "type": "Span", + "start": 44, + "end": 56 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 36, + "end": 56 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key04", + "span": { + "type": "Span", + "start": 57, + "end": 62 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "var", + "span": { + "type": "Span", + "start": 68, + "end": 71 + } + }, + "span": { + "type": "Span", + "start": 67, + "end": 71 + } + }, + "span": { + "type": "Span", + "start": 65, + "end": 72 + } + } + ], + "span": { + "type": "Span", + "start": 65, + "end": 72 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 57, + "end": 72 + } + }, + { + "content": "Errors", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 75, + "end": 84 + } + }, + { + "content": "ERROR Missing variable identifier", + "type": "Comment", + "span": { + "type": "Span", + "start": 86, + "end": 121 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 132, + "end": 132 + } + } + ], + "content": "err01 = {$}\n", + "span": { + "type": "Span", + "start": 122, + "end": 134 + } + }, + { + "content": "ERROR Double $$", + "type": "Comment", + "span": { + "type": "Span", + "start": 134, + "end": 151 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 162, + "end": 162 + } + } + ], + "content": "err02 = {$$var}\n", + "span": { + "type": "Span", + "start": 152, + "end": 168 + } + }, + { + "content": "ERROR Invalid first char of the identifier", + "type": "Comment", + "span": { + "type": "Span", + "start": 168, + "end": 212 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 223, + "end": 223 + } + } + ], + "content": "err03 = {$-var}\n", + "span": { + "type": "Span", + "start": 213, + "end": 229 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 229 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/variant_keys.ftl b/fluent.syntax/src/test/resources/reference_fixtures/variant_keys.ftl new file mode 100644 index 0000000..52f8ca6 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/variant_keys.ftl @@ -0,0 +1,37 @@ +simple-identifier = + { $sel -> + *[key] value + } + +identifier-surrounded-by-whitespace = + { $sel -> + *[ key ] value + } + +int-number = + { $sel -> + *[1] value + } + +float-number = + { $sel -> + *[3.14] value + } + +# ERROR +invalid-identifier = + { $sel -> + *[two words] value + } + +# ERROR +invalid-int = + { $sel -> + *[1 apple] value + } + +# ERROR +invalid-int = + { $sel -> + *[3.14 apples] value + } diff --git a/fluent.syntax/src/test/resources/reference_fixtures/variant_keys.json b/fluent.syntax/src/test/resources/reference_fixtures/variant_keys.json new file mode 100644 index 0000000..e0440e6 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/variant_keys.json @@ -0,0 +1,513 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "simple-identifier", + "span": { + "type": "Span", + "start": 0, + "end": 17 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "sel", + "span": { + "type": "Span", + "start": 27, + "end": 30 + } + }, + "span": { + "type": "Span", + "start": 26, + "end": 30 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 43, + "end": 46 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "value", + "span": { + "type": "Span", + "start": 48, + "end": 53 + } + } + ], + "span": { + "type": "Span", + "start": 48, + "end": 53 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 41, + "end": 53 + } + } + ], + "span": { + "type": "Span", + "start": 26, + "end": 58 + } + }, + "span": { + "type": "Span", + "start": 24, + "end": 59 + } + } + ], + "span": { + "type": "Span", + "start": 20, + "end": 59 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 59 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "identifier-surrounded-by-whitespace", + "span": { + "type": "Span", + "start": 61, + "end": 96 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "sel", + "span": { + "type": "Span", + "start": 106, + "end": 109 + } + }, + "span": { + "type": "Span", + "start": 105, + "end": 109 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 127, + "end": 130 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "value", + "span": { + "type": "Span", + "start": 137, + "end": 142 + } + } + ], + "span": { + "type": "Span", + "start": 137, + "end": 142 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 120, + "end": 142 + } + } + ], + "span": { + "type": "Span", + "start": 105, + "end": 147 + } + }, + "span": { + "type": "Span", + "start": 103, + "end": 148 + } + } + ], + "span": { + "type": "Span", + "start": 99, + "end": 148 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 61, + "end": 148 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "int-number", + "span": { + "type": "Span", + "start": 150, + "end": 160 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "sel", + "span": { + "type": "Span", + "start": 170, + "end": 173 + } + }, + "span": { + "type": "Span", + "start": 169, + "end": 173 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 186, + "end": 187 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "value", + "span": { + "type": "Span", + "start": 189, + "end": 194 + } + } + ], + "span": { + "type": "Span", + "start": 189, + "end": 194 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 184, + "end": 194 + } + } + ], + "span": { + "type": "Span", + "start": 169, + "end": 199 + } + }, + "span": { + "type": "Span", + "start": 167, + "end": 200 + } + } + ], + "span": { + "type": "Span", + "start": 163, + "end": 200 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 150, + "end": 200 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "float-number", + "span": { + "type": "Span", + "start": 202, + "end": 214 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "sel", + "span": { + "type": "Span", + "start": 224, + "end": 227 + } + }, + "span": { + "type": "Span", + "start": 223, + "end": 227 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "value": "3.14", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 240, + "end": 244 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "value", + "span": { + "type": "Span", + "start": 246, + "end": 251 + } + } + ], + "span": { + "type": "Span", + "start": 246, + "end": 251 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 238, + "end": 251 + } + } + ], + "span": { + "type": "Span", + "start": 223, + "end": 256 + } + }, + "span": { + "type": "Span", + "start": 221, + "end": 257 + } + } + ], + "span": { + "type": "Span", + "start": 217, + "end": 257 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 202, + "end": 257 + } + }, + { + "content": "ERROR", + "type": "Comment", + "span": { + "type": "Span", + "start": 259, + "end": 266 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "]" + ], + "message": "Expected token: \"]\"", + "span": { + "type": "Span", + "start": 315, + "end": 315 + } + } + ], + "content": "invalid-identifier =\n { $sel ->\n *[two words] value\n }\n\n", + "span": { + "type": "Span", + "start": 267, + "end": 335 + } + }, + { + "content": "ERROR", + "type": "Comment", + "span": { + "type": "Span", + "start": 335, + "end": 342 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "]" + ], + "message": "Expected token: \"]\"", + "span": { + "type": "Span", + "start": 382, + "end": 382 + } + } + ], + "content": "invalid-int =\n { $sel ->\n *[1 apple] value\n }\n\n", + "span": { + "type": "Span", + "start": 343, + "end": 402 + } + }, + { + "content": "ERROR", + "type": "Comment", + "span": { + "type": "Span", + "start": 402, + "end": 409 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "]" + ], + "message": "Expected token: \"]\"", + "span": { + "type": "Span", + "start": 452, + "end": 452 + } + } + ], + "content": "invalid-int =\n { $sel ->\n *[3.14 apples] value\n }\n", + "span": { + "type": "Span", + "start": 410, + "end": 472 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 472 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/whitespace_in_value.ftl b/fluent.syntax/src/test/resources/reference_fixtures/whitespace_in_value.ftl new file mode 100644 index 0000000..2fba553 --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/whitespace_in_value.ftl @@ -0,0 +1,10 @@ +# Caution, lines 6 and 7 contain white-space-only lines +key = + first line + + + + + + + last line diff --git a/fluent.syntax/src/test/resources/reference_fixtures/whitespace_in_value.json b/fluent.syntax/src/test/resources/reference_fixtures/whitespace_in_value.json new file mode 100644 index 0000000..0819eae --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/whitespace_in_value.json @@ -0,0 +1,56 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 56, + "end": 59 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "first line\n\n\n\n\n\n\nlast line", + "span": { + "type": "Span", + "start": 64, + "end": 96 + } + } + ], + "span": { + "type": "Span", + "start": 62, + "end": 96 + } + }, + "attributes": [], + "comment": { + "content": "Caution, lines 6 and 7 contain white-space-only lines", + "type": "Comment", + "span": { + "type": "Span", + "start": 0, + "end": 55 + } + }, + "span": { + "type": "Span", + "start": 0, + "end": 96 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 97 + } +} diff --git a/fluent.syntax/src/test/resources/reference_fixtures/zero_length.ftl b/fluent.syntax/src/test/resources/reference_fixtures/zero_length.ftl new file mode 100644 index 0000000..e69de29 diff --git a/fluent.syntax/src/test/resources/reference_fixtures/zero_length.json b/fluent.syntax/src/test/resources/reference_fixtures/zero_length.json new file mode 100644 index 0000000..603a5bb --- /dev/null +++ b/fluent.syntax/src/test/resources/reference_fixtures/zero_length.json @@ -0,0 +1,9 @@ +{ + "type": "Resource", + "body": [], + "span": { + "type": "Span", + "start": 0, + "end": 0 + } +} diff --git a/fluent.syntax/src/test/resources/serialized/attribute.ftl b/fluent.syntax/src/test/resources/serialized/attribute.ftl new file mode 100644 index 0000000..5318c86 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/attribute.ftl @@ -0,0 +1,2 @@ +foo = + .attr = Foo Attr diff --git a/fluent.syntax/src/test/resources/serialized/backslash_in_text_element.ftl b/fluent.syntax/src/test/resources/serialized/backslash_in_text_element.ftl new file mode 100644 index 0000000..1a85ef7 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/backslash_in_text_element.ftl @@ -0,0 +1 @@ +foo = \{ placeable } diff --git a/fluent.syntax/src/test/resources/serialized/block_multiline_message.ftl b/fluent.syntax/src/test/resources/serialized/block_multiline_message.ftl new file mode 100644 index 0000000..77beaa2 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/block_multiline_message.ftl @@ -0,0 +1,3 @@ +foo = + Foo + Bar diff --git a/fluent.syntax/src/test/resources/serialized/call_expression.ftl b/fluent.syntax/src/test/resources/serialized/call_expression.ftl new file mode 100644 index 0000000..e6914a7 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/call_expression.ftl @@ -0,0 +1 @@ +foo = { FOO() } diff --git a/fluent.syntax/src/test/resources/serialized/call_expression_with_message_reference.ftl b/fluent.syntax/src/test/resources/serialized/call_expression_with_message_reference.ftl new file mode 100644 index 0000000..8399f01 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/call_expression_with_message_reference.ftl @@ -0,0 +1 @@ +foo = { FOO(bar) } diff --git a/fluent.syntax/src/test/resources/serialized/call_expression_with_named_number_literal.ftl b/fluent.syntax/src/test/resources/serialized/call_expression_with_named_number_literal.ftl new file mode 100644 index 0000000..40f0906 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/call_expression_with_named_number_literal.ftl @@ -0,0 +1 @@ +foo = { FOO(bar: 1) } diff --git a/fluent.syntax/src/test/resources/serialized/call_expression_with_named_string_literal.ftl b/fluent.syntax/src/test/resources/serialized/call_expression_with_named_string_literal.ftl new file mode 100644 index 0000000..8d884a2 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/call_expression_with_named_string_literal.ftl @@ -0,0 +1 @@ +foo = { FOO(bar: "bar") } diff --git a/fluent.syntax/src/test/resources/serialized/call_expression_with_number_literal.ftl b/fluent.syntax/src/test/resources/serialized/call_expression_with_number_literal.ftl new file mode 100644 index 0000000..401dcda --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/call_expression_with_number_literal.ftl @@ -0,0 +1 @@ +foo = { FOO(1) } diff --git a/fluent.syntax/src/test/resources/serialized/call_expression_with_positional_and_named_arguments.ftl b/fluent.syntax/src/test/resources/serialized/call_expression_with_positional_and_named_arguments.ftl new file mode 100644 index 0000000..395df4e --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/call_expression_with_positional_and_named_arguments.ftl @@ -0,0 +1 @@ +foo = { FOO(bar, 1, baz: "baz") } diff --git a/fluent.syntax/src/test/resources/serialized/call_expression_with_string_literal.ftl b/fluent.syntax/src/test/resources/serialized/call_expression_with_string_literal.ftl new file mode 100644 index 0000000..74c5903 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/call_expression_with_string_literal.ftl @@ -0,0 +1 @@ +foo = { FOO("bar") } diff --git a/fluent.syntax/src/test/resources/serialized/call_expression_with_two_named_arguments.ftl b/fluent.syntax/src/test/resources/serialized/call_expression_with_two_named_arguments.ftl new file mode 100644 index 0000000..90b58f5 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/call_expression_with_two_named_arguments.ftl @@ -0,0 +1 @@ +foo = { FOO(bar: "bar", baz: "baz") } diff --git a/fluent.syntax/src/test/resources/serialized/call_expression_with_two_positional_arguments.ftl b/fluent.syntax/src/test/resources/serialized/call_expression_with_two_positional_arguments.ftl new file mode 100644 index 0000000..86b046e --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/call_expression_with_two_positional_arguments.ftl @@ -0,0 +1 @@ +foo = { FOO(bar, baz) } diff --git a/fluent.syntax/src/test/resources/serialized/call_expression_with_variable_reference.ftl b/fluent.syntax/src/test/resources/serialized/call_expression_with_variable_reference.ftl new file mode 100644 index 0000000..5ff4e63 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/call_expression_with_variable_reference.ftl @@ -0,0 +1 @@ +foo = { FOO($bar) } diff --git a/fluent.syntax/src/test/resources/serialized/escaped_special_char_in_string_literal.ftl b/fluent.syntax/src/test/resources/serialized/escaped_special_char_in_string_literal.ftl new file mode 100644 index 0000000..47e28a1 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/escaped_special_char_in_string_literal.ftl @@ -0,0 +1 @@ +foo = { "Escaped \" quote" } diff --git a/fluent.syntax/src/test/resources/serialized/group_comment.ftl b/fluent.syntax/src/test/resources/serialized/group_comment.ftl new file mode 100644 index 0000000..a197f12 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/group_comment.ftl @@ -0,0 +1,8 @@ +foo = Foo + +## Comment Header +## +## A multiline +## group comment + +bar = Bar diff --git a/fluent.syntax/src/test/resources/serialized/inline_multiline_message.exp.txt b/fluent.syntax/src/test/resources/serialized/inline_multiline_message.exp.txt new file mode 100644 index 0000000..77beaa2 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/inline_multiline_message.exp.txt @@ -0,0 +1,3 @@ +foo = + Foo + Bar diff --git a/fluent.syntax/src/test/resources/serialized/inline_multiline_message.ftl b/fluent.syntax/src/test/resources/serialized/inline_multiline_message.ftl new file mode 100644 index 0000000..bf4a190 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/inline_multiline_message.ftl @@ -0,0 +1,2 @@ +foo = Foo + Bar diff --git a/fluent.syntax/src/test/resources/serialized/message_attribute_reference.ftl b/fluent.syntax/src/test/resources/serialized/message_attribute_reference.ftl new file mode 100644 index 0000000..204a902 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/message_attribute_reference.ftl @@ -0,0 +1 @@ +foo = Foo { bar.baz } diff --git a/fluent.syntax/src/test/resources/serialized/message_comment.ftl b/fluent.syntax/src/test/resources/serialized/message_comment.ftl new file mode 100644 index 0000000..32bc3a6 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/message_comment.ftl @@ -0,0 +1,3 @@ +# A multiline +# message comment. +foo = Foo diff --git a/fluent.syntax/src/test/resources/serialized/message_reference.ftl b/fluent.syntax/src/test/resources/serialized/message_reference.ftl new file mode 100644 index 0000000..8fe530a --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/message_reference.ftl @@ -0,0 +1 @@ +foo = Foo { bar } diff --git a/fluent.syntax/src/test/resources/serialized/message_with_whitespace.ftl b/fluent.syntax/src/test/resources/serialized/message_with_whitespace.ftl new file mode 100644 index 0000000..b82cfc4 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/message_with_whitespace.ftl @@ -0,0 +1,9 @@ +# Attached comment +key1 = Value1 +key2 = Value2 + +key3 = Value3 + +# Some comment + +key4 = Value4 diff --git a/fluent.syntax/src/test/resources/serialized/message_without_eol.exp.txt b/fluent.syntax/src/test/resources/serialized/message_without_eol.exp.txt new file mode 100644 index 0000000..3036ce1 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/message_without_eol.exp.txt @@ -0,0 +1 @@ +foo = Foo diff --git a/fluent.syntax/src/test/resources/serialized/message_without_eol.ftl b/fluent.syntax/src/test/resources/serialized/message_without_eol.ftl new file mode 100644 index 0000000..5619eea --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/message_without_eol.ftl @@ -0,0 +1 @@ +foo = Foo \ No newline at end of file diff --git a/fluent.syntax/src/test/resources/serialized/multiline_attribute.ftl b/fluent.syntax/src/test/resources/serialized/multiline_attribute.ftl new file mode 100644 index 0000000..a8b0e7e --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/multiline_attribute.ftl @@ -0,0 +1,4 @@ +foo = + .attr = + Foo Attr + Continued diff --git a/fluent.syntax/src/test/resources/serialized/multiline_starting_inline.exp.txt b/fluent.syntax/src/test/resources/serialized/multiline_starting_inline.exp.txt new file mode 100644 index 0000000..04cb6df --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/multiline_starting_inline.exp.txt @@ -0,0 +1,3 @@ +foo = + Foo + Baz diff --git a/fluent.syntax/src/test/resources/serialized/multiline_starting_inline.ftl b/fluent.syntax/src/test/resources/serialized/multiline_starting_inline.ftl new file mode 100644 index 0000000..0c1c7c6 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/multiline_starting_inline.ftl @@ -0,0 +1,2 @@ +foo = Foo + Baz diff --git a/fluent.syntax/src/test/resources/serialized/multiline_starting_inline_with_a_special_char.ftl b/fluent.syntax/src/test/resources/serialized/multiline_starting_inline_with_a_special_char.ftl new file mode 100644 index 0000000..a7c5540 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/multiline_starting_inline_with_a_special_char.ftl @@ -0,0 +1,2 @@ +foo = *Foo + Baz diff --git a/fluent.syntax/src/test/resources/serialized/multiline_value_and_attributes.ftl b/fluent.syntax/src/test/resources/serialized/multiline_value_and_attributes.ftl new file mode 100644 index 0000000..690ef04 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/multiline_value_and_attributes.ftl @@ -0,0 +1,5 @@ +foo = + Foo Value + Continued + .attr-a = Foo Attr A + .attr-b = Foo Attr B diff --git a/fluent.syntax/src/test/resources/serialized/multiline_variant.ftl b/fluent.syntax/src/test/resources/serialized/multiline_variant.ftl new file mode 100644 index 0000000..e257cc1 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/multiline_variant.ftl @@ -0,0 +1,6 @@ +foo = + { $sel -> + *[a] + AAA + BBB + } diff --git a/fluent.syntax/src/test/resources/serialized/multiline_variant_with_first_line_inline.exp.txt b/fluent.syntax/src/test/resources/serialized/multiline_variant_with_first_line_inline.exp.txt new file mode 100644 index 0000000..e257cc1 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/multiline_variant_with_first_line_inline.exp.txt @@ -0,0 +1,6 @@ +foo = + { $sel -> + *[a] + AAA + BBB + } diff --git a/fluent.syntax/src/test/resources/serialized/multiline_variant_with_first_line_inline.ftl b/fluent.syntax/src/test/resources/serialized/multiline_variant_with_first_line_inline.ftl new file mode 100644 index 0000000..867ac7c --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/multiline_variant_with_first_line_inline.ftl @@ -0,0 +1,5 @@ +foo = + { $sel -> + *[a] AAA + BBB + } diff --git a/fluent.syntax/src/test/resources/serialized/multiline_with_placeable.ftl b/fluent.syntax/src/test/resources/serialized/multiline_with_placeable.ftl new file mode 100644 index 0000000..bffc6fc --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/multiline_with_placeable.ftl @@ -0,0 +1,3 @@ +foo = + Foo { bar } + Baz diff --git a/fluent.syntax/src/test/resources/serialized/nested_placeable.ftl b/fluent.syntax/src/test/resources/serialized/nested_placeable.ftl new file mode 100644 index 0000000..e6cd34a --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/nested_placeable.ftl @@ -0,0 +1 @@ +foo = {{ FOO() }} diff --git a/fluent.syntax/src/test/resources/serialized/nested_select_expression.ftl b/fluent.syntax/src/test/resources/serialized/nested_select_expression.ftl new file mode 100644 index 0000000..f091568 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/nested_select_expression.ftl @@ -0,0 +1,7 @@ +foo = + { $a -> + *[a] + { $b -> + *[b] Foo + } + } diff --git a/fluent.syntax/src/test/resources/serialized/number_literal.ftl b/fluent.syntax/src/test/resources/serialized/number_literal.ftl new file mode 100644 index 0000000..518ca43 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/number_literal.ftl @@ -0,0 +1 @@ +foo = Foo { 1 } diff --git a/fluent.syntax/src/test/resources/serialized/resource_comment.ftl b/fluent.syntax/src/test/resources/serialized/resource_comment.ftl new file mode 100644 index 0000000..4c3a027 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/resource_comment.ftl @@ -0,0 +1,4 @@ +### A multiline +### resource comment. + +foo = Foo diff --git a/fluent.syntax/src/test/resources/serialized/select_expression.ftl b/fluent.syntax/src/test/resources/serialized/select_expression.ftl new file mode 100644 index 0000000..eb22d5d --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/select_expression.ftl @@ -0,0 +1,5 @@ +foo = + { $sel -> + *[a] A + [b] B + } diff --git a/fluent.syntax/src/test/resources/serialized/select_expression_in_block_pattern.ftl b/fluent.syntax/src/test/resources/serialized/select_expression_in_block_pattern.ftl new file mode 100644 index 0000000..4ba7222 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/select_expression_in_block_pattern.ftl @@ -0,0 +1,5 @@ +foo = + Foo { $sel -> + *[a] A + [b] B + } diff --git a/fluent.syntax/src/test/resources/serialized/select_expression_in_inline_pattern.exp.txt b/fluent.syntax/src/test/resources/serialized/select_expression_in_inline_pattern.exp.txt new file mode 100644 index 0000000..4ba7222 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/select_expression_in_inline_pattern.exp.txt @@ -0,0 +1,5 @@ +foo = + Foo { $sel -> + *[a] A + [b] B + } diff --git a/fluent.syntax/src/test/resources/serialized/select_expression_in_inline_pattern.ftl b/fluent.syntax/src/test/resources/serialized/select_expression_in_inline_pattern.ftl new file mode 100644 index 0000000..f96bf5d --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/select_expression_in_inline_pattern.ftl @@ -0,0 +1,4 @@ +foo = Foo { $sel -> + *[a] A + [b] B + } diff --git a/fluent.syntax/src/test/resources/serialized/select_expression_in_inline_pattern_starting_with_a_special_char.ftl b/fluent.syntax/src/test/resources/serialized/select_expression_in_inline_pattern_starting_with_a_special_char.ftl new file mode 100644 index 0000000..f3e43d2 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/select_expression_in_inline_pattern_starting_with_a_special_char.ftl @@ -0,0 +1,4 @@ +foo = .Foo { $sel -> + *[a] A + [b] B + } diff --git a/fluent.syntax/src/test/resources/serialized/select_expression_in_multiline_pattern.ftl b/fluent.syntax/src/test/resources/serialized/select_expression_in_multiline_pattern.ftl new file mode 100644 index 0000000..194cf10 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/select_expression_in_multiline_pattern.ftl @@ -0,0 +1,6 @@ +foo = + Foo + Bar { $sel -> + *[a] A + [b] B + } diff --git a/fluent.syntax/src/test/resources/serialized/selector_number_literal.ftl b/fluent.syntax/src/test/resources/serialized/selector_number_literal.ftl new file mode 100644 index 0000000..ae7b53c --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/selector_number_literal.ftl @@ -0,0 +1,4 @@ +foo = + { 1 -> + *[a] A + } diff --git a/fluent.syntax/src/test/resources/serialized/selector_string_literal.ftl b/fluent.syntax/src/test/resources/serialized/selector_string_literal.ftl new file mode 100644 index 0000000..9444247 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/selector_string_literal.ftl @@ -0,0 +1,4 @@ +foo = + { "bar" -> + *[a] A + } diff --git a/fluent.syntax/src/test/resources/serialized/selector_term_attribute_reference.ftl b/fluent.syntax/src/test/resources/serialized/selector_term_attribute_reference.ftl new file mode 100644 index 0000000..5bd0ef7 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/selector_term_attribute_reference.ftl @@ -0,0 +1,4 @@ +foo = + { -bar.baz -> + *[a] A + } diff --git a/fluent.syntax/src/test/resources/serialized/selector_variable_reference.ftl b/fluent.syntax/src/test/resources/serialized/selector_variable_reference.ftl new file mode 100644 index 0000000..6f22c14 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/selector_variable_reference.ftl @@ -0,0 +1,4 @@ +foo = + { $bar -> + *[a] A + } diff --git a/fluent.syntax/src/test/resources/serialized/standalone_comment.ftl b/fluent.syntax/src/test/resources/serialized/standalone_comment.ftl new file mode 100644 index 0000000..7d9933a --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/standalone_comment.ftl @@ -0,0 +1,5 @@ +foo = Foo + +# A standalone comment + +bar = Bar diff --git a/fluent.syntax/src/test/resources/serialized/string_literal.ftl b/fluent.syntax/src/test/resources/serialized/string_literal.ftl new file mode 100644 index 0000000..8d99ec4 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/string_literal.ftl @@ -0,0 +1 @@ +foo = Foo { "bar" } diff --git a/fluent.syntax/src/test/resources/serialized/term.ftl b/fluent.syntax/src/test/resources/serialized/term.ftl new file mode 100644 index 0000000..a8b3fcc --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/term.ftl @@ -0,0 +1 @@ +-foo = Foo diff --git a/fluent.syntax/src/test/resources/serialized/term_reference.ftl b/fluent.syntax/src/test/resources/serialized/term_reference.ftl new file mode 100644 index 0000000..2d30982 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/term_reference.ftl @@ -0,0 +1 @@ +foo = Foo { -bar } diff --git a/fluent.syntax/src/test/resources/serialized/term_reference_call.ftl b/fluent.syntax/src/test/resources/serialized/term_reference_call.ftl new file mode 100644 index 0000000..6fdd0fd --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/term_reference_call.ftl @@ -0,0 +1 @@ +foo = { -term() } diff --git a/fluent.syntax/src/test/resources/serialized/two_attribute.ftl b/fluent.syntax/src/test/resources/serialized/two_attribute.ftl new file mode 100644 index 0000000..c1a037d --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/two_attribute.ftl @@ -0,0 +1,3 @@ +foo = + .attr-a = Foo Attr A + .attr-b = Foo Attr B diff --git a/fluent.syntax/src/test/resources/serialized/two_simple_messages.ftl b/fluent.syntax/src/test/resources/serialized/two_simple_messages.ftl new file mode 100644 index 0000000..c6068c4 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/two_simple_messages.ftl @@ -0,0 +1,2 @@ +foo = Foo +bar = Bar diff --git a/fluent.syntax/src/test/resources/serialized/unicode_escape_sequence.ftl b/fluent.syntax/src/test/resources/serialized/unicode_escape_sequence.ftl new file mode 100644 index 0000000..0f94bbc --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/unicode_escape_sequence.ftl @@ -0,0 +1 @@ +foo = { "\u0065" } diff --git a/fluent.syntax/src/test/resources/serialized/value_and_attributes.ftl b/fluent.syntax/src/test/resources/serialized/value_and_attributes.ftl new file mode 100644 index 0000000..8f549f6 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/value_and_attributes.ftl @@ -0,0 +1,3 @@ +foo = Foo Value + .attr-a = Foo Attr A + .attr-b = Foo Attr B diff --git a/fluent.syntax/src/test/resources/serialized/variable_reference.ftl b/fluent.syntax/src/test/resources/serialized/variable_reference.ftl new file mode 100644 index 0000000..6ce1201 --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/variable_reference.ftl @@ -0,0 +1 @@ +foo = Foo { $bar } diff --git a/fluent.syntax/src/test/resources/serialized/variant_key_number.ftl b/fluent.syntax/src/test/resources/serialized/variant_key_number.ftl new file mode 100644 index 0000000..b76dc2b --- /dev/null +++ b/fluent.syntax/src/test/resources/serialized/variant_key_number.ftl @@ -0,0 +1,4 @@ +foo = + { $sel -> + *[1] 1 + } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/.gitattributes b/fluent.syntax/src/test/resources/structure_fixtures/.gitattributes new file mode 100644 index 0000000..e6bac30 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/.gitattributes @@ -0,0 +1 @@ +crlf.ftl eol=crlf diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_expression_with_wrong_attr.ftl b/fluent.syntax/src/test/resources/structure_fixtures/attribute_expression_with_wrong_attr.ftl new file mode 100644 index 0000000..72693a2 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/attribute_expression_with_wrong_attr.ftl @@ -0,0 +1,2 @@ +err1 = { foo.23 } +err2 = { foo. } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_expression_with_wrong_attr.json b/fluent.syntax/src/test/resources/structure_fixtures/attribute_expression_with_wrong_attr.json new file mode 100644 index 0000000..23bca44 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/attribute_expression_with_wrong_attr.json @@ -0,0 +1,58 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 13, + "end": 13 + } + } + ], + "content": "err1 = { foo.23 }\n", + "span": { + "type": "Span", + "start": 0, + "end": 18 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 31, + "end": 31 + } + } + ], + "content": "err2 = { foo. }\n", + "span": { + "type": "Span", + "start": 18, + "end": 34 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 34 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_of_private_as_placeable.ftl b/fluent.syntax/src/test/resources/structure_fixtures/attribute_of_private_as_placeable.ftl new file mode 100644 index 0000000..0ea9335 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/attribute_of_private_as_placeable.ftl @@ -0,0 +1 @@ +err1 = { -brand.gender } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_of_private_as_placeable.json b/fluent.syntax/src/test/resources/structure_fixtures/attribute_of_private_as_placeable.json new file mode 100644 index 0000000..3821979 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/attribute_of_private_as_placeable.json @@ -0,0 +1,32 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0019", + "arguments": [], + "message": "Attributes of terms cannot be used as placeables", + "span": { + "type": "Span", + "start": 23, + "end": 23 + } + } + ], + "content": "err1 = { -brand.gender }\n", + "span": { + "type": "Span", + "start": 0, + "end": 25 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 25 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_of_public_as_selector.ftl b/fluent.syntax/src/test/resources/structure_fixtures/attribute_of_public_as_selector.ftl new file mode 100644 index 0000000..e2247b1 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/attribute_of_public_as_selector.ftl @@ -0,0 +1,5 @@ +err1 = + { foo.bar -> + [1] One + *[2] Two + } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_of_public_as_selector.json b/fluent.syntax/src/test/resources/structure_fixtures/attribute_of_public_as_selector.json new file mode 100644 index 0000000..7adaaeb --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/attribute_of_public_as_selector.json @@ -0,0 +1,32 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0018", + "arguments": [], + "message": "Attributes of messages cannot be used as selectors", + "span": { + "type": "Span", + "start": 21, + "end": 21 + } + } + ], + "content": "err1 =\n { foo.bar ->\n [1] One\n *[2] Two\n }\n", + "span": { + "type": "Span", + "start": 0, + "end": 62 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 62 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_starts_from_nl.ftl b/fluent.syntax/src/test/resources/structure_fixtures/attribute_starts_from_nl.ftl new file mode 100644 index 0000000..e996c6b --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/attribute_starts_from_nl.ftl @@ -0,0 +1,2 @@ +foo = Value +.attr = Value 2 diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_starts_from_nl.json b/fluent.syntax/src/test/resources/structure_fixtures/attribute_starts_from_nl.json new file mode 100644 index 0000000..d7d64c4 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/attribute_starts_from_nl.json @@ -0,0 +1,85 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "foo", + "span": { + "type": "Span", + "start": 0, + "end": 3 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 6, + "end": 11 + } + } + ], + "span": { + "type": "Span", + "start": 6, + "end": 11 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 13, + "end": 17 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 2", + "span": { + "type": "Span", + "start": 20, + "end": 27 + } + } + ], + "span": { + "type": "Span", + "start": 20, + "end": 27 + } + }, + "span": { + "type": "Span", + "start": 12, + "end": 27 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 27 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 28 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_with_empty_pattern.ftl b/fluent.syntax/src/test/resources/structure_fixtures/attribute_with_empty_pattern.ftl new file mode 100644 index 0000000..aba7c42 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/attribute_with_empty_pattern.ftl @@ -0,0 +1,17 @@ +key1 = Value 1 + .attr = + +key2 = + .attr = + +key3 = + .attr1 = Attr 1 + .attr2 = + +key4 = + .attr1 = + .attr2 = Attr 2 + +key5 = + .attr1 = + .attr2 = diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_with_empty_pattern.json b/fluent.syntax/src/test/resources/structure_fixtures/attribute_with_empty_pattern.json new file mode 100644 index 0000000..d08648e --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/attribute_with_empty_pattern.json @@ -0,0 +1,120 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0012", + "arguments": [], + "message": "Expected value", + "span": { + "type": "Span", + "start": 26, + "end": 26 + } + } + ], + "content": "key1 = Value 1\n .attr =\n\n", + "span": { + "type": "Span", + "start": 0, + "end": 28 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0012", + "arguments": [], + "message": "Expected value", + "span": { + "type": "Span", + "start": 46, + "end": 46 + } + } + ], + "content": "key2 =\n .attr =\n\n", + "span": { + "type": "Span", + "start": 28, + "end": 48 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0012", + "arguments": [], + "message": "Expected value", + "span": { + "type": "Span", + "start": 87, + "end": 87 + } + } + ], + "content": "key3 =\n .attr1 = Attr 1\n .attr2 =\n\n", + "span": { + "type": "Span", + "start": 48, + "end": 89 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0012", + "arguments": [], + "message": "Expected value", + "span": { + "type": "Span", + "start": 108, + "end": 108 + } + } + ], + "content": "key4 =\n .attr1 =\n .attr2 = Attr 2\n\n", + "span": { + "type": "Span", + "start": 89, + "end": 130 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0012", + "arguments": [], + "message": "Expected value", + "span": { + "type": "Span", + "start": 149, + "end": 149 + } + } + ], + "content": "key5 =\n .attr1 =\n .attr2 =\n", + "span": { + "type": "Span", + "start": 130, + "end": 163 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 163 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_without_equal_sign.ftl b/fluent.syntax/src/test/resources/structure_fixtures/attribute_without_equal_sign.ftl new file mode 100644 index 0000000..79374a6 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/attribute_without_equal_sign.ftl @@ -0,0 +1,2 @@ +key = Value + .label diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_without_equal_sign.json b/fluent.syntax/src/test/resources/structure_fixtures/attribute_without_equal_sign.json new file mode 100644 index 0000000..5a81df0 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/attribute_without_equal_sign.json @@ -0,0 +1,34 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 22, + "end": 22 + } + } + ], + "content": "key = Value\n .label\n", + "span": { + "type": "Span", + "start": 0, + "end": 23 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 23 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/blank_lines.ftl b/fluent.syntax/src/test/resources/structure_fixtures/blank_lines.ftl new file mode 100644 index 0000000..0d20739 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/blank_lines.ftl @@ -0,0 +1,27 @@ +### NOTE: Disable final newline insertion and trimming when editing this file. + +key01 = Value 01 + +key02 = Value 02 + + +key03 = + + Value 03 + + Continued + +# There are four spaces on the line between "Value 04" and "Continued". +key04 = + + Value 04 + + Continued + +# There are four spaces on the line following "Value 05". +key05 = + Value 05 + +# There are four spaces on the line following "Value 06". +key06 = Value 06 + \ No newline at end of file diff --git a/fluent.syntax/src/test/resources/structure_fixtures/blank_lines.json b/fluent.syntax/src/test/resources/structure_fixtures/blank_lines.json new file mode 100644 index 0000000..6d04d43 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/blank_lines.json @@ -0,0 +1,271 @@ +{ + "type": "Resource", + "body": [ + { + "content": "NOTE: Disable final newline insertion and trimming when editing this file.", + "type": "ResourceComment", + "span": { + "type": "Span", + "start": 0, + "end": 78 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01", + "span": { + "type": "Span", + "start": 80, + "end": 85 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 01", + "span": { + "type": "Span", + "start": 88, + "end": 96 + } + } + ], + "span": { + "type": "Span", + "start": 88, + "end": 96 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 80, + "end": 96 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key02", + "span": { + "type": "Span", + "start": 98, + "end": 103 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 02", + "span": { + "type": "Span", + "start": 106, + "end": 114 + } + } + ], + "span": { + "type": "Span", + "start": 106, + "end": 114 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 98, + "end": 114 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key03", + "span": { + "type": "Span", + "start": 117, + "end": 122 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 03\n\nContinued", + "span": { + "type": "Span", + "start": 130, + "end": 153 + } + } + ], + "span": { + "type": "Span", + "start": 126, + "end": 153 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 117, + "end": 153 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key04", + "span": { + "type": "Span", + "start": 227, + "end": 232 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 04\n\nContinued", + "span": { + "type": "Span", + "start": 240, + "end": 267 + } + } + ], + "span": { + "type": "Span", + "start": 236, + "end": 267 + } + }, + "attributes": [], + "comment": { + "content": "There are four spaces on the line between \"Value 04\" and \"Continued\".", + "type": "Comment", + "span": { + "type": "Span", + "start": 155, + "end": 226 + } + }, + "span": { + "type": "Span", + "start": 155, + "end": 267 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key05", + "span": { + "type": "Span", + "start": 327, + "end": 332 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 05", + "span": { + "type": "Span", + "start": 339, + "end": 347 + } + } + ], + "span": { + "type": "Span", + "start": 335, + "end": 347 + } + }, + "attributes": [], + "comment": { + "content": "There are four spaces on the line following \"Value 05\".", + "type": "Comment", + "span": { + "type": "Span", + "start": 269, + "end": 326 + } + }, + "span": { + "type": "Span", + "start": 269, + "end": 347 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key06", + "span": { + "type": "Span", + "start": 411, + "end": 416 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 06", + "span": { + "type": "Span", + "start": 419, + "end": 427 + } + } + ], + "span": { + "type": "Span", + "start": 419, + "end": 427 + } + }, + "attributes": [], + "comment": { + "content": "There are four spaces on the line following \"Value 06\".", + "type": "Comment", + "span": { + "type": "Span", + "start": 353, + "end": 410 + } + }, + "span": { + "type": "Span", + "start": 353, + "end": 427 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 432 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/broken_number.ftl b/fluent.syntax/src/test/resources/structure_fixtures/broken_number.ftl new file mode 100644 index 0000000..91b074b --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/broken_number.ftl @@ -0,0 +1,5 @@ +err01 = { -2.4.5 } +err02 = { -2.4. } +err03 = { -.4 } +err04 = { -2..4 } +err05 = { 24d } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/broken_number.json b/fluent.syntax/src/test/resources/structure_fixtures/broken_number.json new file mode 100644 index 0000000..f0270d5 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/broken_number.json @@ -0,0 +1,130 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 14, + "end": 14 + } + } + ], + "content": "err01 = { -2.4.5 }\n", + "span": { + "type": "Span", + "start": 0, + "end": 19 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 33, + "end": 33 + } + } + ], + "content": "err02 = { -2.4. }\n", + "span": { + "type": "Span", + "start": 19, + "end": 37 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 48, + "end": 48 + } + } + ], + "content": "err03 = { -.4 }\n", + "span": { + "type": "Span", + "start": 37, + "end": 53 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "0-9" + ], + "message": "Expected a character from range: \"0-9\"", + "span": { + "type": "Span", + "start": 66, + "end": 66 + } + } + ], + "content": "err04 = { -2..4 }\n", + "span": { + "type": "Span", + "start": 53, + "end": 71 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 83, + "end": 83 + } + } + ], + "content": "err05 = { 24d }\n", + "span": { + "type": "Span", + "start": 71, + "end": 87 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 87 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/call_expression_errors.ftl b/fluent.syntax/src/test/resources/structure_fixtures/call_expression_errors.ftl new file mode 100644 index 0000000..d596f24 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/call_expression_errors.ftl @@ -0,0 +1,3 @@ +err01 = { no-caps-name() } +err02 = { BUILTIN(2: "foo") } +err03 = { BUILTIN(key: foo) } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/call_expression_errors.json b/fluent.syntax/src/test/resources/structure_fixtures/call_expression_errors.json new file mode 100644 index 0000000..3710025 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/call_expression_errors.json @@ -0,0 +1,76 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0008", + "arguments": [], + "message": "The callee has to be an upper-case identifier or a term", + "span": { + "type": "Span", + "start": 22, + "end": 22 + } + } + ], + "content": "err01 = { no-caps-name() }\n", + "span": { + "type": "Span", + "start": 0, + "end": 27 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0009", + "arguments": [], + "message": "The argument name has to be a simple identifier", + "span": { + "type": "Span", + "start": 46, + "end": 46 + } + } + ], + "content": "err02 = { BUILTIN(2: \"foo\") }\n", + "span": { + "type": "Span", + "start": 27, + "end": 57 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0014", + "arguments": [], + "message": "Expected literal", + "span": { + "type": "Span", + "start": 80, + "end": 80 + } + } + ], + "content": "err03 = { BUILTIN(key: foo) }\n", + "span": { + "type": "Span", + "start": 57, + "end": 87 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 87 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/comment_with_eof.ftl b/fluent.syntax/src/test/resources/structure_fixtures/comment_with_eof.ftl new file mode 100644 index 0000000..d5811bf --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/comment_with_eof.ftl @@ -0,0 +1 @@ +# This is a comment with no new line \ No newline at end of file diff --git a/fluent.syntax/src/test/resources/structure_fixtures/comment_with_eof.json b/fluent.syntax/src/test/resources/structure_fixtures/comment_with_eof.json new file mode 100644 index 0000000..04b927b --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/comment_with_eof.json @@ -0,0 +1,19 @@ +{ + "type": "Resource", + "body": [ + { + "content": "This is a comment with no new line", + "type": "Comment", + "span": { + "type": "Span", + "start": 0, + "end": 36 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 36 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/crlf.ftl b/fluent.syntax/src/test/resources/structure_fixtures/crlf.ftl new file mode 100644 index 0000000..df3a02c --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/crlf.ftl @@ -0,0 +1,14 @@ + +key01 = Value 01 +key02 = + + Value 02 + Continued + + .title = Title + +# ERROR Unclosed StringLiteral +err03 = { "str + +# ERROR Missing newline after ->. +err04 = { $sel -> } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/crlf.json b/fluent.syntax/src/test/resources/structure_fixtures/crlf.json new file mode 100644 index 0000000..313d75f --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/crlf.json @@ -0,0 +1,187 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01", + "span": { + "type": "Span", + "start": 2, + "end": 7 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 01", + "span": { + "type": "Span", + "start": 10, + "end": 18 + } + } + ], + "span": { + "type": "Span", + "start": 10, + "end": 18 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 2, + "end": 18 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key02", + "span": { + "type": "Span", + "start": 20, + "end": 25 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 02\nContinued", + "span": { + "type": "Span", + "start": 35, + "end": 58 + } + } + ], + "span": { + "type": "Span", + "start": 31, + "end": 58 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "title", + "span": { + "type": "Span", + "start": 67, + "end": 72 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Title", + "span": { + "type": "Span", + "start": 75, + "end": 80 + } + } + ], + "span": { + "type": "Span", + "start": 75, + "end": 80 + } + }, + "span": { + "type": "Span", + "start": 66, + "end": 80 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 20, + "end": 80 + } + }, + { + "type": "Comment", + "content": "ERROR Unclosed StringLiteral", + "span": { + "type": "Span", + "start": 84, + "end": 114 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0020", + "arguments": [], + "message": "Unterminated string expression", + "span": { + "type": "Span", + "start": 130, + "end": 130 + } + } + ], + "content": "err03 = { \"str\r\n\r\n", + "span": { + "type": "Span", + "start": 116, + "end": 134 + } + }, + { + "type": "Comment", + "content": "ERROR Missing newline after ->.", + "span": { + "type": "Span", + "start": 134, + "end": 167 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "␤" + ], + "message": "Expected token: \"␤\"", + "span": { + "type": "Span", + "start": 187, + "end": 187 + } + } + ], + "content": "err04 = { $sel -> }\r\n", + "span": { + "type": "Span", + "start": 169, + "end": 190 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 190 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/dash_at_eof.ftl b/fluent.syntax/src/test/resources/structure_fixtures/dash_at_eof.ftl new file mode 100644 index 0000000..ba59813 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/dash_at_eof.ftl @@ -0,0 +1,8 @@ +### BE CAREFUL WHEN EDITING THIS FILE +### +### The last character in this file is the dash ("-") on line 8. +### We want to test a literal which starts like a negative number. +### Most editors automatically add a trailing newline at EOF. +### If you edit this file make sure to turn this behavior off. + +key1 = {- \ No newline at end of file diff --git a/fluent.syntax/src/test/resources/structure_fixtures/dash_at_eof.json b/fluent.syntax/src/test/resources/structure_fixtures/dash_at_eof.json new file mode 100644 index 0000000..9bfa7e2 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/dash_at_eof.json @@ -0,0 +1,43 @@ +{ + "type": "Resource", + "body": [ + { + "content": "BE CAREFUL WHEN EDITING THIS FILE\n\nThe last character in this file is the dash (\"-\") on line 8.\nWe want to test a literal which starts like a negative number.\nMost editors automatically add a trailing newline at EOF.\nIf you edit this file make sure to turn this behavior off.", + "type": "ResourceComment", + "span": { + "type": "Span", + "start": 0, + "end": 298 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 309, + "end": 309 + } + } + ], + "content": "key1 = {-", + "span": { + "type": "Span", + "start": 300, + "end": 309 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 309 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/elements_indent.ftl b/fluent.syntax/src/test/resources/structure_fixtures/elements_indent.ftl new file mode 100644 index 0000000..6ac7af1 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/elements_indent.ftl @@ -0,0 +1,6 @@ +foo = Foo +.attr = Foo Attr + +bar = Bar + .attr1 = Bar Attr 1 +.attr2 = Bar Attr 2 diff --git a/fluent.syntax/src/test/resources/structure_fixtures/elements_indent.json b/fluent.syntax/src/test/resources/structure_fixtures/elements_indent.json new file mode 100644 index 0000000..6a23cc2 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/elements_indent.json @@ -0,0 +1,196 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "foo", + "span": { + "type": "Span", + "start": 0, + "end": 3 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Foo", + "span": { + "type": "Span", + "start": 6, + "end": 9 + } + } + ], + "span": { + "type": "Span", + "start": 6, + "end": 9 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 11, + "end": 15 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Foo Attr", + "span": { + "type": "Span", + "start": 18, + "end": 26 + } + } + ], + "span": { + "type": "Span", + "start": 18, + "end": 26 + } + }, + "span": { + "type": "Span", + "start": 10, + "end": 26 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 26 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "bar", + "span": { + "type": "Span", + "start": 28, + "end": 31 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Bar", + "span": { + "type": "Span", + "start": 34, + "end": 37 + } + } + ], + "span": { + "type": "Span", + "start": 34, + "end": 37 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr1", + "span": { + "type": "Span", + "start": 43, + "end": 48 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Bar Attr 1", + "span": { + "type": "Span", + "start": 51, + "end": 61 + } + } + ], + "span": { + "type": "Span", + "start": 51, + "end": 61 + } + }, + "span": { + "type": "Span", + "start": 42, + "end": 61 + } + }, + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr2", + "span": { + "type": "Span", + "start": 63, + "end": 68 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Bar Attr 2", + "span": { + "type": "Span", + "start": 71, + "end": 81 + } + } + ], + "span": { + "type": "Span", + "start": 71, + "end": 81 + } + }, + "span": { + "type": "Span", + "start": 62, + "end": 81 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 28, + "end": 81 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 82 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/empty_resource.ftl b/fluent.syntax/src/test/resources/structure_fixtures/empty_resource.ftl new file mode 100644 index 0000000..e69de29 diff --git a/fluent.syntax/src/test/resources/structure_fixtures/empty_resource.json b/fluent.syntax/src/test/resources/structure_fixtures/empty_resource.json new file mode 100644 index 0000000..603a5bb --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/empty_resource.json @@ -0,0 +1,9 @@ +{ + "type": "Resource", + "body": [], + "span": { + "type": "Span", + "start": 0, + "end": 0 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/empty_resource_with_ws.ftl b/fluent.syntax/src/test/resources/structure_fixtures/empty_resource_with_ws.ftl new file mode 100644 index 0000000..4e43544 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/empty_resource_with_ws.ftl @@ -0,0 +1,2 @@ + + diff --git a/fluent.syntax/src/test/resources/structure_fixtures/empty_resource_with_ws.json b/fluent.syntax/src/test/resources/structure_fixtures/empty_resource_with_ws.json new file mode 100644 index 0000000..4d3a2b7 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/empty_resource_with_ws.json @@ -0,0 +1,9 @@ +{ + "type": "Resource", + "body": [], + "span": { + "type": "Span", + "start": 0, + "end": 10 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/escape_sequences.ftl b/fluent.syntax/src/test/resources/structure_fixtures/escape_sequences.ftl new file mode 100644 index 0000000..d6155a9 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/escape_sequences.ftl @@ -0,0 +1,24 @@ +## Literal text +text-backslash-one = Value with \ a backslash +text-backslash-two = Value with \\ two backslashes +text-backslash-brace = Value with \{placeable} +text-backslash-u = \u0041 +text-backslash-backslash-u = \\u0041 + +## String literals +quote-in-string = {"\""} +backslash-in-string = {"\\"} +# ERROR Mismatched quote +mismatched-quote = {"\\""} +# ERROR Unknown escape +unknown-escape = {"\x"} + +## Unicode escapes +string-unicode-sequence = {"\u0041"} +string-escaped-unicode = {"\\u0041"} +# ERROR Unknown escape +unknown-unicode = {"\u000z"} + +## Literal braces +brace-open = An opening {"{"} brace. +brace-close = A closing {"}"} brace. diff --git a/fluent.syntax/src/test/resources/structure_fixtures/escape_sequences.json b/fluent.syntax/src/test/resources/structure_fixtures/escape_sequences.json new file mode 100644 index 0000000..e639175 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/escape_sequences.json @@ -0,0 +1,673 @@ +{ + "type": "Resource", + "body": [ + { + "content": "Literal text", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 0, + "end": 15 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "text-backslash-one", + "span": { + "type": "Span", + "start": 16, + "end": 34 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value with \\ a backslash", + "span": { + "type": "Span", + "start": 37, + "end": 61 + } + } + ], + "span": { + "type": "Span", + "start": 37, + "end": 61 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 16, + "end": 61 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "text-backslash-two", + "span": { + "type": "Span", + "start": 62, + "end": 80 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value with \\\\ two backslashes", + "span": { + "type": "Span", + "start": 83, + "end": 112 + } + } + ], + "span": { + "type": "Span", + "start": 83, + "end": 112 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 62, + "end": 112 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "text-backslash-brace", + "span": { + "type": "Span", + "start": 113, + "end": 133 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value with \\", + "span": { + "type": "Span", + "start": 136, + "end": 148 + } + }, + { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "placeable", + "span": { + "type": "Span", + "start": 149, + "end": 158 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 149, + "end": 158 + } + }, + "span": { + "type": "Span", + "start": 148, + "end": 159 + } + } + ], + "span": { + "type": "Span", + "start": 136, + "end": 159 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 113, + "end": 159 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "text-backslash-u", + "span": { + "type": "Span", + "start": 160, + "end": 176 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "\\u0041", + "span": { + "type": "Span", + "start": 179, + "end": 185 + } + } + ], + "span": { + "type": "Span", + "start": 179, + "end": 185 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 160, + "end": 185 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "text-backslash-backslash-u", + "span": { + "type": "Span", + "start": 186, + "end": 212 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "\\\\u0041", + "span": { + "type": "Span", + "start": 215, + "end": 222 + } + } + ], + "span": { + "type": "Span", + "start": 215, + "end": 222 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 186, + "end": 222 + } + }, + { + "content": "String literals", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 224, + "end": 242 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "quote-in-string", + "span": { + "type": "Span", + "start": 243, + "end": 258 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "\\\"", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 262, + "end": 266 + } + }, + "span": { + "type": "Span", + "start": 261, + "end": 267 + } + } + ], + "span": { + "type": "Span", + "start": 261, + "end": 267 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 243, + "end": 267 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "backslash-in-string", + "span": { + "type": "Span", + "start": 268, + "end": 287 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "\\\\", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 291, + "end": 295 + } + }, + "span": { + "type": "Span", + "start": 290, + "end": 296 + } + } + ], + "span": { + "type": "Span", + "start": 290, + "end": 296 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 268, + "end": 296 + } + }, + { + "content": "ERROR Mismatched quote", + "type": "Comment", + "span": { + "type": "Span", + "start": 297, + "end": 321 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 346, + "end": 346 + } + } + ], + "content": "mismatched-quote = {\"\\\\\"\"}\n", + "span": { + "type": "Span", + "start": 322, + "end": 349 + } + }, + { + "content": "ERROR Unknown escape", + "type": "Comment", + "span": { + "type": "Span", + "start": 349, + "end": 371 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0025", + "arguments": [ + "x" + ], + "message": "Unknown escape sequence: \\x.", + "span": { + "type": "Span", + "start": 392, + "end": 392 + } + } + ], + "content": "unknown-escape = {\"\\x\"}\n\n", + "span": { + "type": "Span", + "start": 372, + "end": 397 + } + }, + { + "content": "Unicode escapes", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 397, + "end": 415 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "string-unicode-sequence", + "span": { + "type": "Span", + "start": 416, + "end": 439 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "\\u0041", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 443, + "end": 451 + } + }, + "span": { + "type": "Span", + "start": 442, + "end": 452 + } + } + ], + "span": { + "type": "Span", + "start": 442, + "end": 452 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 416, + "end": 452 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "string-escaped-unicode", + "span": { + "type": "Span", + "start": 453, + "end": 475 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "\\\\u0041", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 479, + "end": 488 + } + }, + "span": { + "type": "Span", + "start": 478, + "end": 489 + } + } + ], + "span": { + "type": "Span", + "start": 478, + "end": 489 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 453, + "end": 489 + } + }, + { + "content": "ERROR Unknown escape", + "type": "Comment", + "span": { + "type": "Span", + "start": 490, + "end": 512 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0026", + "arguments": [ + "\\u000z" + ], + "message": "Invalid Unicode escape sequence: \\u000z.", + "span": { + "type": "Span", + "start": 538, + "end": 538 + } + } + ], + "content": "unknown-unicode = {\"\\u000z\"}\n\n", + "span": { + "type": "Span", + "start": 513, + "end": 543 + } + }, + { + "content": "Literal braces", + "type": "GroupComment", + "span": { + "type": "Span", + "start": 543, + "end": 560 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "brace-open", + "span": { + "type": "Span", + "start": 561, + "end": 571 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "An opening ", + "span": { + "type": "Span", + "start": 574, + "end": 585 + } + }, + { + "type": "Placeable", + "expression": { + "value": "{", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 586, + "end": 589 + } + }, + "span": { + "type": "Span", + "start": 585, + "end": 590 + } + }, + { + "type": "TextElement", + "value": " brace.", + "span": { + "type": "Span", + "start": 590, + "end": 597 + } + } + ], + "span": { + "type": "Span", + "start": 574, + "end": 597 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 561, + "end": 597 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "brace-close", + "span": { + "type": "Span", + "start": 598, + "end": 609 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "A closing ", + "span": { + "type": "Span", + "start": 612, + "end": 622 + } + }, + { + "type": "Placeable", + "expression": { + "value": "}", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 623, + "end": 626 + } + }, + "span": { + "type": "Span", + "start": 622, + "end": 627 + } + }, + { + "type": "TextElement", + "value": " brace.", + "span": { + "type": "Span", + "start": 627, + "end": 634 + } + } + ], + "span": { + "type": "Span", + "start": 612, + "end": 634 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 598, + "end": 634 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 635 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/expressions_call_args.ftl b/fluent.syntax/src/test/resources/structure_fixtures/expressions_call_args.ftl new file mode 100644 index 0000000..471850f --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/expressions_call_args.ftl @@ -0,0 +1,2 @@ +key = { FOO(arg1: 1, + arg2: 2) } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/expressions_call_args.json b/fluent.syntax/src/test/resources/structure_fixtures/expressions_call_args.json new file mode 100644 index 0000000..3fd9bc0 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/expressions_call_args.json @@ -0,0 +1,127 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 0, + "end": 3 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "FunctionReference", + "id": { + "type": "Identifier", + "name": "FOO", + "span": { + "type": "Span", + "start": 8, + "end": 11 + } + }, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [ + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "arg1", + "span": { + "type": "Span", + "start": 12, + "end": 16 + } + }, + "value": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 18, + "end": 19 + } + }, + "span": { + "type": "Span", + "start": 12, + "end": 19 + } + }, + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "arg2", + "span": { + "type": "Span", + "start": 33, + "end": 37 + } + }, + "value": { + "value": "2", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 39, + "end": 40 + } + }, + "span": { + "type": "Span", + "start": 33, + "end": 40 + } + } + ], + "span": { + "type": "Span", + "start": 11, + "end": 41 + } + }, + "span": { + "type": "Span", + "start": 8, + "end": 41 + } + }, + "span": { + "type": "Span", + "start": 6, + "end": 43 + } + } + ], + "span": { + "type": "Span", + "start": 6, + "end": 43 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 43 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 44 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/indent.ftl b/fluent.syntax/src/test/resources/structure_fixtures/indent.ftl new file mode 100644 index 0000000..2976000 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/indent.ftl @@ -0,0 +1,10 @@ + err01 = A + +key2 = { +a } + +key3 = { a +} + +key4 = { +{ a }} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/indent.json b/fluent.syntax/src/test/resources/structure_fixtures/indent.json new file mode 100644 index 0000000..91c2199 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/indent.json @@ -0,0 +1,205 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0002", + "arguments": [], + "message": "Expected an entry start", + "span": { + "type": "Span", + "start": 0, + "end": 0 + } + } + ], + "content": " err01 = A\n\n", + "span": { + "type": "Span", + "start": 0, + "end": 13 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key2", + "span": { + "type": "Span", + "start": 13, + "end": 17 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "a", + "span": { + "type": "Span", + "start": 22, + "end": 23 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 22, + "end": 23 + } + }, + "span": { + "type": "Span", + "start": 20, + "end": 25 + } + } + ], + "span": { + "type": "Span", + "start": 20, + "end": 25 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 13, + "end": 25 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key3", + "span": { + "type": "Span", + "start": 27, + "end": 31 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "a", + "span": { + "type": "Span", + "start": 36, + "end": 37 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 36, + "end": 37 + } + }, + "span": { + "type": "Span", + "start": 34, + "end": 39 + } + } + ], + "span": { + "type": "Span", + "start": 34, + "end": 39 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 27, + "end": 39 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key4", + "span": { + "type": "Span", + "start": 41, + "end": 45 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "a", + "span": { + "type": "Span", + "start": 52, + "end": 53 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 52, + "end": 53 + } + }, + "span": { + "type": "Span", + "start": 50, + "end": 55 + } + }, + "span": { + "type": "Span", + "start": 48, + "end": 56 + } + } + ], + "span": { + "type": "Span", + "start": 48, + "end": 56 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 41, + "end": 56 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 57 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/junk.ftl b/fluent.syntax/src/test/resources/structure_fixtures/junk.ftl new file mode 100644 index 0000000..0ce05ea --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/junk.ftl @@ -0,0 +1,23 @@ +err01 = {1xx} +err02 = {1xx} + +err03 = {1xx} +1xx + +err04 = {1xx} + +1xx + +err05 = { + +1xx + +err06 = {1xx + + .attr = Value + +err07 = { + +key08 = Value + +err09 = { diff --git a/fluent.syntax/src/test/resources/structure_fixtures/junk.json b/fluent.syntax/src/test/resources/structure_fixtures/junk.json new file mode 100644 index 0000000..df2c096 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/junk.json @@ -0,0 +1,238 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 10, + "end": 10 + } + } + ], + "content": "err01 = {1xx}\n", + "span": { + "type": "Span", + "start": 0, + "end": 14 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 24, + "end": 24 + } + } + ], + "content": "err02 = {1xx}\n\n", + "span": { + "type": "Span", + "start": 14, + "end": 29 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 39, + "end": 39 + } + } + ], + "content": "err03 = {1xx}\n1xx\n\n", + "span": { + "type": "Span", + "start": 29, + "end": 48 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 58, + "end": 58 + } + } + ], + "content": "err04 = {1xx}\n\n1xx\n\n", + "span": { + "type": "Span", + "start": 48, + "end": 68 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 80, + "end": 80 + } + } + ], + "content": "err05 = {\n\n1xx\n\n", + "span": { + "type": "Span", + "start": 68, + "end": 84 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 94, + "end": 94 + } + } + ], + "content": "err06 = {1xx\n\n .attr = Value\n\n", + "span": { + "type": "Span", + "start": 84, + "end": 117 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 128, + "end": 128 + } + } + ], + "content": "err07 = {\n\n", + "span": { + "type": "Span", + "start": 117, + "end": 128 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key08", + "span": { + "type": "Span", + "start": 128, + "end": 133 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 136, + "end": 141 + } + } + ], + "span": { + "type": "Span", + "start": 136, + "end": 141 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 128, + "end": 141 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0028", + "arguments": [], + "message": "Expected an inline expression", + "span": { + "type": "Span", + "start": 153, + "end": 153 + } + } + ], + "content": "err09 = {\n", + "span": { + "type": "Span", + "start": 143, + "end": 153 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 153 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/leading_dots.ftl b/fluent.syntax/src/test/resources/structure_fixtures/leading_dots.ftl new file mode 100644 index 0000000..61f395e --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/leading_dots.ftl @@ -0,0 +1,64 @@ +key01 = .Value +key02 = …Value +key03 = {"."}Value +key04 = + {"."}Value + +key05 = Value + {"."}Continued + +key06 = .Value + {"."}Continued + +# ERROR (attr .Continued must have a value) +key07 = Value + .Continued + +# ERROR (attr .Value must have a value) +key08 = + .Value + +# ERROR (attr .Value must have a value) +key09 = + .Value + Continued + +key10 = + .Value = which looks like an attribute + Continued + +key11 = + {"."}Value = which looks like an attribute + Continued + +key12 = + .accesskey = + A + +key13 = + .attribute = .Value + +key14 = + .attribute = + {"."}Value + +key15 = + { 1 -> + [one] .Value + *[other] + {"."}Value + } + +# ERROR (variant must have a value) +key16 = + { 1 -> + *[one] + .Value + } + +# ERROR (unclosed placeable) +key17 = + { 1 -> + *[one] Value + .Continued + } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/leading_dots.json b/fluent.syntax/src/test/resources/structure_fixtures/leading_dots.json new file mode 100644 index 0000000..6b6261d --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/leading_dots.json @@ -0,0 +1,934 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01", + "span": { + "type": "Span", + "start": 0, + "end": 5 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".Value", + "span": { + "type": "Span", + "start": 8, + "end": 14 + } + } + ], + "span": { + "type": "Span", + "start": 8, + "end": 14 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 14 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key02", + "span": { + "type": "Span", + "start": 15, + "end": 20 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "…Value", + "span": { + "type": "Span", + "start": 23, + "end": 29 + } + } + ], + "span": { + "type": "Span", + "start": 23, + "end": 29 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 15, + "end": 29 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key03", + "span": { + "type": "Span", + "start": 30, + "end": 35 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": ".", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 39, + "end": 42 + } + }, + "span": { + "type": "Span", + "start": 38, + "end": 43 + } + }, + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 43, + "end": 48 + } + } + ], + "span": { + "type": "Span", + "start": 38, + "end": 48 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 30, + "end": 48 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key04", + "span": { + "type": "Span", + "start": 49, + "end": 54 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": ".", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 62, + "end": 65 + } + }, + "span": { + "type": "Span", + "start": 61, + "end": 66 + } + }, + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 66, + "end": 71 + } + } + ], + "span": { + "type": "Span", + "start": 57, + "end": 71 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 49, + "end": 71 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key05", + "span": { + "type": "Span", + "start": 73, + "end": 78 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value\n", + "span": { + "type": "Span", + "start": 81, + "end": 91 + } + }, + { + "type": "Placeable", + "expression": { + "value": ".", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 92, + "end": 95 + } + }, + "span": { + "type": "Span", + "start": 91, + "end": 96 + } + }, + { + "type": "TextElement", + "value": "Continued", + "span": { + "type": "Span", + "start": 96, + "end": 105 + } + } + ], + "span": { + "type": "Span", + "start": 81, + "end": 105 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 73, + "end": 105 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key06", + "span": { + "type": "Span", + "start": 107, + "end": 112 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".Value\n", + "span": { + "type": "Span", + "start": 115, + "end": 126 + } + }, + { + "type": "Placeable", + "expression": { + "value": ".", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 127, + "end": 130 + } + }, + "span": { + "type": "Span", + "start": 126, + "end": 131 + } + }, + { + "type": "TextElement", + "value": "Continued", + "span": { + "type": "Span", + "start": 131, + "end": 140 + } + } + ], + "span": { + "type": "Span", + "start": 115, + "end": 140 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 107, + "end": 140 + } + }, + { + "content": "ERROR (attr .Continued must have a value)", + "type": "Comment", + "span": { + "type": "Span", + "start": 142, + "end": 185 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 214, + "end": 214 + } + } + ], + "content": "key07 = Value\n .Continued\n\n", + "span": { + "type": "Span", + "start": 186, + "end": 216 + } + }, + { + "content": "ERROR (attr .Value must have a value)", + "type": "Comment", + "span": { + "type": "Span", + "start": 216, + "end": 255 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 274, + "end": 274 + } + } + ], + "content": "key08 =\n .Value\n\n", + "span": { + "type": "Span", + "start": 256, + "end": 276 + } + }, + { + "content": "ERROR (attr .Value must have a value)", + "type": "Comment", + "span": { + "type": "Span", + "start": 276, + "end": 315 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 334, + "end": 334 + } + } + ], + "content": "key09 =\n .Value\n Continued\n\n", + "span": { + "type": "Span", + "start": 316, + "end": 350 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key10", + "span": { + "type": "Span", + "start": 350, + "end": 355 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "Value", + "span": { + "type": "Span", + "start": 363, + "end": 368 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "which looks like an attribute\nContinued", + "span": { + "type": "Span", + "start": 371, + "end": 414 + } + } + ], + "span": { + "type": "Span", + "start": 371, + "end": 414 + } + }, + "span": { + "type": "Span", + "start": 362, + "end": 414 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 350, + "end": 414 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key11", + "span": { + "type": "Span", + "start": 416, + "end": 421 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": ".", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 429, + "end": 432 + } + }, + "span": { + "type": "Span", + "start": 428, + "end": 433 + } + }, + { + "type": "TextElement", + "value": "Value = which looks like an attribute\nContinued", + "span": { + "type": "Span", + "start": 433, + "end": 484 + } + } + ], + "span": { + "type": "Span", + "start": 424, + "end": 484 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 416, + "end": 484 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key12", + "span": { + "type": "Span", + "start": 486, + "end": 491 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "accesskey", + "span": { + "type": "Span", + "start": 499, + "end": 508 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "A", + "span": { + "type": "Span", + "start": 515, + "end": 516 + } + } + ], + "span": { + "type": "Span", + "start": 511, + "end": 516 + } + }, + "span": { + "type": "Span", + "start": 498, + "end": 516 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 486, + "end": 516 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key13", + "span": { + "type": "Span", + "start": 522, + "end": 527 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attribute", + "span": { + "type": "Span", + "start": 535, + "end": 544 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".Value", + "span": { + "type": "Span", + "start": 547, + "end": 553 + } + } + ], + "span": { + "type": "Span", + "start": 547, + "end": 553 + } + }, + "span": { + "type": "Span", + "start": 534, + "end": 553 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 522, + "end": 553 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key14", + "span": { + "type": "Span", + "start": 555, + "end": 560 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attribute", + "span": { + "type": "Span", + "start": 568, + "end": 577 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": ".", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 590, + "end": 593 + } + }, + "span": { + "type": "Span", + "start": 589, + "end": 594 + } + }, + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 594, + "end": 599 + } + } + ], + "span": { + "type": "Span", + "start": 580, + "end": 599 + } + }, + "span": { + "type": "Span", + "start": 567, + "end": 599 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 555, + "end": 599 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key15", + "span": { + "type": "Span", + "start": 601, + "end": 606 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 615, + "end": 616 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "one", + "span": { + "type": "Span", + "start": 629, + "end": 632 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".Value", + "span": { + "type": "Span", + "start": 634, + "end": 640 + } + } + ], + "span": { + "type": "Span", + "start": 634, + "end": 640 + } + }, + "default": false, + "span": { + "type": "Span", + "start": 628, + "end": 640 + } + }, + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "other", + "span": { + "type": "Span", + "start": 650, + "end": 655 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": ".", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 670, + "end": 673 + } + }, + "span": { + "type": "Span", + "start": 669, + "end": 674 + } + }, + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 674, + "end": 679 + } + } + ], + "span": { + "type": "Span", + "start": 657, + "end": 679 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 648, + "end": 679 + } + } + ], + "span": { + "type": "Span", + "start": 615, + "end": 684 + } + }, + "span": { + "type": "Span", + "start": 613, + "end": 685 + } + } + ], + "span": { + "type": "Span", + "start": 609, + "end": 685 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 601, + "end": 685 + } + }, + { + "content": "ERROR (variant must have a value)", + "type": "Comment", + "span": { + "type": "Span", + "start": 687, + "end": 722 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0012", + "arguments": [], + "message": "Expected value", + "span": { + "type": "Span", + "start": 755, + "end": 755 + } + } + ], + "content": "key16 =\n { 1 ->\n *[one]\n .Value\n }\n\n", + "span": { + "type": "Span", + "start": 723, + "end": 781 + } + }, + { + "content": "ERROR (unclosed placeable)", + "type": "Comment", + "span": { + "type": "Span", + "start": 781, + "end": 809 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 860, + "end": 860 + } + } + ], + "content": "key17 =\n { 1 ->\n *[one] Value\n .Continued\n }\n", + "span": { + "type": "Span", + "start": 810, + "end": 877 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 877 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines.ftl b/fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines.ftl new file mode 100644 index 0000000..a8b99eb --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines.ftl @@ -0,0 +1,3 @@ + + +key01 = Value diff --git a/fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines.json b/fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines.json new file mode 100644 index 0000000..904b973 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines.json @@ -0,0 +1,48 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01", + "span": { + "type": "Span", + "start": 2, + "end": 7 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 10, + "end": 15 + } + } + ], + "span": { + "type": "Span", + "start": 10, + "end": 15 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 2, + "end": 15 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 16 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines_with_ws.ftl b/fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines_with_ws.ftl new file mode 100644 index 0000000..8006426 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines_with_ws.ftl @@ -0,0 +1,5 @@ + + + + +key01 = Value diff --git a/fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines_with_ws.json b/fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines_with_ws.json new file mode 100644 index 0000000..3e14614 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines_with_ws.json @@ -0,0 +1,48 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01", + "span": { + "type": "Span", + "start": 10, + "end": 15 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 18, + "end": 23 + } + } + ], + "span": { + "type": "Span", + "start": 18, + "end": 23 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 10, + "end": 23 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 24 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/message_reference_as_selector.ftl b/fluent.syntax/src/test/resources/structure_fixtures/message_reference_as_selector.ftl new file mode 100644 index 0000000..6a0c6e1 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/message_reference_as_selector.ftl @@ -0,0 +1,4 @@ +err1 = + { foo -> + *[1] One + } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/message_reference_as_selector.json b/fluent.syntax/src/test/resources/structure_fixtures/message_reference_as_selector.json new file mode 100644 index 0000000..392c1a3 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/message_reference_as_selector.json @@ -0,0 +1,32 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0016", + "arguments": [], + "message": "Message references cannot be used as selectors", + "span": { + "type": "Span", + "start": 17, + "end": 17 + } + } + ], + "content": "err1 =\n { foo ->\n *[1] One\n }\n", + "span": { + "type": "Span", + "start": 0, + "end": 42 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 42 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_multiline_pattern.ftl b/fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_multiline_pattern.ftl new file mode 100644 index 0000000..dfd6f27 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_multiline_pattern.ftl @@ -0,0 +1,14 @@ +### BE CAREFUL WHEN EDITING THIS FILE +### +### The last character in this file is the space (" ") on line 14. +### We want to test a message with no value and with no EOL at the +### end of the fie. Most editors automatically add a trailing newline +### at EOF. If you edit this file make sure to turn this behavior off. + +key1 = + + + +key2 = + + \ No newline at end of file diff --git a/fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_multiline_pattern.json b/fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_multiline_pattern.json new file mode 100644 index 0000000..c9ed8ff --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_multiline_pattern.json @@ -0,0 +1,67 @@ +{ + "type": "Resource", + "body": [ + { + "content": "BE CAREFUL WHEN EDITING THIS FILE\n\nThe last character in this file is the space (\" \") on line 14.\nWe want to test a message with no value and with no EOL at the\nend of the fie. Most editors automatically add a trailing newline\nat EOF. If you edit this file make sure to turn this behavior off.", + "type": "ResourceComment", + "span": { + "type": "Span", + "start": 0, + "end": 316 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0005", + "arguments": [ + "key1" + ], + "message": "Expected message \"key1\" to have a value or attributes", + "span": { + "type": "Span", + "start": 324, + "end": 324 + } + } + ], + "content": "key1 =\n\n \n\n", + "span": { + "type": "Span", + "start": 318, + "end": 329 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0005", + "arguments": [ + "key2" + ], + "message": "Expected message \"key2\" to have a value or attributes", + "span": { + "type": "Span", + "start": 335, + "end": 335 + } + } + ], + "content": "key2 =\n\n ", + "span": { + "type": "Span", + "start": 329, + "end": 338 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 338 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_pattern.ftl b/fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_pattern.ftl new file mode 100644 index 0000000..3e236b6 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_pattern.ftl @@ -0,0 +1,18 @@ +### BE CAREFUL WHEN EDITING THIS FILE +### +### The last character in this file is the equals sign in `key5 =` in line 18. +### We want to test a message with no value and with no EOL after its +### identifier. Most editors automatically add a trailing newline at EOF. +### If you edit this file make sure to turn this behavior off. + +key1 = + +key2 = + +key3 = + .attr = Attr + +key4 = + .attr = Attr + +key5 = \ No newline at end of file diff --git a/fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_pattern.json b/fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_pattern.json new file mode 100644 index 0000000..43c95d0 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_pattern.json @@ -0,0 +1,205 @@ +{ + "type": "Resource", + "body": [ + { + "content": "BE CAREFUL WHEN EDITING THIS FILE\n\nThe last character in this file is the equals sign in `key5 =` in line 18.\nWe want to test a message with no value and with no EOL after its\nidentifier. Most editors automatically add a trailing newline at EOF.\nIf you edit this file make sure to turn this behavior off.", + "type": "ResourceComment", + "span": { + "type": "Span", + "start": 0, + "end": 327 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0005", + "arguments": [ + "key1" + ], + "message": "Expected message \"key1\" to have a value or attributes", + "span": { + "type": "Span", + "start": 335, + "end": 335 + } + } + ], + "content": "key1 =\n\n", + "span": { + "type": "Span", + "start": 329, + "end": 337 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0005", + "arguments": [ + "key2" + ], + "message": "Expected message \"key2\" to have a value or attributes", + "span": { + "type": "Span", + "start": 343, + "end": 343 + } + } + ], + "content": "key2 = \n\n", + "span": { + "type": "Span", + "start": 337, + "end": 346 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key3", + "span": { + "type": "Span", + "start": 346, + "end": 350 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 358, + "end": 362 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attr", + "span": { + "type": "Span", + "start": 365, + "end": 369 + } + } + ], + "span": { + "type": "Span", + "start": 365, + "end": 369 + } + }, + "span": { + "type": "Span", + "start": 357, + "end": 369 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 346, + "end": 369 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key4", + "span": { + "type": "Span", + "start": 371, + "end": 375 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 384, + "end": 388 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attr", + "span": { + "type": "Span", + "start": 391, + "end": 395 + } + } + ], + "span": { + "type": "Span", + "start": 391, + "end": 395 + } + }, + "span": { + "type": "Span", + "start": 383, + "end": 395 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 371, + "end": 395 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0005", + "arguments": [ + "key5" + ], + "message": "Expected message \"key5\" to have a value or attributes", + "span": { + "type": "Span", + "start": 403, + "end": 403 + } + } + ], + "content": "key5 =", + "span": { + "type": "Span", + "start": 397, + "end": 403 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 403 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline-comment.ftl b/fluent.syntax/src/test/resources/structure_fixtures/multiline-comment.ftl new file mode 100644 index 0000000..2fbc819 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/multiline-comment.ftl @@ -0,0 +1,6 @@ + +# This is +# +# An example of a multiline comment + +key = Value diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline-comment.json b/fluent.syntax/src/test/resources/structure_fixtures/multiline-comment.json new file mode 100644 index 0000000..65b9469 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/multiline-comment.json @@ -0,0 +1,57 @@ +{ + "type": "Resource", + "body": [ + { + "content": "This is\n\nAn example of a multiline comment", + "type": "Comment", + "span": { + "type": "Span", + "start": 1, + "end": 48 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 50, + "end": 53 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 56, + "end": 61 + } + } + ], + "span": { + "type": "Span", + "start": 56, + "end": 61 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 50, + "end": 61 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 62 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline_pattern.ftl b/fluent.syntax/src/test/resources/structure_fixtures/multiline_pattern.ftl new file mode 100644 index 0000000..41611a0 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/multiline_pattern.ftl @@ -0,0 +1,20 @@ +key01 = Value + Continued here. + +key02 = + Value + Continued here. + +# ERROR "Continued" looks like a new message. +# key03 parses fine with just "Value". +key03 = + Value +Continued here + and here. + +# ERROR "Continued" and "and" look like new messages +# key04 parses fine with just "Value". +key04 = + Value +Continued here +and even here. diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline_pattern.json b/fluent.syntax/src/test/resources/structure_fixtures/multiline_pattern.json new file mode 100644 index 0000000..9999da2 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/multiline_pattern.json @@ -0,0 +1,250 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01", + "span": { + "type": "Span", + "start": 0, + "end": 5 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value\nContinued here.", + "span": { + "type": "Span", + "start": 8, + "end": 33 + } + } + ], + "span": { + "type": "Span", + "start": 8, + "end": 33 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 33 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key02", + "span": { + "type": "Span", + "start": 35, + "end": 40 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value\nContinued here.", + "span": { + "type": "Span", + "start": 47, + "end": 72 + } + } + ], + "span": { + "type": "Span", + "start": 43, + "end": 72 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 35, + "end": 72 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key03", + "span": { + "type": "Span", + "start": 159, + "end": 164 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 171, + "end": 176 + } + } + ], + "span": { + "type": "Span", + "start": 167, + "end": 176 + } + }, + "attributes": [], + "comment": { + "content": "ERROR \"Continued\" looks like a new message.\nkey03 parses fine with just \"Value\".", + "type": "Comment", + "span": { + "type": "Span", + "start": 74, + "end": 158 + } + }, + "span": { + "type": "Span", + "start": 74, + "end": 176 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 187, + "end": 187 + } + } + ], + "content": "Continued here\n and here.\n\n", + "span": { + "type": "Span", + "start": 177, + "end": 207 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key04", + "span": { + "type": "Span", + "start": 299, + "end": 304 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 311, + "end": 316 + } + } + ], + "span": { + "type": "Span", + "start": 307, + "end": 316 + } + }, + "attributes": [], + "comment": { + "content": "ERROR \"Continued\" and \"and\" look like new messages\nkey04 parses fine with just \"Value\".", + "type": "Comment", + "span": { + "type": "Span", + "start": 207, + "end": 298 + } + }, + "span": { + "type": "Span", + "start": 207, + "end": 316 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 327, + "end": 327 + } + } + ], + "content": "Continued here\n", + "span": { + "type": "Span", + "start": 317, + "end": 332 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 336, + "end": 336 + } + } + ], + "content": "and even here.\n", + "span": { + "type": "Span", + "start": 332, + "end": 347 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 347 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline_string.ftl b/fluent.syntax/src/test/resources/structure_fixtures/multiline_string.ftl new file mode 100644 index 0000000..ee5359a --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/multiline_string.ftl @@ -0,0 +1,3 @@ +err01 = { BUILTIN(key: " + text + ") } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline_string.json b/fluent.syntax/src/test/resources/structure_fixtures/multiline_string.json new file mode 100644 index 0000000..431307d --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/multiline_string.json @@ -0,0 +1,32 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0020", + "arguments": [], + "message": "Unterminated string expression", + "span": { + "type": "Span", + "start": 24, + "end": 24 + } + } + ], + "content": "err01 = { BUILTIN(key: \"\n text\n \") }\n", + "span": { + "type": "Span", + "start": 0, + "end": 40 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 40 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline_with_non_empty_first_line.ftl b/fluent.syntax/src/test/resources/structure_fixtures/multiline_with_non_empty_first_line.ftl new file mode 100644 index 0000000..e39b65d --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/multiline_with_non_empty_first_line.ftl @@ -0,0 +1,2 @@ +key = Value + Value 2 diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline_with_non_empty_first_line.json b/fluent.syntax/src/test/resources/structure_fixtures/multiline_with_non_empty_first_line.json new file mode 100644 index 0000000..5327545 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/multiline_with_non_empty_first_line.json @@ -0,0 +1,48 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 0, + "end": 3 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value\nValue 2", + "span": { + "type": "Span", + "start": 6, + "end": 23 + } + } + ], + "span": { + "type": "Span", + "start": 6, + "end": 23 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 23 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 24 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline_with_placeables.ftl b/fluent.syntax/src/test/resources/structure_fixtures/multiline_with_placeables.ftl new file mode 100644 index 0000000..2606ff1 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/multiline_with_placeables.ftl @@ -0,0 +1,3 @@ +key = + Foo { bar } + Baz diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline_with_placeables.json b/fluent.syntax/src/test/resources/structure_fixtures/multiline_with_placeables.json new file mode 100644 index 0000000..8268bd5 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/multiline_with_placeables.json @@ -0,0 +1,83 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 0, + "end": 3 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Foo ", + "span": { + "type": "Span", + "start": 10, + "end": 14 + } + }, + { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "bar", + "span": { + "type": "Span", + "start": 16, + "end": 19 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 16, + "end": 19 + } + }, + "span": { + "type": "Span", + "start": 14, + "end": 21 + } + }, + { + "type": "TextElement", + "value": "\nBaz", + "span": { + "type": "Span", + "start": 21, + "end": 29 + } + } + ], + "span": { + "type": "Span", + "start": 6, + "end": 29 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 29 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 30 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/non_id_attribute_name.ftl b/fluent.syntax/src/test/resources/structure_fixtures/non_id_attribute_name.ftl new file mode 100644 index 0000000..91ddabe --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/non_id_attribute_name.ftl @@ -0,0 +1,2 @@ +err01 = Value + .2 = Foo diff --git a/fluent.syntax/src/test/resources/structure_fixtures/non_id_attribute_name.json b/fluent.syntax/src/test/resources/structure_fixtures/non_id_attribute_name.json new file mode 100644 index 0000000..90c1044 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/non_id_attribute_name.json @@ -0,0 +1,34 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 19, + "end": 19 + } + } + ], + "content": "err01 = Value\n .2 = Foo\n", + "span": { + "type": "Span", + "start": 0, + "end": 27 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 27 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/placeable_at_eol.ftl b/fluent.syntax/src/test/resources/structure_fixtures/placeable_at_eol.ftl new file mode 100644 index 0000000..0046013 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/placeable_at_eol.ftl @@ -0,0 +1,9 @@ +key1 = + A multiline message with a { placeable } + at the end of line. The message should + consist of three lines of text. + +key2 = + A multiline message with a { placeable } + +key3 = A singleline message with a { placeable } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/placeable_at_eol.json b/fluent.syntax/src/test/resources/structure_fixtures/placeable_at_eol.json new file mode 100644 index 0000000..523a2da --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/placeable_at_eol.json @@ -0,0 +1,211 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key1", + "span": { + "type": "Span", + "start": 0, + "end": 4 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "A multiline message with a ", + "span": { + "type": "Span", + "start": 11, + "end": 38 + } + }, + { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "placeable", + "span": { + "type": "Span", + "start": 40, + "end": 49 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 40, + "end": 49 + } + }, + "span": { + "type": "Span", + "start": 38, + "end": 51 + } + }, + { + "type": "TextElement", + "value": "\nat the end of line. The message should\nconsist of three lines of text.", + "span": { + "type": "Span", + "start": 51, + "end": 131 + } + } + ], + "span": { + "type": "Span", + "start": 7, + "end": 131 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 131 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key2", + "span": { + "type": "Span", + "start": 133, + "end": 137 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "A multiline message with a ", + "span": { + "type": "Span", + "start": 144, + "end": 171 + } + }, + { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "placeable", + "span": { + "type": "Span", + "start": 173, + "end": 182 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 173, + "end": 182 + } + }, + "span": { + "type": "Span", + "start": 171, + "end": 184 + } + } + ], + "span": { + "type": "Span", + "start": 140, + "end": 184 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 133, + "end": 184 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key3", + "span": { + "type": "Span", + "start": 186, + "end": 190 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "A singleline message with a ", + "span": { + "type": "Span", + "start": 193, + "end": 221 + } + }, + { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "placeable", + "span": { + "type": "Span", + "start": 223, + "end": 232 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 223, + "end": 232 + } + }, + "span": { + "type": "Span", + "start": 221, + "end": 234 + } + } + ], + "span": { + "type": "Span", + "start": 193, + "end": 234 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 186, + "end": 234 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 235 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/placeable_at_line_extremes.ftl b/fluent.syntax/src/test/resources/structure_fixtures/placeable_at_line_extremes.ftl new file mode 100644 index 0000000..64389db --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/placeable_at_line_extremes.ftl @@ -0,0 +1,8 @@ +key1 = + { foo } + +key2 = + Foo { foo } + +key3 = + { foo } Foo diff --git a/fluent.syntax/src/test/resources/structure_fixtures/placeable_at_line_extremes.json b/fluent.syntax/src/test/resources/structure_fixtures/placeable_at_line_extremes.json new file mode 100644 index 0000000..35a30a3 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/placeable_at_line_extremes.json @@ -0,0 +1,193 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key1", + "span": { + "type": "Span", + "start": 0, + "end": 4 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "foo", + "span": { + "type": "Span", + "start": 13, + "end": 16 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 13, + "end": 16 + } + }, + "span": { + "type": "Span", + "start": 11, + "end": 18 + } + } + ], + "span": { + "type": "Span", + "start": 7, + "end": 18 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 18 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key2", + "span": { + "type": "Span", + "start": 20, + "end": 24 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Foo ", + "span": { + "type": "Span", + "start": 31, + "end": 35 + } + }, + { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "foo", + "span": { + "type": "Span", + "start": 37, + "end": 40 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 37, + "end": 40 + } + }, + "span": { + "type": "Span", + "start": 35, + "end": 42 + } + } + ], + "span": { + "type": "Span", + "start": 27, + "end": 42 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 20, + "end": 42 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key3", + "span": { + "type": "Span", + "start": 44, + "end": 48 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "foo", + "span": { + "type": "Span", + "start": 57, + "end": 60 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 57, + "end": 60 + } + }, + "span": { + "type": "Span", + "start": 55, + "end": 62 + } + }, + { + "type": "TextElement", + "value": " Foo", + "span": { + "type": "Span", + "start": 62, + "end": 66 + } + } + ], + "span": { + "type": "Span", + "start": 51, + "end": 66 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 44, + "end": 66 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 67 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/placeable_in_placeable.ftl b/fluent.syntax/src/test/resources/structure_fixtures/placeable_in_placeable.ftl new file mode 100644 index 0000000..8a03558 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/placeable_in_placeable.ftl @@ -0,0 +1,12 @@ +key1 = {{ foo }} + +key2 = { { foo } } + +key3 = + { + { foo } + } + +err1 = { { foo } + +err2 = { foo } } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/placeable_in_placeable.json b/fluent.syntax/src/test/resources/structure_fixtures/placeable_in_placeable.json new file mode 100644 index 0000000..97ff7b7 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/placeable_in_placeable.json @@ -0,0 +1,245 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key1", + "span": { + "type": "Span", + "start": 0, + "end": 4 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "foo", + "span": { + "type": "Span", + "start": 10, + "end": 13 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 10, + "end": 13 + } + }, + "span": { + "type": "Span", + "start": 8, + "end": 15 + } + }, + "span": { + "type": "Span", + "start": 7, + "end": 16 + } + } + ], + "span": { + "type": "Span", + "start": 7, + "end": 16 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 16 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key2", + "span": { + "type": "Span", + "start": 18, + "end": 22 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "foo", + "span": { + "type": "Span", + "start": 30, + "end": 33 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 30, + "end": 33 + } + }, + "span": { + "type": "Span", + "start": 28, + "end": 35 + } + }, + "span": { + "type": "Span", + "start": 25, + "end": 38 + } + } + ], + "span": { + "type": "Span", + "start": 25, + "end": 38 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 18, + "end": 38 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key3", + "span": { + "type": "Span", + "start": 40, + "end": 44 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "foo", + "span": { + "type": "Span", + "start": 59, + "end": 62 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 59, + "end": 62 + } + }, + "span": { + "type": "Span", + "start": 57, + "end": 64 + } + }, + "span": { + "type": "Span", + "start": 50, + "end": 69 + } + } + ], + "span": { + "type": "Span", + "start": 47, + "end": 69 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 40, + "end": 69 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 90, + "end": 90 + } + } + ], + "content": "err1 = { { foo }\n\n", + "span": { + "type": "Span", + "start": 71, + "end": 90 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0027", + "arguments": [], + "message": "Unbalanced closing brace in TextElement.", + "span": { + "type": "Span", + "start": 105, + "end": 105 + } + } + ], + "content": "err2 = { foo } }\n", + "span": { + "type": "Span", + "start": 90, + "end": 107 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 107 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/placeable_without_close_bracket.ftl b/fluent.syntax/src/test/resources/structure_fixtures/placeable_without_close_bracket.ftl new file mode 100644 index 0000000..8674d20 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/placeable_without_close_bracket.ftl @@ -0,0 +1 @@ +err01 = { $num diff --git a/fluent.syntax/src/test/resources/structure_fixtures/placeable_without_close_bracket.json b/fluent.syntax/src/test/resources/structure_fixtures/placeable_without_close_bracket.json new file mode 100644 index 0000000..7e9d6da --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/placeable_without_close_bracket.json @@ -0,0 +1,34 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 15, + "end": 15 + } + } + ], + "content": "err01 = { $num\n", + "span": { + "type": "Span", + "start": 0, + "end": 15 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 15 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/resource_comment.ftl b/fluent.syntax/src/test/resources/structure_fixtures/resource_comment.ftl new file mode 100644 index 0000000..aa103e6 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/resource_comment.ftl @@ -0,0 +1,2 @@ +### This is a resource wide comment +### It's multiline diff --git a/fluent.syntax/src/test/resources/structure_fixtures/resource_comment.json b/fluent.syntax/src/test/resources/structure_fixtures/resource_comment.json new file mode 100644 index 0000000..44225a6 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/resource_comment.json @@ -0,0 +1,19 @@ +{ + "type": "Resource", + "body": [ + { + "content": "This is a resource wide comment\nIt's multiline", + "type": "ResourceComment", + "span": { + "type": "Span", + "start": 0, + "end": 54 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 55 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/resource_comment_trailing_line.ftl b/fluent.syntax/src/test/resources/structure_fixtures/resource_comment_trailing_line.ftl new file mode 100644 index 0000000..c392d21 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/resource_comment_trailing_line.ftl @@ -0,0 +1,3 @@ +### This is a comment +### This comment is multiline +### diff --git a/fluent.syntax/src/test/resources/structure_fixtures/resource_comment_trailing_line.json b/fluent.syntax/src/test/resources/structure_fixtures/resource_comment_trailing_line.json new file mode 100644 index 0000000..6378aa7 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/resource_comment_trailing_line.json @@ -0,0 +1,19 @@ +{ + "type": "Resource", + "body": [ + { + "content": "This is a comment\nThis comment is multiline\n", + "type": "ResourceComment", + "span": { + "type": "Span", + "start": 0, + "end": 55 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 56 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/second_attribute_starts_from_nl.ftl b/fluent.syntax/src/test/resources/structure_fixtures/second_attribute_starts_from_nl.ftl new file mode 100644 index 0000000..9a0d7f1 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/second_attribute_starts_from_nl.ftl @@ -0,0 +1,3 @@ +key = Value + .label = Value +.accesskey = K diff --git a/fluent.syntax/src/test/resources/structure_fixtures/second_attribute_starts_from_nl.json b/fluent.syntax/src/test/resources/structure_fixtures/second_attribute_starts_from_nl.json new file mode 100644 index 0000000..9331bf1 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/second_attribute_starts_from_nl.json @@ -0,0 +1,121 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 0, + "end": 3 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 6, + "end": 11 + } + } + ], + "span": { + "type": "Span", + "start": 6, + "end": 11 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "label", + "span": { + "type": "Span", + "start": 17, + "end": 22 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 25, + "end": 30 + } + } + ], + "span": { + "type": "Span", + "start": 25, + "end": 30 + } + }, + "span": { + "type": "Span", + "start": 16, + "end": 30 + } + }, + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "accesskey", + "span": { + "type": "Span", + "start": 32, + "end": 41 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "K", + "span": { + "type": "Span", + "start": 44, + "end": 45 + } + } + ], + "span": { + "type": "Span", + "start": 44, + "end": 45 + } + }, + "span": { + "type": "Span", + "start": 31, + "end": 45 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 45 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 46 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/select_expression_with_two_selectors.ftl b/fluent.syntax/src/test/resources/structure_fixtures/select_expression_with_two_selectors.ftl new file mode 100644 index 0000000..cb12732 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/select_expression_with_two_selectors.ftl @@ -0,0 +1 @@ +err01 = { $foo $faa } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/select_expression_with_two_selectors.json b/fluent.syntax/src/test/resources/structure_fixtures/select_expression_with_two_selectors.json new file mode 100644 index 0000000..3967220 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/select_expression_with_two_selectors.json @@ -0,0 +1,34 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 15, + "end": 15 + } + } + ], + "content": "err01 = { $foo $faa }\n", + "span": { + "type": "Span", + "start": 0, + "end": 22 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 22 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_arrow.ftl b/fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_arrow.ftl new file mode 100644 index 0000000..bb5a84b --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_arrow.ftl @@ -0,0 +1 @@ +err01 = { $foo - } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_arrow.json b/fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_arrow.json new file mode 100644 index 0000000..0182c80 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_arrow.json @@ -0,0 +1,34 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 15, + "end": 15 + } + } + ], + "content": "err01 = { $foo - }\n", + "span": { + "type": "Span", + "start": 0, + "end": 19 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 19 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_variants.ftl b/fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_variants.ftl new file mode 100644 index 0000000..4f263e4 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_variants.ftl @@ -0,0 +1,6 @@ +err01 = { $foo -> } + +err02 = { $foo -> + } + +err03 = { $foo -> diff --git a/fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_variants.json b/fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_variants.json new file mode 100644 index 0000000..0be7010 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_variants.json @@ -0,0 +1,78 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "␤" + ], + "message": "Expected token: \"␤\"", + "span": { + "type": "Span", + "start": 18, + "end": 18 + } + } + ], + "content": "err01 = { $foo -> }\n\n", + "span": { + "type": "Span", + "start": 0, + "end": 21 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0011", + "arguments": [], + "message": "Expected at least one variant after \"->\"", + "span": { + "type": "Span", + "start": 43, + "end": 43 + } + } + ], + "content": "err02 = { $foo ->\n }\n\n", + "span": { + "type": "Span", + "start": 21, + "end": 46 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0011", + "arguments": [], + "message": "Expected at least one variant after \"->\"", + "span": { + "type": "Span", + "start": 64, + "end": 64 + } + } + ], + "content": "err03 = { $foo ->\n", + "span": { + "type": "Span", + "start": 46, + "end": 64 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 64 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/select_expressions.ftl b/fluent.syntax/src/test/resources/structure_fixtures/select_expressions.ftl new file mode 100644 index 0000000..5a96bf9 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/select_expressions.ftl @@ -0,0 +1,9 @@ +# ERROR No blanks are allowed between * and [. +err01 = { $sel -> + * [key] Value +} + +# ERROR Missing default variant. +err02 = { $sel -> + [key] Value +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/select_expressions.json b/fluent.syntax/src/test/resources/structure_fixtures/select_expressions.json new file mode 100644 index 0000000..523eba2 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/select_expressions.json @@ -0,0 +1,72 @@ +{ + "type": "Resource", + "body": [ + { + "content": "ERROR No blanks are allowed between * and [.", + "type": "Comment", + "span": { + "type": "Span", + "start": 0, + "end": 46 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0011", + "arguments": [], + "message": "Expected at least one variant after \"->\"", + "span": { + "type": "Span", + "start": 69, + "end": 69 + } + } + ], + "content": "err01 = { $sel ->\n * [key] Value\n}\n\n", + "span": { + "type": "Span", + "start": 47, + "end": 87 + } + }, + { + "content": "ERROR Missing default variant.", + "type": "Comment", + "span": { + "type": "Span", + "start": 87, + "end": 119 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0010", + "arguments": [], + "message": "Expected one of the variants to be marked as default (*)", + "span": { + "type": "Span", + "start": 154, + "end": 154 + } + } + ], + "content": "err02 = { $sel ->\n [key] Value\n}\n", + "span": { + "type": "Span", + "start": 120, + "end": 156 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 156 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/simple_message.ftl b/fluent.syntax/src/test/resources/structure_fixtures/simple_message.ftl new file mode 100644 index 0000000..3036ce1 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/simple_message.ftl @@ -0,0 +1 @@ +foo = Foo diff --git a/fluent.syntax/src/test/resources/structure_fixtures/simple_message.json b/fluent.syntax/src/test/resources/structure_fixtures/simple_message.json new file mode 100644 index 0000000..0774ae5 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/simple_message.json @@ -0,0 +1,48 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "foo", + "span": { + "type": "Span", + "start": 0, + "end": 3 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Foo", + "span": { + "type": "Span", + "start": 6, + "end": 9 + } + } + ], + "span": { + "type": "Span", + "start": 6, + "end": 9 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 9 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 10 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/single_char_id.ftl b/fluent.syntax/src/test/resources/structure_fixtures/single_char_id.ftl new file mode 100644 index 0000000..b4d3b47 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/single_char_id.ftl @@ -0,0 +1,2 @@ +k = Value + .l = Foo diff --git a/fluent.syntax/src/test/resources/structure_fixtures/single_char_id.json b/fluent.syntax/src/test/resources/structure_fixtures/single_char_id.json new file mode 100644 index 0000000..1f77c70 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/single_char_id.json @@ -0,0 +1,85 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "k", + "span": { + "type": "Span", + "start": 0, + "end": 1 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 4, + "end": 9 + } + } + ], + "span": { + "type": "Span", + "start": 4, + "end": 9 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "l", + "span": { + "type": "Span", + "start": 15, + "end": 16 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Foo", + "span": { + "type": "Span", + "start": 19, + "end": 22 + } + } + ], + "span": { + "type": "Span", + "start": 19, + "end": 22 + } + }, + "span": { + "type": "Span", + "start": 14, + "end": 22 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 22 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 23 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/sparse-messages.ftl b/fluent.syntax/src/test/resources/structure_fixtures/sparse-messages.ftl new file mode 100644 index 0000000..efc46e6 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/sparse-messages.ftl @@ -0,0 +1,45 @@ +key = + + + Value + +key2 = + + + .attr = Attribute + + +key3 = + Value + Value2 + + + Value 4 + Value3 + + + + .attr2 = Attr 2 + + +key5 = Value 5 + +key6 = { $sel -> + + [one] One + + *[two] Two + + } + +key8 = + + { + + $sel + + -> + + *[one] One + + } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/sparse-messages.json b/fluent.syntax/src/test/resources/structure_fixtures/sparse-messages.json new file mode 100644 index 0000000..d6f61a1 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/sparse-messages.json @@ -0,0 +1,457 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 0, + "end": 3 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 12, + "end": 17 + } + } + ], + "span": { + "type": "Span", + "start": 8, + "end": 17 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 17 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key2", + "span": { + "type": "Span", + "start": 19, + "end": 23 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 33, + "end": 37 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attribute", + "span": { + "type": "Span", + "start": 40, + "end": 49 + } + } + ], + "span": { + "type": "Span", + "start": 40, + "end": 49 + } + }, + "span": { + "type": "Span", + "start": 32, + "end": 49 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 19, + "end": 49 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key3", + "span": { + "type": "Span", + "start": 52, + "end": 56 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value\nValue2\n\n\nValue 4\nValue3", + "span": { + "type": "Span", + "start": 63, + "end": 104 + } + } + ], + "span": { + "type": "Span", + "start": 59, + "end": 104 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr2", + "span": { + "type": "Span", + "start": 113, + "end": 118 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attr 2", + "span": { + "type": "Span", + "start": 121, + "end": 127 + } + } + ], + "span": { + "type": "Span", + "start": 121, + "end": 127 + } + }, + "span": { + "type": "Span", + "start": 112, + "end": 127 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 52, + "end": 127 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key5", + "span": { + "type": "Span", + "start": 130, + "end": 134 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 5", + "span": { + "type": "Span", + "start": 137, + "end": 144 + } + } + ], + "span": { + "type": "Span", + "start": 137, + "end": 144 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 130, + "end": 144 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key6", + "span": { + "type": "Span", + "start": 146, + "end": 150 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "sel", + "span": { + "type": "Span", + "start": 156, + "end": 159 + } + }, + "span": { + "type": "Span", + "start": 155, + "end": 159 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "one", + "span": { + "type": "Span", + "start": 174, + "end": 177 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "One", + "span": { + "type": "Span", + "start": 179, + "end": 182 + } + } + ], + "span": { + "type": "Span", + "start": 179, + "end": 182 + } + }, + "default": false, + "span": { + "type": "Span", + "start": 173, + "end": 182 + } + }, + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "two", + "span": { + "type": "Span", + "start": 194, + "end": 197 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Two", + "span": { + "type": "Span", + "start": 199, + "end": 202 + } + } + ], + "span": { + "type": "Span", + "start": 199, + "end": 202 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 192, + "end": 202 + } + } + ], + "span": { + "type": "Span", + "start": 155, + "end": 208 + } + }, + "span": { + "type": "Span", + "start": 153, + "end": 209 + } + } + ], + "span": { + "type": "Span", + "start": 153, + "end": 209 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 146, + "end": 209 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key8", + "span": { + "type": "Span", + "start": 211, + "end": 215 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "sel", + "span": { + "type": "Span", + "start": 231, + "end": 234 + } + }, + "span": { + "type": "Span", + "start": 230, + "end": 234 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "one", + "span": { + "type": "Span", + "start": 257, + "end": 260 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "One", + "span": { + "type": "Span", + "start": 262, + "end": 265 + } + } + ], + "span": { + "type": "Span", + "start": 262, + "end": 265 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 255, + "end": 265 + } + } + ], + "span": { + "type": "Span", + "start": 230, + "end": 271 + } + }, + "span": { + "type": "Span", + "start": 221, + "end": 272 + } + } + ], + "span": { + "type": "Span", + "start": 219, + "end": 272 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 211, + "end": 272 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 273 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/standalone_comment.ftl b/fluent.syntax/src/test/resources/structure_fixtures/standalone_comment.ftl new file mode 100644 index 0000000..91ff612 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/standalone_comment.ftl @@ -0,0 +1,3 @@ +foo = Value + +# This is a standalone comment diff --git a/fluent.syntax/src/test/resources/structure_fixtures/standalone_comment.json b/fluent.syntax/src/test/resources/structure_fixtures/standalone_comment.json new file mode 100644 index 0000000..8f146b7 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/standalone_comment.json @@ -0,0 +1,57 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "foo", + "span": { + "type": "Span", + "start": 0, + "end": 3 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 6, + "end": 11 + } + } + ], + "span": { + "type": "Span", + "start": 6, + "end": 11 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 11 + } + }, + { + "content": "This is a standalone comment", + "type": "Comment", + "span": { + "type": "Span", + "start": 13, + "end": 43 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 44 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/standalone_identifier.ftl b/fluent.syntax/src/test/resources/structure_fixtures/standalone_identifier.ftl new file mode 100644 index 0000000..859c579 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/standalone_identifier.ftl @@ -0,0 +1,2 @@ +foo +# ~ERROR E0003, pos 3, args "=" diff --git a/fluent.syntax/src/test/resources/structure_fixtures/standalone_identifier.json b/fluent.syntax/src/test/resources/structure_fixtures/standalone_identifier.json new file mode 100644 index 0000000..aa826cc --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/standalone_identifier.json @@ -0,0 +1,43 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 3, + "end": 3 + } + } + ], + "content": "foo\n", + "span": { + "type": "Span", + "start": 0, + "end": 4 + } + }, + { + "content": "~ERROR E0003, pos 3, args \"=\"", + "type": "Comment", + "span": { + "type": "Span", + "start": 4, + "end": 35 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 36 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/term.ftl b/fluent.syntax/src/test/resources/structure_fixtures/term.ftl new file mode 100644 index 0000000..ec82dde --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/term.ftl @@ -0,0 +1,24 @@ +-term = + { $case -> + *[uppercase] Term + [lowercase] term + } + .attr = a + +key01 = {-term} +key02 = {-term()} +key03 = {-term(case: "uppercase")} + + +key04 = + { -term.attr -> + [a] { -term } A + [b] { -term() } B + *[x] X + } + +-err1 = +-err2 = + .attr = Attribute +--err3 = Error +err4 = { --err4 } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/term.json b/fluent.syntax/src/test/resources/structure_fixtures/term.json new file mode 100644 index 0000000..b5b17d6 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/term.json @@ -0,0 +1,742 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 1, + "end": 5 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "case", + "span": { + "type": "Span", + "start": 15, + "end": 19 + } + }, + "span": { + "type": "Span", + "start": 14, + "end": 19 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "uppercase", + "span": { + "type": "Span", + "start": 32, + "end": 41 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Term", + "span": { + "type": "Span", + "start": 43, + "end": 47 + } + } + ], + "span": { + "type": "Span", + "start": 43, + "end": 47 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 30, + "end": 47 + } + }, + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "lowercase", + "span": { + "type": "Span", + "start": 57, + "end": 66 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "term", + "span": { + "type": "Span", + "start": 68, + "end": 72 + } + } + ], + "span": { + "type": "Span", + "start": 68, + "end": 72 + } + }, + "default": false, + "span": { + "type": "Span", + "start": 56, + "end": 72 + } + } + ], + "span": { + "type": "Span", + "start": 14, + "end": 77 + } + }, + "span": { + "type": "Span", + "start": 12, + "end": 78 + } + } + ], + "span": { + "type": "Span", + "start": 8, + "end": 78 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 84, + "end": 88 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "a", + "span": { + "type": "Span", + "start": 91, + "end": 92 + } + } + ], + "span": { + "type": "Span", + "start": 91, + "end": 92 + } + }, + "span": { + "type": "Span", + "start": 83, + "end": 92 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 92 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01", + "span": { + "type": "Span", + "start": 94, + "end": 99 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "TermReference", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 104, + "end": 108 + } + }, + "attribute": null, + "arguments": null, + "span": { + "type": "Span", + "start": 103, + "end": 108 + } + }, + "span": { + "type": "Span", + "start": 102, + "end": 109 + } + } + ], + "span": { + "type": "Span", + "start": 102, + "end": 109 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 94, + "end": 109 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key02", + "span": { + "type": "Span", + "start": 110, + "end": 115 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "TermReference", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 120, + "end": 124 + } + }, + "attribute": null, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [], + "span": { + "type": "Span", + "start": 124, + "end": 126 + } + }, + "span": { + "type": "Span", + "start": 119, + "end": 126 + } + }, + "span": { + "type": "Span", + "start": 118, + "end": 127 + } + } + ], + "span": { + "type": "Span", + "start": 118, + "end": 127 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 110, + "end": 127 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key03", + "span": { + "type": "Span", + "start": 128, + "end": 133 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "TermReference", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 138, + "end": 142 + } + }, + "attribute": null, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [ + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "case", + "span": { + "type": "Span", + "start": 143, + "end": 147 + } + }, + "value": { + "value": "uppercase", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 149, + "end": 160 + } + }, + "span": { + "type": "Span", + "start": 143, + "end": 160 + } + } + ], + "span": { + "type": "Span", + "start": 142, + "end": 161 + } + }, + "span": { + "type": "Span", + "start": 137, + "end": 161 + } + }, + "span": { + "type": "Span", + "start": 136, + "end": 162 + } + } + ], + "span": { + "type": "Span", + "start": 136, + "end": 162 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 128, + "end": 162 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key04", + "span": { + "type": "Span", + "start": 165, + "end": 170 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "TermReference", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 180, + "end": 184 + } + }, + "attribute": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 185, + "end": 189 + } + }, + "arguments": null, + "span": { + "type": "Span", + "start": 179, + "end": 189 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "a", + "span": { + "type": "Span", + "start": 202, + "end": 203 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "TermReference", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 208, + "end": 212 + } + }, + "attribute": null, + "arguments": null, + "span": { + "type": "Span", + "start": 207, + "end": 212 + } + }, + "span": { + "type": "Span", + "start": 205, + "end": 214 + } + }, + { + "type": "TextElement", + "value": " A", + "span": { + "type": "Span", + "start": 214, + "end": 216 + } + } + ], + "span": { + "type": "Span", + "start": 205, + "end": 216 + } + }, + "default": false, + "span": { + "type": "Span", + "start": 201, + "end": 216 + } + }, + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "b", + "span": { + "type": "Span", + "start": 226, + "end": 227 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "TermReference", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 232, + "end": 236 + } + }, + "attribute": null, + "arguments": { + "type": "CallArguments", + "positional": [], + "named": [], + "span": { + "type": "Span", + "start": 236, + "end": 238 + } + }, + "span": { + "type": "Span", + "start": 231, + "end": 238 + } + }, + "span": { + "type": "Span", + "start": 229, + "end": 240 + } + }, + { + "type": "TextElement", + "value": " B", + "span": { + "type": "Span", + "start": 240, + "end": 242 + } + } + ], + "span": { + "type": "Span", + "start": 229, + "end": 242 + } + }, + "default": false, + "span": { + "type": "Span", + "start": 225, + "end": 242 + } + }, + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "x", + "span": { + "type": "Span", + "start": 252, + "end": 253 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "X", + "span": { + "type": "Span", + "start": 255, + "end": 256 + } + } + ], + "span": { + "type": "Span", + "start": 255, + "end": 256 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 250, + "end": 256 + } + } + ], + "span": { + "type": "Span", + "start": 179, + "end": 261 + } + }, + "span": { + "type": "Span", + "start": 177, + "end": 262 + } + } + ], + "span": { + "type": "Span", + "start": 173, + "end": 262 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 165, + "end": 262 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0006", + "arguments": [ + "err1" + ], + "message": "Expected term \"-err1\" to have a value", + "span": { + "type": "Span", + "start": 271, + "end": 271 + } + } + ], + "content": "-err1 =\n", + "span": { + "type": "Span", + "start": 264, + "end": 272 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0006", + "arguments": [ + "err2" + ], + "message": "Expected term \"-err2\" to have a value", + "span": { + "type": "Span", + "start": 279, + "end": 279 + } + } + ], + "content": "-err2 =\n .attr = Attribute\n", + "span": { + "type": "Span", + "start": 272, + "end": 302 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 303, + "end": 303 + } + } + ], + "content": "--err3 = Error\n", + "span": { + "type": "Span", + "start": 302, + "end": 317 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 327, + "end": 327 + } + } + ], + "content": "err4 = { --err4 }\n", + "span": { + "type": "Span", + "start": 317, + "end": 335 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 335 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/term_with_empty_pattern.ftl b/fluent.syntax/src/test/resources/structure_fixtures/term_with_empty_pattern.ftl new file mode 100644 index 0000000..6df6810 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/term_with_empty_pattern.ftl @@ -0,0 +1,6 @@ +-foo = + .attr = Attribute + +-bar = + +-baz diff --git a/fluent.syntax/src/test/resources/structure_fixtures/term_with_empty_pattern.json b/fluent.syntax/src/test/resources/structure_fixtures/term_with_empty_pattern.json new file mode 100644 index 0000000..7f5392f --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/term_with_empty_pattern.json @@ -0,0 +1,82 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0006", + "arguments": [ + "foo" + ], + "message": "Expected term \"-foo\" to have a value", + "span": { + "type": "Span", + "start": 6, + "end": 6 + } + } + ], + "content": "-foo =\n .attr = Attribute\n\n", + "span": { + "type": "Span", + "start": 0, + "end": 30 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0006", + "arguments": [ + "bar" + ], + "message": "Expected term \"-bar\" to have a value", + "span": { + "type": "Span", + "start": 36, + "end": 36 + } + } + ], + "content": "-bar =\n\n", + "span": { + "type": "Span", + "start": 30, + "end": 38 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 42, + "end": 42 + } + } + ], + "content": "-baz\n", + "span": { + "type": "Span", + "start": 38, + "end": 43 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 43 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/unclosed.ftl b/fluent.syntax/src/test/resources/structure_fixtures/unclosed.ftl new file mode 100644 index 0000000..0fe9fd4 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/unclosed.ftl @@ -0,0 +1,12 @@ +err01 = { +key02 = Value 02 + +err03 = { +FUNC( +arg +, +namedArg: "Value" +, +key04 = Value 04 +) +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/unclosed.json b/fluent.syntax/src/test/resources/structure_fixtures/unclosed.json new file mode 100644 index 0000000..678a058 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/unclosed.json @@ -0,0 +1,154 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 10, + "end": 10 + } + } + ], + "content": "err01 = {\n", + "span": { + "type": "Span", + "start": 0, + "end": 10 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key02", + "span": { + "type": "Span", + "start": 10, + "end": 15 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 02", + "span": { + "type": "Span", + "start": 18, + "end": 26 + } + } + ], + "span": { + "type": "Span", + "start": 18, + "end": 26 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 10, + "end": 26 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0021", + "arguments": [], + "message": "Positional arguments must not follow named arguments", + "span": { + "type": "Span", + "start": 70, + "end": 70 + } + } + ], + "content": "err03 = {\nFUNC(\narg\n,\nnamedArg: \"Value\"\n,\n", + "span": { + "type": "Span", + "start": 28, + "end": 70 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key04", + "span": { + "type": "Span", + "start": 70, + "end": 75 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 04", + "span": { + "type": "Span", + "start": 78, + "end": 86 + } + } + ], + "span": { + "type": "Span", + "start": 78, + "end": 86 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 70, + "end": 86 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0002", + "arguments": [], + "message": "Expected an entry start", + "span": { + "type": "Span", + "start": 87, + "end": 87 + } + } + ], + "content": ")\n}\n", + "span": { + "type": "Span", + "start": 87, + "end": 91 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 91 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/unclosed_empty_placeable_error.ftl b/fluent.syntax/src/test/resources/structure_fixtures/unclosed_empty_placeable_error.ftl new file mode 100644 index 0000000..3892b04 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/unclosed_empty_placeable_error.ftl @@ -0,0 +1,3 @@ +err01 = { +key01 = Bar +err02 = { diff --git a/fluent.syntax/src/test/resources/structure_fixtures/unclosed_empty_placeable_error.json b/fluent.syntax/src/test/resources/structure_fixtures/unclosed_empty_placeable_error.json new file mode 100644 index 0000000..8fedae5 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/unclosed_empty_placeable_error.json @@ -0,0 +1,94 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 10, + "end": 10 + } + } + ], + "content": "err01 = {\n", + "span": { + "type": "Span", + "start": 0, + "end": 10 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01", + "span": { + "type": "Span", + "start": 10, + "end": 15 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Bar", + "span": { + "type": "Span", + "start": 18, + "end": 21 + } + } + ], + "span": { + "type": "Span", + "start": 18, + "end": 21 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 10, + "end": 21 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0028", + "arguments": [], + "message": "Expected an inline expression", + "span": { + "type": "Span", + "start": 32, + "end": 32 + } + } + ], + "content": "err02 = {\n", + "span": { + "type": "Span", + "start": 22, + "end": 32 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 32 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/unknown_entry_start.ftl b/fluent.syntax/src/test/resources/structure_fixtures/unknown_entry_start.ftl new file mode 100644 index 0000000..a27df37 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/unknown_entry_start.ftl @@ -0,0 +1,2 @@ + +8err = Foo diff --git a/fluent.syntax/src/test/resources/structure_fixtures/unknown_entry_start.json b/fluent.syntax/src/test/resources/structure_fixtures/unknown_entry_start.json new file mode 100644 index 0000000..41222d5 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/unknown_entry_start.json @@ -0,0 +1,32 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0002", + "arguments": [], + "message": "Expected an entry start", + "span": { + "type": "Span", + "start": 1, + "end": 1 + } + } + ], + "content": "8err = Foo\n", + "span": { + "type": "Span", + "start": 1, + "end": 12 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 12 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_ends_abruptly.ftl b/fluent.syntax/src/test/resources/structure_fixtures/variant_ends_abruptly.ftl new file mode 100644 index 0000000..aaead3d --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/variant_ends_abruptly.ftl @@ -0,0 +1,3 @@ +key = { $foo -> + *[ +# ~ERROR E0013, pos 23 diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_ends_abruptly.json b/fluent.syntax/src/test/resources/structure_fixtures/variant_ends_abruptly.json new file mode 100644 index 0000000..ab9a216 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/variant_ends_abruptly.json @@ -0,0 +1,43 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 23, + "end": 23 + } + } + ], + "content": "key = { $foo ->\n *[\n", + "span": { + "type": "Span", + "start": 0, + "end": 23 + } + }, + { + "content": "~ERROR E0013, pos 23", + "type": "Comment", + "span": { + "type": "Span", + "start": 23, + "end": 45 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 46 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_keys.ftl b/fluent.syntax/src/test/resources/structure_fixtures/variant_keys.ftl new file mode 100644 index 0000000..fc0f241 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/variant_keys.ftl @@ -0,0 +1,61 @@ +key01 = { $sel -> + *[ + key + ] Value +} + +key02 = { $sel -> + *[ + key + ] + + Value +} + +err01 = { $sel -> + *["key"] Value +} + +err02 = { $sel -> + *[-key] Value +} + +err03 = { $sel -> + *[-key.attr] Value +} + +err04 = { $sel -> + *[-key()] Value +} + +err05 = { $sel -> + *[-key.attr()] Value +} + +err06 = { $sel -> + *[key.attr] Value +} + +err07 = { $sel -> + *[$key] Value +} + +err08 = { $sel -> + *[FUNC()] Value +} + +err09 = { $sel -> + *[{key}] Value +} + +err10 = { $sel -> + *[{"key"}] Value +} + +err11 = { $sel -> + *[{3.14}] Value +} + +err12 = { $sel -> + *[{$key}] Value +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_keys.json b/fluent.syntax/src/test/resources/structure_fixtures/variant_keys.json new file mode 100644 index 0000000..3a93576 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/variant_keys.json @@ -0,0 +1,500 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01", + "span": { + "type": "Span", + "start": 0, + "end": 5 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "sel", + "span": { + "type": "Span", + "start": 11, + "end": 14 + } + }, + "span": { + "type": "Span", + "start": 10, + "end": 14 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 33, + "end": 36 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 43, + "end": 48 + } + } + ], + "span": { + "type": "Span", + "start": 43, + "end": 48 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 22, + "end": 48 + } + } + ], + "span": { + "type": "Span", + "start": 10, + "end": 49 + } + }, + "span": { + "type": "Span", + "start": 8, + "end": 50 + } + } + ], + "span": { + "type": "Span", + "start": 8, + "end": 50 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 50 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key02", + "span": { + "type": "Span", + "start": 52, + "end": 57 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "sel", + "span": { + "type": "Span", + "start": 63, + "end": 66 + } + }, + "span": { + "type": "Span", + "start": 62, + "end": 66 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 85, + "end": 88 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 104, + "end": 109 + } + } + ], + "span": { + "type": "Span", + "start": 100, + "end": 109 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 74, + "end": 109 + } + } + ], + "span": { + "type": "Span", + "start": 62, + "end": 110 + } + }, + "span": { + "type": "Span", + "start": 60, + "end": 111 + } + } + ], + "span": { + "type": "Span", + "start": 60, + "end": 111 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 52, + "end": 111 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 137, + "end": 137 + } + } + ], + "content": "err01 = { $sel ->\n *[\"key\"] Value\n}\n\n", + "span": { + "type": "Span", + "start": 113, + "end": 153 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "0-9" + ], + "message": "Expected a character from range: \"0-9\"", + "span": { + "type": "Span", + "start": 178, + "end": 178 + } + } + ], + "content": "err02 = { $sel ->\n *[-key] Value\n}\n\n", + "span": { + "type": "Span", + "start": 153, + "end": 192 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "0-9" + ], + "message": "Expected a character from range: \"0-9\"", + "span": { + "type": "Span", + "start": 217, + "end": 217 + } + } + ], + "content": "err03 = { $sel ->\n *[-key.attr] Value\n}\n\n", + "span": { + "type": "Span", + "start": 192, + "end": 236 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "0-9" + ], + "message": "Expected a character from range: \"0-9\"", + "span": { + "type": "Span", + "start": 261, + "end": 261 + } + } + ], + "content": "err04 = { $sel ->\n *[-key()] Value\n}\n\n", + "span": { + "type": "Span", + "start": 236, + "end": 277 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "0-9" + ], + "message": "Expected a character from range: \"0-9\"", + "span": { + "type": "Span", + "start": 302, + "end": 302 + } + } + ], + "content": "err05 = { $sel ->\n *[-key.attr()] Value\n}\n\n", + "span": { + "type": "Span", + "start": 277, + "end": 323 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "]" + ], + "message": "Expected token: \"]\"", + "span": { + "type": "Span", + "start": 350, + "end": 350 + } + } + ], + "content": "err06 = { $sel ->\n *[key.attr] Value\n}\n\n", + "span": { + "type": "Span", + "start": 323, + "end": 366 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 390, + "end": 390 + } + } + ], + "content": "err07 = { $sel ->\n *[$key] Value\n}\n\n", + "span": { + "type": "Span", + "start": 366, + "end": 405 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "]" + ], + "message": "Expected token: \"]\"", + "span": { + "type": "Span", + "start": 433, + "end": 433 + } + } + ], + "content": "err08 = { $sel ->\n *[FUNC()] Value\n}\n\n", + "span": { + "type": "Span", + "start": 405, + "end": 446 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 470, + "end": 470 + } + } + ], + "content": "err09 = { $sel ->\n *[{key}] Value\n}\n\n", + "span": { + "type": "Span", + "start": 446, + "end": 486 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 510, + "end": 510 + } + } + ], + "content": "err10 = { $sel ->\n *[{\"key\"}] Value\n}\n\n", + "span": { + "type": "Span", + "start": 486, + "end": 528 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 552, + "end": 552 + } + } + ], + "content": "err11 = { $sel ->\n *[{3.14}] Value\n}\n\n", + "span": { + "type": "Span", + "start": 528, + "end": 569 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0004", + "arguments": [ + "a-zA-Z" + ], + "message": "Expected a character from range: \"a-zA-Z\"", + "span": { + "type": "Span", + "start": 593, + "end": 593 + } + } + ], + "content": "err12 = { $sel ->\n *[{$key}] Value\n}\n", + "span": { + "type": "Span", + "start": 569, + "end": 609 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 609 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_starts_from_nl.ftl b/fluent.syntax/src/test/resources/structure_fixtures/variant_starts_from_nl.ftl new file mode 100644 index 0000000..f494d91 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/variant_starts_from_nl.ftl @@ -0,0 +1,3 @@ +-term = { $sel -> +*[one] Value + } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_starts_from_nl.json b/fluent.syntax/src/test/resources/structure_fixtures/variant_starts_from_nl.json new file mode 100644 index 0000000..4d982ad --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/variant_starts_from_nl.json @@ -0,0 +1,111 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 1, + "end": 5 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "sel", + "span": { + "type": "Span", + "start": 11, + "end": 14 + } + }, + "span": { + "type": "Span", + "start": 10, + "end": 14 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "one", + "span": { + "type": "Span", + "start": 20, + "end": 23 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 25, + "end": 30 + } + } + ], + "span": { + "type": "Span", + "start": 25, + "end": 30 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 18, + "end": 30 + } + } + ], + "span": { + "type": "Span", + "start": 10, + "end": 35 + } + }, + "span": { + "type": "Span", + "start": 8, + "end": 36 + } + } + ], + "span": { + "type": "Span", + "start": 8, + "end": 36 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 36 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 37 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_with_digit_key.ftl b/fluent.syntax/src/test/resources/structure_fixtures/variant_with_digit_key.ftl new file mode 100644 index 0000000..ab09051 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/variant_with_digit_key.ftl @@ -0,0 +1,3 @@ +-term = { $sel -> + *[-2] Foo + } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_with_digit_key.json b/fluent.syntax/src/test/resources/structure_fixtures/variant_with_digit_key.json new file mode 100644 index 0000000..cf96f84 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/variant_with_digit_key.json @@ -0,0 +1,111 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 1, + "end": 5 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "sel", + "span": { + "type": "Span", + "start": 11, + "end": 14 + } + }, + "span": { + "type": "Span", + "start": 10, + "end": 14 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "value": "-2", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 28, + "end": 30 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Foo", + "span": { + "type": "Span", + "start": 32, + "end": 35 + } + } + ], + "span": { + "type": "Span", + "start": 32, + "end": 35 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 26, + "end": 35 + } + } + ], + "span": { + "type": "Span", + "start": 10, + "end": 40 + } + }, + "span": { + "type": "Span", + "start": 8, + "end": 41 + } + } + ], + "span": { + "type": "Span", + "start": 8, + "end": 41 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 41 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 42 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_with_empty_pattern.ftl b/fluent.syntax/src/test/resources/structure_fixtures/variant_with_empty_pattern.ftl new file mode 100644 index 0000000..155072a --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/variant_with_empty_pattern.ftl @@ -0,0 +1,9 @@ +key1 = + { 1 -> + *[one] {""} + } + +err2 = + { $sel -> + *[one] + } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_with_empty_pattern.json b/fluent.syntax/src/test/resources/structure_fixtures/variant_with_empty_pattern.json new file mode 100644 index 0000000..c46c71e --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/variant_with_empty_pattern.json @@ -0,0 +1,133 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key1", + "span": { + "type": "Span", + "start": 0, + "end": 4 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "value": "1", + "type": "NumberLiteral", + "span": { + "type": "Span", + "start": 13, + "end": 14 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "one", + "span": { + "type": "Span", + "start": 27, + "end": 30 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 33, + "end": 35 + } + }, + "span": { + "type": "Span", + "start": 32, + "end": 36 + } + } + ], + "span": { + "type": "Span", + "start": 32, + "end": 36 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 25, + "end": 36 + } + } + ], + "span": { + "type": "Span", + "start": 13, + "end": 41 + } + }, + "span": { + "type": "Span", + "start": 11, + "end": 42 + } + } + ], + "span": { + "type": "Span", + "start": 7, + "end": 42 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 42 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0012", + "arguments": [], + "message": "Expected value", + "span": { + "type": "Span", + "start": 78, + "end": 78 + } + } + ], + "content": "err2 =\n { $sel ->\n *[one]\n }\n", + "span": { + "type": "Span", + "start": 44, + "end": 85 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 85 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_with_leading_space_in_name.ftl b/fluent.syntax/src/test/resources/structure_fixtures/variant_with_leading_space_in_name.ftl new file mode 100644 index 0000000..27bf1ee --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/variant_with_leading_space_in_name.ftl @@ -0,0 +1,3 @@ +-term = { $sel -> + *[ one] Foo + } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_with_leading_space_in_name.json b/fluent.syntax/src/test/resources/structure_fixtures/variant_with_leading_space_in_name.json new file mode 100644 index 0000000..5a99158 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/variant_with_leading_space_in_name.json @@ -0,0 +1,111 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "term", + "span": { + "type": "Span", + "start": 1, + "end": 5 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "sel", + "span": { + "type": "Span", + "start": 11, + "end": 14 + } + }, + "span": { + "type": "Span", + "start": 10, + "end": 14 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "one", + "span": { + "type": "Span", + "start": 30, + "end": 33 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Foo", + "span": { + "type": "Span", + "start": 35, + "end": 38 + } + } + ], + "span": { + "type": "Span", + "start": 35, + "end": 38 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 26, + "end": 38 + } + } + ], + "span": { + "type": "Span", + "start": 10, + "end": 43 + } + }, + "span": { + "type": "Span", + "start": 8, + "end": 44 + } + } + ], + "span": { + "type": "Span", + "start": 8, + "end": 44 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 44 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 45 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_with_symbol_with_space.ftl b/fluent.syntax/src/test/resources/structure_fixtures/variant_with_symbol_with_space.ftl new file mode 100644 index 0000000..39442fc --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/variant_with_symbol_with_space.ftl @@ -0,0 +1,3 @@ +-err01 = { $sel -> + *[New York] Nowy Jork + } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_with_symbol_with_space.json b/fluent.syntax/src/test/resources/structure_fixtures/variant_with_symbol_with_space.json new file mode 100644 index 0000000..601d0dd --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/variant_with_symbol_with_space.json @@ -0,0 +1,34 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "arguments": [ + "]" + ], + "message": "Expected token: \"]\"", + "span": { + "type": "Span", + "start": 33, + "end": 33 + } + } + ], + "content": "-err01 = { $sel ->\n *[New York] Nowy Jork\n }\n", + "span": { + "type": "Span", + "start": 0, + "end": 55 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 55 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variants_with_two_defaults.ftl b/fluent.syntax/src/test/resources/structure_fixtures/variants_with_two_defaults.ftl new file mode 100644 index 0000000..355031a --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/variants_with_two_defaults.ftl @@ -0,0 +1,4 @@ +-err01 = { $sel -> + *[one] Foo + *[two] Two + } diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variants_with_two_defaults.json b/fluent.syntax/src/test/resources/structure_fixtures/variants_with_two_defaults.json new file mode 100644 index 0000000..636147e --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/variants_with_two_defaults.json @@ -0,0 +1,32 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0015", + "arguments": [], + "message": "Only one variant can be marked as default (*)", + "span": { + "type": "Span", + "start": 38, + "end": 38 + } + } + ], + "content": "-err01 = { $sel ->\n *[one] Foo\n *[two] Two\n }\n", + "span": { + "type": "Span", + "start": 0, + "end": 55 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 55 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/whitespace_leading.ftl b/fluent.syntax/src/test/resources/structure_fixtures/whitespace_leading.ftl new file mode 100644 index 0000000..eba1e05 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/whitespace_leading.ftl @@ -0,0 +1,8 @@ +# < whitespace > +key1 = Value + +# ↓ nbsp +key2 =   Value + +key3 = {""} Value +key4 = {" "}Value diff --git a/fluent.syntax/src/test/resources/structure_fixtures/whitespace_leading.json b/fluent.syntax/src/test/resources/structure_fixtures/whitespace_leading.json new file mode 100644 index 0000000..1155bd4 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/whitespace_leading.json @@ -0,0 +1,212 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key1", + "span": { + "type": "Span", + "start": 21, + "end": 25 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 41, + "end": 46 + } + } + ], + "span": { + "type": "Span", + "start": 41, + "end": 46 + } + }, + "attributes": [], + "comment": { + "content": " < whitespace >", + "type": "Comment", + "span": { + "type": "Span", + "start": 0, + "end": 20 + } + }, + "span": { + "type": "Span", + "start": 0, + "end": 46 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key2", + "span": { + "type": "Span", + "start": 65, + "end": 69 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "  Value", + "span": { + "type": "Span", + "start": 75, + "end": 84 + } + } + ], + "span": { + "type": "Span", + "start": 75, + "end": 84 + } + }, + "attributes": [], + "comment": { + "content": " ↓ nbsp", + "type": "Comment", + "span": { + "type": "Span", + "start": 48, + "end": 64 + } + }, + "span": { + "type": "Span", + "start": 48, + "end": 84 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key3", + "span": { + "type": "Span", + "start": 86, + "end": 90 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": "", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 94, + "end": 96 + } + }, + "span": { + "type": "Span", + "start": 93, + "end": 97 + } + }, + { + "type": "TextElement", + "value": " Value", + "span": { + "type": "Span", + "start": 97, + "end": 111 + } + } + ], + "span": { + "type": "Span", + "start": 93, + "end": 111 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 86, + "end": 111 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key4", + "span": { + "type": "Span", + "start": 112, + "end": 116 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "value": " ", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 120, + "end": 131 + } + }, + "span": { + "type": "Span", + "start": 119, + "end": 132 + } + }, + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 132, + "end": 137 + } + } + ], + "span": { + "type": "Span", + "start": 119, + "end": 137 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 112, + "end": 137 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 138 + } +} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/whitespace_trailing.ftl b/fluent.syntax/src/test/resources/structure_fixtures/whitespace_trailing.ftl new file mode 100644 index 0000000..0604828 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/whitespace_trailing.ftl @@ -0,0 +1,8 @@ +# < whitespace > +key1 = Value + +# ↓ nbsp +key2 = Value   + +key3 = Value {placeable}. +key4 = Value{" "} diff --git a/fluent.syntax/src/test/resources/structure_fixtures/whitespace_trailing.json b/fluent.syntax/src/test/resources/structure_fixtures/whitespace_trailing.json new file mode 100644 index 0000000..93d6789 --- /dev/null +++ b/fluent.syntax/src/test/resources/structure_fixtures/whitespace_trailing.json @@ -0,0 +1,230 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key1", + "span": { + "type": "Span", + "start": 27, + "end": 31 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 34, + "end": 53 + } + } + ], + "span": { + "type": "Span", + "start": 34, + "end": 53 + } + }, + "attributes": [], + "comment": { + "content": " < whitespace >", + "type": "Comment", + "span": { + "type": "Span", + "start": 0, + "end": 26 + } + }, + "span": { + "type": "Span", + "start": 0, + "end": 53 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key2", + "span": { + "type": "Span", + "start": 77, + "end": 81 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value  ", + "span": { + "type": "Span", + "start": 84, + "end": 96 + } + } + ], + "span": { + "type": "Span", + "start": 84, + "end": 96 + } + }, + "attributes": [], + "comment": { + "content": " ↓ nbsp", + "type": "Comment", + "span": { + "type": "Span", + "start": 55, + "end": 76 + } + }, + "span": { + "type": "Span", + "start": 55, + "end": 96 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key3", + "span": { + "type": "Span", + "start": 98, + "end": 102 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value ", + "span": { + "type": "Span", + "start": 105, + "end": 124 + } + }, + { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "placeable", + "span": { + "type": "Span", + "start": 125, + "end": 134 + } + }, + "attribute": null, + "span": { + "type": "Span", + "start": 125, + "end": 134 + } + }, + "span": { + "type": "Span", + "start": 124, + "end": 135 + } + }, + { + "type": "TextElement", + "value": ".", + "span": { + "type": "Span", + "start": 135, + "end": 136 + } + } + ], + "span": { + "type": "Span", + "start": 105, + "end": 136 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 98, + "end": 136 + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key4", + "span": { + "type": "Span", + "start": 137, + "end": 141 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 144, + "end": 149 + } + }, + { + "type": "Placeable", + "expression": { + "value": " ", + "type": "StringLiteral", + "span": { + "type": "Span", + "start": 150, + "end": 163 + } + }, + "span": { + "type": "Span", + "start": 149, + "end": 164 + } + } + ], + "span": { + "type": "Span", + "start": 144, + "end": 164 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 137, + "end": 164 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 165 + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..d64cd4917707c1f8861d8cb53dd15194d4248596 GIT binary patch literal 43462 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a80b22c --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..1aa94a4 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..25da30d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..8be0397 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = "fluent-java" + +include("fluent.syntax") From 5aa1ff08f201c5e763d7fa0a233621b070af3a27 Mon Sep 17 00:00:00 2001 From: Voomra Date: Mon, 21 Jul 2025 15:27:25 +0300 Subject: [PATCH 02/11] build: upgrade Gradle 8.6 -> 8.10 --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 7 +++++-- gradlew.bat | 2 ++ 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 6db8473..3503191 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,4 @@ wrapper { - gradleVersion = "8.6" + gradleVersion = "8.10" distributionType = Wrapper.DistributionType.BIN } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd4917707c1f8861d8cb53dd15194d4248596..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch delta 34592 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 delta 34555 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a80b22c..9355b41 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a4..f5feea6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 25da30d..9d21a21 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## From 16f22f636b44246b51c269e8e8345e477f5db9a8 Mon Sep 17 00:00:00 2001 From: Voomra Date: Mon, 21 Jul 2025 15:48:39 +0300 Subject: [PATCH 03/11] docs: README --- README.MD | 10 ++++++++++ fluent.syntax/README.MD | 31 +++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 README.MD create mode 100644 fluent.syntax/README.MD diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..9569e8b --- /dev/null +++ b/README.MD @@ -0,0 +1,10 @@ +# Project Fluent + +Это набор Java пакетов для использования [Fluent localization system](http://projectfluent.org/). + +fluent-java состоит из следующих пакетов: + +## fluent.syntax + +Пакет [syntax](fluent.syntax) включает в себя синтаксический анализатор (parser), сериализатор (serializer), +и инструменты обхода, такие как Visitor. Данный пакет понадобится при работе над инструментами для Fluent в Java. diff --git a/fluent.syntax/README.MD b/fluent.syntax/README.MD new file mode 100644 index 0000000..9d3b9f3 --- /dev/null +++ b/fluent.syntax/README.MD @@ -0,0 +1,31 @@ +# fluent.syntax + +Чтение, запись и прочие преобразования файлов [Fluent](https://projectfluent.org/). + +Этот пакет включает в себя синтаксический анализатор (parser), сериализатор (serializer), +и инструменты обхода, такие как Visitor. Данный пакет понадобится при работе над инструментами для Fluent в Java. + +```java +import ru.di9.fluent.syntax.ast.Message; +import ru.di9.fluent.syntax.ast.Resource; +import ru.di9.fluent.syntax.parser.FluentParser; +import ru.di9.fluent.syntax.serializer.FluentSerializer; + +public class App { + public static void main(String[] args) { + var parser = new FluentParser(); + Resource resource = parser.parse("a-key = String to localize"); + + System.out.println(((Message) resource.getBody().get(0)).getId().getName()); + // "a-key" + + var serializer = new FluentSerializer(); + + System.out.println(serializer.serialize(resource)); + // "a-key = String to localize" + + System.out.println(serializer.serialize(resource.getBody().get(0))); + // "a-key = String to localize" + } +} +``` From 8dd80fb0128d78baad7905a25bfae5342db290e1 Mon Sep 17 00:00:00 2001 From: Voomra Date: Mon, 21 Jul 2025 16:33:11 +0300 Subject: [PATCH 04/11] =?UTF-8?q?build:=20=D0=BC=D0=B5=D0=BB=D0=BA=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D0=B2=20build.?= =?UTF-8?q?gradle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fluent.syntax/build.gradle | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/fluent.syntax/build.gradle b/fluent.syntax/build.gradle index 3329a63..f470401 100644 --- a/fluent.syntax/build.gradle +++ b/fluent.syntax/build.gradle @@ -1,15 +1,14 @@ plugins { - id "java" -} - -compileJava { - targetCompatibility = sourceCompatibility = JavaVersion.VERSION_17 - options.encoding = "UTF-8" + id("java") } group = "ru.di9.fluent" version = "1.0-SNAPSHOT" +java.toolchain { + languageVersion = JavaLanguageVersion.of(17) +} + repositories { mavenLocal() mavenCentral() @@ -42,4 +41,3 @@ dependencies { test { useJUnitPlatform() } - From 2f142d6da3c1b4f87297777dfa9e35fc3cc84983 Mon Sep 17 00:00:00 2001 From: Voomra Date: Mon, 21 Jul 2025 16:45:21 +0300 Subject: [PATCH 05/11] refac: fluent.syntax -> fluent-syntax --- README.MD | 4 ++-- {fluent.syntax => fluent-syntax}/README.MD | 2 +- {fluent.syntax => fluent-syntax}/build.gradle | 0 .../src/main/java/ru/di9/fluent/syntax/MathUtils.java | 0 .../src/main/java/ru/di9/fluent/syntax/StringUtils.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/Annotation.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/Attribute.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/BaseComment.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/BaseNode.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/CallArgument.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/CallArguments.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/Comment.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/Entry.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/Expression.java | 0 .../main/java/ru/di9/fluent/syntax/ast/FunctionReference.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/GroupComment.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/Identifier.java | 0 .../main/java/ru/di9/fluent/syntax/ast/InsidePlaceable.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/Junk.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/Literal.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/Message.java | 0 .../main/java/ru/di9/fluent/syntax/ast/MessageReference.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/NamedArgument.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/NumberLiteral.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/Pattern.java | 0 .../main/java/ru/di9/fluent/syntax/ast/PatternElement.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/Placeable.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/Resource.java | 0 .../main/java/ru/di9/fluent/syntax/ast/ResourceComment.java | 0 .../main/java/ru/di9/fluent/syntax/ast/SelectExpression.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/Span.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/StringLiteral.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/SyntaxNode.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/Term.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/TermReference.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/TextElement.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/TopLevel.java | 0 .../main/java/ru/di9/fluent/syntax/ast/VariableReference.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/Variant.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/VariantKey.java | 0 .../src/main/java/ru/di9/fluent/syntax/ast/Whitespace.java | 0 .../main/java/ru/di9/fluent/syntax/parser/FluentParser.java | 0 .../main/java/ru/di9/fluent/syntax/parser/FluentStream.java | 0 .../src/main/java/ru/di9/fluent/syntax/parser/Indent.java | 0 .../main/java/ru/di9/fluent/syntax/parser/ParseException.java | 0 .../main/java/ru/di9/fluent/syntax/parser/ParserStream.java | 0 .../main/java/ru/di9/fluent/syntax/processor/Processor.java | 0 .../ru/di9/fluent/syntax/serializer/FluentSerializer.java | 0 .../ru/di9/fluent/syntax/serializer/SerializeException.java | 0 .../src/main/java/ru/di9/fluent/syntax/visitor/Visitor.java | 0 .../src/test/java/ru/di9/fluent/syntax/MathUtilsTest.java | 0 .../src/test/java/ru/di9/fluent/syntax/StringUtilsTest.java | 0 .../src/test/java/ru/di9/fluent/syntax/ast/BaseNodeTest.java | 0 .../ru/di9/fluent/syntax/parser/AbstractFixturesTest.java | 0 .../java/ru/di9/fluent/syntax/parser/FluentStreamTest.java | 0 .../java/ru/di9/fluent/syntax/parser/ParserStreamTest.java | 0 .../test/java/ru/di9/fluent/syntax/parser/ReferenceTest.java | 0 .../test/java/ru/di9/fluent/syntax/parser/StructureTest.java | 0 .../java/ru/di9/fluent/syntax/processor/ProcessorTest.java | 0 .../ru/di9/fluent/syntax/serializer/SerializeEntryTest.java | 0 .../di9/fluent/syntax/serializer/SerializeResourceTest.java | 0 .../test/java/ru/di9/fluent/syntax/visitor/VisitorTest.java | 0 .../src/test/java/ru/di9/fluent/test/utils/AstAssert.java | 0 .../java/ru/di9/fluent/test/utils/BaseNodeJsonSerializer.java | 0 .../src/test/java/ru/di9/fluent/test/utils/FileUtils.java | 0 .../java/ru/di9/fluent/test/utils/NullIgnoreComparator.java | 0 .../src/test/java/ru/di9/fluent/test/utils/Tuple3.java | 0 .../src/test/resources/reference_fixtures/.gitattributes | 0 .../src/test/resources/reference_fixtures/any_char.ftl | 0 .../src/test/resources/reference_fixtures/any_char.json | 0 .../src/test/resources/reference_fixtures/astral.ftl | 0 .../src/test/resources/reference_fixtures/astral.json | 0 .../test/resources/reference_fixtures/call_expressions.ftl | 0 .../test/resources/reference_fixtures/call_expressions.json | 0 .../test/resources/reference_fixtures/callee_expressions.ftl | 0 .../test/resources/reference_fixtures/callee_expressions.json | 0 .../src/test/resources/reference_fixtures/comments.ftl | 0 .../src/test/resources/reference_fixtures/comments.json | 0 .../src/test/resources/reference_fixtures/cr.ftl | 0 .../src/test/resources/reference_fixtures/cr.json | 0 .../src/test/resources/reference_fixtures/crlf.ftl | 0 .../src/test/resources/reference_fixtures/crlf.json | 0 .../src/test/resources/reference_fixtures/eof_comment.ftl | 0 .../src/test/resources/reference_fixtures/eof_comment.json | 0 .../src/test/resources/reference_fixtures/eof_empty.ftl | 0 .../src/test/resources/reference_fixtures/eof_empty.json | 0 .../src/test/resources/reference_fixtures/eof_id.ftl | 0 .../src/test/resources/reference_fixtures/eof_id.json | 0 .../src/test/resources/reference_fixtures/eof_id_equals.ftl | 0 .../src/test/resources/reference_fixtures/eof_id_equals.json | 0 .../src/test/resources/reference_fixtures/eof_junk.ftl | 0 .../src/test/resources/reference_fixtures/eof_junk.json | 0 .../src/test/resources/reference_fixtures/eof_value.ftl | 0 .../src/test/resources/reference_fixtures/eof_value.json | 0 .../test/resources/reference_fixtures/escaped_characters.ftl | 0 .../test/resources/reference_fixtures/escaped_characters.json | 0 .../src/test/resources/reference_fixtures/junk.ftl | 0 .../src/test/resources/reference_fixtures/junk.json | 0 .../src/test/resources/reference_fixtures/leading_dots.ftl | 0 .../src/test/resources/reference_fixtures/leading_dots.json | 0 .../test/resources/reference_fixtures/literal_expressions.ftl | 0 .../resources/reference_fixtures/literal_expressions.json | 0 .../test/resources/reference_fixtures/member_expressions.ftl | 0 .../test/resources/reference_fixtures/member_expressions.json | 0 .../src/test/resources/reference_fixtures/messages.ftl | 0 .../src/test/resources/reference_fixtures/messages.json | 0 .../src/test/resources/reference_fixtures/mixed_entries.ftl | 0 .../src/test/resources/reference_fixtures/mixed_entries.json | 0 .../test/resources/reference_fixtures/multiline_values.ftl | 0 .../test/resources/reference_fixtures/multiline_values.json | 0 .../src/test/resources/reference_fixtures/numbers.ftl | 0 .../src/test/resources/reference_fixtures/numbers.json | 0 .../src/test/resources/reference_fixtures/obsolete.ftl | 0 .../src/test/resources/reference_fixtures/obsolete.json | 0 .../src/test/resources/reference_fixtures/placeables.ftl | 0 .../src/test/resources/reference_fixtures/placeables.json | 0 .../resources/reference_fixtures/reference_expressions.ftl | 0 .../resources/reference_fixtures/reference_expressions.json | 0 .../test/resources/reference_fixtures/select_expressions.ftl | 0 .../test/resources/reference_fixtures/select_expressions.json | 0 .../src/test/resources/reference_fixtures/select_indent.ftl | 0 .../src/test/resources/reference_fixtures/select_indent.json | 0 .../src/test/resources/reference_fixtures/sparse_entries.ftl | 0 .../src/test/resources/reference_fixtures/sparse_entries.json | 0 .../src/test/resources/reference_fixtures/special_chars.ftl | 0 .../src/test/resources/reference_fixtures/special_chars.json | 0 .../src/test/resources/reference_fixtures/tab.ftl | 0 .../src/test/resources/reference_fixtures/tab.json | 0 .../src/test/resources/reference_fixtures/term_parameters.ftl | 0 .../test/resources/reference_fixtures/term_parameters.json | 0 .../src/test/resources/reference_fixtures/terms.ftl | 0 .../src/test/resources/reference_fixtures/terms.json | 0 .../src/test/resources/reference_fixtures/variables.ftl | 0 .../src/test/resources/reference_fixtures/variables.json | 0 .../src/test/resources/reference_fixtures/variant_keys.ftl | 0 .../src/test/resources/reference_fixtures/variant_keys.json | 0 .../test/resources/reference_fixtures/whitespace_in_value.ftl | 0 .../resources/reference_fixtures/whitespace_in_value.json | 0 .../src/test/resources/reference_fixtures/zero_length.ftl | 0 .../src/test/resources/reference_fixtures/zero_length.json | 0 .../src/test/resources/serialized/attribute.ftl | 0 .../test/resources/serialized/backslash_in_text_element.ftl | 0 .../src/test/resources/serialized/block_multiline_message.ftl | 0 .../src/test/resources/serialized/call_expression.ftl | 0 .../serialized/call_expression_with_message_reference.ftl | 0 .../serialized/call_expression_with_named_number_literal.ftl | 0 .../serialized/call_expression_with_named_string_literal.ftl | 0 .../serialized/call_expression_with_number_literal.ftl | 0 .../call_expression_with_positional_and_named_arguments.ftl | 0 .../serialized/call_expression_with_string_literal.ftl | 0 .../serialized/call_expression_with_two_named_arguments.ftl | 0 .../call_expression_with_two_positional_arguments.ftl | 0 .../serialized/call_expression_with_variable_reference.ftl | 0 .../serialized/escaped_special_char_in_string_literal.ftl | 0 .../src/test/resources/serialized/group_comment.ftl | 0 .../resources/serialized/inline_multiline_message.exp.txt | 0 .../test/resources/serialized/inline_multiline_message.ftl | 0 .../test/resources/serialized/message_attribute_reference.ftl | 0 .../src/test/resources/serialized/message_comment.ftl | 0 .../src/test/resources/serialized/message_reference.ftl | 0 .../src/test/resources/serialized/message_with_whitespace.ftl | 0 .../src/test/resources/serialized/message_without_eol.exp.txt | 0 .../src/test/resources/serialized/message_without_eol.ftl | 0 .../src/test/resources/serialized/multiline_attribute.ftl | 0 .../resources/serialized/multiline_starting_inline.exp.txt | 0 .../test/resources/serialized/multiline_starting_inline.ftl | 0 .../multiline_starting_inline_with_a_special_char.ftl | 0 .../resources/serialized/multiline_value_and_attributes.ftl | 0 .../src/test/resources/serialized/multiline_variant.ftl | 0 .../multiline_variant_with_first_line_inline.exp.txt | 0 .../serialized/multiline_variant_with_first_line_inline.ftl | 0 .../test/resources/serialized/multiline_with_placeable.ftl | 0 .../src/test/resources/serialized/nested_placeable.ftl | 0 .../test/resources/serialized/nested_select_expression.ftl | 0 .../src/test/resources/serialized/number_literal.ftl | 0 .../src/test/resources/serialized/resource_comment.ftl | 0 .../src/test/resources/serialized/select_expression.ftl | 0 .../serialized/select_expression_in_block_pattern.ftl | 0 .../serialized/select_expression_in_inline_pattern.exp.txt | 0 .../serialized/select_expression_in_inline_pattern.ftl | 0 ...ression_in_inline_pattern_starting_with_a_special_char.ftl | 0 .../serialized/select_expression_in_multiline_pattern.ftl | 0 .../src/test/resources/serialized/selector_number_literal.ftl | 0 .../src/test/resources/serialized/selector_string_literal.ftl | 0 .../serialized/selector_term_attribute_reference.ftl | 0 .../test/resources/serialized/selector_variable_reference.ftl | 0 .../src/test/resources/serialized/standalone_comment.ftl | 0 .../src/test/resources/serialized/string_literal.ftl | 0 .../src/test/resources/serialized/term.ftl | 0 .../src/test/resources/serialized/term_reference.ftl | 0 .../src/test/resources/serialized/term_reference_call.ftl | 0 .../src/test/resources/serialized/two_attribute.ftl | 0 .../src/test/resources/serialized/two_simple_messages.ftl | 0 .../src/test/resources/serialized/unicode_escape_sequence.ftl | 0 .../src/test/resources/serialized/value_and_attributes.ftl | 0 .../src/test/resources/serialized/variable_reference.ftl | 0 .../src/test/resources/serialized/variant_key_number.ftl | 0 .../src/test/resources/structure_fixtures/.gitattributes | 0 .../attribute_expression_with_wrong_attr.ftl | 0 .../attribute_expression_with_wrong_attr.json | 0 .../structure_fixtures/attribute_of_private_as_placeable.ftl | 0 .../structure_fixtures/attribute_of_private_as_placeable.json | 0 .../structure_fixtures/attribute_of_public_as_selector.ftl | 0 .../structure_fixtures/attribute_of_public_as_selector.json | 0 .../resources/structure_fixtures/attribute_starts_from_nl.ftl | 0 .../structure_fixtures/attribute_starts_from_nl.json | 0 .../structure_fixtures/attribute_with_empty_pattern.ftl | 0 .../structure_fixtures/attribute_with_empty_pattern.json | 0 .../structure_fixtures/attribute_without_equal_sign.ftl | 0 .../structure_fixtures/attribute_without_equal_sign.json | 0 .../src/test/resources/structure_fixtures/blank_lines.ftl | 0 .../src/test/resources/structure_fixtures/blank_lines.json | 0 .../src/test/resources/structure_fixtures/broken_number.ftl | 0 .../src/test/resources/structure_fixtures/broken_number.json | 0 .../resources/structure_fixtures/call_expression_errors.ftl | 0 .../resources/structure_fixtures/call_expression_errors.json | 0 .../test/resources/structure_fixtures/comment_with_eof.ftl | 0 .../test/resources/structure_fixtures/comment_with_eof.json | 0 .../src/test/resources/structure_fixtures/crlf.ftl | 0 .../src/test/resources/structure_fixtures/crlf.json | 0 .../src/test/resources/structure_fixtures/dash_at_eof.ftl | 0 .../src/test/resources/structure_fixtures/dash_at_eof.json | 0 .../src/test/resources/structure_fixtures/elements_indent.ftl | 0 .../test/resources/structure_fixtures/elements_indent.json | 0 .../src/test/resources/structure_fixtures/empty_resource.ftl | 0 .../src/test/resources/structure_fixtures/empty_resource.json | 0 .../resources/structure_fixtures/empty_resource_with_ws.ftl | 0 .../resources/structure_fixtures/empty_resource_with_ws.json | 0 .../test/resources/structure_fixtures/escape_sequences.ftl | 0 .../test/resources/structure_fixtures/escape_sequences.json | 0 .../resources/structure_fixtures/expressions_call_args.ftl | 0 .../resources/structure_fixtures/expressions_call_args.json | 0 .../src/test/resources/structure_fixtures/indent.ftl | 0 .../src/test/resources/structure_fixtures/indent.json | 0 .../src/test/resources/structure_fixtures/junk.ftl | 0 .../src/test/resources/structure_fixtures/junk.json | 0 .../src/test/resources/structure_fixtures/leading_dots.ftl | 0 .../src/test/resources/structure_fixtures/leading_dots.json | 0 .../test/resources/structure_fixtures/leading_empty_lines.ftl | 0 .../resources/structure_fixtures/leading_empty_lines.json | 0 .../structure_fixtures/leading_empty_lines_with_ws.ftl | 0 .../structure_fixtures/leading_empty_lines_with_ws.json | 0 .../structure_fixtures/message_reference_as_selector.ftl | 0 .../structure_fixtures/message_reference_as_selector.json | 0 .../message_with_empty_multiline_pattern.ftl | 0 .../message_with_empty_multiline_pattern.json | 0 .../structure_fixtures/message_with_empty_pattern.ftl | 0 .../structure_fixtures/message_with_empty_pattern.json | 0 .../test/resources/structure_fixtures/multiline-comment.ftl | 0 .../test/resources/structure_fixtures/multiline-comment.json | 0 .../test/resources/structure_fixtures/multiline_pattern.ftl | 0 .../test/resources/structure_fixtures/multiline_pattern.json | 0 .../test/resources/structure_fixtures/multiline_string.ftl | 0 .../test/resources/structure_fixtures/multiline_string.json | 0 .../multiline_with_non_empty_first_line.ftl | 0 .../multiline_with_non_empty_first_line.json | 0 .../structure_fixtures/multiline_with_placeables.ftl | 0 .../structure_fixtures/multiline_with_placeables.json | 0 .../resources/structure_fixtures/non_id_attribute_name.ftl | 0 .../resources/structure_fixtures/non_id_attribute_name.json | 0 .../test/resources/structure_fixtures/placeable_at_eol.ftl | 0 .../test/resources/structure_fixtures/placeable_at_eol.json | 0 .../structure_fixtures/placeable_at_line_extremes.ftl | 0 .../structure_fixtures/placeable_at_line_extremes.json | 0 .../resources/structure_fixtures/placeable_in_placeable.ftl | 0 .../resources/structure_fixtures/placeable_in_placeable.json | 0 .../structure_fixtures/placeable_without_close_bracket.ftl | 0 .../structure_fixtures/placeable_without_close_bracket.json | 0 .../test/resources/structure_fixtures/resource_comment.ftl | 0 .../test/resources/structure_fixtures/resource_comment.json | 0 .../structure_fixtures/resource_comment_trailing_line.ftl | 0 .../structure_fixtures/resource_comment_trailing_line.json | 0 .../structure_fixtures/second_attribute_starts_from_nl.ftl | 0 .../structure_fixtures/second_attribute_starts_from_nl.json | 0 .../select_expression_with_two_selectors.ftl | 0 .../select_expression_with_two_selectors.json | 0 .../structure_fixtures/select_expression_without_arrow.ftl | 0 .../structure_fixtures/select_expression_without_arrow.json | 0 .../structure_fixtures/select_expression_without_variants.ftl | 0 .../select_expression_without_variants.json | 0 .../test/resources/structure_fixtures/select_expressions.ftl | 0 .../test/resources/structure_fixtures/select_expressions.json | 0 .../src/test/resources/structure_fixtures/simple_message.ftl | 0 .../src/test/resources/structure_fixtures/simple_message.json | 0 .../src/test/resources/structure_fixtures/single_char_id.ftl | 0 .../src/test/resources/structure_fixtures/single_char_id.json | 0 .../src/test/resources/structure_fixtures/sparse-messages.ftl | 0 .../test/resources/structure_fixtures/sparse-messages.json | 0 .../test/resources/structure_fixtures/standalone_comment.ftl | 0 .../test/resources/structure_fixtures/standalone_comment.json | 0 .../resources/structure_fixtures/standalone_identifier.ftl | 0 .../resources/structure_fixtures/standalone_identifier.json | 0 .../src/test/resources/structure_fixtures/term.ftl | 0 .../src/test/resources/structure_fixtures/term.json | 0 .../resources/structure_fixtures/term_with_empty_pattern.ftl | 0 .../resources/structure_fixtures/term_with_empty_pattern.json | 0 .../src/test/resources/structure_fixtures/unclosed.ftl | 0 .../src/test/resources/structure_fixtures/unclosed.json | 0 .../structure_fixtures/unclosed_empty_placeable_error.ftl | 0 .../structure_fixtures/unclosed_empty_placeable_error.json | 0 .../test/resources/structure_fixtures/unknown_entry_start.ftl | 0 .../resources/structure_fixtures/unknown_entry_start.json | 0 .../resources/structure_fixtures/variant_ends_abruptly.ftl | 0 .../resources/structure_fixtures/variant_ends_abruptly.json | 0 .../src/test/resources/structure_fixtures/variant_keys.ftl | 0 .../src/test/resources/structure_fixtures/variant_keys.json | 0 .../resources/structure_fixtures/variant_starts_from_nl.ftl | 0 .../resources/structure_fixtures/variant_starts_from_nl.json | 0 .../resources/structure_fixtures/variant_with_digit_key.ftl | 0 .../resources/structure_fixtures/variant_with_digit_key.json | 0 .../structure_fixtures/variant_with_empty_pattern.ftl | 0 .../structure_fixtures/variant_with_empty_pattern.json | 0 .../structure_fixtures/variant_with_leading_space_in_name.ftl | 0 .../variant_with_leading_space_in_name.json | 0 .../structure_fixtures/variant_with_symbol_with_space.ftl | 0 .../structure_fixtures/variant_with_symbol_with_space.json | 0 .../structure_fixtures/variants_with_two_defaults.ftl | 0 .../structure_fixtures/variants_with_two_defaults.json | 0 .../test/resources/structure_fixtures/whitespace_leading.ftl | 0 .../test/resources/structure_fixtures/whitespace_leading.json | 0 .../test/resources/structure_fixtures/whitespace_trailing.ftl | 0 .../resources/structure_fixtures/whitespace_trailing.json | 0 settings.gradle | 2 +- 323 files changed, 4 insertions(+), 4 deletions(-) rename {fluent.syntax => fluent-syntax}/README.MD (98%) rename {fluent.syntax => fluent-syntax}/build.gradle (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/MathUtils.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/StringUtils.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/Annotation.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/Attribute.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/BaseComment.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/BaseNode.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/CallArgument.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/CallArguments.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/Comment.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/Entry.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/Expression.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/FunctionReference.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/GroupComment.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/Identifier.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/InsidePlaceable.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/Junk.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/Literal.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/Message.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/MessageReference.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/NamedArgument.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/NumberLiteral.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/Pattern.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/PatternElement.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/Placeable.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/Resource.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/ResourceComment.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/SelectExpression.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/Span.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/StringLiteral.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/SyntaxNode.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/Term.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/TermReference.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/TextElement.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/TopLevel.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/VariableReference.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/Variant.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/VariantKey.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/ast/Whitespace.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/parser/FluentParser.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/parser/FluentStream.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/parser/Indent.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/parser/ParseException.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/parser/ParserStream.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/processor/Processor.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/serializer/FluentSerializer.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/serializer/SerializeException.java (100%) rename {fluent.syntax => fluent-syntax}/src/main/java/ru/di9/fluent/syntax/visitor/Visitor.java (100%) rename {fluent.syntax => fluent-syntax}/src/test/java/ru/di9/fluent/syntax/MathUtilsTest.java (100%) rename {fluent.syntax => fluent-syntax}/src/test/java/ru/di9/fluent/syntax/StringUtilsTest.java (100%) rename {fluent.syntax => fluent-syntax}/src/test/java/ru/di9/fluent/syntax/ast/BaseNodeTest.java (100%) rename {fluent.syntax => fluent-syntax}/src/test/java/ru/di9/fluent/syntax/parser/AbstractFixturesTest.java (100%) rename {fluent.syntax => fluent-syntax}/src/test/java/ru/di9/fluent/syntax/parser/FluentStreamTest.java (100%) rename {fluent.syntax => fluent-syntax}/src/test/java/ru/di9/fluent/syntax/parser/ParserStreamTest.java (100%) rename {fluent.syntax => fluent-syntax}/src/test/java/ru/di9/fluent/syntax/parser/ReferenceTest.java (100%) rename {fluent.syntax => fluent-syntax}/src/test/java/ru/di9/fluent/syntax/parser/StructureTest.java (100%) rename {fluent.syntax => fluent-syntax}/src/test/java/ru/di9/fluent/syntax/processor/ProcessorTest.java (100%) rename {fluent.syntax => fluent-syntax}/src/test/java/ru/di9/fluent/syntax/serializer/SerializeEntryTest.java (100%) rename {fluent.syntax => fluent-syntax}/src/test/java/ru/di9/fluent/syntax/serializer/SerializeResourceTest.java (100%) rename {fluent.syntax => fluent-syntax}/src/test/java/ru/di9/fluent/syntax/visitor/VisitorTest.java (100%) rename {fluent.syntax => fluent-syntax}/src/test/java/ru/di9/fluent/test/utils/AstAssert.java (100%) rename {fluent.syntax => fluent-syntax}/src/test/java/ru/di9/fluent/test/utils/BaseNodeJsonSerializer.java (100%) rename {fluent.syntax => fluent-syntax}/src/test/java/ru/di9/fluent/test/utils/FileUtils.java (100%) rename {fluent.syntax => fluent-syntax}/src/test/java/ru/di9/fluent/test/utils/NullIgnoreComparator.java (100%) rename {fluent.syntax => fluent-syntax}/src/test/java/ru/di9/fluent/test/utils/Tuple3.java (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/.gitattributes (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/any_char.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/any_char.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/astral.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/astral.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/call_expressions.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/call_expressions.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/callee_expressions.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/callee_expressions.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/comments.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/comments.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/cr.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/cr.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/crlf.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/crlf.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/eof_comment.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/eof_comment.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/eof_empty.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/eof_empty.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/eof_id.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/eof_id.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/eof_id_equals.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/eof_id_equals.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/eof_junk.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/eof_junk.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/eof_value.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/eof_value.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/escaped_characters.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/escaped_characters.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/junk.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/junk.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/leading_dots.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/leading_dots.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/literal_expressions.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/literal_expressions.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/member_expressions.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/member_expressions.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/messages.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/messages.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/mixed_entries.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/mixed_entries.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/multiline_values.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/multiline_values.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/numbers.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/numbers.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/obsolete.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/obsolete.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/placeables.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/placeables.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/reference_expressions.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/reference_expressions.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/select_expressions.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/select_expressions.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/select_indent.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/select_indent.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/sparse_entries.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/sparse_entries.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/special_chars.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/special_chars.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/tab.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/tab.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/term_parameters.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/term_parameters.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/terms.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/terms.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/variables.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/variables.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/variant_keys.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/variant_keys.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/whitespace_in_value.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/whitespace_in_value.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/zero_length.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/reference_fixtures/zero_length.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/attribute.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/backslash_in_text_element.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/block_multiline_message.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/call_expression.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/call_expression_with_message_reference.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/call_expression_with_named_number_literal.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/call_expression_with_named_string_literal.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/call_expression_with_number_literal.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/call_expression_with_positional_and_named_arguments.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/call_expression_with_string_literal.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/call_expression_with_two_named_arguments.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/call_expression_with_two_positional_arguments.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/call_expression_with_variable_reference.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/escaped_special_char_in_string_literal.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/group_comment.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/inline_multiline_message.exp.txt (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/inline_multiline_message.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/message_attribute_reference.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/message_comment.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/message_reference.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/message_with_whitespace.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/message_without_eol.exp.txt (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/message_without_eol.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/multiline_attribute.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/multiline_starting_inline.exp.txt (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/multiline_starting_inline.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/multiline_starting_inline_with_a_special_char.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/multiline_value_and_attributes.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/multiline_variant.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/multiline_variant_with_first_line_inline.exp.txt (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/multiline_variant_with_first_line_inline.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/multiline_with_placeable.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/nested_placeable.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/nested_select_expression.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/number_literal.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/resource_comment.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/select_expression.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/select_expression_in_block_pattern.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/select_expression_in_inline_pattern.exp.txt (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/select_expression_in_inline_pattern.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/select_expression_in_inline_pattern_starting_with_a_special_char.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/select_expression_in_multiline_pattern.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/selector_number_literal.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/selector_string_literal.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/selector_term_attribute_reference.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/selector_variable_reference.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/standalone_comment.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/string_literal.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/term.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/term_reference.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/term_reference_call.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/two_attribute.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/two_simple_messages.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/unicode_escape_sequence.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/value_and_attributes.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/variable_reference.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/serialized/variant_key_number.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/.gitattributes (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/attribute_expression_with_wrong_attr.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/attribute_expression_with_wrong_attr.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/attribute_of_private_as_placeable.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/attribute_of_private_as_placeable.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/attribute_of_public_as_selector.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/attribute_of_public_as_selector.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/attribute_starts_from_nl.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/attribute_starts_from_nl.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/attribute_with_empty_pattern.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/attribute_with_empty_pattern.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/attribute_without_equal_sign.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/attribute_without_equal_sign.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/blank_lines.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/blank_lines.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/broken_number.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/broken_number.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/call_expression_errors.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/call_expression_errors.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/comment_with_eof.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/comment_with_eof.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/crlf.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/crlf.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/dash_at_eof.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/dash_at_eof.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/elements_indent.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/elements_indent.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/empty_resource.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/empty_resource.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/empty_resource_with_ws.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/empty_resource_with_ws.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/escape_sequences.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/escape_sequences.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/expressions_call_args.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/expressions_call_args.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/indent.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/indent.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/junk.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/junk.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/leading_dots.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/leading_dots.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/leading_empty_lines.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/leading_empty_lines.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/leading_empty_lines_with_ws.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/leading_empty_lines_with_ws.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/message_reference_as_selector.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/message_reference_as_selector.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/message_with_empty_multiline_pattern.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/message_with_empty_multiline_pattern.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/message_with_empty_pattern.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/message_with_empty_pattern.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/multiline-comment.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/multiline-comment.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/multiline_pattern.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/multiline_pattern.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/multiline_string.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/multiline_string.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/multiline_with_non_empty_first_line.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/multiline_with_non_empty_first_line.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/multiline_with_placeables.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/multiline_with_placeables.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/non_id_attribute_name.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/non_id_attribute_name.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/placeable_at_eol.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/placeable_at_eol.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/placeable_at_line_extremes.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/placeable_at_line_extremes.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/placeable_in_placeable.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/placeable_in_placeable.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/placeable_without_close_bracket.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/placeable_without_close_bracket.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/resource_comment.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/resource_comment.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/resource_comment_trailing_line.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/resource_comment_trailing_line.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/second_attribute_starts_from_nl.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/second_attribute_starts_from_nl.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/select_expression_with_two_selectors.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/select_expression_with_two_selectors.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/select_expression_without_arrow.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/select_expression_without_arrow.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/select_expression_without_variants.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/select_expression_without_variants.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/select_expressions.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/select_expressions.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/simple_message.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/simple_message.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/single_char_id.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/single_char_id.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/sparse-messages.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/sparse-messages.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/standalone_comment.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/standalone_comment.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/standalone_identifier.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/standalone_identifier.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/term.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/term.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/term_with_empty_pattern.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/term_with_empty_pattern.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/unclosed.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/unclosed.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/unclosed_empty_placeable_error.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/unclosed_empty_placeable_error.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/unknown_entry_start.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/unknown_entry_start.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/variant_ends_abruptly.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/variant_ends_abruptly.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/variant_keys.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/variant_keys.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/variant_starts_from_nl.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/variant_starts_from_nl.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/variant_with_digit_key.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/variant_with_digit_key.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/variant_with_empty_pattern.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/variant_with_empty_pattern.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/variant_with_leading_space_in_name.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/variant_with_leading_space_in_name.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/variant_with_symbol_with_space.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/variant_with_symbol_with_space.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/variants_with_two_defaults.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/variants_with_two_defaults.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/whitespace_leading.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/whitespace_leading.json (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/whitespace_trailing.ftl (100%) rename {fluent.syntax => fluent-syntax}/src/test/resources/structure_fixtures/whitespace_trailing.json (100%) diff --git a/README.MD b/README.MD index 9569e8b..db3ab56 100644 --- a/README.MD +++ b/README.MD @@ -4,7 +4,7 @@ fluent-java состоит из следующих пакетов: -## fluent.syntax +## fluent-syntax -Пакет [syntax](fluent.syntax) включает в себя синтаксический анализатор (parser), сериализатор (serializer), +Пакет [fluent-syntax](fluent-syntax) включает в себя синтаксический анализатор (parser), сериализатор (serializer), и инструменты обхода, такие как Visitor. Данный пакет понадобится при работе над инструментами для Fluent в Java. diff --git a/fluent.syntax/README.MD b/fluent-syntax/README.MD similarity index 98% rename from fluent.syntax/README.MD rename to fluent-syntax/README.MD index 9d3b9f3..b5b2b3d 100644 --- a/fluent.syntax/README.MD +++ b/fluent-syntax/README.MD @@ -1,4 +1,4 @@ -# fluent.syntax +# fluent-syntax Чтение, запись и прочие преобразования файлов [Fluent](https://projectfluent.org/). diff --git a/fluent.syntax/build.gradle b/fluent-syntax/build.gradle similarity index 100% rename from fluent.syntax/build.gradle rename to fluent-syntax/build.gradle diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/MathUtils.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/MathUtils.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/MathUtils.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/MathUtils.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/StringUtils.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/StringUtils.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/StringUtils.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/StringUtils.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Annotation.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Annotation.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Annotation.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Annotation.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Attribute.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Attribute.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Attribute.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Attribute.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/BaseComment.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/BaseComment.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/BaseComment.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/BaseComment.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/BaseNode.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/BaseNode.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/BaseNode.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/BaseNode.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/CallArgument.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/CallArgument.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/CallArgument.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/CallArgument.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/CallArguments.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/CallArguments.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/CallArguments.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/CallArguments.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Comment.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Comment.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Comment.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Comment.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Entry.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Entry.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Entry.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Entry.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Expression.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Expression.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Expression.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Expression.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/FunctionReference.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/FunctionReference.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/FunctionReference.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/FunctionReference.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/GroupComment.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/GroupComment.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/GroupComment.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/GroupComment.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Identifier.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Identifier.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Identifier.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Identifier.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/InsidePlaceable.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/InsidePlaceable.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/InsidePlaceable.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/InsidePlaceable.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Junk.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Junk.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Junk.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Junk.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Literal.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Literal.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Literal.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Literal.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Message.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Message.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Message.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Message.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/MessageReference.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/MessageReference.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/MessageReference.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/MessageReference.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/NamedArgument.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/NamedArgument.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/NamedArgument.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/NamedArgument.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/NumberLiteral.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/NumberLiteral.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/NumberLiteral.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/NumberLiteral.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Pattern.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Pattern.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Pattern.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Pattern.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/PatternElement.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/PatternElement.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/PatternElement.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/PatternElement.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Placeable.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Placeable.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Placeable.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Placeable.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Resource.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Resource.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Resource.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Resource.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/ResourceComment.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/ResourceComment.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/ResourceComment.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/ResourceComment.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/SelectExpression.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/SelectExpression.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/SelectExpression.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/SelectExpression.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Span.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Span.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Span.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Span.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/StringLiteral.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/StringLiteral.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/StringLiteral.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/StringLiteral.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/SyntaxNode.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/SyntaxNode.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/SyntaxNode.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/SyntaxNode.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Term.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Term.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Term.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Term.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/TermReference.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/TermReference.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/TermReference.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/TermReference.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/TextElement.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/TextElement.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/TextElement.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/TextElement.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/TopLevel.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/TopLevel.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/TopLevel.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/TopLevel.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/VariableReference.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/VariableReference.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/VariableReference.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/VariableReference.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Variant.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Variant.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Variant.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Variant.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/VariantKey.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/VariantKey.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/VariantKey.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/VariantKey.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Whitespace.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Whitespace.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/ast/Whitespace.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/ast/Whitespace.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentParser.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentParser.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentParser.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentParser.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentStream.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentStream.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentStream.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentStream.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/Indent.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/Indent.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/Indent.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/Indent.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/ParseException.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/ParseException.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/ParseException.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/ParseException.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/ParserStream.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/ParserStream.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/parser/ParserStream.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/ParserStream.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/processor/Processor.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/processor/Processor.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/processor/Processor.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/processor/Processor.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/serializer/FluentSerializer.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/serializer/FluentSerializer.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/serializer/FluentSerializer.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/serializer/FluentSerializer.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/serializer/SerializeException.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/serializer/SerializeException.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/serializer/SerializeException.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/serializer/SerializeException.java diff --git a/fluent.syntax/src/main/java/ru/di9/fluent/syntax/visitor/Visitor.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/visitor/Visitor.java similarity index 100% rename from fluent.syntax/src/main/java/ru/di9/fluent/syntax/visitor/Visitor.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/visitor/Visitor.java diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/MathUtilsTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/MathUtilsTest.java similarity index 100% rename from fluent.syntax/src/test/java/ru/di9/fluent/syntax/MathUtilsTest.java rename to fluent-syntax/src/test/java/ru/di9/fluent/syntax/MathUtilsTest.java diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/StringUtilsTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/StringUtilsTest.java similarity index 100% rename from fluent.syntax/src/test/java/ru/di9/fluent/syntax/StringUtilsTest.java rename to fluent-syntax/src/test/java/ru/di9/fluent/syntax/StringUtilsTest.java diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/ast/BaseNodeTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/ast/BaseNodeTest.java similarity index 100% rename from fluent.syntax/src/test/java/ru/di9/fluent/syntax/ast/BaseNodeTest.java rename to fluent-syntax/src/test/java/ru/di9/fluent/syntax/ast/BaseNodeTest.java diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/AbstractFixturesTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/AbstractFixturesTest.java similarity index 100% rename from fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/AbstractFixturesTest.java rename to fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/AbstractFixturesTest.java diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/FluentStreamTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/FluentStreamTest.java similarity index 100% rename from fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/FluentStreamTest.java rename to fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/FluentStreamTest.java diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/ParserStreamTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/ParserStreamTest.java similarity index 100% rename from fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/ParserStreamTest.java rename to fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/ParserStreamTest.java diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/ReferenceTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/ReferenceTest.java similarity index 100% rename from fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/ReferenceTest.java rename to fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/ReferenceTest.java diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/StructureTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/StructureTest.java similarity index 100% rename from fluent.syntax/src/test/java/ru/di9/fluent/syntax/parser/StructureTest.java rename to fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/StructureTest.java diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/processor/ProcessorTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/processor/ProcessorTest.java similarity index 100% rename from fluent.syntax/src/test/java/ru/di9/fluent/syntax/processor/ProcessorTest.java rename to fluent-syntax/src/test/java/ru/di9/fluent/syntax/processor/ProcessorTest.java diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeEntryTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeEntryTest.java similarity index 100% rename from fluent.syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeEntryTest.java rename to fluent-syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeEntryTest.java diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeResourceTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeResourceTest.java similarity index 100% rename from fluent.syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeResourceTest.java rename to fluent-syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeResourceTest.java diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/syntax/visitor/VisitorTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/visitor/VisitorTest.java similarity index 100% rename from fluent.syntax/src/test/java/ru/di9/fluent/syntax/visitor/VisitorTest.java rename to fluent-syntax/src/test/java/ru/di9/fluent/syntax/visitor/VisitorTest.java diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/AstAssert.java b/fluent-syntax/src/test/java/ru/di9/fluent/test/utils/AstAssert.java similarity index 100% rename from fluent.syntax/src/test/java/ru/di9/fluent/test/utils/AstAssert.java rename to fluent-syntax/src/test/java/ru/di9/fluent/test/utils/AstAssert.java diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/BaseNodeJsonSerializer.java b/fluent-syntax/src/test/java/ru/di9/fluent/test/utils/BaseNodeJsonSerializer.java similarity index 100% rename from fluent.syntax/src/test/java/ru/di9/fluent/test/utils/BaseNodeJsonSerializer.java rename to fluent-syntax/src/test/java/ru/di9/fluent/test/utils/BaseNodeJsonSerializer.java diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/FileUtils.java b/fluent-syntax/src/test/java/ru/di9/fluent/test/utils/FileUtils.java similarity index 100% rename from fluent.syntax/src/test/java/ru/di9/fluent/test/utils/FileUtils.java rename to fluent-syntax/src/test/java/ru/di9/fluent/test/utils/FileUtils.java diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/NullIgnoreComparator.java b/fluent-syntax/src/test/java/ru/di9/fluent/test/utils/NullIgnoreComparator.java similarity index 100% rename from fluent.syntax/src/test/java/ru/di9/fluent/test/utils/NullIgnoreComparator.java rename to fluent-syntax/src/test/java/ru/di9/fluent/test/utils/NullIgnoreComparator.java diff --git a/fluent.syntax/src/test/java/ru/di9/fluent/test/utils/Tuple3.java b/fluent-syntax/src/test/java/ru/di9/fluent/test/utils/Tuple3.java similarity index 100% rename from fluent.syntax/src/test/java/ru/di9/fluent/test/utils/Tuple3.java rename to fluent-syntax/src/test/java/ru/di9/fluent/test/utils/Tuple3.java diff --git a/fluent.syntax/src/test/resources/reference_fixtures/.gitattributes b/fluent-syntax/src/test/resources/reference_fixtures/.gitattributes similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/.gitattributes rename to fluent-syntax/src/test/resources/reference_fixtures/.gitattributes diff --git a/fluent.syntax/src/test/resources/reference_fixtures/any_char.ftl b/fluent-syntax/src/test/resources/reference_fixtures/any_char.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/any_char.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/any_char.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/any_char.json b/fluent-syntax/src/test/resources/reference_fixtures/any_char.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/any_char.json rename to fluent-syntax/src/test/resources/reference_fixtures/any_char.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/astral.ftl b/fluent-syntax/src/test/resources/reference_fixtures/astral.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/astral.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/astral.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/astral.json b/fluent-syntax/src/test/resources/reference_fixtures/astral.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/astral.json rename to fluent-syntax/src/test/resources/reference_fixtures/astral.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/call_expressions.ftl b/fluent-syntax/src/test/resources/reference_fixtures/call_expressions.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/call_expressions.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/call_expressions.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/call_expressions.json b/fluent-syntax/src/test/resources/reference_fixtures/call_expressions.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/call_expressions.json rename to fluent-syntax/src/test/resources/reference_fixtures/call_expressions.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/callee_expressions.ftl b/fluent-syntax/src/test/resources/reference_fixtures/callee_expressions.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/callee_expressions.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/callee_expressions.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/callee_expressions.json b/fluent-syntax/src/test/resources/reference_fixtures/callee_expressions.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/callee_expressions.json rename to fluent-syntax/src/test/resources/reference_fixtures/callee_expressions.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/comments.ftl b/fluent-syntax/src/test/resources/reference_fixtures/comments.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/comments.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/comments.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/comments.json b/fluent-syntax/src/test/resources/reference_fixtures/comments.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/comments.json rename to fluent-syntax/src/test/resources/reference_fixtures/comments.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/cr.ftl b/fluent-syntax/src/test/resources/reference_fixtures/cr.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/cr.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/cr.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/cr.json b/fluent-syntax/src/test/resources/reference_fixtures/cr.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/cr.json rename to fluent-syntax/src/test/resources/reference_fixtures/cr.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/crlf.ftl b/fluent-syntax/src/test/resources/reference_fixtures/crlf.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/crlf.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/crlf.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/crlf.json b/fluent-syntax/src/test/resources/reference_fixtures/crlf.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/crlf.json rename to fluent-syntax/src/test/resources/reference_fixtures/crlf.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_comment.ftl b/fluent-syntax/src/test/resources/reference_fixtures/eof_comment.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/eof_comment.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/eof_comment.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_comment.json b/fluent-syntax/src/test/resources/reference_fixtures/eof_comment.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/eof_comment.json rename to fluent-syntax/src/test/resources/reference_fixtures/eof_comment.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_empty.ftl b/fluent-syntax/src/test/resources/reference_fixtures/eof_empty.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/eof_empty.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/eof_empty.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_empty.json b/fluent-syntax/src/test/resources/reference_fixtures/eof_empty.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/eof_empty.json rename to fluent-syntax/src/test/resources/reference_fixtures/eof_empty.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_id.ftl b/fluent-syntax/src/test/resources/reference_fixtures/eof_id.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/eof_id.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/eof_id.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_id.json b/fluent-syntax/src/test/resources/reference_fixtures/eof_id.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/eof_id.json rename to fluent-syntax/src/test/resources/reference_fixtures/eof_id.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_id_equals.ftl b/fluent-syntax/src/test/resources/reference_fixtures/eof_id_equals.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/eof_id_equals.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/eof_id_equals.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_id_equals.json b/fluent-syntax/src/test/resources/reference_fixtures/eof_id_equals.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/eof_id_equals.json rename to fluent-syntax/src/test/resources/reference_fixtures/eof_id_equals.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_junk.ftl b/fluent-syntax/src/test/resources/reference_fixtures/eof_junk.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/eof_junk.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/eof_junk.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_junk.json b/fluent-syntax/src/test/resources/reference_fixtures/eof_junk.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/eof_junk.json rename to fluent-syntax/src/test/resources/reference_fixtures/eof_junk.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_value.ftl b/fluent-syntax/src/test/resources/reference_fixtures/eof_value.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/eof_value.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/eof_value.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/eof_value.json b/fluent-syntax/src/test/resources/reference_fixtures/eof_value.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/eof_value.json rename to fluent-syntax/src/test/resources/reference_fixtures/eof_value.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/escaped_characters.ftl b/fluent-syntax/src/test/resources/reference_fixtures/escaped_characters.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/escaped_characters.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/escaped_characters.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/escaped_characters.json b/fluent-syntax/src/test/resources/reference_fixtures/escaped_characters.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/escaped_characters.json rename to fluent-syntax/src/test/resources/reference_fixtures/escaped_characters.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/junk.ftl b/fluent-syntax/src/test/resources/reference_fixtures/junk.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/junk.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/junk.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/junk.json b/fluent-syntax/src/test/resources/reference_fixtures/junk.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/junk.json rename to fluent-syntax/src/test/resources/reference_fixtures/junk.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/leading_dots.ftl b/fluent-syntax/src/test/resources/reference_fixtures/leading_dots.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/leading_dots.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/leading_dots.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/leading_dots.json b/fluent-syntax/src/test/resources/reference_fixtures/leading_dots.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/leading_dots.json rename to fluent-syntax/src/test/resources/reference_fixtures/leading_dots.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/literal_expressions.ftl b/fluent-syntax/src/test/resources/reference_fixtures/literal_expressions.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/literal_expressions.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/literal_expressions.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/literal_expressions.json b/fluent-syntax/src/test/resources/reference_fixtures/literal_expressions.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/literal_expressions.json rename to fluent-syntax/src/test/resources/reference_fixtures/literal_expressions.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/member_expressions.ftl b/fluent-syntax/src/test/resources/reference_fixtures/member_expressions.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/member_expressions.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/member_expressions.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/member_expressions.json b/fluent-syntax/src/test/resources/reference_fixtures/member_expressions.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/member_expressions.json rename to fluent-syntax/src/test/resources/reference_fixtures/member_expressions.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/messages.ftl b/fluent-syntax/src/test/resources/reference_fixtures/messages.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/messages.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/messages.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/messages.json b/fluent-syntax/src/test/resources/reference_fixtures/messages.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/messages.json rename to fluent-syntax/src/test/resources/reference_fixtures/messages.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/mixed_entries.ftl b/fluent-syntax/src/test/resources/reference_fixtures/mixed_entries.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/mixed_entries.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/mixed_entries.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/mixed_entries.json b/fluent-syntax/src/test/resources/reference_fixtures/mixed_entries.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/mixed_entries.json rename to fluent-syntax/src/test/resources/reference_fixtures/mixed_entries.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/multiline_values.ftl b/fluent-syntax/src/test/resources/reference_fixtures/multiline_values.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/multiline_values.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/multiline_values.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/multiline_values.json b/fluent-syntax/src/test/resources/reference_fixtures/multiline_values.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/multiline_values.json rename to fluent-syntax/src/test/resources/reference_fixtures/multiline_values.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/numbers.ftl b/fluent-syntax/src/test/resources/reference_fixtures/numbers.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/numbers.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/numbers.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/numbers.json b/fluent-syntax/src/test/resources/reference_fixtures/numbers.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/numbers.json rename to fluent-syntax/src/test/resources/reference_fixtures/numbers.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/obsolete.ftl b/fluent-syntax/src/test/resources/reference_fixtures/obsolete.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/obsolete.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/obsolete.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/obsolete.json b/fluent-syntax/src/test/resources/reference_fixtures/obsolete.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/obsolete.json rename to fluent-syntax/src/test/resources/reference_fixtures/obsolete.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/placeables.ftl b/fluent-syntax/src/test/resources/reference_fixtures/placeables.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/placeables.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/placeables.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/placeables.json b/fluent-syntax/src/test/resources/reference_fixtures/placeables.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/placeables.json rename to fluent-syntax/src/test/resources/reference_fixtures/placeables.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/reference_expressions.ftl b/fluent-syntax/src/test/resources/reference_fixtures/reference_expressions.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/reference_expressions.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/reference_expressions.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/reference_expressions.json b/fluent-syntax/src/test/resources/reference_fixtures/reference_expressions.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/reference_expressions.json rename to fluent-syntax/src/test/resources/reference_fixtures/reference_expressions.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/select_expressions.ftl b/fluent-syntax/src/test/resources/reference_fixtures/select_expressions.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/select_expressions.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/select_expressions.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/select_expressions.json b/fluent-syntax/src/test/resources/reference_fixtures/select_expressions.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/select_expressions.json rename to fluent-syntax/src/test/resources/reference_fixtures/select_expressions.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/select_indent.ftl b/fluent-syntax/src/test/resources/reference_fixtures/select_indent.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/select_indent.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/select_indent.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/select_indent.json b/fluent-syntax/src/test/resources/reference_fixtures/select_indent.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/select_indent.json rename to fluent-syntax/src/test/resources/reference_fixtures/select_indent.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/sparse_entries.ftl b/fluent-syntax/src/test/resources/reference_fixtures/sparse_entries.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/sparse_entries.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/sparse_entries.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/sparse_entries.json b/fluent-syntax/src/test/resources/reference_fixtures/sparse_entries.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/sparse_entries.json rename to fluent-syntax/src/test/resources/reference_fixtures/sparse_entries.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/special_chars.ftl b/fluent-syntax/src/test/resources/reference_fixtures/special_chars.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/special_chars.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/special_chars.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/special_chars.json b/fluent-syntax/src/test/resources/reference_fixtures/special_chars.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/special_chars.json rename to fluent-syntax/src/test/resources/reference_fixtures/special_chars.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/tab.ftl b/fluent-syntax/src/test/resources/reference_fixtures/tab.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/tab.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/tab.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/tab.json b/fluent-syntax/src/test/resources/reference_fixtures/tab.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/tab.json rename to fluent-syntax/src/test/resources/reference_fixtures/tab.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/term_parameters.ftl b/fluent-syntax/src/test/resources/reference_fixtures/term_parameters.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/term_parameters.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/term_parameters.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/term_parameters.json b/fluent-syntax/src/test/resources/reference_fixtures/term_parameters.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/term_parameters.json rename to fluent-syntax/src/test/resources/reference_fixtures/term_parameters.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/terms.ftl b/fluent-syntax/src/test/resources/reference_fixtures/terms.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/terms.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/terms.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/terms.json b/fluent-syntax/src/test/resources/reference_fixtures/terms.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/terms.json rename to fluent-syntax/src/test/resources/reference_fixtures/terms.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/variables.ftl b/fluent-syntax/src/test/resources/reference_fixtures/variables.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/variables.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/variables.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/variables.json b/fluent-syntax/src/test/resources/reference_fixtures/variables.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/variables.json rename to fluent-syntax/src/test/resources/reference_fixtures/variables.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/variant_keys.ftl b/fluent-syntax/src/test/resources/reference_fixtures/variant_keys.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/variant_keys.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/variant_keys.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/variant_keys.json b/fluent-syntax/src/test/resources/reference_fixtures/variant_keys.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/variant_keys.json rename to fluent-syntax/src/test/resources/reference_fixtures/variant_keys.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/whitespace_in_value.ftl b/fluent-syntax/src/test/resources/reference_fixtures/whitespace_in_value.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/whitespace_in_value.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/whitespace_in_value.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/whitespace_in_value.json b/fluent-syntax/src/test/resources/reference_fixtures/whitespace_in_value.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/whitespace_in_value.json rename to fluent-syntax/src/test/resources/reference_fixtures/whitespace_in_value.json diff --git a/fluent.syntax/src/test/resources/reference_fixtures/zero_length.ftl b/fluent-syntax/src/test/resources/reference_fixtures/zero_length.ftl similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/zero_length.ftl rename to fluent-syntax/src/test/resources/reference_fixtures/zero_length.ftl diff --git a/fluent.syntax/src/test/resources/reference_fixtures/zero_length.json b/fluent-syntax/src/test/resources/reference_fixtures/zero_length.json similarity index 100% rename from fluent.syntax/src/test/resources/reference_fixtures/zero_length.json rename to fluent-syntax/src/test/resources/reference_fixtures/zero_length.json diff --git a/fluent.syntax/src/test/resources/serialized/attribute.ftl b/fluent-syntax/src/test/resources/serialized/attribute.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/attribute.ftl rename to fluent-syntax/src/test/resources/serialized/attribute.ftl diff --git a/fluent.syntax/src/test/resources/serialized/backslash_in_text_element.ftl b/fluent-syntax/src/test/resources/serialized/backslash_in_text_element.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/backslash_in_text_element.ftl rename to fluent-syntax/src/test/resources/serialized/backslash_in_text_element.ftl diff --git a/fluent.syntax/src/test/resources/serialized/block_multiline_message.ftl b/fluent-syntax/src/test/resources/serialized/block_multiline_message.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/block_multiline_message.ftl rename to fluent-syntax/src/test/resources/serialized/block_multiline_message.ftl diff --git a/fluent.syntax/src/test/resources/serialized/call_expression.ftl b/fluent-syntax/src/test/resources/serialized/call_expression.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/call_expression.ftl rename to fluent-syntax/src/test/resources/serialized/call_expression.ftl diff --git a/fluent.syntax/src/test/resources/serialized/call_expression_with_message_reference.ftl b/fluent-syntax/src/test/resources/serialized/call_expression_with_message_reference.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/call_expression_with_message_reference.ftl rename to fluent-syntax/src/test/resources/serialized/call_expression_with_message_reference.ftl diff --git a/fluent.syntax/src/test/resources/serialized/call_expression_with_named_number_literal.ftl b/fluent-syntax/src/test/resources/serialized/call_expression_with_named_number_literal.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/call_expression_with_named_number_literal.ftl rename to fluent-syntax/src/test/resources/serialized/call_expression_with_named_number_literal.ftl diff --git a/fluent.syntax/src/test/resources/serialized/call_expression_with_named_string_literal.ftl b/fluent-syntax/src/test/resources/serialized/call_expression_with_named_string_literal.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/call_expression_with_named_string_literal.ftl rename to fluent-syntax/src/test/resources/serialized/call_expression_with_named_string_literal.ftl diff --git a/fluent.syntax/src/test/resources/serialized/call_expression_with_number_literal.ftl b/fluent-syntax/src/test/resources/serialized/call_expression_with_number_literal.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/call_expression_with_number_literal.ftl rename to fluent-syntax/src/test/resources/serialized/call_expression_with_number_literal.ftl diff --git a/fluent.syntax/src/test/resources/serialized/call_expression_with_positional_and_named_arguments.ftl b/fluent-syntax/src/test/resources/serialized/call_expression_with_positional_and_named_arguments.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/call_expression_with_positional_and_named_arguments.ftl rename to fluent-syntax/src/test/resources/serialized/call_expression_with_positional_and_named_arguments.ftl diff --git a/fluent.syntax/src/test/resources/serialized/call_expression_with_string_literal.ftl b/fluent-syntax/src/test/resources/serialized/call_expression_with_string_literal.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/call_expression_with_string_literal.ftl rename to fluent-syntax/src/test/resources/serialized/call_expression_with_string_literal.ftl diff --git a/fluent.syntax/src/test/resources/serialized/call_expression_with_two_named_arguments.ftl b/fluent-syntax/src/test/resources/serialized/call_expression_with_two_named_arguments.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/call_expression_with_two_named_arguments.ftl rename to fluent-syntax/src/test/resources/serialized/call_expression_with_two_named_arguments.ftl diff --git a/fluent.syntax/src/test/resources/serialized/call_expression_with_two_positional_arguments.ftl b/fluent-syntax/src/test/resources/serialized/call_expression_with_two_positional_arguments.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/call_expression_with_two_positional_arguments.ftl rename to fluent-syntax/src/test/resources/serialized/call_expression_with_two_positional_arguments.ftl diff --git a/fluent.syntax/src/test/resources/serialized/call_expression_with_variable_reference.ftl b/fluent-syntax/src/test/resources/serialized/call_expression_with_variable_reference.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/call_expression_with_variable_reference.ftl rename to fluent-syntax/src/test/resources/serialized/call_expression_with_variable_reference.ftl diff --git a/fluent.syntax/src/test/resources/serialized/escaped_special_char_in_string_literal.ftl b/fluent-syntax/src/test/resources/serialized/escaped_special_char_in_string_literal.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/escaped_special_char_in_string_literal.ftl rename to fluent-syntax/src/test/resources/serialized/escaped_special_char_in_string_literal.ftl diff --git a/fluent.syntax/src/test/resources/serialized/group_comment.ftl b/fluent-syntax/src/test/resources/serialized/group_comment.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/group_comment.ftl rename to fluent-syntax/src/test/resources/serialized/group_comment.ftl diff --git a/fluent.syntax/src/test/resources/serialized/inline_multiline_message.exp.txt b/fluent-syntax/src/test/resources/serialized/inline_multiline_message.exp.txt similarity index 100% rename from fluent.syntax/src/test/resources/serialized/inline_multiline_message.exp.txt rename to fluent-syntax/src/test/resources/serialized/inline_multiline_message.exp.txt diff --git a/fluent.syntax/src/test/resources/serialized/inline_multiline_message.ftl b/fluent-syntax/src/test/resources/serialized/inline_multiline_message.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/inline_multiline_message.ftl rename to fluent-syntax/src/test/resources/serialized/inline_multiline_message.ftl diff --git a/fluent.syntax/src/test/resources/serialized/message_attribute_reference.ftl b/fluent-syntax/src/test/resources/serialized/message_attribute_reference.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/message_attribute_reference.ftl rename to fluent-syntax/src/test/resources/serialized/message_attribute_reference.ftl diff --git a/fluent.syntax/src/test/resources/serialized/message_comment.ftl b/fluent-syntax/src/test/resources/serialized/message_comment.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/message_comment.ftl rename to fluent-syntax/src/test/resources/serialized/message_comment.ftl diff --git a/fluent.syntax/src/test/resources/serialized/message_reference.ftl b/fluent-syntax/src/test/resources/serialized/message_reference.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/message_reference.ftl rename to fluent-syntax/src/test/resources/serialized/message_reference.ftl diff --git a/fluent.syntax/src/test/resources/serialized/message_with_whitespace.ftl b/fluent-syntax/src/test/resources/serialized/message_with_whitespace.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/message_with_whitespace.ftl rename to fluent-syntax/src/test/resources/serialized/message_with_whitespace.ftl diff --git a/fluent.syntax/src/test/resources/serialized/message_without_eol.exp.txt b/fluent-syntax/src/test/resources/serialized/message_without_eol.exp.txt similarity index 100% rename from fluent.syntax/src/test/resources/serialized/message_without_eol.exp.txt rename to fluent-syntax/src/test/resources/serialized/message_without_eol.exp.txt diff --git a/fluent.syntax/src/test/resources/serialized/message_without_eol.ftl b/fluent-syntax/src/test/resources/serialized/message_without_eol.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/message_without_eol.ftl rename to fluent-syntax/src/test/resources/serialized/message_without_eol.ftl diff --git a/fluent.syntax/src/test/resources/serialized/multiline_attribute.ftl b/fluent-syntax/src/test/resources/serialized/multiline_attribute.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/multiline_attribute.ftl rename to fluent-syntax/src/test/resources/serialized/multiline_attribute.ftl diff --git a/fluent.syntax/src/test/resources/serialized/multiline_starting_inline.exp.txt b/fluent-syntax/src/test/resources/serialized/multiline_starting_inline.exp.txt similarity index 100% rename from fluent.syntax/src/test/resources/serialized/multiline_starting_inline.exp.txt rename to fluent-syntax/src/test/resources/serialized/multiline_starting_inline.exp.txt diff --git a/fluent.syntax/src/test/resources/serialized/multiline_starting_inline.ftl b/fluent-syntax/src/test/resources/serialized/multiline_starting_inline.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/multiline_starting_inline.ftl rename to fluent-syntax/src/test/resources/serialized/multiline_starting_inline.ftl diff --git a/fluent.syntax/src/test/resources/serialized/multiline_starting_inline_with_a_special_char.ftl b/fluent-syntax/src/test/resources/serialized/multiline_starting_inline_with_a_special_char.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/multiline_starting_inline_with_a_special_char.ftl rename to fluent-syntax/src/test/resources/serialized/multiline_starting_inline_with_a_special_char.ftl diff --git a/fluent.syntax/src/test/resources/serialized/multiline_value_and_attributes.ftl b/fluent-syntax/src/test/resources/serialized/multiline_value_and_attributes.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/multiline_value_and_attributes.ftl rename to fluent-syntax/src/test/resources/serialized/multiline_value_and_attributes.ftl diff --git a/fluent.syntax/src/test/resources/serialized/multiline_variant.ftl b/fluent-syntax/src/test/resources/serialized/multiline_variant.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/multiline_variant.ftl rename to fluent-syntax/src/test/resources/serialized/multiline_variant.ftl diff --git a/fluent.syntax/src/test/resources/serialized/multiline_variant_with_first_line_inline.exp.txt b/fluent-syntax/src/test/resources/serialized/multiline_variant_with_first_line_inline.exp.txt similarity index 100% rename from fluent.syntax/src/test/resources/serialized/multiline_variant_with_first_line_inline.exp.txt rename to fluent-syntax/src/test/resources/serialized/multiline_variant_with_first_line_inline.exp.txt diff --git a/fluent.syntax/src/test/resources/serialized/multiline_variant_with_first_line_inline.ftl b/fluent-syntax/src/test/resources/serialized/multiline_variant_with_first_line_inline.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/multiline_variant_with_first_line_inline.ftl rename to fluent-syntax/src/test/resources/serialized/multiline_variant_with_first_line_inline.ftl diff --git a/fluent.syntax/src/test/resources/serialized/multiline_with_placeable.ftl b/fluent-syntax/src/test/resources/serialized/multiline_with_placeable.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/multiline_with_placeable.ftl rename to fluent-syntax/src/test/resources/serialized/multiline_with_placeable.ftl diff --git a/fluent.syntax/src/test/resources/serialized/nested_placeable.ftl b/fluent-syntax/src/test/resources/serialized/nested_placeable.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/nested_placeable.ftl rename to fluent-syntax/src/test/resources/serialized/nested_placeable.ftl diff --git a/fluent.syntax/src/test/resources/serialized/nested_select_expression.ftl b/fluent-syntax/src/test/resources/serialized/nested_select_expression.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/nested_select_expression.ftl rename to fluent-syntax/src/test/resources/serialized/nested_select_expression.ftl diff --git a/fluent.syntax/src/test/resources/serialized/number_literal.ftl b/fluent-syntax/src/test/resources/serialized/number_literal.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/number_literal.ftl rename to fluent-syntax/src/test/resources/serialized/number_literal.ftl diff --git a/fluent.syntax/src/test/resources/serialized/resource_comment.ftl b/fluent-syntax/src/test/resources/serialized/resource_comment.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/resource_comment.ftl rename to fluent-syntax/src/test/resources/serialized/resource_comment.ftl diff --git a/fluent.syntax/src/test/resources/serialized/select_expression.ftl b/fluent-syntax/src/test/resources/serialized/select_expression.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/select_expression.ftl rename to fluent-syntax/src/test/resources/serialized/select_expression.ftl diff --git a/fluent.syntax/src/test/resources/serialized/select_expression_in_block_pattern.ftl b/fluent-syntax/src/test/resources/serialized/select_expression_in_block_pattern.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/select_expression_in_block_pattern.ftl rename to fluent-syntax/src/test/resources/serialized/select_expression_in_block_pattern.ftl diff --git a/fluent.syntax/src/test/resources/serialized/select_expression_in_inline_pattern.exp.txt b/fluent-syntax/src/test/resources/serialized/select_expression_in_inline_pattern.exp.txt similarity index 100% rename from fluent.syntax/src/test/resources/serialized/select_expression_in_inline_pattern.exp.txt rename to fluent-syntax/src/test/resources/serialized/select_expression_in_inline_pattern.exp.txt diff --git a/fluent.syntax/src/test/resources/serialized/select_expression_in_inline_pattern.ftl b/fluent-syntax/src/test/resources/serialized/select_expression_in_inline_pattern.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/select_expression_in_inline_pattern.ftl rename to fluent-syntax/src/test/resources/serialized/select_expression_in_inline_pattern.ftl diff --git a/fluent.syntax/src/test/resources/serialized/select_expression_in_inline_pattern_starting_with_a_special_char.ftl b/fluent-syntax/src/test/resources/serialized/select_expression_in_inline_pattern_starting_with_a_special_char.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/select_expression_in_inline_pattern_starting_with_a_special_char.ftl rename to fluent-syntax/src/test/resources/serialized/select_expression_in_inline_pattern_starting_with_a_special_char.ftl diff --git a/fluent.syntax/src/test/resources/serialized/select_expression_in_multiline_pattern.ftl b/fluent-syntax/src/test/resources/serialized/select_expression_in_multiline_pattern.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/select_expression_in_multiline_pattern.ftl rename to fluent-syntax/src/test/resources/serialized/select_expression_in_multiline_pattern.ftl diff --git a/fluent.syntax/src/test/resources/serialized/selector_number_literal.ftl b/fluent-syntax/src/test/resources/serialized/selector_number_literal.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/selector_number_literal.ftl rename to fluent-syntax/src/test/resources/serialized/selector_number_literal.ftl diff --git a/fluent.syntax/src/test/resources/serialized/selector_string_literal.ftl b/fluent-syntax/src/test/resources/serialized/selector_string_literal.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/selector_string_literal.ftl rename to fluent-syntax/src/test/resources/serialized/selector_string_literal.ftl diff --git a/fluent.syntax/src/test/resources/serialized/selector_term_attribute_reference.ftl b/fluent-syntax/src/test/resources/serialized/selector_term_attribute_reference.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/selector_term_attribute_reference.ftl rename to fluent-syntax/src/test/resources/serialized/selector_term_attribute_reference.ftl diff --git a/fluent.syntax/src/test/resources/serialized/selector_variable_reference.ftl b/fluent-syntax/src/test/resources/serialized/selector_variable_reference.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/selector_variable_reference.ftl rename to fluent-syntax/src/test/resources/serialized/selector_variable_reference.ftl diff --git a/fluent.syntax/src/test/resources/serialized/standalone_comment.ftl b/fluent-syntax/src/test/resources/serialized/standalone_comment.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/standalone_comment.ftl rename to fluent-syntax/src/test/resources/serialized/standalone_comment.ftl diff --git a/fluent.syntax/src/test/resources/serialized/string_literal.ftl b/fluent-syntax/src/test/resources/serialized/string_literal.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/string_literal.ftl rename to fluent-syntax/src/test/resources/serialized/string_literal.ftl diff --git a/fluent.syntax/src/test/resources/serialized/term.ftl b/fluent-syntax/src/test/resources/serialized/term.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/term.ftl rename to fluent-syntax/src/test/resources/serialized/term.ftl diff --git a/fluent.syntax/src/test/resources/serialized/term_reference.ftl b/fluent-syntax/src/test/resources/serialized/term_reference.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/term_reference.ftl rename to fluent-syntax/src/test/resources/serialized/term_reference.ftl diff --git a/fluent.syntax/src/test/resources/serialized/term_reference_call.ftl b/fluent-syntax/src/test/resources/serialized/term_reference_call.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/term_reference_call.ftl rename to fluent-syntax/src/test/resources/serialized/term_reference_call.ftl diff --git a/fluent.syntax/src/test/resources/serialized/two_attribute.ftl b/fluent-syntax/src/test/resources/serialized/two_attribute.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/two_attribute.ftl rename to fluent-syntax/src/test/resources/serialized/two_attribute.ftl diff --git a/fluent.syntax/src/test/resources/serialized/two_simple_messages.ftl b/fluent-syntax/src/test/resources/serialized/two_simple_messages.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/two_simple_messages.ftl rename to fluent-syntax/src/test/resources/serialized/two_simple_messages.ftl diff --git a/fluent.syntax/src/test/resources/serialized/unicode_escape_sequence.ftl b/fluent-syntax/src/test/resources/serialized/unicode_escape_sequence.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/unicode_escape_sequence.ftl rename to fluent-syntax/src/test/resources/serialized/unicode_escape_sequence.ftl diff --git a/fluent.syntax/src/test/resources/serialized/value_and_attributes.ftl b/fluent-syntax/src/test/resources/serialized/value_and_attributes.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/value_and_attributes.ftl rename to fluent-syntax/src/test/resources/serialized/value_and_attributes.ftl diff --git a/fluent.syntax/src/test/resources/serialized/variable_reference.ftl b/fluent-syntax/src/test/resources/serialized/variable_reference.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/variable_reference.ftl rename to fluent-syntax/src/test/resources/serialized/variable_reference.ftl diff --git a/fluent.syntax/src/test/resources/serialized/variant_key_number.ftl b/fluent-syntax/src/test/resources/serialized/variant_key_number.ftl similarity index 100% rename from fluent.syntax/src/test/resources/serialized/variant_key_number.ftl rename to fluent-syntax/src/test/resources/serialized/variant_key_number.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/.gitattributes b/fluent-syntax/src/test/resources/structure_fixtures/.gitattributes similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/.gitattributes rename to fluent-syntax/src/test/resources/structure_fixtures/.gitattributes diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_expression_with_wrong_attr.ftl b/fluent-syntax/src/test/resources/structure_fixtures/attribute_expression_with_wrong_attr.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/attribute_expression_with_wrong_attr.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/attribute_expression_with_wrong_attr.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_expression_with_wrong_attr.json b/fluent-syntax/src/test/resources/structure_fixtures/attribute_expression_with_wrong_attr.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/attribute_expression_with_wrong_attr.json rename to fluent-syntax/src/test/resources/structure_fixtures/attribute_expression_with_wrong_attr.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_of_private_as_placeable.ftl b/fluent-syntax/src/test/resources/structure_fixtures/attribute_of_private_as_placeable.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/attribute_of_private_as_placeable.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/attribute_of_private_as_placeable.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_of_private_as_placeable.json b/fluent-syntax/src/test/resources/structure_fixtures/attribute_of_private_as_placeable.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/attribute_of_private_as_placeable.json rename to fluent-syntax/src/test/resources/structure_fixtures/attribute_of_private_as_placeable.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_of_public_as_selector.ftl b/fluent-syntax/src/test/resources/structure_fixtures/attribute_of_public_as_selector.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/attribute_of_public_as_selector.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/attribute_of_public_as_selector.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_of_public_as_selector.json b/fluent-syntax/src/test/resources/structure_fixtures/attribute_of_public_as_selector.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/attribute_of_public_as_selector.json rename to fluent-syntax/src/test/resources/structure_fixtures/attribute_of_public_as_selector.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_starts_from_nl.ftl b/fluent-syntax/src/test/resources/structure_fixtures/attribute_starts_from_nl.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/attribute_starts_from_nl.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/attribute_starts_from_nl.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_starts_from_nl.json b/fluent-syntax/src/test/resources/structure_fixtures/attribute_starts_from_nl.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/attribute_starts_from_nl.json rename to fluent-syntax/src/test/resources/structure_fixtures/attribute_starts_from_nl.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_with_empty_pattern.ftl b/fluent-syntax/src/test/resources/structure_fixtures/attribute_with_empty_pattern.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/attribute_with_empty_pattern.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/attribute_with_empty_pattern.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_with_empty_pattern.json b/fluent-syntax/src/test/resources/structure_fixtures/attribute_with_empty_pattern.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/attribute_with_empty_pattern.json rename to fluent-syntax/src/test/resources/structure_fixtures/attribute_with_empty_pattern.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_without_equal_sign.ftl b/fluent-syntax/src/test/resources/structure_fixtures/attribute_without_equal_sign.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/attribute_without_equal_sign.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/attribute_without_equal_sign.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/attribute_without_equal_sign.json b/fluent-syntax/src/test/resources/structure_fixtures/attribute_without_equal_sign.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/attribute_without_equal_sign.json rename to fluent-syntax/src/test/resources/structure_fixtures/attribute_without_equal_sign.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/blank_lines.ftl b/fluent-syntax/src/test/resources/structure_fixtures/blank_lines.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/blank_lines.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/blank_lines.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/blank_lines.json b/fluent-syntax/src/test/resources/structure_fixtures/blank_lines.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/blank_lines.json rename to fluent-syntax/src/test/resources/structure_fixtures/blank_lines.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/broken_number.ftl b/fluent-syntax/src/test/resources/structure_fixtures/broken_number.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/broken_number.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/broken_number.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/broken_number.json b/fluent-syntax/src/test/resources/structure_fixtures/broken_number.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/broken_number.json rename to fluent-syntax/src/test/resources/structure_fixtures/broken_number.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/call_expression_errors.ftl b/fluent-syntax/src/test/resources/structure_fixtures/call_expression_errors.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/call_expression_errors.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/call_expression_errors.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/call_expression_errors.json b/fluent-syntax/src/test/resources/structure_fixtures/call_expression_errors.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/call_expression_errors.json rename to fluent-syntax/src/test/resources/structure_fixtures/call_expression_errors.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/comment_with_eof.ftl b/fluent-syntax/src/test/resources/structure_fixtures/comment_with_eof.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/comment_with_eof.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/comment_with_eof.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/comment_with_eof.json b/fluent-syntax/src/test/resources/structure_fixtures/comment_with_eof.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/comment_with_eof.json rename to fluent-syntax/src/test/resources/structure_fixtures/comment_with_eof.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/crlf.ftl b/fluent-syntax/src/test/resources/structure_fixtures/crlf.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/crlf.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/crlf.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/crlf.json b/fluent-syntax/src/test/resources/structure_fixtures/crlf.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/crlf.json rename to fluent-syntax/src/test/resources/structure_fixtures/crlf.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/dash_at_eof.ftl b/fluent-syntax/src/test/resources/structure_fixtures/dash_at_eof.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/dash_at_eof.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/dash_at_eof.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/dash_at_eof.json b/fluent-syntax/src/test/resources/structure_fixtures/dash_at_eof.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/dash_at_eof.json rename to fluent-syntax/src/test/resources/structure_fixtures/dash_at_eof.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/elements_indent.ftl b/fluent-syntax/src/test/resources/structure_fixtures/elements_indent.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/elements_indent.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/elements_indent.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/elements_indent.json b/fluent-syntax/src/test/resources/structure_fixtures/elements_indent.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/elements_indent.json rename to fluent-syntax/src/test/resources/structure_fixtures/elements_indent.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/empty_resource.ftl b/fluent-syntax/src/test/resources/structure_fixtures/empty_resource.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/empty_resource.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/empty_resource.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/empty_resource.json b/fluent-syntax/src/test/resources/structure_fixtures/empty_resource.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/empty_resource.json rename to fluent-syntax/src/test/resources/structure_fixtures/empty_resource.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/empty_resource_with_ws.ftl b/fluent-syntax/src/test/resources/structure_fixtures/empty_resource_with_ws.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/empty_resource_with_ws.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/empty_resource_with_ws.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/empty_resource_with_ws.json b/fluent-syntax/src/test/resources/structure_fixtures/empty_resource_with_ws.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/empty_resource_with_ws.json rename to fluent-syntax/src/test/resources/structure_fixtures/empty_resource_with_ws.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/escape_sequences.ftl b/fluent-syntax/src/test/resources/structure_fixtures/escape_sequences.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/escape_sequences.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/escape_sequences.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/escape_sequences.json b/fluent-syntax/src/test/resources/structure_fixtures/escape_sequences.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/escape_sequences.json rename to fluent-syntax/src/test/resources/structure_fixtures/escape_sequences.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/expressions_call_args.ftl b/fluent-syntax/src/test/resources/structure_fixtures/expressions_call_args.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/expressions_call_args.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/expressions_call_args.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/expressions_call_args.json b/fluent-syntax/src/test/resources/structure_fixtures/expressions_call_args.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/expressions_call_args.json rename to fluent-syntax/src/test/resources/structure_fixtures/expressions_call_args.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/indent.ftl b/fluent-syntax/src/test/resources/structure_fixtures/indent.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/indent.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/indent.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/indent.json b/fluent-syntax/src/test/resources/structure_fixtures/indent.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/indent.json rename to fluent-syntax/src/test/resources/structure_fixtures/indent.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/junk.ftl b/fluent-syntax/src/test/resources/structure_fixtures/junk.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/junk.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/junk.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/junk.json b/fluent-syntax/src/test/resources/structure_fixtures/junk.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/junk.json rename to fluent-syntax/src/test/resources/structure_fixtures/junk.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/leading_dots.ftl b/fluent-syntax/src/test/resources/structure_fixtures/leading_dots.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/leading_dots.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/leading_dots.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/leading_dots.json b/fluent-syntax/src/test/resources/structure_fixtures/leading_dots.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/leading_dots.json rename to fluent-syntax/src/test/resources/structure_fixtures/leading_dots.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines.ftl b/fluent-syntax/src/test/resources/structure_fixtures/leading_empty_lines.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/leading_empty_lines.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines.json b/fluent-syntax/src/test/resources/structure_fixtures/leading_empty_lines.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines.json rename to fluent-syntax/src/test/resources/structure_fixtures/leading_empty_lines.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines_with_ws.ftl b/fluent-syntax/src/test/resources/structure_fixtures/leading_empty_lines_with_ws.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines_with_ws.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/leading_empty_lines_with_ws.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines_with_ws.json b/fluent-syntax/src/test/resources/structure_fixtures/leading_empty_lines_with_ws.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/leading_empty_lines_with_ws.json rename to fluent-syntax/src/test/resources/structure_fixtures/leading_empty_lines_with_ws.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/message_reference_as_selector.ftl b/fluent-syntax/src/test/resources/structure_fixtures/message_reference_as_selector.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/message_reference_as_selector.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/message_reference_as_selector.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/message_reference_as_selector.json b/fluent-syntax/src/test/resources/structure_fixtures/message_reference_as_selector.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/message_reference_as_selector.json rename to fluent-syntax/src/test/resources/structure_fixtures/message_reference_as_selector.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_multiline_pattern.ftl b/fluent-syntax/src/test/resources/structure_fixtures/message_with_empty_multiline_pattern.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_multiline_pattern.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/message_with_empty_multiline_pattern.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_multiline_pattern.json b/fluent-syntax/src/test/resources/structure_fixtures/message_with_empty_multiline_pattern.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_multiline_pattern.json rename to fluent-syntax/src/test/resources/structure_fixtures/message_with_empty_multiline_pattern.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_pattern.ftl b/fluent-syntax/src/test/resources/structure_fixtures/message_with_empty_pattern.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_pattern.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/message_with_empty_pattern.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_pattern.json b/fluent-syntax/src/test/resources/structure_fixtures/message_with_empty_pattern.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/message_with_empty_pattern.json rename to fluent-syntax/src/test/resources/structure_fixtures/message_with_empty_pattern.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline-comment.ftl b/fluent-syntax/src/test/resources/structure_fixtures/multiline-comment.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/multiline-comment.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/multiline-comment.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline-comment.json b/fluent-syntax/src/test/resources/structure_fixtures/multiline-comment.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/multiline-comment.json rename to fluent-syntax/src/test/resources/structure_fixtures/multiline-comment.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline_pattern.ftl b/fluent-syntax/src/test/resources/structure_fixtures/multiline_pattern.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/multiline_pattern.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/multiline_pattern.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline_pattern.json b/fluent-syntax/src/test/resources/structure_fixtures/multiline_pattern.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/multiline_pattern.json rename to fluent-syntax/src/test/resources/structure_fixtures/multiline_pattern.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline_string.ftl b/fluent-syntax/src/test/resources/structure_fixtures/multiline_string.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/multiline_string.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/multiline_string.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline_string.json b/fluent-syntax/src/test/resources/structure_fixtures/multiline_string.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/multiline_string.json rename to fluent-syntax/src/test/resources/structure_fixtures/multiline_string.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline_with_non_empty_first_line.ftl b/fluent-syntax/src/test/resources/structure_fixtures/multiline_with_non_empty_first_line.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/multiline_with_non_empty_first_line.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/multiline_with_non_empty_first_line.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline_with_non_empty_first_line.json b/fluent-syntax/src/test/resources/structure_fixtures/multiline_with_non_empty_first_line.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/multiline_with_non_empty_first_line.json rename to fluent-syntax/src/test/resources/structure_fixtures/multiline_with_non_empty_first_line.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline_with_placeables.ftl b/fluent-syntax/src/test/resources/structure_fixtures/multiline_with_placeables.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/multiline_with_placeables.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/multiline_with_placeables.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/multiline_with_placeables.json b/fluent-syntax/src/test/resources/structure_fixtures/multiline_with_placeables.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/multiline_with_placeables.json rename to fluent-syntax/src/test/resources/structure_fixtures/multiline_with_placeables.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/non_id_attribute_name.ftl b/fluent-syntax/src/test/resources/structure_fixtures/non_id_attribute_name.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/non_id_attribute_name.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/non_id_attribute_name.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/non_id_attribute_name.json b/fluent-syntax/src/test/resources/structure_fixtures/non_id_attribute_name.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/non_id_attribute_name.json rename to fluent-syntax/src/test/resources/structure_fixtures/non_id_attribute_name.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/placeable_at_eol.ftl b/fluent-syntax/src/test/resources/structure_fixtures/placeable_at_eol.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/placeable_at_eol.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/placeable_at_eol.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/placeable_at_eol.json b/fluent-syntax/src/test/resources/structure_fixtures/placeable_at_eol.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/placeable_at_eol.json rename to fluent-syntax/src/test/resources/structure_fixtures/placeable_at_eol.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/placeable_at_line_extremes.ftl b/fluent-syntax/src/test/resources/structure_fixtures/placeable_at_line_extremes.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/placeable_at_line_extremes.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/placeable_at_line_extremes.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/placeable_at_line_extremes.json b/fluent-syntax/src/test/resources/structure_fixtures/placeable_at_line_extremes.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/placeable_at_line_extremes.json rename to fluent-syntax/src/test/resources/structure_fixtures/placeable_at_line_extremes.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/placeable_in_placeable.ftl b/fluent-syntax/src/test/resources/structure_fixtures/placeable_in_placeable.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/placeable_in_placeable.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/placeable_in_placeable.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/placeable_in_placeable.json b/fluent-syntax/src/test/resources/structure_fixtures/placeable_in_placeable.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/placeable_in_placeable.json rename to fluent-syntax/src/test/resources/structure_fixtures/placeable_in_placeable.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/placeable_without_close_bracket.ftl b/fluent-syntax/src/test/resources/structure_fixtures/placeable_without_close_bracket.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/placeable_without_close_bracket.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/placeable_without_close_bracket.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/placeable_without_close_bracket.json b/fluent-syntax/src/test/resources/structure_fixtures/placeable_without_close_bracket.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/placeable_without_close_bracket.json rename to fluent-syntax/src/test/resources/structure_fixtures/placeable_without_close_bracket.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/resource_comment.ftl b/fluent-syntax/src/test/resources/structure_fixtures/resource_comment.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/resource_comment.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/resource_comment.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/resource_comment.json b/fluent-syntax/src/test/resources/structure_fixtures/resource_comment.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/resource_comment.json rename to fluent-syntax/src/test/resources/structure_fixtures/resource_comment.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/resource_comment_trailing_line.ftl b/fluent-syntax/src/test/resources/structure_fixtures/resource_comment_trailing_line.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/resource_comment_trailing_line.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/resource_comment_trailing_line.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/resource_comment_trailing_line.json b/fluent-syntax/src/test/resources/structure_fixtures/resource_comment_trailing_line.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/resource_comment_trailing_line.json rename to fluent-syntax/src/test/resources/structure_fixtures/resource_comment_trailing_line.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/second_attribute_starts_from_nl.ftl b/fluent-syntax/src/test/resources/structure_fixtures/second_attribute_starts_from_nl.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/second_attribute_starts_from_nl.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/second_attribute_starts_from_nl.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/second_attribute_starts_from_nl.json b/fluent-syntax/src/test/resources/structure_fixtures/second_attribute_starts_from_nl.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/second_attribute_starts_from_nl.json rename to fluent-syntax/src/test/resources/structure_fixtures/second_attribute_starts_from_nl.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/select_expression_with_two_selectors.ftl b/fluent-syntax/src/test/resources/structure_fixtures/select_expression_with_two_selectors.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/select_expression_with_two_selectors.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/select_expression_with_two_selectors.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/select_expression_with_two_selectors.json b/fluent-syntax/src/test/resources/structure_fixtures/select_expression_with_two_selectors.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/select_expression_with_two_selectors.json rename to fluent-syntax/src/test/resources/structure_fixtures/select_expression_with_two_selectors.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_arrow.ftl b/fluent-syntax/src/test/resources/structure_fixtures/select_expression_without_arrow.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_arrow.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/select_expression_without_arrow.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_arrow.json b/fluent-syntax/src/test/resources/structure_fixtures/select_expression_without_arrow.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_arrow.json rename to fluent-syntax/src/test/resources/structure_fixtures/select_expression_without_arrow.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_variants.ftl b/fluent-syntax/src/test/resources/structure_fixtures/select_expression_without_variants.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_variants.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/select_expression_without_variants.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_variants.json b/fluent-syntax/src/test/resources/structure_fixtures/select_expression_without_variants.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/select_expression_without_variants.json rename to fluent-syntax/src/test/resources/structure_fixtures/select_expression_without_variants.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/select_expressions.ftl b/fluent-syntax/src/test/resources/structure_fixtures/select_expressions.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/select_expressions.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/select_expressions.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/select_expressions.json b/fluent-syntax/src/test/resources/structure_fixtures/select_expressions.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/select_expressions.json rename to fluent-syntax/src/test/resources/structure_fixtures/select_expressions.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/simple_message.ftl b/fluent-syntax/src/test/resources/structure_fixtures/simple_message.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/simple_message.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/simple_message.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/simple_message.json b/fluent-syntax/src/test/resources/structure_fixtures/simple_message.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/simple_message.json rename to fluent-syntax/src/test/resources/structure_fixtures/simple_message.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/single_char_id.ftl b/fluent-syntax/src/test/resources/structure_fixtures/single_char_id.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/single_char_id.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/single_char_id.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/single_char_id.json b/fluent-syntax/src/test/resources/structure_fixtures/single_char_id.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/single_char_id.json rename to fluent-syntax/src/test/resources/structure_fixtures/single_char_id.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/sparse-messages.ftl b/fluent-syntax/src/test/resources/structure_fixtures/sparse-messages.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/sparse-messages.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/sparse-messages.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/sparse-messages.json b/fluent-syntax/src/test/resources/structure_fixtures/sparse-messages.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/sparse-messages.json rename to fluent-syntax/src/test/resources/structure_fixtures/sparse-messages.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/standalone_comment.ftl b/fluent-syntax/src/test/resources/structure_fixtures/standalone_comment.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/standalone_comment.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/standalone_comment.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/standalone_comment.json b/fluent-syntax/src/test/resources/structure_fixtures/standalone_comment.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/standalone_comment.json rename to fluent-syntax/src/test/resources/structure_fixtures/standalone_comment.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/standalone_identifier.ftl b/fluent-syntax/src/test/resources/structure_fixtures/standalone_identifier.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/standalone_identifier.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/standalone_identifier.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/standalone_identifier.json b/fluent-syntax/src/test/resources/structure_fixtures/standalone_identifier.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/standalone_identifier.json rename to fluent-syntax/src/test/resources/structure_fixtures/standalone_identifier.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/term.ftl b/fluent-syntax/src/test/resources/structure_fixtures/term.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/term.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/term.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/term.json b/fluent-syntax/src/test/resources/structure_fixtures/term.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/term.json rename to fluent-syntax/src/test/resources/structure_fixtures/term.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/term_with_empty_pattern.ftl b/fluent-syntax/src/test/resources/structure_fixtures/term_with_empty_pattern.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/term_with_empty_pattern.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/term_with_empty_pattern.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/term_with_empty_pattern.json b/fluent-syntax/src/test/resources/structure_fixtures/term_with_empty_pattern.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/term_with_empty_pattern.json rename to fluent-syntax/src/test/resources/structure_fixtures/term_with_empty_pattern.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/unclosed.ftl b/fluent-syntax/src/test/resources/structure_fixtures/unclosed.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/unclosed.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/unclosed.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/unclosed.json b/fluent-syntax/src/test/resources/structure_fixtures/unclosed.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/unclosed.json rename to fluent-syntax/src/test/resources/structure_fixtures/unclosed.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/unclosed_empty_placeable_error.ftl b/fluent-syntax/src/test/resources/structure_fixtures/unclosed_empty_placeable_error.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/unclosed_empty_placeable_error.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/unclosed_empty_placeable_error.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/unclosed_empty_placeable_error.json b/fluent-syntax/src/test/resources/structure_fixtures/unclosed_empty_placeable_error.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/unclosed_empty_placeable_error.json rename to fluent-syntax/src/test/resources/structure_fixtures/unclosed_empty_placeable_error.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/unknown_entry_start.ftl b/fluent-syntax/src/test/resources/structure_fixtures/unknown_entry_start.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/unknown_entry_start.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/unknown_entry_start.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/unknown_entry_start.json b/fluent-syntax/src/test/resources/structure_fixtures/unknown_entry_start.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/unknown_entry_start.json rename to fluent-syntax/src/test/resources/structure_fixtures/unknown_entry_start.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_ends_abruptly.ftl b/fluent-syntax/src/test/resources/structure_fixtures/variant_ends_abruptly.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/variant_ends_abruptly.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/variant_ends_abruptly.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_ends_abruptly.json b/fluent-syntax/src/test/resources/structure_fixtures/variant_ends_abruptly.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/variant_ends_abruptly.json rename to fluent-syntax/src/test/resources/structure_fixtures/variant_ends_abruptly.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_keys.ftl b/fluent-syntax/src/test/resources/structure_fixtures/variant_keys.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/variant_keys.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/variant_keys.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_keys.json b/fluent-syntax/src/test/resources/structure_fixtures/variant_keys.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/variant_keys.json rename to fluent-syntax/src/test/resources/structure_fixtures/variant_keys.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_starts_from_nl.ftl b/fluent-syntax/src/test/resources/structure_fixtures/variant_starts_from_nl.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/variant_starts_from_nl.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/variant_starts_from_nl.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_starts_from_nl.json b/fluent-syntax/src/test/resources/structure_fixtures/variant_starts_from_nl.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/variant_starts_from_nl.json rename to fluent-syntax/src/test/resources/structure_fixtures/variant_starts_from_nl.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_with_digit_key.ftl b/fluent-syntax/src/test/resources/structure_fixtures/variant_with_digit_key.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/variant_with_digit_key.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/variant_with_digit_key.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_with_digit_key.json b/fluent-syntax/src/test/resources/structure_fixtures/variant_with_digit_key.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/variant_with_digit_key.json rename to fluent-syntax/src/test/resources/structure_fixtures/variant_with_digit_key.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_with_empty_pattern.ftl b/fluent-syntax/src/test/resources/structure_fixtures/variant_with_empty_pattern.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/variant_with_empty_pattern.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/variant_with_empty_pattern.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_with_empty_pattern.json b/fluent-syntax/src/test/resources/structure_fixtures/variant_with_empty_pattern.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/variant_with_empty_pattern.json rename to fluent-syntax/src/test/resources/structure_fixtures/variant_with_empty_pattern.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_with_leading_space_in_name.ftl b/fluent-syntax/src/test/resources/structure_fixtures/variant_with_leading_space_in_name.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/variant_with_leading_space_in_name.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/variant_with_leading_space_in_name.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_with_leading_space_in_name.json b/fluent-syntax/src/test/resources/structure_fixtures/variant_with_leading_space_in_name.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/variant_with_leading_space_in_name.json rename to fluent-syntax/src/test/resources/structure_fixtures/variant_with_leading_space_in_name.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_with_symbol_with_space.ftl b/fluent-syntax/src/test/resources/structure_fixtures/variant_with_symbol_with_space.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/variant_with_symbol_with_space.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/variant_with_symbol_with_space.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variant_with_symbol_with_space.json b/fluent-syntax/src/test/resources/structure_fixtures/variant_with_symbol_with_space.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/variant_with_symbol_with_space.json rename to fluent-syntax/src/test/resources/structure_fixtures/variant_with_symbol_with_space.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variants_with_two_defaults.ftl b/fluent-syntax/src/test/resources/structure_fixtures/variants_with_two_defaults.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/variants_with_two_defaults.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/variants_with_two_defaults.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/variants_with_two_defaults.json b/fluent-syntax/src/test/resources/structure_fixtures/variants_with_two_defaults.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/variants_with_two_defaults.json rename to fluent-syntax/src/test/resources/structure_fixtures/variants_with_two_defaults.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/whitespace_leading.ftl b/fluent-syntax/src/test/resources/structure_fixtures/whitespace_leading.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/whitespace_leading.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/whitespace_leading.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/whitespace_leading.json b/fluent-syntax/src/test/resources/structure_fixtures/whitespace_leading.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/whitespace_leading.json rename to fluent-syntax/src/test/resources/structure_fixtures/whitespace_leading.json diff --git a/fluent.syntax/src/test/resources/structure_fixtures/whitespace_trailing.ftl b/fluent-syntax/src/test/resources/structure_fixtures/whitespace_trailing.ftl similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/whitespace_trailing.ftl rename to fluent-syntax/src/test/resources/structure_fixtures/whitespace_trailing.ftl diff --git a/fluent.syntax/src/test/resources/structure_fixtures/whitespace_trailing.json b/fluent-syntax/src/test/resources/structure_fixtures/whitespace_trailing.json similarity index 100% rename from fluent.syntax/src/test/resources/structure_fixtures/whitespace_trailing.json rename to fluent-syntax/src/test/resources/structure_fixtures/whitespace_trailing.json diff --git a/settings.gradle b/settings.gradle index 8be0397..5d1c7af 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,3 @@ rootProject.name = "fluent-java" -include("fluent.syntax") +include("fluent-syntax") From f8ee40f7c6bc80a99e3910187f72ff36f0b63bd6 Mon Sep 17 00:00:00 2001 From: Voomra Date: Mon, 21 Jul 2025 16:48:46 +0300 Subject: [PATCH 06/11] build: maven-publish plugin --- fluent-syntax/build.gradle | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/fluent-syntax/build.gradle b/fluent-syntax/build.gradle index f470401..0719a15 100644 --- a/fluent-syntax/build.gradle +++ b/fluent-syntax/build.gradle @@ -1,5 +1,6 @@ plugins { id("java") + id("maven-publish") } group = "ru.di9.fluent" @@ -41,3 +42,15 @@ dependencies { test { useJUnitPlatform() } + +publishing { + publications { + mavenBinary(MavenPublication) { + groupId = project.group + artifactId = project.name + version = project.version + + from components.java + } + } +} From 61c7acae39d40175f848953522f0b56ba7364d6b Mon Sep 17 00:00:00 2001 From: Voomra Date: Wed, 15 Apr 2026 19:59:19 +0300 Subject: [PATCH 07/11] =?UTF-8?q?build:=20=D0=BF=D0=B5=D1=80=D0=B5=D1=85?= =?UTF-8?q?=D0=BE=D0=B4=20=D0=BD=D0=B0=20Gradle=20Kotlin=20DSL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle => build.gradle.kts | 2 +- fluent-syntax/build.gradle | 56 -------------------------- fluent-syntax/build.gradle.kts | 43 ++++++++++++++++++++ gradle/libs.versions.toml | 7 ++++ settings.gradle => settings.gradle.kts | 0 5 files changed, 51 insertions(+), 57 deletions(-) rename build.gradle => build.gradle.kts (75%) delete mode 100644 fluent-syntax/build.gradle create mode 100644 fluent-syntax/build.gradle.kts create mode 100644 gradle/libs.versions.toml rename settings.gradle => settings.gradle.kts (100%) diff --git a/build.gradle b/build.gradle.kts similarity index 75% rename from build.gradle rename to build.gradle.kts index 3503191..9fb736f 100644 --- a/build.gradle +++ b/build.gradle.kts @@ -1,4 +1,4 @@ -wrapper { +tasks.withType { gradleVersion = "8.10" distributionType = Wrapper.DistributionType.BIN } diff --git a/fluent-syntax/build.gradle b/fluent-syntax/build.gradle deleted file mode 100644 index 0719a15..0000000 --- a/fluent-syntax/build.gradle +++ /dev/null @@ -1,56 +0,0 @@ -plugins { - id("java") - id("maven-publish") -} - -group = "ru.di9.fluent" -version = "1.0-SNAPSHOT" - -java.toolchain { - languageVersion = JavaLanguageVersion.of(17) -} - -repositories { - mavenLocal() - mavenCentral() -} - -ext { - assertjVersion = "3.24.2" - gsonVersion = "2.9.1" - joorVersion = "0.9.15" - jsonAssertVersion = "1.5.1" - junitVersion = "5.9.2" - lombokVersion = "1.18.30" -} - -dependencies { - annotationProcessor("org.projectlombok:lombok:$lombokVersion") - compileOnly("org.projectlombok:lombok:$lombokVersion") - - testImplementation(platform("org.junit:junit-bom:$junitVersion")) - testImplementation("org.junit.jupiter:junit-jupiter") - - testAnnotationProcessor("org.projectlombok:lombok:$lombokVersion") - testCompileOnly("org.projectlombok:lombok:$lombokVersion") - testImplementation("org.assertj:assertj-core:$assertjVersion") - testImplementation("com.google.code.gson:gson:$gsonVersion") - testImplementation("org.jooq:joor:$joorVersion") - testImplementation("org.skyscreamer:jsonassert:$jsonAssertVersion") -} - -test { - useJUnitPlatform() -} - -publishing { - publications { - mavenBinary(MavenPublication) { - groupId = project.group - artifactId = project.name - version = project.version - - from components.java - } - } -} diff --git a/fluent-syntax/build.gradle.kts b/fluent-syntax/build.gradle.kts new file mode 100644 index 0000000..6024dcc --- /dev/null +++ b/fluent-syntax/build.gradle.kts @@ -0,0 +1,43 @@ +plugins { + java + id("maven-publish") +} + +group = "ru.di9.fluent" +version = "1.0-SNAPSHOT" + +java.toolchain { + languageVersion = JavaLanguageVersion.of(17) +} + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + annotationProcessor(libs.lombok) + compileOnly(libs.lombok) + + testImplementation(libs.junit) + testAnnotationProcessor(libs.lombok) + testCompileOnly(libs.lombok) + testImplementation(libs.assertj) + testImplementation(libs.gson) + testImplementation(libs.joor) + testImplementation(libs.jsonassert) +} + +tasks.withType { + useJUnitPlatform() +} + +publishing.publications { + create("mavenBinary") { + groupId = project.group.toString() + artifactId = project.name + version = project.version.toString() + + from(components["java"]) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..7d25ded --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,7 @@ +[libraries] +lombok = { group = "org.projectlombok", name = "lombok", version = "1.18.30" } +junit = { group = "org.junit.jupiter", name = "junit-jupiter", version = "5.9.2" } +assertj = { group = "org.assertj", name = "assertj-core", version = "3.24.2" } +gson = { group = "com.google.code.gson", name = "gson", version = "2.9.1" } +joor = { group = "org.jooq", name = "joor", version = "0.9.15" } +jsonassert = { group = "org.skyscreamer", name = "jsonassert", version = "1.5.1" } diff --git a/settings.gradle b/settings.gradle.kts similarity index 100% rename from settings.gradle rename to settings.gradle.kts From 2ff9281704fbf4354d0406afe515db6be5a41fc7 Mon Sep 17 00:00:00 2001 From: Voomra Date: Wed, 15 Apr 2026 23:06:34 +0300 Subject: [PATCH 08/11] build: upgrade Gradle --- build.gradle.kts | 2 +- fluent-syntax/build.gradle.kts | 5 ++++- gradle.properties | 1 + gradle/libs.versions.toml | 4 +++- gradle/wrapper/gradle-wrapper.jar | Bin 43583 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 9 ++++----- gradlew.bat | 4 ++-- 8 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 gradle.properties diff --git a/build.gradle.kts b/build.gradle.kts index 9fb736f..1d1f26c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,4 @@ tasks.withType { - gradleVersion = "8.10" + gradleVersion = "8.14.4" distributionType = Wrapper.DistributionType.BIN } diff --git a/fluent-syntax/build.gradle.kts b/fluent-syntax/build.gradle.kts index 6024dcc..9827b44 100644 --- a/fluent-syntax/build.gradle.kts +++ b/fluent-syntax/build.gradle.kts @@ -19,7 +19,10 @@ dependencies { annotationProcessor(libs.lombok) compileOnly(libs.lombok) - testImplementation(libs.junit) + testImplementation(platform(libs.junit.platform)) + testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.launcher) + testAnnotationProcessor(libs.lombok) testCompileOnly(libs.lombok) testImplementation(libs.assertj) diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..55a902b --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.warning.mode=all diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7d25ded..8400d93 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,8 @@ [libraries] lombok = { group = "org.projectlombok", name = "lombok", version = "1.18.30" } -junit = { group = "org.junit.jupiter", name = "junit-jupiter", version = "5.9.2" } +junit-platform = { group = "org.junit", name = "junit-bom", version = "5.9.2" } +junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter" } +junit-launcher = { group = "org.junit.platform", name = "junit-platform-launcher" } assertj = { group = "org.assertj", name = "assertj-core", version = "3.24.2" } gson = { group = "com.google.code.gson", name = "gson", version = "2.9.1" } joor = { group = "org.jooq", name = "joor", version = "0.9.15" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b9530d66f5e68d973ea569d8e19de379189..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch delta 34943 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

      Gn4F+B~IivciU9^t0j-Mgao3 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 DDw4oP diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9355b41..aaaabb3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6..23d15a9 100755 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9d21a21..db3a6ac 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell From 97f36cb809c525b010f4d16c68aff256d4532636 Mon Sep 17 00:00:00 2001 From: Voomra Date: Wed, 15 Apr 2026 23:31:24 +0300 Subject: [PATCH 09/11] build: upgrade Java --- fluent-syntax/build.gradle.kts | 2 +- .../fluent/syntax/parser/FluentParser.java | 135 +++++++++--------- .../fluent/syntax/parser/FluentStream.java | 32 ++--- .../fluent/syntax/parser/ParserStream.java | 9 +- .../syntax/serializer/FluentSerializer.java | 104 ++++++-------- .../fluent/syntax/{ => utils}/MathUtils.java | 2 +- .../syntax/{ => utils}/StringUtils.java | 2 +- .../di9/fluent/syntax/ast/BaseNodeTest.java | 16 +-- .../syntax/parser/AbstractFixturesTest.java | 5 +- .../syntax/parser/FluentStreamTest.java | 4 +- .../syntax/serializer/SerializeEntryTest.java | 5 +- .../{ => syntax}/test/utils/AstAssert.java | 8 +- .../syntax/{ => utils}/MathUtilsTest.java | 2 +- .../syntax/{ => utils}/StringUtilsTest.java | 2 +- .../fluent/syntax/visitor/VisitorTest.java | 7 +- .../test/utils/BaseNodeJsonSerializer.java | 2 +- .../ru/di9/fluent/test/utils/FileUtils.java | 4 +- 17 files changed, 167 insertions(+), 174 deletions(-) rename fluent-syntax/src/main/java/ru/di9/fluent/syntax/{ => utils}/MathUtils.java (79%) rename fluent-syntax/src/main/java/ru/di9/fluent/syntax/{ => utils}/StringUtils.java (96%) rename fluent-syntax/src/test/java/ru/di9/fluent/{ => syntax}/test/utils/AstAssert.java (85%) rename fluent-syntax/src/test/java/ru/di9/fluent/syntax/{ => utils}/MathUtilsTest.java (90%) rename fluent-syntax/src/test/java/ru/di9/fluent/syntax/{ => utils}/StringUtilsTest.java (94%) diff --git a/fluent-syntax/build.gradle.kts b/fluent-syntax/build.gradle.kts index 9827b44..968ecaa 100644 --- a/fluent-syntax/build.gradle.kts +++ b/fluent-syntax/build.gradle.kts @@ -7,7 +7,7 @@ group = "ru.di9.fluent" version = "1.0-SNAPSHOT" java.toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(21) } repositories { diff --git a/fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentParser.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentParser.java index e74d9f1..860897f 100644 --- a/fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentParser.java +++ b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentParser.java @@ -5,7 +5,7 @@ import ru.di9.fluent.syntax.ast.*; import java.util.*; import java.util.function.Predicate; -import static ru.di9.fluent.syntax.StringUtils.inRange_09; +import static ru.di9.fluent.syntax.utils.StringUtils.inRange_09; import static ru.di9.fluent.syntax.parser.FluentStream.*; import static ru.di9.fluent.syntax.parser.ParseException.ErrorCode.*; @@ -18,13 +18,13 @@ public class FluentParser { int spanStart = ps.index; List entries = new ArrayList<>(); Comment lastComment = null; - var blankLines = ps.skipBlankBlock(); + String blankLines = ps.skipBlankBlock(); if (!blankLines.isEmpty()) { entries.add(new Whitespace(blankLines)); } while (ps.currentChar().isPresent()) { - var entry = getEntryOrJunk(ps); + TopLevel entry = getEntryOrJunk(ps); blankLines = ps.skipBlankBlock(); // Regular Comments require special logic. Comments may be attached to @@ -78,7 +78,7 @@ public class FluentParser { int entryStartPos = ps.index; try { - var entry = getEntry(ps); + Entry entry = getEntry(ps); ps.expectLineEnd(); return entry; } catch (ParseException e) { @@ -91,7 +91,7 @@ public class FluentParser { } // Create a Junk instance - var slice = ps.string.substring(entryStartPos, nextEntryStart); + String slice = ps.string.substring(entryStartPos, nextEntryStart); var junk = new Junk(slice); if (withSpans) { junk.addSpan(entryStartPos, nextEntryStart); @@ -107,7 +107,7 @@ public class FluentParser { } private Entry getEntry(FluentStream ps) { - var currentChar = ps.currentChar(); + Optional currentChar = ps.currentChar(); if (currentChar.filter(v -> v == '#').isPresent()) { return getComment(ps); @@ -126,13 +126,13 @@ public class FluentParser { private Message getMessage(FluentStream ps) { int spanStart = ps.index; - var id = getIdentifier(ps); + Identifier id = getIdentifier(ps); ps.skipBlankInline(); ps.expectChar('='); - var value = maybeGetPattern(ps); - var attrs = getAttributes(ps); + Pattern value = maybeGetPattern(ps); + Collection attrs = getAttributes(ps); if (value == null && attrs.isEmpty()) { throw new ParseException(E0005, id.getName()); @@ -150,17 +150,17 @@ public class FluentParser { int spanStart = ps.index; ps.expectChar('-'); - var id = getIdentifier(ps); + Identifier id = getIdentifier(ps); ps.skipBlankInline(); ps.expectChar('='); - var value = maybeGetPattern(ps); + Pattern value = maybeGetPattern(ps); if (value == null) { throw new ParseException(E0006, id.getName()); } - var attrs = getAttributes(ps); + Collection attrs = getAttributes(ps); var term = new Term(id, value); term.getAttributes().addAll(attrs); @@ -176,7 +176,7 @@ public class FluentParser { ps.peekBlank(); while (ps.isAttributeStart()) { ps.skipToPeek(); - var attr = getAttribute(ps); + Attribute attr = getAttribute(ps); attrs.add(attr); ps.peekBlank(); } @@ -187,12 +187,12 @@ public class FluentParser { int spanStart = ps.index; ps.expectChar('.'); - var key = getIdentifier(ps); + Identifier key = getIdentifier(ps); ps.skipBlankInline(); ps.expectChar('='); - var value = maybeGetPattern(ps); + Pattern value = maybeGetPattern(ps); if (value == null) { throw new ParseException(E0012); } @@ -234,8 +234,8 @@ public class FluentParser { if (isBlock) { // A block pattern is a pattern which starts on a new line. Store and // measure the indent of this first line for the dedentation logic. - var blankStart = ps.index; - var firstIndent = ps.skipBlankInline(); + int blankStart = ps.index; + String firstIndent = ps.skipBlankInline(); elements.add(getIndent(ps, firstIndent, blankStart)); commonIndentLength = firstIndent.length(); } @@ -243,14 +243,14 @@ public class FluentParser { Optional opt; elements: while ((opt = ps.currentChar()).isPresent()) { - var ch = opt.get(); + Character ch = opt.get(); switch (ch) { case EOL -> { - var blankStart = ps.index; - var blankLines = ps.peekBlankBlock(); + int blankStart = ps.index; + String blankLines = ps.peekBlankBlock(); if (ps.isValueContinuation()) { ps.skipToPeek(); - var indent = ps.skipBlankInline(); + String indent = ps.skipBlankInline(); commonIndentLength = Math.min(commonIndentLength, indent.length()); elements.add(getIndent(ps, blankLines + indent, blankStart)); continue; @@ -267,7 +267,7 @@ public class FluentParser { } } - var dedented = dedent(elements, commonIndentLength); + List dedented = dedent(elements, commonIndentLength); var pattern = new Pattern(dedented); if (withSpans) { pattern.addSpan(spanStart, ps.index); @@ -281,7 +281,7 @@ public class FluentParser { private List dedent(Collection elements, int commonIndent) { ArrayList trimmed = new ArrayList<>(); - for (var element : elements) { + for (PatternElement element : elements) { if (element instanceof Placeable pl) { trimmed.add(pl); continue; @@ -296,22 +296,10 @@ public class FluentParser { } if (!trimmed.isEmpty()) { - var prev = trimmed.get(trimmed.size() - 1); + PatternElement prev = trimmed.getLast(); if (prev instanceof TextElement prevTE) { // Join adjacent TextElements by replacing them with their sum. - String newVal; - if (element instanceof TextElement elmTE) { - newVal = elmTE.getValue(); - } else if (element instanceof Indent elmInd) { - newVal = elmInd.getValue(); - } else { - throw new IllegalStateException("Unexpected PatternElement type"); - } - - var sum = new TextElement(prevTE.getValue() + newVal); - if (withSpans && prevTE.getSpan().isPresent() && element.getSpan().isPresent()) { - sum.addSpan(prevTE.getSpan().get().getStart(), element.getSpan().get().getEnd()); - } + TextElement sum = getTextElement(element, prevTE); trimmed.set(trimmed.size() - 1, sum); continue; @@ -335,17 +323,34 @@ public class FluentParser { } // Trim trailing whitespace from the Pattern. - var lastElement = trimmed.get(trimmed.size() - 1); + PatternElement lastElement = trimmed.getLast(); if (lastElement instanceof TextElement lastElmTE) { lastElmTE.setValue(TRAILING_WS_RE.matcher(lastElmTE.getValue()).replaceAll("")); if (lastElmTE.getValue().isEmpty()) { - trimmed.remove(trimmed.size() - 1); + trimmed.removeLast(); } } return trimmed; } + private TextElement getTextElement(PatternElement element, TextElement prevTE) { + String newVal; + if (element instanceof TextElement elmTE) { + newVal = elmTE.getValue(); + } else if (element instanceof Indent elmInd) { + newVal = elmInd.getValue(); + } else { + throw new IllegalStateException("Unexpected PatternElement type"); + } + + var sum = new TextElement(prevTE.getValue() + newVal); + if (withSpans && prevTE.getSpan().isPresent() && element.getSpan().isPresent()) { + sum.addSpan(prevTE.getSpan().get().getStart(), element.getSpan().get().getEnd()); + } + return sum; + } + private TextElement getTextElement(FluentStream ps) { var buffer = new StringBuilder(); int spanStart = ps.index; @@ -375,13 +380,13 @@ public class FluentParser { } private PatternElement getPlaceable(FluentStream ps) { - var spanStart = ps.index; + int spanStart = ps.index; ps.expectChar('{'); ps.skipBlank(); SyntaxNode expression; if (ps.currentChar().filter(v -> v == '{').isPresent()) { - var child = getPlaceable(ps); + PatternElement child = getPlaceable(ps); ps.skipBlank(); expression = child; } else { @@ -401,7 +406,7 @@ public class FluentParser { private Expression getExpression(FluentStream ps) { int spanStart = ps.index; - var selector = getInlineExpression(ps); + Expression selector = getInlineExpression(ps); ps.skipBlank(); if (ps.currentChar().filter(v -> v == '-').isPresent()) { @@ -425,7 +430,7 @@ public class FluentParser { ps.skipBlankInline(); ps.expectLineEnd(); - var variants = getVariants(ps); + List variants = getVariants(ps); var selectExpression = new SelectExpression(selector, variants); if (withSpans) { @@ -448,7 +453,7 @@ public class FluentParser { ps.skipBlank(); while (ps.isVariantStart()) { - var variant = getVariant(ps, hasDefault); + Variant variant = getVariant(ps, hasDefault); if (variant.isDefault()) { hasDefault = true; @@ -486,13 +491,13 @@ public class FluentParser { ps.skipBlank(); - var key = getVariantKey(ps); + VariantKey key = getVariantKey(ps); ps.skipBlank(); ps.expectChar(']'); //val value = this.maybeGetPattern(ps) ?: throw ParseError("E0012") - var value = maybeGetPattern(ps); + Pattern value = maybeGetPattern(ps); if (value == null) { throw new ParseException(E0012); } @@ -515,7 +520,7 @@ public class FluentParser { } private Expression getInlineExpression(FluentStream ps) { - var spanStart = ps.index; + int spanStart = ps.index; if (ps.isNumberStart()) { return getNumber(ps); @@ -527,7 +532,7 @@ public class FluentParser { if (ps.currentChar().filter(v -> v == '$').isPresent()) { ps.next(); - var id = getIdentifier(ps); + Identifier id = getIdentifier(ps); var variableReference = new VariableReference(id); if (withSpans) { @@ -539,7 +544,7 @@ public class FluentParser { if (ps.currentChar().filter(v -> v == '-').isPresent()) { ps.next(); - var id = getIdentifier(ps); + Identifier id = getIdentifier(ps); Identifier attr = null; if (ps.currentChar().filter(v -> v == '.').isPresent()) { @@ -563,7 +568,7 @@ public class FluentParser { } if (ps.isIdentifierStart()) { - var id = getIdentifier(ps); + Identifier id = getIdentifier(ps); ps.peekBlank(); if (ps.currentPeek().filter(v -> v == '(').isPresent()) { @@ -573,7 +578,7 @@ public class FluentParser { } ps.skipToPeek(); - var args = getCallArguments(ps); + CallArguments args = getCallArguments(ps); var functionReference = new FunctionReference(id, args); if (withSpans) { functionReference.addSpan(spanStart, ps.index); @@ -599,7 +604,7 @@ public class FluentParser { } private CallArguments getCallArguments(FluentStream ps) { - var spanStart = ps.index; + int spanStart = ps.index; List positional = new ArrayList<>(); List named = new ArrayList<>(); Set argumentNames = new HashSet<>(); @@ -612,7 +617,7 @@ public class FluentParser { break; } - var arg = getCallArgument(ps); + CallArgument arg = getCallArgument(ps); if (arg instanceof NamedArgument na) { if (argumentNames.contains(na.getName().getName())) { throw new ParseException(E0022); @@ -648,8 +653,8 @@ public class FluentParser { } private CallArgument getCallArgument(FluentStream ps) { - var spanStart = ps.index; - var exp = getInlineExpression(ps); + int spanStart = ps.index; + Expression exp = getInlineExpression(ps); ps.skipBlank(); @@ -661,7 +666,7 @@ public class FluentParser { ps.next(); ps.skipBlank(); - var value = getLiteral(ps); + Literal value = getLiteral(ps); var namedArgument = new NamedArgument(mr.getId(), value); if (withSpans) { @@ -687,7 +692,7 @@ public class FluentParser { } private StringLiteral getString(FluentStream ps) { - var spanStart = ps.index; + int spanStart = ps.index; ps.expectChar('"'); var value = new StringBuilder(); @@ -695,7 +700,7 @@ public class FluentParser { Predicate filter = x -> x != '"' && x != EOL; Optional opt; while ((opt = ps.takeChar(filter)).isPresent()) { - var ch = opt.get(); + Character ch = opt.get(); value.append(ch == '\\' ? getEscapeSequence(ps) : ch); } @@ -714,12 +719,12 @@ public class FluentParser { } private String getEscapeSequence(FluentStream ps) { - var nextOpt = ps.currentChar(); + Optional nextOpt = ps.currentChar(); if (nextOpt.isEmpty()) { throw new ParseException(E0025, (Character) null); } - var next = nextOpt.get(); + Character next = nextOpt.get(); return switch (next) { case '\\', '"' -> { ps.next(); @@ -736,7 +741,7 @@ public class FluentParser { var sequence = new StringBuilder(); for (int i = 0; i < digits; i++) { - var opt = ps.takeHexDigit(); + Optional opt = ps.takeHexDigit(); if (opt.isEmpty()) { throw new ParseException(E0026, "\\%s%s%s".formatted(u, sequence, ps.currentChar().orElseThrow())); } @@ -747,7 +752,7 @@ public class FluentParser { } private NumberLiteral getNumber(FluentStream ps) { - var spanStart = ps.index; + int spanStart = ps.index; var value = new StringBuilder(); if (ps.currentChar().filter(v -> v == '-').isPresent()) { @@ -794,7 +799,7 @@ public class FluentParser { private Identifier getIdentifier(FluentStream ps) { int spanStart = ps.index; - var name = new StringBuilder().append(ps.takeIDStart()); + StringBuilder name = new StringBuilder().append(ps.takeIDStart()); Optional opt; while ((opt = ps.takeIDChar()).isPresent()) { name.append(opt.get()); @@ -816,11 +821,11 @@ public class FluentParser { final int GROUP_COMMENT = 1; final int RESOURCE_COMMENT = 2; - var level = ANY; + int level = ANY; var content = new StringBuilder(); while (true) { - var i = -1; + int i = -1; int thisLevel; if (level == ANY) { diff --git a/fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentStream.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentStream.java index 0ef0994..c2f4dd0 100644 --- a/fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentStream.java +++ b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/FluentStream.java @@ -1,12 +1,12 @@ package ru.di9.fluent.syntax.parser; -import ru.di9.fluent.syntax.MathUtils; +import ru.di9.fluent.syntax.utils.MathUtils; import java.util.Optional; import java.util.function.Predicate; import java.util.regex.Pattern; -import static ru.di9.fluent.syntax.StringUtils.*; +import static ru.di9.fluent.syntax.utils.StringUtils.*; import static ru.di9.fluent.syntax.parser.ParseException.ErrorCode.E0003; import static ru.di9.fluent.syntax.parser.ParseException.ErrorCode.E0004; @@ -29,17 +29,17 @@ public class FluentStream extends ParserStream { } public String skipBlankInline() { - var blank = peekBlankInline(); + String blank = peekBlankInline(); skipToPeek(); return blank; } public String peekBlankBlock() { - var blank = new StringBuilder(); + StringBuilder blank = new StringBuilder(); while (true) { - var lineStart = peekOffset; + int lineStart = peekOffset; peekBlankInline(); - var currentPeek = currentPeek(); + Optional currentPeek = currentPeek(); if (currentPeek.filter(v -> v == EOL).isPresent()) { blank.append(EOL); peek(); @@ -56,7 +56,7 @@ public class FluentStream extends ParserStream { } public String skipBlankBlock() { - var blank = peekBlankBlock(); + String blank = peekBlankBlock(); skipToPeek(); return blank; } @@ -79,7 +79,7 @@ public class FluentStream extends ParserStream { } public void expectLineEnd() { - var opt = currentChar(); + Optional opt = currentChar(); if (opt.isEmpty()/*EOF*/) { // EOF is a valid line end in Fluent. @@ -97,7 +97,7 @@ public class FluentStream extends ParserStream { } public Optional takeChar(Predicate f) { - var ch = currentChar().filter(f); + Optional ch = currentChar().filter(f); ch.ifPresent(x -> next()); return ch; } @@ -107,7 +107,7 @@ public class FluentStream extends ParserStream { } public boolean isNumberStart() { - var opt = currentChar(); + Optional opt = currentChar(); if (opt.filter(v -> v == '-').isPresent()) { opt = peek(); } @@ -123,7 +123,7 @@ public class FluentStream extends ParserStream { } public boolean isValueContinuation() { - var column1 = peekOffset; + int column1 = peekOffset; peekBlankInline(); if (currentPeek().filter(v -> v == '{').isPresent()) { @@ -156,7 +156,7 @@ public class FluentStream extends ParserStream { } int lvl = MathUtils.clamp(level, -1, 2); - var i = 0; + int i = 0; while (i <= lvl || (lvl == -1 && i < 3)) { if (peek().filter(v -> v != '#').isPresent()) { if (i <= lvl && lvl != -1) { @@ -175,7 +175,7 @@ public class FluentStream extends ParserStream { } public boolean isVariantStart() { - var currentPeekOffset = peekOffset; + int currentPeekOffset = peekOffset; if (currentPeek().filter(v -> v == '*').isPresent()) { peek(); } @@ -190,7 +190,7 @@ public class FluentStream extends ParserStream { } public void skipToNextEntryStart(int junkStart) { - var lastNewline = string.lastIndexOf(EOL, index); + int lastNewline = string.lastIndexOf(EOL, index); if (junkStart < lastNewline) { // Last seen newline is _after_ the junk start. It's safe to rewind // without the risk of resuming at the same broken entry. @@ -205,7 +205,7 @@ public class FluentStream extends ParserStream { } // Break if the first char in this line looks like an entry start. - var firstOpt = next(); + Optional firstOpt = next(); if (firstOpt.isEmpty()/*EOF*/) { continue; } @@ -219,7 +219,7 @@ public class FluentStream extends ParserStream { public char takeIDStart() { if (currentChar().map(this::isCharIdStart).orElse(false)) { - var ret = currentChar(); + Optional ret = currentChar(); if (ret.isPresent()) { next(); return ret.get(); diff --git a/fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/ParserStream.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/ParserStream.java index 7c218ed..3cc4983 100644 --- a/fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/ParserStream.java +++ b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/parser/ParserStream.java @@ -2,7 +2,7 @@ package ru.di9.fluent.syntax.parser; import java.util.Optional; -import static ru.di9.fluent.syntax.StringUtils.getCharAt; +import static ru.di9.fluent.syntax.utils.StringUtils.getCharAt; public class ParserStream { @@ -71,9 +71,10 @@ public class ParserStream { // beginning of the compound CRLF sequence. This ensures slices of // [inclusive, exclusive) continue to work properly. - var ch = getCharAt(string, offset); - var opt = ch.filter(v -> v == '\r') - .flatMap(x -> getCharAt(string, offset + 1).filter(v -> v == '\n')); + Optional ch = getCharAt(string, offset); + Optional opt = ch.filter(v -> v == '\r') + .flatMap(x -> getCharAt(string, offset + 1) + .filter(v -> v == '\n')); return opt.isPresent() ? opt : ch; } diff --git a/fluent-syntax/src/main/java/ru/di9/fluent/syntax/serializer/FluentSerializer.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/serializer/FluentSerializer.java index 1eb463e..f89bac5 100644 --- a/fluent-syntax/src/main/java/ru/di9/fluent/syntax/serializer/FluentSerializer.java +++ b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/serializer/FluentSerializer.java @@ -17,33 +17,25 @@ public class FluentSerializer { } public String serialize(TopLevel topLevel) { - if (topLevel instanceof Entry entry) { - return serializeEntry(entry); - } else if (topLevel instanceof Whitespace whitespace) { - return whitespace.getContent(); - } else if (topLevel instanceof Junk junk) { - return withJunk ? junk.getContent() : ""; - } else { - throw new SerializeException("Unknown top-level entry type '%s'".formatted(topLevel.getClass())); - } + return switch (topLevel) { + case Entry entry -> serializeEntry(entry); + case Whitespace whitespace -> whitespace.getContent(); + case Junk junk -> withJunk ? junk.getContent() : ""; + default -> throw new SerializeException("Unknown top-level entry type '%s'".formatted(topLevel.getClass())); + }; } /// PRIVATE //////////////////////////////////////////////////////////////////////////////////////////////////////// private String serializeEntry(Entry entry) { - if (entry instanceof Message message) { - return serializeMessage(message); - } else if (entry instanceof Term term) { - return serializeTerm(term); - } else if (entry instanceof Comment comment) { - return serializeComment(comment, "#"); - } else if (entry instanceof GroupComment comment) { - return serializeComment(comment, "##"); - } else if (entry instanceof ResourceComment comment) { - return serializeComment(comment, "###"); - } else { - throw new SerializeException("Unknown entry type '%s'".formatted(entry.getClass())); - } + return switch (entry) { + case Message message -> serializeMessage(message); + case Term term -> serializeTerm(term); + case Comment comment -> serializeComment(comment, "#"); + case GroupComment comment -> serializeComment(comment, "##"); + case ResourceComment comment -> serializeComment(comment, "###"); + default -> throw new SerializeException("Unknown entry type '%s'".formatted(entry.getClass())); + }; } private String serializeMessage(Message message) { @@ -129,51 +121,47 @@ public class FluentSerializer { private String serializePlaceable(Placeable placeable) { InsidePlaceable expression = placeable.getExpression(); - if (expression instanceof Placeable placeable1) { - return "{" + serializePlaceable(placeable1) + "}"; - } else if (expression instanceof SelectExpression selectExpression) { - return "{ " + serializeExpression(selectExpression) + "}"; - } else if (expression instanceof Expression expression1) { - return "{ " + serializeExpression(expression1) + " }"; - } else { - throw new SerializeException("Unknown placeable type '%s'".formatted(expression.getClass())); - } + return switch (expression) { + case Placeable placeable1 -> "{" + serializePlaceable(placeable1) + "}"; + case SelectExpression selectExpression -> "{ " + serializeExpression(selectExpression) + "}"; + case Expression expression1 -> "{ " + serializeExpression(expression1) + " }"; + default -> throw new SerializeException("Unknown placeable type '%s'".formatted(expression.getClass())); + }; } private String serializeExpression(Expression expression) { var builder = new StringBuilder(); - if (expression instanceof StringLiteral stringLiteral) { - builder.append('"').append(stringLiteral.getValue()).append('"'); - } else if (expression instanceof NumberLiteral numberLiteral) { - builder.append(numberLiteral.getValue()); - } else if (expression instanceof VariableReference variableReference) { - builder.append('$').append(variableReference.getId().getName()); - } else if (expression instanceof TermReference termReference) { - builder.append('-').append(termReference.getId().getName()); + switch (expression) { + case StringLiteral stringLiteral -> builder.append('"').append(stringLiteral.getValue()).append('"'); + case NumberLiteral numberLiteral -> builder.append(numberLiteral.getValue()); + case VariableReference variableReference -> builder.append('$').append(variableReference.getId().getName()); + case TermReference termReference -> { + builder.append('-').append(termReference.getId().getName()); - termReference.getAttribute().ifPresent(attribute -> + termReference.getAttribute().ifPresent(attribute -> builder.append('.').append(attribute.getName())); - termReference.getArguments().ifPresent(arguments -> + termReference.getArguments().ifPresent(arguments -> builder.append(serializeCallArguments(arguments))); - } else if (expression instanceof MessageReference messageReference) { - builder.append(messageReference.getId().getName()); + } + case MessageReference messageReference -> { + builder.append(messageReference.getId().getName()); - messageReference.getAttribute().ifPresent(attribute -> + messageReference.getAttribute().ifPresent(attribute -> builder.append('.').append(attribute.getName())); - } else if (expression instanceof FunctionReference functionReference) { - builder + } + case FunctionReference functionReference -> builder .append(functionReference.getId().getName()) .append(serializeCallArguments(functionReference.getArguments())); - } else if (expression instanceof SelectExpression selectExpression) { - builder.append(serializeExpression(selectExpression.getSelector())).append(" ->"); - for (Variant variant : selectExpression.getVariants()) { - builder.append(serializeVariant(variant)); + case SelectExpression selectExpression -> { + builder.append(serializeExpression(selectExpression.getSelector())).append(" ->"); + for (Variant variant : selectExpression.getVariants()) { + builder.append(serializeVariant(variant)); + } + builder.append('\n'); } - builder.append('\n'); - } else { - throw new SerializeException("Unknown expression type '%s".formatted(expression.getClass())); + default -> throw new SerializeException("Unknown expression type '%s".formatted(expression.getClass())); } return builder.toString(); @@ -185,7 +173,7 @@ public class FluentSerializer { var builder = new StringBuilder("("); if (hasPositional) { - var positional = callArguments.getPositional() + String positional = callArguments.getPositional() .stream() .map(this::serializeExpression) .collect(Collectors.joining(", ")); @@ -193,7 +181,7 @@ public class FluentSerializer { } if (hasNamed) { - var named = callArguments.getNamed() + String named = callArguments.getNamed() .stream() .map(this::serializeNamedArgument) .collect(Collectors.joining(", ")); @@ -209,8 +197,8 @@ public class FluentSerializer { } private String serializeVariant(Variant variant) { - var key = serializeVariantKey(variant.getKey()); - var value = serializePattern(variant.getValue()).replaceAll("\n", "\n "); + String key = serializeVariantKey(variant.getKey()); + String value = serializePattern(variant.getValue()).replaceAll("\n", "\n "); var builder = new StringBuilder("\n "); if (variant.isDefault()) { @@ -240,7 +228,7 @@ public class FluentSerializer { boolean isMultiline = pattern.getElements().stream().anyMatch(it -> isSelectExpr(it) || includesLine(it)); if (isMultiline) { if (!pattern.getElements().isEmpty()) { - var firstElement = pattern.getElements().get(0); + PatternElement firstElement = pattern.getElements().getFirst(); if (firstElement instanceof TextElement te) { if (!te.getValue().isEmpty()) { char firstChar = te.getValue().charAt(0); diff --git a/fluent-syntax/src/main/java/ru/di9/fluent/syntax/MathUtils.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/utils/MathUtils.java similarity index 79% rename from fluent-syntax/src/main/java/ru/di9/fluent/syntax/MathUtils.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/utils/MathUtils.java index 3ae1de1..a8cce5d 100644 --- a/fluent-syntax/src/main/java/ru/di9/fluent/syntax/MathUtils.java +++ b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/utils/MathUtils.java @@ -1,4 +1,4 @@ -package ru.di9.fluent.syntax; +package ru.di9.fluent.syntax.utils; public interface MathUtils { static int clamp(int value, int min, int max) { diff --git a/fluent-syntax/src/main/java/ru/di9/fluent/syntax/StringUtils.java b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/utils/StringUtils.java similarity index 96% rename from fluent-syntax/src/main/java/ru/di9/fluent/syntax/StringUtils.java rename to fluent-syntax/src/main/java/ru/di9/fluent/syntax/utils/StringUtils.java index ced45ca..22a3d31 100644 --- a/fluent-syntax/src/main/java/ru/di9/fluent/syntax/StringUtils.java +++ b/fluent-syntax/src/main/java/ru/di9/fluent/syntax/utils/StringUtils.java @@ -1,4 +1,4 @@ -package ru.di9.fluent.syntax; +package ru.di9.fluent.syntax.utils; import java.util.Optional; diff --git a/fluent-syntax/src/test/java/ru/di9/fluent/syntax/ast/BaseNodeTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/ast/BaseNodeTest.java index b6d3755..005c64b 100644 --- a/fluent-syntax/src/test/java/ru/di9/fluent/syntax/ast/BaseNodeTest.java +++ b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/ast/BaseNodeTest.java @@ -2,8 +2,7 @@ package ru.di9.fluent.syntax.ast; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.assertj.core.api.Assertions.assertThat; class BaseNodeTest { @@ -13,12 +12,11 @@ class BaseNodeTest { var m11 = new Message(new Identifier("test-id"), new Pattern(new TextElement("localized"))); var m2 = new Message(new Identifier("test-id"), new Pattern(new TextElement("different"))); - assertEquals(m1, m11); - assertNotEquals(m1, m2); - assertEquals(m1.getId(), m2.getId()); - assertNotEquals(m1.getValue(), m2.getValue()); - //Шта? - assertNotEquals(m1, null); - assertNotEquals(null, m1); + assertThat(m1) + .isEqualTo(m11) + .isNotEqualTo(m2); + + assertThat(m1.getId()).isEqualTo(m2.getId()); + assertThat(m1.getValue()).isNotEqualTo(m2.getValue()); } } diff --git a/fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/AbstractFixturesTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/AbstractFixturesTest.java index 3ae538d..d1c748e 100644 --- a/fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/AbstractFixturesTest.java +++ b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/AbstractFixturesTest.java @@ -2,7 +2,8 @@ package ru.di9.fluent.syntax.parser; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; -import ru.di9.fluent.test.utils.AstAssert; +import ru.di9.fluent.syntax.ast.Resource; +import ru.di9.fluent.syntax.test.utils.AstAssert; import ru.di9.fluent.test.utils.Tuple3; import java.io.IOException; @@ -43,7 +44,7 @@ public abstract class AbstractFixturesTest { var parser = new FluentParser(); parser.withSpans = isWithSpans(tuple.value1()); parser.withJunkAnnotations = isWithJunkAnnotations(tuple.value1()); - var resource = parser.parse(tuple.value2()); + Resource resource = parser.parse(tuple.value2()); AstAssert.assertThat(resource) .isEqualAstJson(tuple.value3()); diff --git a/fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/FluentStreamTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/FluentStreamTest.java index b4e39ef..da118c8 100644 --- a/fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/FluentStreamTest.java +++ b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/FluentStreamTest.java @@ -42,7 +42,7 @@ class FluentStreamTest { assertThatNoException().isThrownBy(ps2::expectLineEnd); var ps3 = new FluentStream(" "); - var catchException = catchThrowableOfType(ps3::expectLineEnd, ParseException.class); + ParseException catchException = catchThrowableOfType(ps3::expectLineEnd, ParseException.class); assertThat(catchException.getCode()).isEqualTo(E0003); } @@ -50,7 +50,7 @@ class FluentStreamTest { void testExpectChar() { var ps = new FluentStream("z"); assertThatNoException().isThrownBy(() -> ps.expectChar('z')); - var catchException = catchThrowableOfType(() -> ps.expectChar('a'), ParseException.class); + ParseException catchException = catchThrowableOfType(() -> ps.expectChar('a'), ParseException.class); assertThat(catchException.getCode()).isEqualTo(E0003); } diff --git a/fluent-syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeEntryTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeEntryTest.java index 85568c8..d4e5ef4 100644 --- a/fluent-syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeEntryTest.java +++ b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/serializer/SerializeEntryTest.java @@ -1,6 +1,7 @@ package ru.di9.fluent.syntax.serializer; import org.junit.jupiter.api.Test; +import ru.di9.fluent.syntax.ast.TopLevel; import ru.di9.fluent.syntax.parser.FluentParser; import static org.assertj.core.api.Assertions.assertThat; @@ -14,10 +15,10 @@ class SerializeEntryTest { key = Value"""; var parser = new FluentParser(); - var topLevel = parser.parse(input).getBody().get(0); + TopLevel topLevel = parser.parse(input).getBody().getFirst(); var serializer = new FluentSerializer(); - var serialized = serializer.serialize(topLevel).trim(); + String serialized = serializer.serialize(topLevel).trim(); assertThat(serialized).isEqualTo(input); } diff --git a/fluent-syntax/src/test/java/ru/di9/fluent/test/utils/AstAssert.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/test/utils/AstAssert.java similarity index 85% rename from fluent-syntax/src/test/java/ru/di9/fluent/test/utils/AstAssert.java rename to fluent-syntax/src/test/java/ru/di9/fluent/syntax/test/utils/AstAssert.java index ead430c..eb41b56 100644 --- a/fluent-syntax/src/test/java/ru/di9/fluent/test/utils/AstAssert.java +++ b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/test/utils/AstAssert.java @@ -1,4 +1,4 @@ -package ru.di9.fluent.test.utils; +package ru.di9.fluent.syntax.test.utils; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -7,6 +7,8 @@ import org.json.JSONException; import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; import ru.di9.fluent.syntax.ast.BaseNode; +import ru.di9.fluent.test.utils.BaseNodeJsonSerializer; +import ru.di9.fluent.test.utils.NullIgnoreComparator; import java.io.PrintStream; @@ -26,13 +28,13 @@ public class AstAssert extends AbstractAssert { @SuppressWarnings("unused") public AstAssert printAstJson(PrintStream printStream) { - var json = gson.toJson(actual); + String json = gson.toJson(actual); printStream.println(json); return this; } public AstAssert isEqualAstJson(String astJson) { - var actualJson = gson.toJson(actual); + String actualJson = gson.toJson(actual); try { JSONAssert.assertEquals(astJson, actualJson, nullIgnoreComparator); diff --git a/fluent-syntax/src/test/java/ru/di9/fluent/syntax/MathUtilsTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/utils/MathUtilsTest.java similarity index 90% rename from fluent-syntax/src/test/java/ru/di9/fluent/syntax/MathUtilsTest.java rename to fluent-syntax/src/test/java/ru/di9/fluent/syntax/utils/MathUtilsTest.java index 7eb0c5f..1f369dd 100644 --- a/fluent-syntax/src/test/java/ru/di9/fluent/syntax/MathUtilsTest.java +++ b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/utils/MathUtilsTest.java @@ -1,4 +1,4 @@ -package ru.di9.fluent.syntax; +package ru.di9.fluent.syntax.utils; import org.junit.jupiter.api.Test; diff --git a/fluent-syntax/src/test/java/ru/di9/fluent/syntax/StringUtilsTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/utils/StringUtilsTest.java similarity index 94% rename from fluent-syntax/src/test/java/ru/di9/fluent/syntax/StringUtilsTest.java rename to fluent-syntax/src/test/java/ru/di9/fluent/syntax/utils/StringUtilsTest.java index d4ac741..a82f94d 100644 --- a/fluent-syntax/src/test/java/ru/di9/fluent/syntax/StringUtilsTest.java +++ b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/utils/StringUtilsTest.java @@ -1,4 +1,4 @@ -package ru.di9.fluent.syntax; +package ru.di9.fluent.syntax.utils; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/fluent-syntax/src/test/java/ru/di9/fluent/syntax/visitor/VisitorTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/visitor/VisitorTest.java index 7c243c0..a7a709d 100644 --- a/fluent-syntax/src/test/java/ru/di9/fluent/syntax/visitor/VisitorTest.java +++ b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/visitor/VisitorTest.java @@ -3,10 +3,7 @@ package ru.di9.fluent.syntax.visitor; import lombok.Getter; import lombok.Setter; import org.junit.jupiter.api.Test; -import ru.di9.fluent.syntax.ast.Identifier; -import ru.di9.fluent.syntax.ast.Pattern; -import ru.di9.fluent.syntax.ast.TextElement; -import ru.di9.fluent.syntax.ast.Variant; +import ru.di9.fluent.syntax.ast.*; import ru.di9.fluent.syntax.parser.FluentParser; import java.util.Iterator; @@ -29,7 +26,7 @@ class VisitorTest { msg = foo {$var -> *[other] bar } baz"""; - var res = parser.parse(source); + Resource res = parser.parse(source); visitor.visit(res); assertEquals(3, visitor.wordCount); assertEquals(2, visitor.patternCount); diff --git a/fluent-syntax/src/test/java/ru/di9/fluent/test/utils/BaseNodeJsonSerializer.java b/fluent-syntax/src/test/java/ru/di9/fluent/test/utils/BaseNodeJsonSerializer.java index c2f3857..79006e8 100644 --- a/fluent-syntax/src/test/java/ru/di9/fluent/test/utils/BaseNodeJsonSerializer.java +++ b/fluent-syntax/src/test/java/ru/di9/fluent/test/utils/BaseNodeJsonSerializer.java @@ -16,7 +16,7 @@ public class BaseNodeJsonSerializer implements JsonSerializer { var root = new JsonObject(); root.add("type", new JsonPrimitive(baseNode.getClass().getSimpleName())); - var fields = Reflect.on(baseNode).fields(); + Map fields = Reflect.on(baseNode).fields(); for (Map.Entry field : fields.entrySet()) { String name = field.getKey().equalsIgnoreCase("isDefault") ? "default" : field.getKey(); diff --git a/fluent-syntax/src/test/java/ru/di9/fluent/test/utils/FileUtils.java b/fluent-syntax/src/test/java/ru/di9/fluent/test/utils/FileUtils.java index 8a3e7ea..3a2dc55 100644 --- a/fluent-syntax/src/test/java/ru/di9/fluent/test/utils/FileUtils.java +++ b/fluent-syntax/src/test/java/ru/di9/fluent/test/utils/FileUtils.java @@ -5,13 +5,13 @@ import java.nio.file.Path; public interface FileUtils { static String getExt(String fileName) { - var idx = fileName.lastIndexOf('.'); + int idx = fileName.lastIndexOf('.'); if (idx < 0) return ""; return fileName.substring(idx + 1); } static String getName(String fileName) { - var idx = fileName.lastIndexOf('.'); + int idx = fileName.lastIndexOf('.'); if (idx < 0) return fileName; return fileName.substring(0, idx); } From 8f476300c2aed63fb80f1e7fa5ca9e40d8c3373b Mon Sep 17 00:00:00 2001 From: Voomra Date: Thu, 16 Apr 2026 00:30:27 +0300 Subject: [PATCH 10/11] build: upgrade AssertJ --- .../java/ru/di9/fluent/syntax/parser/FluentStreamTest.java | 4 ++-- gradle/libs.versions.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/FluentStreamTest.java b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/FluentStreamTest.java index da118c8..b65b6a6 100644 --- a/fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/FluentStreamTest.java +++ b/fluent-syntax/src/test/java/ru/di9/fluent/syntax/parser/FluentStreamTest.java @@ -42,7 +42,7 @@ class FluentStreamTest { assertThatNoException().isThrownBy(ps2::expectLineEnd); var ps3 = new FluentStream(" "); - ParseException catchException = catchThrowableOfType(ps3::expectLineEnd, ParseException.class); + ParseException catchException = catchThrowableOfType(ParseException.class, ps3::expectLineEnd); assertThat(catchException.getCode()).isEqualTo(E0003); } @@ -50,7 +50,7 @@ class FluentStreamTest { void testExpectChar() { var ps = new FluentStream("z"); assertThatNoException().isThrownBy(() -> ps.expectChar('z')); - ParseException catchException = catchThrowableOfType(() -> ps.expectChar('a'), ParseException.class); + ParseException catchException = catchThrowableOfType(ParseException.class, () -> ps.expectChar('a')); assertThat(catchException.getCode()).isEqualTo(E0003); } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8400d93..3ea1d09 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ lombok = { group = "org.projectlombok", name = "lombok", version = "1.18.30" } junit-platform = { group = "org.junit", name = "junit-bom", version = "5.9.2" } junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter" } junit-launcher = { group = "org.junit.platform", name = "junit-platform-launcher" } -assertj = { group = "org.assertj", name = "assertj-core", version = "3.24.2" } +assertj = { group = "org.assertj", name = "assertj-core", version = "3.27.7" } gson = { group = "com.google.code.gson", name = "gson", version = "2.9.1" } joor = { group = "org.jooq", name = "joor", version = "0.9.15" } jsonassert = { group = "org.skyscreamer", name = "jsonassert", version = "1.5.1" } From fccccbe87b4f7c3756783c7eee86ec5906f6f497 Mon Sep 17 00:00:00 2001 From: Voomra Date: Fri, 17 Apr 2026 03:25:39 +0300 Subject: [PATCH 11/11] feat: release fluent-syntax 0.1.0 --- fluent-syntax/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fluent-syntax/build.gradle.kts b/fluent-syntax/build.gradle.kts index 968ecaa..5c1e3bf 100644 --- a/fluent-syntax/build.gradle.kts +++ b/fluent-syntax/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } group = "ru.di9.fluent" -version = "1.0-SNAPSHOT" +version = "0.1.0" java.toolchain { languageVersion = JavaLanguageVersion.of(21)