From 499c699cd136c67714ae958e2af74dfe7746ecd2 Mon Sep 17 00:00:00 2001 From: Voomra Date: Fri, 29 Mar 2024 13:44:27 +0300 Subject: [PATCH] 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")