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.
87mod helper;
88
89#[cfg(feature = "serde")]
90use 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))]
116pub 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"))]
199pub 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))]
258pub 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))]
312pub 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))]
392pub 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"))]
470pub 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))]
540pub 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))]
589pub 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"))]
673pub 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"))]
758pub 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>"))]
804pub 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"))]
885pub 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"))]
954pub 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"))]
1010pub 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))]
1440pub enum Expression<S> {
1441 Select {
1442 selector: InlineExpression<S>,
1443 variants: Vec<Variant<S>>,
1444 },
1445 Inline(InlineExpression<S>),
1446}
1447