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 | |