1 //! Abstract Syntax Tree representation of the Fluent Translation List. 2 //! 3 //! The AST of Fluent contains all nodes structures to represent a complete 4 //! representation of the FTL resource. 5 //! 6 //! The tree preserves all semantic information and allow for round-trip 7 //! of a canonically written FTL resource. 8 //! 9 //! The root node is called [`Resource`] and contains a list of [`Entry`] nodes 10 //! representing all possible entries in the Fluent Translation List. 11 //! 12 //! # Example 13 //! 14 //! ``` 15 //! use fluent_syntax::parser; 16 //! use fluent_syntax::ast; 17 //! 18 //! let ftl = r#" 19 //! 20 //! ## This is a message comment 21 //! hello-world = Hello World! 22 //! .tooltip = Tooltip for you, { $userName }. 23 //! 24 //! "#; 25 //! 26 //! let resource = parser::parse(ftl) 27 //! .expect("Failed to parse an FTL resource."); 28 //! 29 //! assert_eq!( 30 //! resource.body[0], 31 //! ast::Entry::Message( 32 //! ast::Message { 33 //! id: ast::Identifier { 34 //! name: "hello-world" 35 //! }, 36 //! value: Some(ast::Pattern { 37 //! elements: vec![ 38 //! ast::PatternElement::TextElement { 39 //! value: "Hello World!" 40 //! }, 41 //! ] 42 //! }), 43 //! attributes: vec![ 44 //! ast::Attribute { 45 //! id: ast::Identifier { 46 //! name: "tooltip" 47 //! }, 48 //! value: ast::Pattern { 49 //! elements: vec![ 50 //! ast::PatternElement::TextElement { 51 //! value: "Tooltip for you, " 52 //! }, 53 //! ast::PatternElement::Placeable { 54 //! expression: ast::Expression::Inline( 55 //! ast::InlineExpression::VariableReference { 56 //! id: ast::Identifier { 57 //! name: "userName" 58 //! } 59 //! } 60 //! ) 61 //! }, 62 //! ast::PatternElement::TextElement { 63 //! value: "." 64 //! }, 65 //! ] 66 //! } 67 //! } 68 //! ], 69 //! comment: Some( 70 //! ast::Comment { 71 //! content: vec!["This is a message comment"] 72 //! } 73 //! ) 74 //! } 75 //! ), 76 //! ); 77 //! ``` 78 //! 79 //! ## Errors 80 //! 81 //! Fluent AST preserves blocks containing invaid syntax as [`Entry::Junk`]. 82 //! 83 //! ## White space 84 //! 85 //! At the moment, AST does not preserve white space. In result only a 86 //! canonical form of the AST is suitable for a round-trip. 87 mod helper; 88 89 #[cfg(feature = "serde")] 90 use serde::{Deserialize, Serialize}; 91 92 /// Root node of a Fluent Translation List. 93 /// 94 /// A [`Resource`] contains a body with a list of [`Entry`] nodes. 95 /// 96 /// # Example 97 /// 98 /// ``` 99 /// use fluent_syntax::parser; 100 /// use fluent_syntax::ast; 101 /// 102 /// let ftl = ""; 103 /// 104 /// let resource = parser::parse(ftl) 105 /// .expect("Failed to parse an FTL resource."); 106 /// 107 /// assert_eq!( 108 /// resource, 109 /// ast::Resource { 110 /// body: vec![] 111 /// } 112 /// ); 113 /// ``` 114 #[derive(Debug, PartialEq, Clone)] 115 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 116 pub struct Resource<S> { 117 pub body: Vec<Entry<S>>, 118 } 119 120 /// A top-level node representing an entry of a [`Resource`]. 121 /// 122 /// Every [`Entry`] is a standalone element and the parser is capable 123 /// of recovering from errors by identifying a beginning of a next entry. 124 /// 125 /// # Example 126 /// 127 /// ``` 128 /// use fluent_syntax::parser; 129 /// use fluent_syntax::ast; 130 /// 131 /// let ftl = r#" 132 /// 133 /// key = Value 134 /// 135 /// "#; 136 /// 137 /// let resource = parser::parse(ftl) 138 /// .expect("Failed to parse an FTL resource."); 139 /// 140 /// assert_eq!( 141 /// resource, 142 /// ast::Resource { 143 /// body: vec![ 144 /// ast::Entry::Message( 145 /// ast::Message { 146 /// id: ast::Identifier { 147 /// name: "key" 148 /// }, 149 /// value: Some(ast::Pattern { 150 /// elements: vec![ 151 /// ast::PatternElement::TextElement { 152 /// value: "Value" 153 /// }, 154 /// ] 155 /// }), 156 /// attributes: vec![], 157 /// comment: None, 158 /// } 159 /// ) 160 /// ] 161 /// } 162 /// ); 163 /// ``` 164 /// 165 /// # Junk Entry 166 /// 167 /// If FTL source contains invalid FTL content, it will be preserved 168 /// in form of [`Entry::Junk`] nodes. 169 /// 170 /// # Example 171 /// 172 /// ``` 173 /// use fluent_syntax::parser; 174 /// use fluent_syntax::ast; 175 /// 176 /// let ftl = r#" 177 /// 178 /// g@rb@ge En!ry 179 /// 180 /// "#; 181 /// 182 /// let (resource, _) = parser::parse(ftl) 183 /// .expect_err("Failed to parse an FTL resource."); 184 /// 185 /// assert_eq!( 186 /// resource, 187 /// ast::Resource { 188 /// body: vec![ 189 /// ast::Entry::Junk { 190 /// content: "g@rb@ge En!ry\n\n" 191 /// } 192 /// ] 193 /// } 194 /// ); 195 /// ``` 196 #[derive(Debug, PartialEq, Clone)] 197 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 198 #[cfg_attr(feature = "serde", serde(tag = "type"))] 199 pub enum Entry<S> { 200 Message(Message<S>), 201 Term(Term<S>), 202 Comment(Comment<S>), 203 GroupComment(Comment<S>), 204 ResourceComment(Comment<S>), 205 Junk { content: S }, 206 } 207 208 /// Message node represents the most common [`Entry`] in an FTL [`Resource`]. 209 /// 210 /// A message is a localization unit with a [`Identifier`] unique within a given 211 /// [`Resource`], and a value or attributes with associated [`Pattern`]. 212 /// 213 /// A message can contain a simple text value, or a compound combination of value 214 /// and attributes which together can be used to localize a complex User Interface 215 /// element. 216 /// 217 /// Finally, each [`Message`] may have an associated [`Comment`]. 218 /// 219 /// # Example 220 /// 221 /// ``` 222 /// use fluent_syntax::parser; 223 /// use fluent_syntax::ast; 224 /// 225 /// let ftl = r#" 226 /// 227 /// hello-world = Hello, World! 228 /// 229 /// "#; 230 /// 231 /// let resource = parser::parse(ftl) 232 /// .expect("Failed to parse an FTL resource."); 233 /// 234 /// assert_eq!( 235 /// resource, 236 /// ast::Resource { 237 /// body: vec![ 238 /// ast::Entry::Message(ast::Message { 239 /// id: ast::Identifier { 240 /// name: "hello-world" 241 /// }, 242 /// value: Some(ast::Pattern { 243 /// elements: vec![ 244 /// ast::PatternElement::TextElement { 245 /// value: "Hello, World!" 246 /// } 247 /// ] 248 /// }), 249 /// attributes: vec![], 250 /// comment: None, 251 /// }) 252 /// ] 253 /// } 254 /// ); 255 /// ``` 256 #[derive(Debug, PartialEq, Clone)] 257 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 258 pub struct Message<S> { 259 pub id: Identifier<S>, 260 pub value: Option<Pattern<S>>, 261 pub attributes: Vec<Attribute<S>>, 262 pub comment: Option<Comment<S>>, 263 } 264 265 /// A Fluent [`Term`]. 266 /// 267 /// Terms are semantically similar to [`Message`] nodes, but 268 /// they represent a separate concept in Fluent system. 269 /// 270 /// Every term has to have a value, and the parser will 271 /// report errors when term references are used in wrong positions. 272 /// 273 /// # Example 274 /// 275 /// ``` 276 /// use fluent_syntax::parser; 277 /// use fluent_syntax::ast; 278 /// 279 /// let ftl = r#" 280 /// 281 /// -brand-name = Nightly 282 /// 283 /// "#; 284 /// 285 /// let resource = parser::parse(ftl) 286 /// .expect("Failed to parse an FTL resource."); 287 /// 288 /// assert_eq!( 289 /// resource, 290 /// ast::Resource { 291 /// body: vec![ 292 /// ast::Entry::Term(ast::Term { 293 /// id: ast::Identifier { 294 /// name: "brand-name" 295 /// }, 296 /// value: ast::Pattern { 297 /// elements: vec![ 298 /// ast::PatternElement::TextElement { 299 /// value: "Nightly" 300 /// } 301 /// ] 302 /// }, 303 /// attributes: vec![], 304 /// comment: None, 305 /// }) 306 /// ] 307 /// } 308 /// ); 309 /// ``` 310 #[derive(Debug, PartialEq, Clone)] 311 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 312 pub struct Term<S> { 313 pub id: Identifier<S>, 314 pub value: Pattern<S>, 315 pub attributes: Vec<Attribute<S>>, 316 pub comment: Option<Comment<S>>, 317 } 318 319 /// Pattern contains a value of a [`Message`], [`Term`] or an [`Attribute`]. 320 /// 321 /// Each pattern is a list of [`PatternElement`] nodes representing 322 /// either a simple textual value, or a combination of text literals 323 /// and placeholder [`Expression`] nodes. 324 /// 325 /// # Example 326 /// 327 /// ``` 328 /// use fluent_syntax::parser; 329 /// use fluent_syntax::ast; 330 /// 331 /// let ftl = r#" 332 /// 333 /// hello-world = Hello, World! 334 /// 335 /// welcome = Welcome, { $userName }. 336 /// 337 /// "#; 338 /// 339 /// let resource = parser::parse(ftl) 340 /// .expect("Failed to parse an FTL resource."); 341 /// 342 /// assert_eq!( 343 /// resource, 344 /// ast::Resource { 345 /// body: vec![ 346 /// ast::Entry::Message(ast::Message { 347 /// id: ast::Identifier { 348 /// name: "hello-world" 349 /// }, 350 /// value: Some(ast::Pattern { 351 /// elements: vec![ 352 /// ast::PatternElement::TextElement { 353 /// value: "Hello, World!" 354 /// } 355 /// ] 356 /// }), 357 /// attributes: vec![], 358 /// comment: None, 359 /// }), 360 /// ast::Entry::Message(ast::Message { 361 /// id: ast::Identifier { 362 /// name: "welcome" 363 /// }, 364 /// value: Some(ast::Pattern { 365 /// elements: vec![ 366 /// ast::PatternElement::TextElement { 367 /// value: "Welcome, " 368 /// }, 369 /// ast::PatternElement::Placeable { 370 /// expression: ast::Expression::Inline( 371 /// ast::InlineExpression::VariableReference { 372 /// id: ast::Identifier { 373 /// name: "userName" 374 /// } 375 /// } 376 /// ) 377 /// }, 378 /// ast::PatternElement::TextElement { 379 /// value: "." 380 /// } 381 /// ] 382 /// }), 383 /// attributes: vec![], 384 /// comment: None, 385 /// }), 386 /// ] 387 /// } 388 /// ); 389 /// ``` 390 #[derive(Debug, PartialEq, Clone)] 391 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 392 pub struct Pattern<S> { 393 pub elements: Vec<PatternElement<S>>, 394 } 395 396 /// PatternElement is an element of a [`Pattern`]. 397 /// 398 /// Each [`PatternElement`] node represents 399 /// either a simple textual value, or a combination of text literals 400 /// and placeholder [`Expression`] nodes. 401 /// 402 /// # Example 403 /// 404 /// ``` 405 /// use fluent_syntax::parser; 406 /// use fluent_syntax::ast; 407 /// 408 /// let ftl = r#" 409 /// 410 /// hello-world = Hello, World! 411 /// 412 /// welcome = Welcome, { $userName }. 413 /// 414 /// "#; 415 /// 416 /// let resource = parser::parse(ftl) 417 /// .expect("Failed to parse an FTL resource."); 418 /// 419 /// assert_eq!( 420 /// resource, 421 /// ast::Resource { 422 /// body: vec![ 423 /// ast::Entry::Message(ast::Message { 424 /// id: ast::Identifier { 425 /// name: "hello-world" 426 /// }, 427 /// value: Some(ast::Pattern { 428 /// elements: vec![ 429 /// ast::PatternElement::TextElement { 430 /// value: "Hello, World!" 431 /// } 432 /// ] 433 /// }), 434 /// attributes: vec![], 435 /// comment: None, 436 /// }), 437 /// ast::Entry::Message(ast::Message { 438 /// id: ast::Identifier { 439 /// name: "welcome" 440 /// }, 441 /// value: Some(ast::Pattern { 442 /// elements: vec![ 443 /// ast::PatternElement::TextElement { 444 /// value: "Welcome, " 445 /// }, 446 /// ast::PatternElement::Placeable { 447 /// expression: ast::Expression::Inline( 448 /// ast::InlineExpression::VariableReference { 449 /// id: ast::Identifier { 450 /// name: "userName" 451 /// } 452 /// } 453 /// ) 454 /// }, 455 /// ast::PatternElement::TextElement { 456 /// value: "." 457 /// } 458 /// ] 459 /// }), 460 /// attributes: vec![], 461 /// comment: None, 462 /// }), 463 /// ] 464 /// } 465 /// ); 466 /// ``` 467 #[derive(Debug, PartialEq, Clone)] 468 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 469 #[cfg_attr(feature = "serde", serde(tag = "type"))] 470 pub enum PatternElement<S> { 471 TextElement { value: S }, 472 Placeable { expression: Expression<S> }, 473 } 474 475 /// Attribute represents a part of a [`Message`] or [`Term`]. 476 /// 477 /// Attributes are used to express a compound list of keyed 478 /// [`Pattern`] elements on an entry. 479 /// 480 /// # Example 481 /// 482 /// ``` 483 /// use fluent_syntax::parser; 484 /// use fluent_syntax::ast; 485 /// 486 /// let ftl = r#" 487 /// 488 /// hello-world = 489 /// .title = This is a title 490 /// .accesskey = T 491 /// 492 /// "#; 493 /// 494 /// let resource = parser::parse(ftl) 495 /// .expect("Failed to parse an FTL resource."); 496 /// 497 /// assert_eq!( 498 /// resource, 499 /// ast::Resource { 500 /// body: vec![ 501 /// ast::Entry::Message(ast::Message { 502 /// id: ast::Identifier { 503 /// name: "hello-world" 504 /// }, 505 /// value: None, 506 /// attributes: vec![ 507 /// ast::Attribute { 508 /// id: ast::Identifier { 509 /// name: "title" 510 /// }, 511 /// value: ast::Pattern { 512 /// elements: vec![ 513 /// ast::PatternElement::TextElement { 514 /// value: "This is a title" 515 /// }, 516 /// ] 517 /// } 518 /// }, 519 /// ast::Attribute { 520 /// id: ast::Identifier { 521 /// name: "accesskey" 522 /// }, 523 /// value: ast::Pattern { 524 /// elements: vec![ 525 /// ast::PatternElement::TextElement { 526 /// value: "T" 527 /// }, 528 /// ] 529 /// } 530 /// } 531 /// ], 532 /// comment: None, 533 /// }), 534 /// ] 535 /// } 536 /// ); 537 /// ``` 538 #[derive(Debug, PartialEq, Clone)] 539 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 540 pub struct Attribute<S> { 541 pub id: Identifier<S>, 542 pub value: Pattern<S>, 543 } 544 545 /// Identifier is part of nodes such as [`Message`], [`Term`] and [`Attribute`]. 546 /// 547 /// It is used to associate a unique key with an [`Entry`] or an [`Attribute`] 548 /// and in [`Expression`] nodes to refer to another entry. 549 /// 550 /// # Example 551 /// 552 /// ``` 553 /// use fluent_syntax::parser; 554 /// use fluent_syntax::ast; 555 /// 556 /// let ftl = r#" 557 /// 558 /// hello-world = Value 559 /// 560 /// "#; 561 /// 562 /// let resource = parser::parse(ftl) 563 /// .expect("Failed to parse an FTL resource."); 564 /// 565 /// assert_eq!( 566 /// resource, 567 /// ast::Resource { 568 /// body: vec![ 569 /// ast::Entry::Message(ast::Message { 570 /// id: ast::Identifier { 571 /// name: "hello-world" 572 /// }, 573 /// value: Some(ast::Pattern { 574 /// elements: vec![ 575 /// ast::PatternElement::TextElement { 576 /// value: "Value" 577 /// } 578 /// ] 579 /// }), 580 /// attributes: vec![], 581 /// comment: None, 582 /// }), 583 /// ] 584 /// } 585 /// ); 586 /// ``` 587 #[derive(Debug, PartialEq, Clone)] 588 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 589 pub struct Identifier<S> { 590 pub name: S, 591 } 592 593 /// Variant is a single branch of a value in a [`Select`](Expression::Select) expression. 594 /// 595 /// It's a pair of [`VariantKey`] and [`Pattern`]. If the selector match the 596 /// key, then the value of the variant is returned as the value of the expression. 597 /// 598 /// # Example 599 /// 600 /// ``` 601 /// use fluent_syntax::parser; 602 /// use fluent_syntax::ast; 603 /// 604 /// let ftl = r#" 605 /// 606 /// hello-world = { $var -> 607 /// [key1] Value 1 608 /// *[other] Value 2 609 /// } 610 /// 611 /// "#; 612 /// 613 /// let resource = parser::parse(ftl) 614 /// .expect("Failed to parse an FTL resource."); 615 /// 616 /// assert_eq!( 617 /// resource, 618 /// ast::Resource { 619 /// body: vec![ 620 /// ast::Entry::Message(ast::Message { 621 /// id: ast::Identifier { 622 /// name: "hello-world" 623 /// }, 624 /// value: Some(ast::Pattern { 625 /// elements: vec![ 626 /// ast::PatternElement::Placeable { 627 /// expression: ast::Expression::Select { 628 /// selector: ast::InlineExpression::VariableReference { 629 /// id: ast::Identifier { name: "var" }, 630 /// }, 631 /// variants: vec![ 632 /// ast::Variant { 633 /// key: ast::VariantKey::Identifier { 634 /// name: "key1" 635 /// }, 636 /// value: ast::Pattern { 637 /// elements: vec![ 638 /// ast::PatternElement::TextElement { 639 /// value: "Value 1", 640 /// } 641 /// ] 642 /// }, 643 /// default: false, 644 /// }, 645 /// ast::Variant { 646 /// key: ast::VariantKey::Identifier { 647 /// name: "other" 648 /// }, 649 /// value: ast::Pattern { 650 /// elements: vec![ 651 /// ast::PatternElement::TextElement { 652 /// value: "Value 2", 653 /// } 654 /// ] 655 /// }, 656 /// default: true, 657 /// }, 658 /// ] 659 /// } 660 /// } 661 /// ] 662 /// }), 663 /// attributes: vec![], 664 /// comment: None, 665 /// }), 666 /// ] 667 /// } 668 /// ); 669 /// ``` 670 #[derive(Debug, PartialEq, Clone)] 671 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 672 #[cfg_attr(feature = "serde", serde(tag = "type"))] 673 pub struct Variant<S> { 674 pub key: VariantKey<S>, 675 pub value: Pattern<S>, 676 pub default: bool, 677 } 678 679 /// A key of a [`Variant`]. 680 /// 681 /// Variant key can be either an identifier or a number. 682 /// 683 /// # Example 684 /// 685 /// ``` 686 /// use fluent_syntax::parser; 687 /// use fluent_syntax::ast; 688 /// 689 /// let ftl = r#" 690 /// 691 /// hello-world = { $var -> 692 /// [0] Value 1 693 /// *[other] Value 2 694 /// } 695 /// 696 /// "#; 697 /// 698 /// let resource = parser::parse(ftl) 699 /// .expect("Failed to parse an FTL resource."); 700 /// 701 /// assert_eq!( 702 /// resource, 703 /// ast::Resource { 704 /// body: vec![ 705 /// ast::Entry::Message(ast::Message { 706 /// id: ast::Identifier { 707 /// name: "hello-world" 708 /// }, 709 /// value: Some(ast::Pattern { 710 /// elements: vec![ 711 /// ast::PatternElement::Placeable { 712 /// expression: ast::Expression::Select { 713 /// selector: ast::InlineExpression::VariableReference { 714 /// id: ast::Identifier { name: "var" }, 715 /// }, 716 /// variants: vec![ 717 /// ast::Variant { 718 /// key: ast::VariantKey::NumberLiteral { 719 /// value: "0" 720 /// }, 721 /// value: ast::Pattern { 722 /// elements: vec![ 723 /// ast::PatternElement::TextElement { 724 /// value: "Value 1", 725 /// } 726 /// ] 727 /// }, 728 /// default: false, 729 /// }, 730 /// ast::Variant { 731 /// key: ast::VariantKey::Identifier { 732 /// name: "other" 733 /// }, 734 /// value: ast::Pattern { 735 /// elements: vec![ 736 /// ast::PatternElement::TextElement { 737 /// value: "Value 2", 738 /// } 739 /// ] 740 /// }, 741 /// default: true, 742 /// }, 743 /// ] 744 /// } 745 /// } 746 /// ] 747 /// }), 748 /// attributes: vec![], 749 /// comment: None, 750 /// }), 751 /// ] 752 /// } 753 /// ); 754 /// ``` 755 #[derive(Debug, PartialEq, Clone)] 756 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 757 #[cfg_attr(feature = "serde", serde(tag = "type"))] 758 pub enum VariantKey<S> { 759 Identifier { name: S }, 760 NumberLiteral { value: S }, 761 } 762 763 /// Fluent [`Comment`]. 764 /// 765 /// In Fluent, comments may be standalone, or associated with 766 /// an entry such as [`Term`] or [`Message`]. 767 /// 768 /// When used as a standalone [`Entry`], comments may appear in one of 769 /// three levels: 770 /// 771 /// * Standalone comment 772 /// * Group comment associated with a group of messages 773 /// * Resource comment associated with the whole resource 774 /// 775 /// # Example 776 /// 777 /// ``` 778 /// use fluent_syntax::parser; 779 /// use fluent_syntax::ast; 780 /// 781 /// let ftl = r#" 782 /// ## A standalone level comment 783 /// "#; 784 /// 785 /// let resource = parser::parse(ftl) 786 /// .expect("Failed to parse an FTL resource."); 787 /// 788 /// assert_eq!( 789 /// resource, 790 /// ast::Resource { 791 /// body: vec![ 792 /// ast::Entry::Comment(ast::Comment { 793 /// content: vec![ 794 /// "A standalone level comment" 795 /// ] 796 /// }) 797 /// ] 798 /// } 799 /// ); 800 /// ``` 801 #[derive(Debug, PartialEq, Clone)] 802 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 803 #[cfg_attr(feature = "serde", serde(from = "helper::CommentDef<S>"))] 804 pub struct Comment<S> { 805 pub content: Vec<S>, 806 } 807 808 /// List of arguments for a [`FunctionReference`](InlineExpression::FunctionReference) or a 809 /// [`TermReference`](InlineExpression::TermReference). 810 /// 811 /// Function and Term reference may contain a list of positional and 812 /// named arguments passed to them. 813 /// 814 /// # Example 815 /// 816 /// ``` 817 /// use fluent_syntax::parser; 818 /// use fluent_syntax::ast; 819 /// 820 /// let ftl = r#" 821 /// 822 /// key = { FUNC($var1, "literal", style: "long") } 823 /// 824 /// "#; 825 /// 826 /// let resource = parser::parse(ftl) 827 /// .expect("Failed to parse an FTL resource."); 828 /// 829 /// assert_eq!( 830 /// resource, 831 /// ast::Resource { 832 /// body: vec![ 833 /// ast::Entry::Message( 834 /// ast::Message { 835 /// id: ast::Identifier { 836 /// name: "key" 837 /// }, 838 /// value: Some(ast::Pattern { 839 /// elements: vec![ 840 /// ast::PatternElement::Placeable { 841 /// expression: ast::Expression::Inline( 842 /// ast::InlineExpression::FunctionReference { 843 /// id: ast::Identifier { 844 /// name: "FUNC" 845 /// }, 846 /// arguments: ast::CallArguments { 847 /// positional: vec![ 848 /// ast::InlineExpression::VariableReference { 849 /// id: ast::Identifier { 850 /// name: "var1" 851 /// } 852 /// }, 853 /// ast::InlineExpression::StringLiteral { 854 /// value: "literal", 855 /// } 856 /// ], 857 /// named: vec![ 858 /// ast::NamedArgument { 859 /// name: ast::Identifier { 860 /// name: "style" 861 /// }, 862 /// value: ast::InlineExpression::StringLiteral 863 /// { 864 /// value: "long" 865 /// } 866 /// } 867 /// ], 868 /// } 869 /// } 870 /// ) 871 /// }, 872 /// ] 873 /// }), 874 /// attributes: vec![], 875 /// comment: None, 876 /// } 877 /// ) 878 /// ] 879 /// } 880 /// ); 881 /// ``` 882 #[derive(Debug, PartialEq, Clone, Default)] 883 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 884 #[cfg_attr(feature = "serde", serde(tag = "type"))] 885 pub struct CallArguments<S> { 886 pub positional: Vec<InlineExpression<S>>, 887 pub named: Vec<NamedArgument<S>>, 888 } 889 890 /// A key-value pair used in [`CallArguments`]. 891 /// 892 /// # Example 893 /// 894 /// ``` 895 /// use fluent_syntax::parser; 896 /// use fluent_syntax::ast; 897 /// 898 /// let ftl = r#" 899 /// 900 /// key = { FUNC(style: "long") } 901 /// 902 /// "#; 903 /// 904 /// let resource = parser::parse(ftl) 905 /// .expect("Failed to parse an FTL resource."); 906 /// 907 /// assert_eq!( 908 /// resource, 909 /// ast::Resource { 910 /// body: vec![ 911 /// ast::Entry::Message( 912 /// ast::Message { 913 /// id: ast::Identifier { 914 /// name: "key" 915 /// }, 916 /// value: Some(ast::Pattern { 917 /// elements: vec![ 918 /// ast::PatternElement::Placeable { 919 /// expression: ast::Expression::Inline( 920 /// ast::InlineExpression::FunctionReference { 921 /// id: ast::Identifier { 922 /// name: "FUNC" 923 /// }, 924 /// arguments: ast::CallArguments { 925 /// positional: vec![], 926 /// named: vec![ 927 /// ast::NamedArgument { 928 /// name: ast::Identifier { 929 /// name: "style" 930 /// }, 931 /// value: ast::InlineExpression::StringLiteral 932 /// { 933 /// value: "long" 934 /// } 935 /// } 936 /// ], 937 /// } 938 /// } 939 /// ) 940 /// }, 941 /// ] 942 /// }), 943 /// attributes: vec![], 944 /// comment: None, 945 /// } 946 /// ) 947 /// ] 948 /// } 949 /// ); 950 /// ``` 951 #[derive(Debug, PartialEq, Clone)] 952 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 953 #[cfg_attr(feature = "serde", serde(tag = "type"))] 954 pub struct NamedArgument<S> { 955 pub name: Identifier<S>, 956 pub value: InlineExpression<S>, 957 } 958 959 /// A subset of expressions which can be used as [`Placeable`](PatternElement::Placeable), 960 /// [`selector`](Expression::Select), or in [`CallArguments`]. 961 /// 962 /// # Example 963 /// 964 /// ``` 965 /// use fluent_syntax::parser; 966 /// use fluent_syntax::ast; 967 /// 968 /// let ftl = r#" 969 /// 970 /// key = { $emailCount } 971 /// 972 /// "#; 973 /// 974 /// let resource = parser::parse(ftl) 975 /// .expect("Failed to parse an FTL resource."); 976 /// 977 /// assert_eq!( 978 /// resource, 979 /// ast::Resource { 980 /// body: vec![ 981 /// ast::Entry::Message( 982 /// ast::Message { 983 /// id: ast::Identifier { 984 /// name: "key" 985 /// }, 986 /// value: Some(ast::Pattern { 987 /// elements: vec![ 988 /// ast::PatternElement::Placeable { 989 /// expression: ast::Expression::Inline( 990 /// ast::InlineExpression::VariableReference { 991 /// id: ast::Identifier { 992 /// name: "emailCount" 993 /// }, 994 /// } 995 /// ) 996 /// }, 997 /// ] 998 /// }), 999 /// attributes: vec![], 1000 /// comment: None, 1001 /// } 1002 /// ) 1003 /// ] 1004 /// } 1005 /// ); 1006 /// ``` 1007 #[derive(Debug, PartialEq, Clone)] 1008 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 1009 #[cfg_attr(feature = "serde", serde(tag = "type"))] 1010 pub enum InlineExpression<S> { 1011 /// Single line string literal enclosed in `"`. 1012 /// 1013 /// # Example 1014 /// 1015 /// ``` 1016 /// use fluent_syntax::parser; 1017 /// use fluent_syntax::ast; 1018 /// 1019 /// let ftl = r#" 1020 /// 1021 /// key = { "this is a literal" } 1022 /// 1023 /// "#; 1024 /// 1025 /// let resource = parser::parse(ftl) 1026 /// .expect("Failed to parse an FTL resource."); 1027 /// 1028 /// assert_eq!( 1029 /// resource, 1030 /// ast::Resource { 1031 /// body: vec![ 1032 /// ast::Entry::Message( 1033 /// ast::Message { 1034 /// id: ast::Identifier { 1035 /// name: "key" 1036 /// }, 1037 /// value: Some(ast::Pattern { 1038 /// elements: vec![ 1039 /// ast::PatternElement::Placeable { 1040 /// expression: ast::Expression::Inline( 1041 /// ast::InlineExpression::StringLiteral { 1042 /// value: "this is a literal", 1043 /// } 1044 /// ) 1045 /// }, 1046 /// ] 1047 /// }), 1048 /// attributes: vec![], 1049 /// comment: None, 1050 /// } 1051 /// ) 1052 /// ] 1053 /// } 1054 /// ); 1055 /// ``` 1056 StringLiteral { value: S }, 1057 /// A number literal. 1058 /// 1059 /// # Example 1060 /// 1061 /// ``` 1062 /// use fluent_syntax::parser; 1063 /// use fluent_syntax::ast; 1064 /// 1065 /// let ftl = r#" 1066 /// 1067 /// key = { -0.5 } 1068 /// 1069 /// "#; 1070 /// 1071 /// let resource = parser::parse(ftl) 1072 /// .expect("Failed to parse an FTL resource."); 1073 /// 1074 /// assert_eq!( 1075 /// resource, 1076 /// ast::Resource { 1077 /// body: vec![ 1078 /// ast::Entry::Message( 1079 /// ast::Message { 1080 /// id: ast::Identifier { 1081 /// name: "key" 1082 /// }, 1083 /// value: Some(ast::Pattern { 1084 /// elements: vec![ 1085 /// ast::PatternElement::Placeable { 1086 /// expression: ast::Expression::Inline( 1087 /// ast::InlineExpression::NumberLiteral { 1088 /// value: "-0.5", 1089 /// } 1090 /// ) 1091 /// }, 1092 /// ] 1093 /// }), 1094 /// attributes: vec![], 1095 /// comment: None, 1096 /// } 1097 /// ) 1098 /// ] 1099 /// } 1100 /// ); 1101 /// ``` 1102 NumberLiteral { value: S }, 1103 /// A function reference. 1104 /// 1105 /// # Example 1106 /// 1107 /// ``` 1108 /// use fluent_syntax::parser; 1109 /// use fluent_syntax::ast; 1110 /// 1111 /// let ftl = r#" 1112 /// 1113 /// key = { FUNC() } 1114 /// 1115 /// "#; 1116 /// 1117 /// let resource = parser::parse(ftl) 1118 /// .expect("Failed to parse an FTL resource."); 1119 /// 1120 /// assert_eq!( 1121 /// resource, 1122 /// ast::Resource { 1123 /// body: vec![ 1124 /// ast::Entry::Message( 1125 /// ast::Message { 1126 /// id: ast::Identifier { 1127 /// name: "key" 1128 /// }, 1129 /// value: Some(ast::Pattern { 1130 /// elements: vec![ 1131 /// ast::PatternElement::Placeable { 1132 /// expression: ast::Expression::Inline( 1133 /// ast::InlineExpression::FunctionReference { 1134 /// id: ast::Identifier { 1135 /// name: "FUNC" 1136 /// }, 1137 /// arguments: ast::CallArguments::default(), 1138 /// } 1139 /// ) 1140 /// }, 1141 /// ] 1142 /// }), 1143 /// attributes: vec![], 1144 /// comment: None, 1145 /// } 1146 /// ) 1147 /// ] 1148 /// } 1149 /// ); 1150 /// ``` 1151 FunctionReference { 1152 id: Identifier<S>, 1153 arguments: CallArguments<S>, 1154 }, 1155 /// A reference to another message. 1156 /// 1157 /// # Example 1158 /// 1159 /// ``` 1160 /// use fluent_syntax::parser; 1161 /// use fluent_syntax::ast; 1162 /// 1163 /// let ftl = r#" 1164 /// 1165 /// key = { key2 } 1166 /// 1167 /// "#; 1168 /// 1169 /// let resource = parser::parse(ftl) 1170 /// .expect("Failed to parse an FTL resource."); 1171 /// 1172 /// assert_eq!( 1173 /// resource, 1174 /// ast::Resource { 1175 /// body: vec![ 1176 /// ast::Entry::Message( 1177 /// ast::Message { 1178 /// id: ast::Identifier { 1179 /// name: "key" 1180 /// }, 1181 /// value: Some(ast::Pattern { 1182 /// elements: vec![ 1183 /// ast::PatternElement::Placeable { 1184 /// expression: ast::Expression::Inline( 1185 /// ast::InlineExpression::MessageReference { 1186 /// id: ast::Identifier { 1187 /// name: "key2" 1188 /// }, 1189 /// attribute: None, 1190 /// } 1191 /// ) 1192 /// }, 1193 /// ] 1194 /// }), 1195 /// attributes: vec![], 1196 /// comment: None, 1197 /// } 1198 /// ) 1199 /// ] 1200 /// } 1201 /// ); 1202 /// ``` 1203 MessageReference { 1204 id: Identifier<S>, 1205 attribute: Option<Identifier<S>>, 1206 }, 1207 /// A reference to a term. 1208 /// 1209 /// # Example 1210 /// 1211 /// ``` 1212 /// use fluent_syntax::parser; 1213 /// use fluent_syntax::ast; 1214 /// 1215 /// let ftl = r#" 1216 /// 1217 /// key = { -brand-name } 1218 /// 1219 /// "#; 1220 /// 1221 /// let resource = parser::parse(ftl) 1222 /// .expect("Failed to parse an FTL resource."); 1223 /// 1224 /// assert_eq!( 1225 /// resource, 1226 /// ast::Resource { 1227 /// body: vec![ 1228 /// ast::Entry::Message( 1229 /// ast::Message { 1230 /// id: ast::Identifier { 1231 /// name: "key" 1232 /// }, 1233 /// value: Some(ast::Pattern { 1234 /// elements: vec![ 1235 /// ast::PatternElement::Placeable { 1236 /// expression: ast::Expression::Inline( 1237 /// ast::InlineExpression::TermReference { 1238 /// id: ast::Identifier { 1239 /// name: "brand-name" 1240 /// }, 1241 /// attribute: None, 1242 /// arguments: None, 1243 /// } 1244 /// ) 1245 /// }, 1246 /// ] 1247 /// }), 1248 /// attributes: vec![], 1249 /// comment: None, 1250 /// } 1251 /// ) 1252 /// ] 1253 /// } 1254 /// ); 1255 /// ``` 1256 TermReference { 1257 id: Identifier<S>, 1258 attribute: Option<Identifier<S>>, 1259 arguments: Option<CallArguments<S>>, 1260 }, 1261 /// A reference to a variable. 1262 /// 1263 /// # Example 1264 /// 1265 /// ``` 1266 /// use fluent_syntax::parser; 1267 /// use fluent_syntax::ast; 1268 /// 1269 /// let ftl = r#" 1270 /// 1271 /// key = { $var1 } 1272 /// 1273 /// "#; 1274 /// 1275 /// let resource = parser::parse(ftl) 1276 /// .expect("Failed to parse an FTL resource."); 1277 /// 1278 /// assert_eq!( 1279 /// resource, 1280 /// ast::Resource { 1281 /// body: vec![ 1282 /// ast::Entry::Message( 1283 /// ast::Message { 1284 /// id: ast::Identifier { 1285 /// name: "key" 1286 /// }, 1287 /// value: Some(ast::Pattern { 1288 /// elements: vec![ 1289 /// ast::PatternElement::Placeable { 1290 /// expression: ast::Expression::Inline( 1291 /// ast::InlineExpression::VariableReference { 1292 /// id: ast::Identifier { 1293 /// name: "var1" 1294 /// }, 1295 /// } 1296 /// ) 1297 /// }, 1298 /// ] 1299 /// }), 1300 /// attributes: vec![], 1301 /// comment: None, 1302 /// } 1303 /// ) 1304 /// ] 1305 /// } 1306 /// ); 1307 /// ``` 1308 VariableReference { id: Identifier<S> }, 1309 /// A placeable which may contain another expression. 1310 /// 1311 /// # Example 1312 /// 1313 /// ``` 1314 /// use fluent_syntax::parser; 1315 /// use fluent_syntax::ast; 1316 /// 1317 /// let ftl = r#" 1318 /// 1319 /// key = { { "placeable" } } 1320 /// 1321 /// "#; 1322 /// 1323 /// let resource = parser::parse(ftl) 1324 /// .expect("Failed to parse an FTL resource."); 1325 /// 1326 /// assert_eq!( 1327 /// resource, 1328 /// ast::Resource { 1329 /// body: vec![ 1330 /// ast::Entry::Message( 1331 /// ast::Message { 1332 /// id: ast::Identifier { 1333 /// name: "key" 1334 /// }, 1335 /// value: Some(ast::Pattern { 1336 /// elements: vec![ 1337 /// ast::PatternElement::Placeable { 1338 /// expression: ast::Expression::Inline( 1339 /// ast::InlineExpression::Placeable { 1340 /// expression: Box::new( 1341 /// ast::Expression::Inline( 1342 /// ast::InlineExpression::StringLiteral { 1343 /// value: "placeable" 1344 /// } 1345 /// ) 1346 /// ) 1347 /// } 1348 /// ) 1349 /// }, 1350 /// ] 1351 /// }), 1352 /// attributes: vec![], 1353 /// comment: None, 1354 /// } 1355 /// ) 1356 /// ] 1357 /// } 1358 /// ); 1359 /// ``` 1360 Placeable { expression: Box<Expression<S>> }, 1361 } 1362 1363 /// An expression that is either a select expression or an inline expression. 1364 /// 1365 /// # Example 1366 /// 1367 /// ``` 1368 /// use fluent_syntax::parser; 1369 /// use fluent_syntax::ast; 1370 /// 1371 /// let ftl = r#" 1372 /// 1373 /// key = { $var -> 1374 /// [key1] Value 1 1375 /// *[other] Value 2 1376 /// } 1377 /// 1378 /// "#; 1379 /// 1380 /// let resource = parser::parse(ftl) 1381 /// .expect("Failed to parse an FTL resource."); 1382 /// 1383 /// assert_eq!( 1384 /// resource, 1385 /// ast::Resource { 1386 /// body: vec![ 1387 /// ast::Entry::Message(ast::Message { 1388 /// id: ast::Identifier { 1389 /// name: "key" 1390 /// }, 1391 /// value: Some(ast::Pattern { 1392 /// elements: vec![ 1393 /// ast::PatternElement::Placeable { 1394 /// expression: ast::Expression::Select { 1395 /// selector: ast::InlineExpression::VariableReference { 1396 /// id: ast::Identifier { name: "var" }, 1397 /// }, 1398 /// variants: vec![ 1399 /// ast::Variant { 1400 /// key: ast::VariantKey::Identifier { 1401 /// name: "key1" 1402 /// }, 1403 /// value: ast::Pattern { 1404 /// elements: vec![ 1405 /// ast::PatternElement::TextElement { 1406 /// value: "Value 1", 1407 /// } 1408 /// ] 1409 /// }, 1410 /// default: false, 1411 /// }, 1412 /// ast::Variant { 1413 /// key: ast::VariantKey::Identifier { 1414 /// name: "other" 1415 /// }, 1416 /// value: ast::Pattern { 1417 /// elements: vec![ 1418 /// ast::PatternElement::TextElement { 1419 /// value: "Value 2", 1420 /// } 1421 /// ] 1422 /// }, 1423 /// default: true, 1424 /// }, 1425 /// ] 1426 /// } 1427 /// } 1428 /// ] 1429 /// }), 1430 /// attributes: vec![], 1431 /// comment: None, 1432 /// }), 1433 /// ] 1434 /// } 1435 /// ); 1436 /// ``` 1437 #[derive(Debug, PartialEq, Clone)] 1438 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 1439 #[cfg_attr(feature = "serde", serde(untagged))] 1440 pub enum Expression<S> { 1441 Select { 1442 selector: InlineExpression<S>, 1443 variants: Vec<Variant<S>>, 1444 }, 1445 Inline(InlineExpression<S>), 1446 } 1447