1use crate::grammar::parse_many;
2use crate::grammar::twig::expression::{parse_twig_expression, TWIG_EXPRESSION_RECOVERY_SET};
3use crate::parser::event::CompletedMarker;
4use crate::parser::{ParseErrorBuilder, Parser};
5use crate::syntax::untyped::SyntaxKind;
6use crate::T;
7use regex::Regex;
8use std::sync::LazyLock;
9
10pub static TWIG_NAME_REGEX: LazyLock<Regex> =
12 LazyLock::new(|| Regex::new(r"^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$").unwrap());
13
14pub(crate) fn parse_twig_literal(parser: &mut Parser) -> Option<CompletedMarker> {
15 if parser.at(T![number]) {
16 Some(parse_twig_number(parser))
17 } else if parser.at_set(&[T!["\""], T!["'"]]) {
18 Some(parse_twig_string(parser, true))
19 } else if parser.at(T!["["]) {
20 Some(parse_twig_array(parser))
21 } else if parser.at(T!["null"]) {
22 Some(parse_twig_null(parser))
23 } else if parser.at_set(&[T!["true"], T!["false"]]) {
24 Some(parse_twig_boolean(parser))
25 } else if parser.at(T!["{"]) {
26 Some(parse_twig_hash(parser))
27 } else {
28 let mut node = parse_twig_name(parser)?;
30
31 if parser.at(T!["("]) {
33 node = parse_twig_function(parser, node);
34 } else if parser.at(T!["=>"]) {
35 let m = parser.precede(node);
37 let last_node = parser.complete(m, SyntaxKind::TWIG_ARGUMENTS);
38 node = parse_twig_arrow_function(parser, last_node);
39 }
40
41 Some(node)
42 }
43}
44
45pub(crate) fn parse_postfix_operators(
47 parser: &mut Parser,
48 mut node: CompletedMarker,
49) -> CompletedMarker {
50 parse_many(
51 parser,
52 |_| false,
53 |p| {
54 if p.at(T!["."]) {
55 node = parse_twig_accessor(p, node.clone());
56 } else if p.at(T!["["]) {
57 node = parse_twig_indexer(p, node.clone());
58 } else if p.at(T!["|"]) {
59 node = parse_twig_filter(p, node.clone());
60 }
61 },
62 );
63
64 node
65}
66
67fn parse_twig_number(parser: &mut Parser) -> CompletedMarker {
68 debug_assert!(parser.at(T![number]));
69 let m = parser.start();
70 parser.bump();
71
72 parser.complete(m, SyntaxKind::TWIG_LITERAL_NUMBER)
73}
74
75pub(crate) fn parse_twig_string(
76 parser: &mut Parser,
77 mut interpolation_allowed: bool,
78) -> CompletedMarker {
79 debug_assert!(parser.at_set(&[T!["\""], T!["'"]]));
80 let m = parser.start();
81 let starting_quote_token = parser.bump();
82 let quote_kind = starting_quote_token.kind;
83 interpolation_allowed = match quote_kind {
84 T!["\""] => interpolation_allowed,
85 _ => false, };
87
88 let m_inner = parser.start();
89 parse_many(
90 parser,
91 |p| p.at(quote_kind),
92 |p| {
93 if p.at_following(&[T!["\\"], quote_kind]) {
94 p.bump();
96 p.bump();
97 } else if p.at_following(&[T!["#"], T!["{"]]) {
98 if !interpolation_allowed {
99 let opening_token = p.bump();
100 let interpolation_error = ParseErrorBuilder::new(
101 "no string interpolation, because it isn't allowed here",
102 )
103 .at_token(opening_token);
104 p.add_error(interpolation_error);
105 return;
106 }
107
108 p.explicitly_consume_trivia(); let interpolation_m = p.start();
111 p.bump(); p.bump();
113 if parse_twig_expression(p).is_none() {
114 p.add_error(ParseErrorBuilder::new("twig expression"));
115 }
116 p.expect(T!["}"], TWIG_EXPRESSION_RECOVERY_SET);
117 p.complete(
118 interpolation_m,
119 SyntaxKind::TWIG_LITERAL_STRING_INTERPOLATION,
120 );
121 } else {
122 p.bump();
124 }
125 },
126 );
127 parser.explicitly_consume_trivia(); parser.complete(m_inner, SyntaxKind::TWIG_LITERAL_STRING_INNER);
129
130 parser.expect(quote_kind, TWIG_EXPRESSION_RECOVERY_SET);
131 parser.complete(m, SyntaxKind::TWIG_LITERAL_STRING)
132}
133
134fn parse_twig_array(parser: &mut Parser) -> CompletedMarker {
135 debug_assert!(parser.at(T!["["]));
136 let m = parser.start();
137 parser.bump();
138
139 let list_m = parser.start();
141 parse_many(
142 parser,
143 |p| p.at(T!["]"]),
144 |p| {
145 parse_twig_expression(p);
146
147 if p.at(T![","]) {
148 p.bump();
150 } else if !p.at(T!["]"]) {
151 p.add_error(ParseErrorBuilder::new(","));
152 }
153 },
154 );
155 parser.complete(list_m, SyntaxKind::TWIG_LITERAL_ARRAY_INNER);
156
157 parser.expect(T!["]"], TWIG_EXPRESSION_RECOVERY_SET);
158 parser.complete(m, SyntaxKind::TWIG_LITERAL_ARRAY)
159}
160
161fn parse_twig_null(parser: &mut Parser) -> CompletedMarker {
162 debug_assert!(parser.at(T!["null"]));
163 let m = parser.start();
164 parser.bump();
165
166 parser.complete(m, SyntaxKind::TWIG_LITERAL_NULL)
167}
168
169fn parse_twig_boolean(parser: &mut Parser) -> CompletedMarker {
170 debug_assert!(parser.at_set(&[T!["true"], T!["false"]]));
171 let m = parser.start();
172 parser.bump();
173
174 parser.complete(m, SyntaxKind::TWIG_LITERAL_BOOLEAN)
175}
176
177pub(crate) fn parse_twig_hash(parser: &mut Parser) -> CompletedMarker {
178 debug_assert!(parser.at(T!["{"]));
179 let m = parser.start();
180 parser.bump();
181
182 let list_m = parser.start();
184 parse_many(
185 parser,
186 |p| p.at(T!["}"]),
187 |p| {
188 parse_twig_hash_pair(p);
189
190 if p.at(T![","]) {
191 p.bump();
193 } else if !p.at(T!["}"]) {
194 p.add_error(ParseErrorBuilder::new(","));
195 }
196 },
197 );
198 parser.complete(list_m, SyntaxKind::TWIG_LITERAL_HASH_ITEMS);
199
200 parser.expect(T!["}"], TWIG_EXPRESSION_RECOVERY_SET);
201 parser.complete(m, SyntaxKind::TWIG_LITERAL_HASH)
202}
203
204fn parse_twig_hash_pair(parser: &mut Parser) -> Option<CompletedMarker> {
205 let key = if parser.at(T![number]) {
206 let m = parse_twig_number(parser);
207 let preceded = parser.precede(m);
208 parser.complete(preceded, SyntaxKind::TWIG_LITERAL_HASH_KEY)
209 } else if parser.at_set(&[T!["'"], T!["\""]]) {
210 let m = parse_twig_string(parser, false); let preceded = parser.precede(m);
212 parser.complete(preceded, SyntaxKind::TWIG_LITERAL_HASH_KEY)
213 } else if parser.at(T!["("]) {
214 let m = parser.start();
215 parser.bump();
216 if parse_twig_expression(parser).is_none() {
217 parser.add_error(ParseErrorBuilder::new("twig expression"));
218 parser.recover(TWIG_EXPRESSION_RECOVERY_SET);
219 }
220 parser.expect(T![")"], TWIG_EXPRESSION_RECOVERY_SET);
221 parser.complete(m, SyntaxKind::TWIG_LITERAL_HASH_KEY)
222 } else {
223 let token_text = parser.peek_token()?.text;
224 if TWIG_NAME_REGEX.is_match(token_text) {
225 let m = parser.start();
226 parser.bump_as(SyntaxKind::TK_WORD);
227 parser.complete(m, SyntaxKind::TWIG_LITERAL_HASH_KEY)
228 } else {
229 return None;
230 }
231 };
232
233 if parser.at(T![":"]) {
235 parser.bump();
236 if parse_twig_expression(parser).is_none() {
237 parser.add_error(ParseErrorBuilder::new("value as twig expression"));
238 parser.recover(TWIG_EXPRESSION_RECOVERY_SET);
239 }
240 }
241
242 let preceded = parser.precede(key);
243 Some(parser.complete(preceded, SyntaxKind::TWIG_LITERAL_HASH_PAIR))
244}
245
246pub(crate) fn parse_twig_filter(
247 parser: &mut Parser,
248 mut last_node: CompletedMarker,
249) -> CompletedMarker {
250 debug_assert!(parser.at(T!["|"]));
251
252 let m = parser.precede(last_node);
254 last_node = parser.complete(m, SyntaxKind::TWIG_OPERAND);
255 let outer = parser.precede(last_node);
256
257 parser.bump();
259
260 let m = parser.start();
262 if parse_twig_name(parser).is_none() {
263 parser.add_error(ParseErrorBuilder::new("twig filter"));
264 parser.recover(TWIG_EXPRESSION_RECOVERY_SET);
265 } else if parser.at(T!["("]) {
266 let arguments_m = parser.start();
268 parser.bump();
269 parse_many(
270 parser,
271 |p| p.at(T![")"]),
272 |p| {
273 parse_twig_function_argument(p);
274 if p.at(T![","]) {
275 p.bump();
276 } else if !p.at(T![")"]) {
277 p.add_error(ParseErrorBuilder::new(","));
278 }
279 },
280 );
281 parser.expect(T![")"], TWIG_EXPRESSION_RECOVERY_SET);
282 parser.complete(arguments_m, SyntaxKind::TWIG_ARGUMENTS);
283 }
284 parser.complete(m, SyntaxKind::TWIG_OPERAND);
285
286 parser.complete(outer, SyntaxKind::TWIG_FILTER)
288}
289
290fn parse_twig_indexer(parser: &mut Parser, mut last_node: CompletedMarker) -> CompletedMarker {
291 debug_assert!(parser.at(T!["["]));
292
293 let m = parser.precede(last_node);
295 last_node = parser.complete(m, SyntaxKind::TWIG_OPERAND);
296 let outer = parser.precede(last_node);
297
298 parser.bump();
300
301 let index_m = parser.start();
302
303 let missing_lower_slice_bound = parse_twig_expression(parser).is_none();
305
306 let mut is_slice = false;
308 let missing_upper_slice_bound = if parser.at(T![":"]) {
309 parser.bump();
310 is_slice = true; parse_twig_expression(parser).is_none()
312 } else {
313 true
314 };
315
316 if missing_lower_slice_bound && missing_upper_slice_bound {
317 parser.add_error(ParseErrorBuilder::new("twig expression"));
318 parser.recover(TWIG_EXPRESSION_RECOVERY_SET);
319 }
320
321 parser.complete(
322 index_m,
323 if is_slice {
324 SyntaxKind::TWIG_INDEX_RANGE
325 } else {
326 SyntaxKind::TWIG_INDEX
327 },
328 );
329
330 parser.expect(T!["]"], TWIG_EXPRESSION_RECOVERY_SET);
331
332 parser.complete(outer, SyntaxKind::TWIG_INDEX_LOOKUP)
334}
335
336fn parse_twig_accessor(parser: &mut Parser, mut last_node: CompletedMarker) -> CompletedMarker {
337 debug_assert!(parser.at(T!["."]));
338
339 let m = parser.precede(last_node);
341 last_node = parser.complete(m, SyntaxKind::TWIG_OPERAND);
342 let outer = parser.precede(last_node);
343
344 parser.bump();
346
347 let m = parser.start();
349 if parser.at(T![number]) {
350 let n = parse_twig_number(parser);
352 let n = parser.precede(n);
353 parser.complete(n, SyntaxKind::TWIG_EXPRESSION);
354 parser.complete(m, SyntaxKind::TWIG_INDEX);
355 let node = parser.complete(outer, SyntaxKind::TWIG_INDEX_LOOKUP);
356 return node;
357 } else if parse_twig_name(parser).is_none() {
358 parser.add_error(ParseErrorBuilder::new(
359 "twig variable property, key or method",
360 ));
361 parser.recover(TWIG_EXPRESSION_RECOVERY_SET);
362 }
363 parser.complete(m, SyntaxKind::TWIG_OPERAND);
364
365 let mut node = parser.complete(outer, SyntaxKind::TWIG_ACCESSOR);
367
368 if parser.at(T!["("]) {
370 node = parse_twig_function(parser, node);
371 }
372
373 node
374}
375
376pub(crate) fn parse_twig_function(
377 parser: &mut Parser,
378 mut last_node: CompletedMarker,
379) -> CompletedMarker {
380 debug_assert!(parser.at(T!["("]));
381
382 let m = parser.precede(last_node);
384 last_node = parser.complete(m, SyntaxKind::TWIG_OPERAND);
385 let outer = parser.precede(last_node);
386
387 let arguments_m = parser.start();
389 parser.bump();
391 parse_many(
392 parser,
393 |p| p.at(T![")"]),
394 |p| {
395 parse_twig_function_argument(p);
396 if p.at(T![","]) {
397 p.bump();
398 } else if !p.at(T![")"]) {
399 p.add_error(ParseErrorBuilder::new(","));
400 }
401 },
402 );
403 parser.expect(T![")"], TWIG_EXPRESSION_RECOVERY_SET);
404 parser.complete(arguments_m, SyntaxKind::TWIG_ARGUMENTS);
405
406 parser.complete(outer, SyntaxKind::TWIG_FUNCTION_CALL)
408}
409
410pub(crate) fn parse_twig_arrow_function(
411 parser: &mut Parser,
412 last_node: CompletedMarker,
413) -> CompletedMarker {
414 debug_assert!(parser.at(T!["=>"]));
415
416 let outer = parser.precede(last_node);
417
418 parser.bump();
420
421 if parse_twig_expression(parser).is_none() {
423 parser.add_error(ParseErrorBuilder::new(
424 "single twig expression as the body of the closure",
425 ));
426 parser.recover(TWIG_EXPRESSION_RECOVERY_SET);
427 }
428
429 parser.complete(outer, SyntaxKind::TWIG_ARROW_FUNCTION)
431}
432
433pub(crate) fn parse_twig_function_argument(parser: &mut Parser) -> Option<CompletedMarker> {
434 if parser.at_following(&[T![word], T!["="]]) {
437 let named_arg_m = parser.start();
438 parser.bump();
439 parser.expect(T!["="], TWIG_EXPRESSION_RECOVERY_SET);
440 parse_twig_expression(parser);
441 Some(parser.complete(named_arg_m, SyntaxKind::TWIG_NAMED_ARGUMENT))
442 } else {
443 parse_twig_expression(parser)
444 }
445}
446
447pub(crate) fn parse_twig_name(parser: &mut Parser) -> Option<CompletedMarker> {
448 let is_at_special = parser.at_set(&[T!["same as"], T!["divisible by"]]);
450 let token_text = parser.peek_token()?.text;
451 if !is_at_special && !TWIG_NAME_REGEX.is_match(token_text) {
452 return None;
453 }
454
455 let m = parser.start();
456 parser.bump_as(SyntaxKind::TK_WORD);
457 let m = parser.complete(m, SyntaxKind::TWIG_LITERAL_NAME);
458 Some(m)
459}
460
461#[cfg(test)]
462mod tests {
463 use expect_test::expect;
464
465 use crate::parser::check_parse;
466
467 #[test]
468 fn parse_twig_string_single_quotes() {
469 check_parse(
470 r#"{{ 'hel"lo world' }}"#,
471 expect![[r#"
472 ROOT@0..20
473 TWIG_VAR@0..20
474 TK_OPEN_CURLY_CURLY@0..2 "{{"
475 TWIG_EXPRESSION@2..17
476 TWIG_LITERAL_STRING@2..17
477 TK_WHITESPACE@2..3 " "
478 TK_SINGLE_QUOTES@3..4 "'"
479 TWIG_LITERAL_STRING_INNER@4..16
480 TK_WORD@4..7 "hel"
481 TK_DOUBLE_QUOTES@7..8 "\""
482 TK_WORD@8..10 "lo"
483 TK_WHITESPACE@10..11 " "
484 TK_WORD@11..16 "world"
485 TK_SINGLE_QUOTES@16..17 "'"
486 TK_WHITESPACE@17..18 " "
487 TK_CLOSE_CURLY_CURLY@18..20 "}}""#]],
488 );
489 }
490
491 #[test]
492 fn parse_twig_string_double_quotes() {
493 check_parse(
494 r#"{{ "hel'lo world" }}"#,
495 expect![[r#"
496 ROOT@0..20
497 TWIG_VAR@0..20
498 TK_OPEN_CURLY_CURLY@0..2 "{{"
499 TWIG_EXPRESSION@2..17
500 TWIG_LITERAL_STRING@2..17
501 TK_WHITESPACE@2..3 " "
502 TK_DOUBLE_QUOTES@3..4 "\""
503 TWIG_LITERAL_STRING_INNER@4..16
504 TK_WORD@4..7 "hel"
505 TK_SINGLE_QUOTES@7..8 "'"
506 TK_WORD@8..10 "lo"
507 TK_WHITESPACE@10..11 " "
508 TK_WORD@11..16 "world"
509 TK_DOUBLE_QUOTES@16..17 "\""
510 TK_WHITESPACE@17..18 " "
511 TK_CLOSE_CURLY_CURLY@18..20 "}}""#]],
512 );
513 }
514
515 #[test]
516 fn parse_twig_string_escaped_double_quotes() {
517 check_parse(
518 r#"{{ "hel\"lo world" }}"#,
519 expect![[r#"
520 ROOT@0..21
521 TWIG_VAR@0..21
522 TK_OPEN_CURLY_CURLY@0..2 "{{"
523 TWIG_EXPRESSION@2..18
524 TWIG_LITERAL_STRING@2..18
525 TK_WHITESPACE@2..3 " "
526 TK_DOUBLE_QUOTES@3..4 "\""
527 TWIG_LITERAL_STRING_INNER@4..17
528 TK_WORD@4..7 "hel"
529 TK_BACKWARD_SLASH@7..8 "\\"
530 TK_DOUBLE_QUOTES@8..9 "\""
531 TK_WORD@9..11 "lo"
532 TK_WHITESPACE@11..12 " "
533 TK_WORD@12..17 "world"
534 TK_DOUBLE_QUOTES@17..18 "\""
535 TK_WHITESPACE@18..19 " "
536 TK_CLOSE_CURLY_CURLY@19..21 "}}""#]],
537 );
538 }
539
540 #[test]
541 fn parse_twig_string_with_leading_and_trailing_trivia() {
542 check_parse(
543 r#"{{ " , " }}"#,
544 expect![[r#"
545 ROOT@0..11
546 TWIG_VAR@0..11
547 TK_OPEN_CURLY_CURLY@0..2 "{{"
548 TWIG_EXPRESSION@2..8
549 TWIG_LITERAL_STRING@2..8
550 TK_WHITESPACE@2..3 " "
551 TK_DOUBLE_QUOTES@3..4 "\""
552 TWIG_LITERAL_STRING_INNER@4..7
553 TK_WHITESPACE@4..5 " "
554 TK_COMMA@5..6 ","
555 TK_WHITESPACE@6..7 " "
556 TK_DOUBLE_QUOTES@7..8 "\""
557 TK_WHITESPACE@8..9 " "
558 TK_CLOSE_CURLY_CURLY@9..11 "}}""#]],
559 );
560 }
561
562 #[test]
563 fn parse_twig_string_interpolation() {
564 check_parse(
565 r#"{{ "foo #{1 + 2} baz" }}"#,
566 expect![[r##"
567 ROOT@0..24
568 TWIG_VAR@0..24
569 TK_OPEN_CURLY_CURLY@0..2 "{{"
570 TWIG_EXPRESSION@2..21
571 TWIG_LITERAL_STRING@2..21
572 TK_WHITESPACE@2..3 " "
573 TK_DOUBLE_QUOTES@3..4 "\""
574 TWIG_LITERAL_STRING_INNER@4..20
575 TK_WORD@4..7 "foo"
576 TK_WHITESPACE@7..8 " "
577 TWIG_LITERAL_STRING_INTERPOLATION@8..16
578 TK_HASHTAG@8..9 "#"
579 TK_OPEN_CURLY@9..10 "{"
580 TWIG_EXPRESSION@10..15
581 TWIG_BINARY_EXPRESSION@10..15
582 TWIG_EXPRESSION@10..11
583 TWIG_LITERAL_NUMBER@10..11
584 TK_NUMBER@10..11 "1"
585 TK_WHITESPACE@11..12 " "
586 TK_PLUS@12..13 "+"
587 TWIG_EXPRESSION@13..15
588 TWIG_LITERAL_NUMBER@13..15
589 TK_WHITESPACE@13..14 " "
590 TK_NUMBER@14..15 "2"
591 TK_CLOSE_CURLY@15..16 "}"
592 TK_WHITESPACE@16..17 " "
593 TK_WORD@17..20 "baz"
594 TK_DOUBLE_QUOTES@20..21 "\""
595 TK_WHITESPACE@21..22 " "
596 TK_CLOSE_CURLY_CURLY@22..24 "}}""##]],
597 );
598 }
599
600 #[test]
601 fn parse_twig_string_interpolation_missing_expression() {
602 check_parse(
603 r#"{{ "foo #{ } baz" }}"#,
604 expect![[r##"
605 ROOT@0..20
606 TWIG_VAR@0..20
607 TK_OPEN_CURLY_CURLY@0..2 "{{"
608 TWIG_EXPRESSION@2..17
609 TWIG_LITERAL_STRING@2..17
610 TK_WHITESPACE@2..3 " "
611 TK_DOUBLE_QUOTES@3..4 "\""
612 TWIG_LITERAL_STRING_INNER@4..16
613 TK_WORD@4..7 "foo"
614 TK_WHITESPACE@7..8 " "
615 TWIG_LITERAL_STRING_INTERPOLATION@8..12
616 TK_HASHTAG@8..9 "#"
617 TK_OPEN_CURLY@9..10 "{"
618 TK_WHITESPACE@10..11 " "
619 TK_CLOSE_CURLY@11..12 "}"
620 TK_WHITESPACE@12..13 " "
621 TK_WORD@13..16 "baz"
622 TK_DOUBLE_QUOTES@16..17 "\""
623 TK_WHITESPACE@17..18 " "
624 TK_CLOSE_CURLY_CURLY@18..20 "}}"
625 error at 11..12: expected twig expression but found }"##]],
626 );
627 }
628
629 #[test]
630 fn parse_twig_integer_number() {
631 check_parse(
632 "{{ 42 }}",
633 expect![[r#"
634 ROOT@0..8
635 TWIG_VAR@0..8
636 TK_OPEN_CURLY_CURLY@0..2 "{{"
637 TWIG_EXPRESSION@2..5
638 TWIG_LITERAL_NUMBER@2..5
639 TK_WHITESPACE@2..3 " "
640 TK_NUMBER@3..5 "42"
641 TK_WHITESPACE@5..6 " "
642 TK_CLOSE_CURLY_CURLY@6..8 "}}""#]],
643 );
644 }
645
646 #[test]
647 fn parse_twig_floating_point_number() {
648 check_parse(
649 "{{ 0.3337 }}",
650 expect![[r#"
651 ROOT@0..12
652 TWIG_VAR@0..12
653 TK_OPEN_CURLY_CURLY@0..2 "{{"
654 TWIG_EXPRESSION@2..9
655 TWIG_LITERAL_NUMBER@2..9
656 TK_WHITESPACE@2..3 " "
657 TK_NUMBER@3..9 "0.3337"
658 TK_WHITESPACE@9..10 " "
659 TK_CLOSE_CURLY_CURLY@10..12 "}}""#]],
660 );
661 }
662
663 #[test]
664 fn parse_twig_number_array() {
665 check_parse(
666 "{{ [1, 2, 3] }}",
667 expect![[r#"
668 ROOT@0..15
669 TWIG_VAR@0..15
670 TK_OPEN_CURLY_CURLY@0..2 "{{"
671 TWIG_EXPRESSION@2..12
672 TWIG_LITERAL_ARRAY@2..12
673 TK_WHITESPACE@2..3 " "
674 TK_OPEN_SQUARE@3..4 "["
675 TWIG_LITERAL_ARRAY_INNER@4..11
676 TWIG_EXPRESSION@4..5
677 TWIG_LITERAL_NUMBER@4..5
678 TK_NUMBER@4..5 "1"
679 TK_COMMA@5..6 ","
680 TWIG_EXPRESSION@6..8
681 TWIG_LITERAL_NUMBER@6..8
682 TK_WHITESPACE@6..7 " "
683 TK_NUMBER@7..8 "2"
684 TK_COMMA@8..9 ","
685 TWIG_EXPRESSION@9..11
686 TWIG_LITERAL_NUMBER@9..11
687 TK_WHITESPACE@9..10 " "
688 TK_NUMBER@10..11 "3"
689 TK_CLOSE_SQUARE@11..12 "]"
690 TK_WHITESPACE@12..13 " "
691 TK_CLOSE_CURLY_CURLY@13..15 "}}""#]],
692 );
693 }
694
695 #[test]
696 fn parse_twig_number_array_missing_comma() {
697 check_parse(
698 "{{ [1, 2 3] }}",
699 expect![[r#"
700 ROOT@0..14
701 TWIG_VAR@0..14
702 TK_OPEN_CURLY_CURLY@0..2 "{{"
703 TWIG_EXPRESSION@2..11
704 TWIG_LITERAL_ARRAY@2..11
705 TK_WHITESPACE@2..3 " "
706 TK_OPEN_SQUARE@3..4 "["
707 TWIG_LITERAL_ARRAY_INNER@4..10
708 TWIG_EXPRESSION@4..5
709 TWIG_LITERAL_NUMBER@4..5
710 TK_NUMBER@4..5 "1"
711 TK_COMMA@5..6 ","
712 TWIG_EXPRESSION@6..8
713 TWIG_LITERAL_NUMBER@6..8
714 TK_WHITESPACE@6..7 " "
715 TK_NUMBER@7..8 "2"
716 TWIG_EXPRESSION@8..10
717 TWIG_LITERAL_NUMBER@8..10
718 TK_WHITESPACE@8..9 " "
719 TK_NUMBER@9..10 "3"
720 TK_CLOSE_SQUARE@10..11 "]"
721 TK_WHITESPACE@11..12 " "
722 TK_CLOSE_CURLY_CURLY@12..14 "}}"
723 error at 9..10: expected , but found number"#]],
724 );
725 }
726
727 #[test]
728 fn parse_twig_string_array() {
729 check_parse(
730 r#"{{ ["hello", "trailing", "comma",] }}"#,
731 expect![[r#"
732 ROOT@0..37
733 TWIG_VAR@0..37
734 TK_OPEN_CURLY_CURLY@0..2 "{{"
735 TWIG_EXPRESSION@2..34
736 TWIG_LITERAL_ARRAY@2..34
737 TK_WHITESPACE@2..3 " "
738 TK_OPEN_SQUARE@3..4 "["
739 TWIG_LITERAL_ARRAY_INNER@4..33
740 TWIG_EXPRESSION@4..11
741 TWIG_LITERAL_STRING@4..11
742 TK_DOUBLE_QUOTES@4..5 "\""
743 TWIG_LITERAL_STRING_INNER@5..10
744 TK_WORD@5..10 "hello"
745 TK_DOUBLE_QUOTES@10..11 "\""
746 TK_COMMA@11..12 ","
747 TWIG_EXPRESSION@12..23
748 TWIG_LITERAL_STRING@12..23
749 TK_WHITESPACE@12..13 " "
750 TK_DOUBLE_QUOTES@13..14 "\""
751 TWIG_LITERAL_STRING_INNER@14..22
752 TK_WORD@14..22 "trailing"
753 TK_DOUBLE_QUOTES@22..23 "\""
754 TK_COMMA@23..24 ","
755 TWIG_EXPRESSION@24..32
756 TWIG_LITERAL_STRING@24..32
757 TK_WHITESPACE@24..25 " "
758 TK_DOUBLE_QUOTES@25..26 "\""
759 TWIG_LITERAL_STRING_INNER@26..31
760 TK_WORD@26..31 "comma"
761 TK_DOUBLE_QUOTES@31..32 "\""
762 TK_COMMA@32..33 ","
763 TK_CLOSE_SQUARE@33..34 "]"
764 TK_WHITESPACE@34..35 " "
765 TK_CLOSE_CURLY_CURLY@35..37 "}}""#]],
766 );
767 }
768
769 #[test]
770 fn parse_twig_null() {
771 check_parse(
772 "{{ null }}",
773 expect![[r#"
774 ROOT@0..10
775 TWIG_VAR@0..10
776 TK_OPEN_CURLY_CURLY@0..2 "{{"
777 TWIG_EXPRESSION@2..7
778 TWIG_LITERAL_NULL@2..7
779 TK_WHITESPACE@2..3 " "
780 TK_NULL@3..7 "null"
781 TK_WHITESPACE@7..8 " "
782 TK_CLOSE_CURLY_CURLY@8..10 "}}""#]],
783 );
784 }
785
786 #[test]
787 fn parse_twig_boolean_true() {
788 check_parse(
789 "{{ true }}",
790 expect![[r#"
791 ROOT@0..10
792 TWIG_VAR@0..10
793 TK_OPEN_CURLY_CURLY@0..2 "{{"
794 TWIG_EXPRESSION@2..7
795 TWIG_LITERAL_BOOLEAN@2..7
796 TK_WHITESPACE@2..3 " "
797 TK_TRUE@3..7 "true"
798 TK_WHITESPACE@7..8 " "
799 TK_CLOSE_CURLY_CURLY@8..10 "}}""#]],
800 );
801 }
802
803 #[test]
804 fn parse_twig_boolean_false() {
805 check_parse(
806 "{{ false }}",
807 expect![[r#"
808 ROOT@0..11
809 TWIG_VAR@0..11
810 TK_OPEN_CURLY_CURLY@0..2 "{{"
811 TWIG_EXPRESSION@2..8
812 TWIG_LITERAL_BOOLEAN@2..8
813 TK_WHITESPACE@2..3 " "
814 TK_FALSE@3..8 "false"
815 TK_WHITESPACE@8..9 " "
816 TK_CLOSE_CURLY_CURLY@9..11 "}}""#]],
817 );
818 }
819
820 #[test]
821 fn parse_twig_number_hash() {
822 check_parse(
823 "{{ { 1: 'hello', 2: 'world' } }}",
824 expect![[r#"
825 ROOT@0..32
826 TWIG_VAR@0..32
827 TK_OPEN_CURLY_CURLY@0..2 "{{"
828 TWIG_EXPRESSION@2..29
829 TWIG_LITERAL_HASH@2..29
830 TK_WHITESPACE@2..3 " "
831 TK_OPEN_CURLY@3..4 "{"
832 TWIG_LITERAL_HASH_ITEMS@4..27
833 TWIG_LITERAL_HASH_PAIR@4..15
834 TWIG_LITERAL_HASH_KEY@4..6
835 TWIG_LITERAL_NUMBER@4..6
836 TK_WHITESPACE@4..5 " "
837 TK_NUMBER@5..6 "1"
838 TK_COLON@6..7 ":"
839 TWIG_EXPRESSION@7..15
840 TWIG_LITERAL_STRING@7..15
841 TK_WHITESPACE@7..8 " "
842 TK_SINGLE_QUOTES@8..9 "'"
843 TWIG_LITERAL_STRING_INNER@9..14
844 TK_WORD@9..14 "hello"
845 TK_SINGLE_QUOTES@14..15 "'"
846 TK_COMMA@15..16 ","
847 TWIG_LITERAL_HASH_PAIR@16..27
848 TWIG_LITERAL_HASH_KEY@16..18
849 TWIG_LITERAL_NUMBER@16..18
850 TK_WHITESPACE@16..17 " "
851 TK_NUMBER@17..18 "2"
852 TK_COLON@18..19 ":"
853 TWIG_EXPRESSION@19..27
854 TWIG_LITERAL_STRING@19..27
855 TK_WHITESPACE@19..20 " "
856 TK_SINGLE_QUOTES@20..21 "'"
857 TWIG_LITERAL_STRING_INNER@21..26
858 TK_WORD@21..26 "world"
859 TK_SINGLE_QUOTES@26..27 "'"
860 TK_WHITESPACE@27..28 " "
861 TK_CLOSE_CURLY@28..29 "}"
862 TK_WHITESPACE@29..30 " "
863 TK_CLOSE_CURLY_CURLY@30..32 "}}""#]],
864 );
865 }
866
867 #[test]
868 fn parse_twig_string_hash() {
869 check_parse(
870 "{{ { 'hello': 42, 'world': 33 } }}",
871 expect![[r#"
872 ROOT@0..34
873 TWIG_VAR@0..34
874 TK_OPEN_CURLY_CURLY@0..2 "{{"
875 TWIG_EXPRESSION@2..31
876 TWIG_LITERAL_HASH@2..31
877 TK_WHITESPACE@2..3 " "
878 TK_OPEN_CURLY@3..4 "{"
879 TWIG_LITERAL_HASH_ITEMS@4..29
880 TWIG_LITERAL_HASH_PAIR@4..16
881 TWIG_LITERAL_HASH_KEY@4..12
882 TWIG_LITERAL_STRING@4..12
883 TK_WHITESPACE@4..5 " "
884 TK_SINGLE_QUOTES@5..6 "'"
885 TWIG_LITERAL_STRING_INNER@6..11
886 TK_WORD@6..11 "hello"
887 TK_SINGLE_QUOTES@11..12 "'"
888 TK_COLON@12..13 ":"
889 TWIG_EXPRESSION@13..16
890 TWIG_LITERAL_NUMBER@13..16
891 TK_WHITESPACE@13..14 " "
892 TK_NUMBER@14..16 "42"
893 TK_COMMA@16..17 ","
894 TWIG_LITERAL_HASH_PAIR@17..29
895 TWIG_LITERAL_HASH_KEY@17..25
896 TWIG_LITERAL_STRING@17..25
897 TK_WHITESPACE@17..18 " "
898 TK_SINGLE_QUOTES@18..19 "'"
899 TWIG_LITERAL_STRING_INNER@19..24
900 TK_WORD@19..24 "world"
901 TK_SINGLE_QUOTES@24..25 "'"
902 TK_COLON@25..26 ":"
903 TWIG_EXPRESSION@26..29
904 TWIG_LITERAL_NUMBER@26..29
905 TK_WHITESPACE@26..27 " "
906 TK_NUMBER@27..29 "33"
907 TK_WHITESPACE@29..30 " "
908 TK_CLOSE_CURLY@30..31 "}"
909 TK_WHITESPACE@31..32 " "
910 TK_CLOSE_CURLY_CURLY@32..34 "}}""#]],
911 );
912 }
913
914 #[test]
915 fn parse_twig_named_hash() {
916 check_parse(
917 "{{ { hello: 42, world: 33 } }}",
918 expect![[r#"
919 ROOT@0..30
920 TWIG_VAR@0..30
921 TK_OPEN_CURLY_CURLY@0..2 "{{"
922 TWIG_EXPRESSION@2..27
923 TWIG_LITERAL_HASH@2..27
924 TK_WHITESPACE@2..3 " "
925 TK_OPEN_CURLY@3..4 "{"
926 TWIG_LITERAL_HASH_ITEMS@4..25
927 TWIG_LITERAL_HASH_PAIR@4..14
928 TWIG_LITERAL_HASH_KEY@4..10
929 TK_WHITESPACE@4..5 " "
930 TK_WORD@5..10 "hello"
931 TK_COLON@10..11 ":"
932 TWIG_EXPRESSION@11..14
933 TWIG_LITERAL_NUMBER@11..14
934 TK_WHITESPACE@11..12 " "
935 TK_NUMBER@12..14 "42"
936 TK_COMMA@14..15 ","
937 TWIG_LITERAL_HASH_PAIR@15..25
938 TWIG_LITERAL_HASH_KEY@15..21
939 TK_WHITESPACE@15..16 " "
940 TK_WORD@16..21 "world"
941 TK_COLON@21..22 ":"
942 TWIG_EXPRESSION@22..25
943 TWIG_LITERAL_NUMBER@22..25
944 TK_WHITESPACE@22..23 " "
945 TK_NUMBER@23..25 "33"
946 TK_WHITESPACE@25..26 " "
947 TK_CLOSE_CURLY@26..27 "}"
948 TK_WHITESPACE@27..28 " "
949 TK_CLOSE_CURLY_CURLY@28..30 "}}""#]],
950 );
951 }
952
953 #[test]
954 fn parse_twig_unquoted_hash_with_only_underscore() {
955 check_parse(
956 "{{ { valid: 42, _: 99 } }}",
957 expect![[r#"
958 ROOT@0..26
959 TWIG_VAR@0..26
960 TK_OPEN_CURLY_CURLY@0..2 "{{"
961 TWIG_EXPRESSION@2..23
962 TWIG_LITERAL_HASH@2..23
963 TK_WHITESPACE@2..3 " "
964 TK_OPEN_CURLY@3..4 "{"
965 TWIG_LITERAL_HASH_ITEMS@4..21
966 TWIG_LITERAL_HASH_PAIR@4..14
967 TWIG_LITERAL_HASH_KEY@4..10
968 TK_WHITESPACE@4..5 " "
969 TK_WORD@5..10 "valid"
970 TK_COLON@10..11 ":"
971 TWIG_EXPRESSION@11..14
972 TWIG_LITERAL_NUMBER@11..14
973 TK_WHITESPACE@11..12 " "
974 TK_NUMBER@12..14 "42"
975 TK_COMMA@14..15 ","
976 TWIG_LITERAL_HASH_PAIR@15..21
977 TWIG_LITERAL_HASH_KEY@15..17
978 TK_WHITESPACE@15..16 " "
979 TK_WORD@16..17 "_"
980 TK_COLON@17..18 ":"
981 TWIG_EXPRESSION@18..21
982 TWIG_LITERAL_NUMBER@18..21
983 TK_WHITESPACE@18..19 " "
984 TK_NUMBER@19..21 "99"
985 TK_WHITESPACE@21..22 " "
986 TK_CLOSE_CURLY@22..23 "}"
987 TK_WHITESPACE@23..24 " "
988 TK_CLOSE_CURLY_CURLY@24..26 "}}""#]],
989 );
990 }
991
992 #[test]
993 fn parse_twig_expression_hash_missing_comma() {
994 check_parse(
995 "{{ { (15): 42 (60): 33 } }}",
996 expect![[r#"
997 ROOT@0..27
998 TWIG_VAR@0..27
999 TK_OPEN_CURLY_CURLY@0..2 "{{"
1000 TWIG_EXPRESSION@2..24
1001 TWIG_LITERAL_HASH@2..24
1002 TK_WHITESPACE@2..3 " "
1003 TK_OPEN_CURLY@3..4 "{"
1004 TWIG_LITERAL_HASH_ITEMS@4..22
1005 TWIG_LITERAL_HASH_PAIR@4..13
1006 TWIG_LITERAL_HASH_KEY@4..9
1007 TK_WHITESPACE@4..5 " "
1008 TK_OPEN_PARENTHESIS@5..6 "("
1009 TWIG_EXPRESSION@6..8
1010 TWIG_LITERAL_NUMBER@6..8
1011 TK_NUMBER@6..8 "15"
1012 TK_CLOSE_PARENTHESIS@8..9 ")"
1013 TK_COLON@9..10 ":"
1014 TWIG_EXPRESSION@10..13
1015 TWIG_LITERAL_NUMBER@10..13
1016 TK_WHITESPACE@10..11 " "
1017 TK_NUMBER@11..13 "42"
1018 TWIG_LITERAL_HASH_PAIR@13..22
1019 TWIG_LITERAL_HASH_KEY@13..18
1020 TK_WHITESPACE@13..14 " "
1021 TK_OPEN_PARENTHESIS@14..15 "("
1022 TWIG_EXPRESSION@15..17
1023 TWIG_LITERAL_NUMBER@15..17
1024 TK_NUMBER@15..17 "60"
1025 TK_CLOSE_PARENTHESIS@17..18 ")"
1026 TK_COLON@18..19 ":"
1027 TWIG_EXPRESSION@19..22
1028 TWIG_LITERAL_NUMBER@19..22
1029 TK_WHITESPACE@19..20 " "
1030 TK_NUMBER@20..22 "33"
1031 TK_WHITESPACE@22..23 " "
1032 TK_CLOSE_CURLY@23..24 "}"
1033 TK_WHITESPACE@24..25 " "
1034 TK_CLOSE_CURLY_CURLY@25..27 "}}"
1035 error at 14..15: expected , but found ("#]],
1036 );
1037 }
1038
1039 #[test]
1040 fn parse_twig_expression_hash_missing_whitespace() {
1041 check_parse(
1042 "{{ { '%total%':reviews.totalReviews } }}",
1043 expect![[r#"
1044 ROOT@0..40
1045 TWIG_VAR@0..40
1046 TK_OPEN_CURLY_CURLY@0..2 "{{"
1047 TWIG_EXPRESSION@2..37
1048 TWIG_LITERAL_HASH@2..37
1049 TK_WHITESPACE@2..3 " "
1050 TK_OPEN_CURLY@3..4 "{"
1051 TWIG_LITERAL_HASH_ITEMS@4..35
1052 TWIG_LITERAL_HASH_PAIR@4..35
1053 TWIG_LITERAL_HASH_KEY@4..14
1054 TWIG_LITERAL_STRING@4..14
1055 TK_WHITESPACE@4..5 " "
1056 TK_SINGLE_QUOTES@5..6 "'"
1057 TWIG_LITERAL_STRING_INNER@6..13
1058 TK_PERCENT@6..7 "%"
1059 TK_WORD@7..12 "total"
1060 TK_PERCENT@12..13 "%"
1061 TK_SINGLE_QUOTES@13..14 "'"
1062 TK_COLON@14..15 ":"
1063 TWIG_EXPRESSION@15..35
1064 TWIG_ACCESSOR@15..35
1065 TWIG_OPERAND@15..22
1066 TWIG_LITERAL_NAME@15..22
1067 TK_WORD@15..22 "reviews"
1068 TK_DOT@22..23 "."
1069 TWIG_OPERAND@23..35
1070 TWIG_LITERAL_NAME@23..35
1071 TK_WORD@23..35 "totalReviews"
1072 TK_WHITESPACE@35..36 " "
1073 TK_CLOSE_CURLY@36..37 "}"
1074 TK_WHITESPACE@37..38 " "
1075 TK_CLOSE_CURLY_CURLY@38..40 "}}""#]],
1076 );
1077 }
1078
1079 #[test]
1080 fn parse_twig_complex_expression_hash() {
1081 check_parse(
1082 "{{ { (foo): 'foo', (1 + 1): 'bar', (foo ~ 'b'): 'baz' } }}",
1083 expect![[r#"
1084 ROOT@0..58
1085 TWIG_VAR@0..58
1086 TK_OPEN_CURLY_CURLY@0..2 "{{"
1087 TWIG_EXPRESSION@2..55
1088 TWIG_LITERAL_HASH@2..55
1089 TK_WHITESPACE@2..3 " "
1090 TK_OPEN_CURLY@3..4 "{"
1091 TWIG_LITERAL_HASH_ITEMS@4..53
1092 TWIG_LITERAL_HASH_PAIR@4..17
1093 TWIG_LITERAL_HASH_KEY@4..10
1094 TK_WHITESPACE@4..5 " "
1095 TK_OPEN_PARENTHESIS@5..6 "("
1096 TWIG_EXPRESSION@6..9
1097 TWIG_LITERAL_NAME@6..9
1098 TK_WORD@6..9 "foo"
1099 TK_CLOSE_PARENTHESIS@9..10 ")"
1100 TK_COLON@10..11 ":"
1101 TWIG_EXPRESSION@11..17
1102 TWIG_LITERAL_STRING@11..17
1103 TK_WHITESPACE@11..12 " "
1104 TK_SINGLE_QUOTES@12..13 "'"
1105 TWIG_LITERAL_STRING_INNER@13..16
1106 TK_WORD@13..16 "foo"
1107 TK_SINGLE_QUOTES@16..17 "'"
1108 TK_COMMA@17..18 ","
1109 TWIG_LITERAL_HASH_PAIR@18..33
1110 TWIG_LITERAL_HASH_KEY@18..26
1111 TK_WHITESPACE@18..19 " "
1112 TK_OPEN_PARENTHESIS@19..20 "("
1113 TWIG_EXPRESSION@20..25
1114 TWIG_BINARY_EXPRESSION@20..25
1115 TWIG_EXPRESSION@20..21
1116 TWIG_LITERAL_NUMBER@20..21
1117 TK_NUMBER@20..21 "1"
1118 TK_WHITESPACE@21..22 " "
1119 TK_PLUS@22..23 "+"
1120 TWIG_EXPRESSION@23..25
1121 TWIG_LITERAL_NUMBER@23..25
1122 TK_WHITESPACE@23..24 " "
1123 TK_NUMBER@24..25 "1"
1124 TK_CLOSE_PARENTHESIS@25..26 ")"
1125 TK_COLON@26..27 ":"
1126 TWIG_EXPRESSION@27..33
1127 TWIG_LITERAL_STRING@27..33
1128 TK_WHITESPACE@27..28 " "
1129 TK_SINGLE_QUOTES@28..29 "'"
1130 TWIG_LITERAL_STRING_INNER@29..32
1131 TK_WORD@29..32 "bar"
1132 TK_SINGLE_QUOTES@32..33 "'"
1133 TK_COMMA@33..34 ","
1134 TWIG_LITERAL_HASH_PAIR@34..53
1135 TWIG_LITERAL_HASH_KEY@34..46
1136 TK_WHITESPACE@34..35 " "
1137 TK_OPEN_PARENTHESIS@35..36 "("
1138 TWIG_EXPRESSION@36..45
1139 TWIG_BINARY_EXPRESSION@36..45
1140 TWIG_EXPRESSION@36..39
1141 TWIG_LITERAL_NAME@36..39
1142 TK_WORD@36..39 "foo"
1143 TK_WHITESPACE@39..40 " "
1144 TK_TILDE@40..41 "~"
1145 TWIG_EXPRESSION@41..45
1146 TWIG_LITERAL_STRING@41..45
1147 TK_WHITESPACE@41..42 " "
1148 TK_SINGLE_QUOTES@42..43 "'"
1149 TWIG_LITERAL_STRING_INNER@43..44
1150 TK_WORD@43..44 "b"
1151 TK_SINGLE_QUOTES@44..45 "'"
1152 TK_CLOSE_PARENTHESIS@45..46 ")"
1153 TK_COLON@46..47 ":"
1154 TWIG_EXPRESSION@47..53
1155 TWIG_LITERAL_STRING@47..53
1156 TK_WHITESPACE@47..48 " "
1157 TK_SINGLE_QUOTES@48..49 "'"
1158 TWIG_LITERAL_STRING_INNER@49..52
1159 TK_WORD@49..52 "baz"
1160 TK_SINGLE_QUOTES@52..53 "'"
1161 TK_WHITESPACE@53..54 " "
1162 TK_CLOSE_CURLY@54..55 "}"
1163 TK_WHITESPACE@55..56 " "
1164 TK_CLOSE_CURLY_CURLY@56..58 "}}""#]],
1165 );
1166 }
1167
1168 #[test]
1169 fn parse_twig_nested_hash() {
1170 check_parse(
1171 "{{ { outer: { inner: 'hello' } } }}",
1172 expect![[r#"
1173 ROOT@0..35
1174 TWIG_VAR@0..35
1175 TK_OPEN_CURLY_CURLY@0..2 "{{"
1176 TWIG_EXPRESSION@2..32
1177 TWIG_LITERAL_HASH@2..32
1178 TK_WHITESPACE@2..3 " "
1179 TK_OPEN_CURLY@3..4 "{"
1180 TWIG_LITERAL_HASH_ITEMS@4..30
1181 TWIG_LITERAL_HASH_PAIR@4..30
1182 TWIG_LITERAL_HASH_KEY@4..10
1183 TK_WHITESPACE@4..5 " "
1184 TK_WORD@5..10 "outer"
1185 TK_COLON@10..11 ":"
1186 TWIG_EXPRESSION@11..30
1187 TWIG_LITERAL_HASH@11..30
1188 TK_WHITESPACE@11..12 " "
1189 TK_OPEN_CURLY@12..13 "{"
1190 TWIG_LITERAL_HASH_ITEMS@13..28
1191 TWIG_LITERAL_HASH_PAIR@13..28
1192 TWIG_LITERAL_HASH_KEY@13..19
1193 TK_WHITESPACE@13..14 " "
1194 TK_WORD@14..19 "inner"
1195 TK_COLON@19..20 ":"
1196 TWIG_EXPRESSION@20..28
1197 TWIG_LITERAL_STRING@20..28
1198 TK_WHITESPACE@20..21 " "
1199 TK_SINGLE_QUOTES@21..22 "'"
1200 TWIG_LITERAL_STRING_INNER@22..27
1201 TK_WORD@22..27 "hello"
1202 TK_SINGLE_QUOTES@27..28 "'"
1203 TK_WHITESPACE@28..29 " "
1204 TK_CLOSE_CURLY@29..30 "}"
1205 TK_WHITESPACE@30..31 " "
1206 TK_CLOSE_CURLY@31..32 "}"
1207 TK_WHITESPACE@32..33 " "
1208 TK_CLOSE_CURLY_CURLY@33..35 "}}""#]],
1209 );
1210 }
1211
1212 #[test]
1213 fn parse_twig_hash_with_omitted_value() {
1214 check_parse(
1215 "{{ { value, is, same, as, key } }}",
1216 expect![[r#"
1217 ROOT@0..34
1218 TWIG_VAR@0..34
1219 TK_OPEN_CURLY_CURLY@0..2 "{{"
1220 TWIG_EXPRESSION@2..31
1221 TWIG_LITERAL_HASH@2..31
1222 TK_WHITESPACE@2..3 " "
1223 TK_OPEN_CURLY@3..4 "{"
1224 TWIG_LITERAL_HASH_ITEMS@4..29
1225 TWIG_LITERAL_HASH_PAIR@4..10
1226 TWIG_LITERAL_HASH_KEY@4..10
1227 TK_WHITESPACE@4..5 " "
1228 TK_WORD@5..10 "value"
1229 TK_COMMA@10..11 ","
1230 TWIG_LITERAL_HASH_PAIR@11..14
1231 TWIG_LITERAL_HASH_KEY@11..14
1232 TK_WHITESPACE@11..12 " "
1233 TK_WORD@12..14 "is"
1234 TK_COMMA@14..15 ","
1235 TWIG_LITERAL_HASH_PAIR@15..20
1236 TWIG_LITERAL_HASH_KEY@15..20
1237 TK_WHITESPACE@15..16 " "
1238 TK_WORD@16..20 "same"
1239 TK_COMMA@20..21 ","
1240 TWIG_LITERAL_HASH_PAIR@21..24
1241 TWIG_LITERAL_HASH_KEY@21..24
1242 TK_WHITESPACE@21..22 " "
1243 TK_WORD@22..24 "as"
1244 TK_COMMA@24..25 ","
1245 TWIG_LITERAL_HASH_PAIR@25..29
1246 TWIG_LITERAL_HASH_KEY@25..29
1247 TK_WHITESPACE@25..26 " "
1248 TK_WORD@26..29 "key"
1249 TK_WHITESPACE@29..30 " "
1250 TK_CLOSE_CURLY@30..31 "}"
1251 TK_WHITESPACE@31..32 " "
1252 TK_CLOSE_CURLY_CURLY@32..34 "}}""#]],
1253 );
1254 }
1255
1256 #[test]
1257 fn parse_twig_array_with_hash_mixed() {
1258 check_parse(
1259 r#"{{ [1, {"foo": "bar"}] }}"#,
1260 expect![[r#"
1261 ROOT@0..25
1262 TWIG_VAR@0..25
1263 TK_OPEN_CURLY_CURLY@0..2 "{{"
1264 TWIG_EXPRESSION@2..22
1265 TWIG_LITERAL_ARRAY@2..22
1266 TK_WHITESPACE@2..3 " "
1267 TK_OPEN_SQUARE@3..4 "["
1268 TWIG_LITERAL_ARRAY_INNER@4..21
1269 TWIG_EXPRESSION@4..5
1270 TWIG_LITERAL_NUMBER@4..5
1271 TK_NUMBER@4..5 "1"
1272 TK_COMMA@5..6 ","
1273 TWIG_EXPRESSION@6..21
1274 TWIG_LITERAL_HASH@6..21
1275 TK_WHITESPACE@6..7 " "
1276 TK_OPEN_CURLY@7..8 "{"
1277 TWIG_LITERAL_HASH_ITEMS@8..20
1278 TWIG_LITERAL_HASH_PAIR@8..20
1279 TWIG_LITERAL_HASH_KEY@8..13
1280 TWIG_LITERAL_STRING@8..13
1281 TK_DOUBLE_QUOTES@8..9 "\""
1282 TWIG_LITERAL_STRING_INNER@9..12
1283 TK_WORD@9..12 "foo"
1284 TK_DOUBLE_QUOTES@12..13 "\""
1285 TK_COLON@13..14 ":"
1286 TWIG_EXPRESSION@14..20
1287 TWIG_LITERAL_STRING@14..20
1288 TK_WHITESPACE@14..15 " "
1289 TK_DOUBLE_QUOTES@15..16 "\""
1290 TWIG_LITERAL_STRING_INNER@16..19
1291 TK_WORD@16..19 "bar"
1292 TK_DOUBLE_QUOTES@19..20 "\""
1293 TK_CLOSE_CURLY@20..21 "}"
1294 TK_CLOSE_SQUARE@21..22 "]"
1295 TK_WHITESPACE@22..23 " "
1296 TK_CLOSE_CURLY_CURLY@23..25 "}}""#]],
1297 );
1298 }
1299
1300 #[test]
1301 fn parse_twig_variable_name() {
1302 check_parse(
1303 "{{ my_variable }}",
1304 expect![[r#"
1305 ROOT@0..17
1306 TWIG_VAR@0..17
1307 TK_OPEN_CURLY_CURLY@0..2 "{{"
1308 TWIG_EXPRESSION@2..14
1309 TWIG_LITERAL_NAME@2..14
1310 TK_WHITESPACE@2..3 " "
1311 TK_WORD@3..14 "my_variable"
1312 TK_WHITESPACE@14..15 " "
1313 TK_CLOSE_CURLY_CURLY@15..17 "}}""#]],
1314 );
1315 }
1316
1317 #[test]
1318 fn parse_twig_token_variable_name() {
1319 check_parse(
1320 "{{ and }}",
1321 expect![[r#"
1322 ROOT@0..9
1323 TWIG_VAR@0..9
1324 TK_OPEN_CURLY_CURLY@0..2 "{{"
1325 TWIG_EXPRESSION@2..6
1326 TWIG_LITERAL_NAME@2..6
1327 TK_WHITESPACE@2..3 " "
1328 TK_WORD@3..6 "and"
1329 TK_WHITESPACE@6..7 " "
1330 TK_CLOSE_CURLY_CURLY@7..9 "}}""#]],
1331 );
1332 }
1333
1334 #[test]
1335 fn parse_twig_variable_get_attribute_expression() {
1336 check_parse(
1337 r"{{ product.prices.euro }}",
1338 expect![[r#"
1339 ROOT@0..25
1340 TWIG_VAR@0..25
1341 TK_OPEN_CURLY_CURLY@0..2 "{{"
1342 TWIG_EXPRESSION@2..22
1343 TWIG_ACCESSOR@2..22
1344 TWIG_OPERAND@2..17
1345 TWIG_ACCESSOR@2..17
1346 TWIG_OPERAND@2..10
1347 TWIG_LITERAL_NAME@2..10
1348 TK_WHITESPACE@2..3 " "
1349 TK_WORD@3..10 "product"
1350 TK_DOT@10..11 "."
1351 TWIG_OPERAND@11..17
1352 TWIG_LITERAL_NAME@11..17
1353 TK_WORD@11..17 "prices"
1354 TK_DOT@17..18 "."
1355 TWIG_OPERAND@18..22
1356 TWIG_LITERAL_NAME@18..22
1357 TK_WORD@18..22 "euro"
1358 TK_WHITESPACE@22..23 " "
1359 TK_CLOSE_CURLY_CURLY@23..25 "}}""#]],
1360 );
1361 }
1362
1363 #[test]
1364 fn parse_twig_variable_with_filters() {
1365 check_parse(
1366 r"{{ product.price|striptags|title }}",
1367 expect![[r#"
1368 ROOT@0..35
1369 TWIG_VAR@0..35
1370 TK_OPEN_CURLY_CURLY@0..2 "{{"
1371 TWIG_EXPRESSION@2..32
1372 TWIG_FILTER@2..32
1373 TWIG_OPERAND@2..26
1374 TWIG_FILTER@2..26
1375 TWIG_OPERAND@2..16
1376 TWIG_ACCESSOR@2..16
1377 TWIG_OPERAND@2..10
1378 TWIG_LITERAL_NAME@2..10
1379 TK_WHITESPACE@2..3 " "
1380 TK_WORD@3..10 "product"
1381 TK_DOT@10..11 "."
1382 TWIG_OPERAND@11..16
1383 TWIG_LITERAL_NAME@11..16
1384 TK_WORD@11..16 "price"
1385 TK_SINGLE_PIPE@16..17 "|"
1386 TWIG_OPERAND@17..26
1387 TWIG_LITERAL_NAME@17..26
1388 TK_WORD@17..26 "striptags"
1389 TK_SINGLE_PIPE@26..27 "|"
1390 TWIG_OPERAND@27..32
1391 TWIG_LITERAL_NAME@27..32
1392 TK_WORD@27..32 "title"
1393 TK_WHITESPACE@32..33 " "
1394 TK_CLOSE_CURLY_CURLY@33..35 "}}""#]],
1395 );
1396 }
1397
1398 #[test]
1399 fn parse_twig_variable_array_accessor() {
1400 check_parse(
1401 r"{{ product.prices['eur'] }}",
1402 expect![[r#"
1403 ROOT@0..27
1404 TWIG_VAR@0..27
1405 TK_OPEN_CURLY_CURLY@0..2 "{{"
1406 TWIG_EXPRESSION@2..24
1407 TWIG_INDEX_LOOKUP@2..24
1408 TWIG_OPERAND@2..17
1409 TWIG_ACCESSOR@2..17
1410 TWIG_OPERAND@2..10
1411 TWIG_LITERAL_NAME@2..10
1412 TK_WHITESPACE@2..3 " "
1413 TK_WORD@3..10 "product"
1414 TK_DOT@10..11 "."
1415 TWIG_OPERAND@11..17
1416 TWIG_LITERAL_NAME@11..17
1417 TK_WORD@11..17 "prices"
1418 TK_OPEN_SQUARE@17..18 "["
1419 TWIG_INDEX@18..23
1420 TWIG_EXPRESSION@18..23
1421 TWIG_LITERAL_STRING@18..23
1422 TK_SINGLE_QUOTES@18..19 "'"
1423 TWIG_LITERAL_STRING_INNER@19..22
1424 TK_WORD@19..22 "eur"
1425 TK_SINGLE_QUOTES@22..23 "'"
1426 TK_CLOSE_SQUARE@23..24 "]"
1427 TK_WHITESPACE@24..25 " "
1428 TK_CLOSE_CURLY_CURLY@25..27 "}}""#]],
1429 );
1430 }
1431
1432 #[test]
1433 fn parse_twig_variable_nested_array_accessor() {
1434 check_parse(
1435 r"{{ product.prices['eur'][0] }}",
1436 expect![[r#"
1437 ROOT@0..30
1438 TWIG_VAR@0..30
1439 TK_OPEN_CURLY_CURLY@0..2 "{{"
1440 TWIG_EXPRESSION@2..27
1441 TWIG_INDEX_LOOKUP@2..27
1442 TWIG_OPERAND@2..24
1443 TWIG_INDEX_LOOKUP@2..24
1444 TWIG_OPERAND@2..17
1445 TWIG_ACCESSOR@2..17
1446 TWIG_OPERAND@2..10
1447 TWIG_LITERAL_NAME@2..10
1448 TK_WHITESPACE@2..3 " "
1449 TK_WORD@3..10 "product"
1450 TK_DOT@10..11 "."
1451 TWIG_OPERAND@11..17
1452 TWIG_LITERAL_NAME@11..17
1453 TK_WORD@11..17 "prices"
1454 TK_OPEN_SQUARE@17..18 "["
1455 TWIG_INDEX@18..23
1456 TWIG_EXPRESSION@18..23
1457 TWIG_LITERAL_STRING@18..23
1458 TK_SINGLE_QUOTES@18..19 "'"
1459 TWIG_LITERAL_STRING_INNER@19..22
1460 TK_WORD@19..22 "eur"
1461 TK_SINGLE_QUOTES@22..23 "'"
1462 TK_CLOSE_SQUARE@23..24 "]"
1463 TK_OPEN_SQUARE@24..25 "["
1464 TWIG_INDEX@25..26
1465 TWIG_EXPRESSION@25..26
1466 TWIG_LITERAL_NUMBER@25..26
1467 TK_NUMBER@25..26 "0"
1468 TK_CLOSE_SQUARE@26..27 "]"
1469 TK_WHITESPACE@27..28 " "
1470 TK_CLOSE_CURLY_CURLY@28..30 "}}""#]],
1471 );
1472 }
1473
1474 #[test]
1475 fn parse_twig_variable_nested_array_accessor_with_dot() {
1476 check_parse(
1477 r"{{ product.prices['eur'].0 }}",
1478 expect![[r#"
1479 ROOT@0..29
1480 TWIG_VAR@0..29
1481 TK_OPEN_CURLY_CURLY@0..2 "{{"
1482 TWIG_EXPRESSION@2..26
1483 TWIG_INDEX_LOOKUP@2..26
1484 TWIG_OPERAND@2..24
1485 TWIG_INDEX_LOOKUP@2..24
1486 TWIG_OPERAND@2..17
1487 TWIG_ACCESSOR@2..17
1488 TWIG_OPERAND@2..10
1489 TWIG_LITERAL_NAME@2..10
1490 TK_WHITESPACE@2..3 " "
1491 TK_WORD@3..10 "product"
1492 TK_DOT@10..11 "."
1493 TWIG_OPERAND@11..17
1494 TWIG_LITERAL_NAME@11..17
1495 TK_WORD@11..17 "prices"
1496 TK_OPEN_SQUARE@17..18 "["
1497 TWIG_INDEX@18..23
1498 TWIG_EXPRESSION@18..23
1499 TWIG_LITERAL_STRING@18..23
1500 TK_SINGLE_QUOTES@18..19 "'"
1501 TWIG_LITERAL_STRING_INNER@19..22
1502 TK_WORD@19..22 "eur"
1503 TK_SINGLE_QUOTES@22..23 "'"
1504 TK_CLOSE_SQUARE@23..24 "]"
1505 TK_DOT@24..25 "."
1506 TWIG_INDEX@25..26
1507 TWIG_EXPRESSION@25..26
1508 TWIG_LITERAL_NUMBER@25..26
1509 TK_NUMBER@25..26 "0"
1510 TK_WHITESPACE@26..27 " "
1511 TK_CLOSE_CURLY_CURLY@27..29 "}}""#]],
1512 );
1513 }
1514
1515 #[test]
1516 fn parse_twig_variable_array_dot_accessor() {
1517 check_parse(
1518 r"{{ product.tags.0.name }}",
1519 expect![[r#"
1520 ROOT@0..25
1521 TWIG_VAR@0..25
1522 TK_OPEN_CURLY_CURLY@0..2 "{{"
1523 TWIG_EXPRESSION@2..22
1524 TWIG_ACCESSOR@2..22
1525 TWIG_OPERAND@2..17
1526 TWIG_INDEX_LOOKUP@2..17
1527 TWIG_OPERAND@2..15
1528 TWIG_ACCESSOR@2..15
1529 TWIG_OPERAND@2..10
1530 TWIG_LITERAL_NAME@2..10
1531 TK_WHITESPACE@2..3 " "
1532 TK_WORD@3..10 "product"
1533 TK_DOT@10..11 "."
1534 TWIG_OPERAND@11..15
1535 TWIG_LITERAL_NAME@11..15
1536 TK_WORD@11..15 "tags"
1537 TK_DOT@15..16 "."
1538 TWIG_INDEX@16..17
1539 TWIG_EXPRESSION@16..17
1540 TWIG_LITERAL_NUMBER@16..17
1541 TK_NUMBER@16..17 "0"
1542 TK_DOT@17..18 "."
1543 TWIG_OPERAND@18..22
1544 TWIG_LITERAL_NAME@18..22
1545 TK_WORD@18..22 "name"
1546 TK_WHITESPACE@22..23 " "
1547 TK_CLOSE_CURLY_CURLY@23..25 "}}""#]],
1548 );
1549 }
1550
1551 #[test]
1552 fn parse_twig_variable_array_range_accessor() {
1553 check_parse(
1554 r"{{ prices[0:10] }}",
1555 expect![[r#"
1556 ROOT@0..18
1557 TWIG_VAR@0..18
1558 TK_OPEN_CURLY_CURLY@0..2 "{{"
1559 TWIG_EXPRESSION@2..15
1560 TWIG_INDEX_LOOKUP@2..15
1561 TWIG_OPERAND@2..9
1562 TWIG_LITERAL_NAME@2..9
1563 TK_WHITESPACE@2..3 " "
1564 TK_WORD@3..9 "prices"
1565 TK_OPEN_SQUARE@9..10 "["
1566 TWIG_INDEX_RANGE@10..14
1567 TWIG_EXPRESSION@10..11
1568 TWIG_LITERAL_NUMBER@10..11
1569 TK_NUMBER@10..11 "0"
1570 TK_COLON@11..12 ":"
1571 TWIG_EXPRESSION@12..14
1572 TWIG_LITERAL_NUMBER@12..14
1573 TK_NUMBER@12..14 "10"
1574 TK_CLOSE_SQUARE@14..15 "]"
1575 TK_WHITESPACE@15..16 " "
1576 TK_CLOSE_CURLY_CURLY@16..18 "}}""#]],
1577 );
1578 }
1579
1580 #[test]
1581 fn parse_twig_variable_array_range_left_accessor() {
1582 check_parse(
1583 r"{{ prices[10:] }}",
1584 expect![[r#"
1585 ROOT@0..17
1586 TWIG_VAR@0..17
1587 TK_OPEN_CURLY_CURLY@0..2 "{{"
1588 TWIG_EXPRESSION@2..14
1589 TWIG_INDEX_LOOKUP@2..14
1590 TWIG_OPERAND@2..9
1591 TWIG_LITERAL_NAME@2..9
1592 TK_WHITESPACE@2..3 " "
1593 TK_WORD@3..9 "prices"
1594 TK_OPEN_SQUARE@9..10 "["
1595 TWIG_INDEX_RANGE@10..13
1596 TWIG_EXPRESSION@10..12
1597 TWIG_LITERAL_NUMBER@10..12
1598 TK_NUMBER@10..12 "10"
1599 TK_COLON@12..13 ":"
1600 TK_CLOSE_SQUARE@13..14 "]"
1601 TK_WHITESPACE@14..15 " "
1602 TK_CLOSE_CURLY_CURLY@15..17 "}}""#]],
1603 );
1604 }
1605
1606 #[test]
1607 fn parse_twig_variable_array_range_right_accessor() {
1608 check_parse(
1609 r"{{ prices[:10] }}",
1610 expect![[r#"
1611 ROOT@0..17
1612 TWIG_VAR@0..17
1613 TK_OPEN_CURLY_CURLY@0..2 "{{"
1614 TWIG_EXPRESSION@2..14
1615 TWIG_INDEX_LOOKUP@2..14
1616 TWIG_OPERAND@2..9
1617 TWIG_LITERAL_NAME@2..9
1618 TK_WHITESPACE@2..3 " "
1619 TK_WORD@3..9 "prices"
1620 TK_OPEN_SQUARE@9..10 "["
1621 TWIG_INDEX_RANGE@10..13
1622 TK_COLON@10..11 ":"
1623 TWIG_EXPRESSION@11..13
1624 TWIG_LITERAL_NUMBER@11..13
1625 TK_NUMBER@11..13 "10"
1626 TK_CLOSE_SQUARE@13..14 "]"
1627 TK_WHITESPACE@14..15 " "
1628 TK_CLOSE_CURLY_CURLY@15..17 "}}""#]],
1629 );
1630 }
1631
1632 #[test]
1633 fn parse_twig_variable_array_range_right_accessor_negative() {
1634 check_parse(
1635 r"{{ prices[:-2] }}",
1636 expect![[r#"
1637 ROOT@0..17
1638 TWIG_VAR@0..17
1639 TK_OPEN_CURLY_CURLY@0..2 "{{"
1640 TWIG_EXPRESSION@2..14
1641 TWIG_INDEX_LOOKUP@2..14
1642 TWIG_OPERAND@2..9
1643 TWIG_LITERAL_NAME@2..9
1644 TK_WHITESPACE@2..3 " "
1645 TK_WORD@3..9 "prices"
1646 TK_OPEN_SQUARE@9..10 "["
1647 TWIG_INDEX_RANGE@10..13
1648 TK_COLON@10..11 ":"
1649 TWIG_EXPRESSION@11..13
1650 TWIG_UNARY_EXPRESSION@11..13
1651 TK_MINUS@11..12 "-"
1652 TWIG_EXPRESSION@12..13
1653 TWIG_LITERAL_NUMBER@12..13
1654 TK_NUMBER@12..13 "2"
1655 TK_CLOSE_SQUARE@13..14 "]"
1656 TK_WHITESPACE@14..15 " "
1657 TK_CLOSE_CURLY_CURLY@15..17 "}}""#]],
1658 );
1659 }
1660
1661 #[test]
1662 fn parse_twig_variable_array_range_right_accessor_variable() {
1663 check_parse(
1664 r"{{ prices[:upperLimit] }}",
1665 expect![[r#"
1666 ROOT@0..25
1667 TWIG_VAR@0..25
1668 TK_OPEN_CURLY_CURLY@0..2 "{{"
1669 TWIG_EXPRESSION@2..22
1670 TWIG_INDEX_LOOKUP@2..22
1671 TWIG_OPERAND@2..9
1672 TWIG_LITERAL_NAME@2..9
1673 TK_WHITESPACE@2..3 " "
1674 TK_WORD@3..9 "prices"
1675 TK_OPEN_SQUARE@9..10 "["
1676 TWIG_INDEX_RANGE@10..21
1677 TK_COLON@10..11 ":"
1678 TWIG_EXPRESSION@11..21
1679 TWIG_LITERAL_NAME@11..21
1680 TK_WORD@11..21 "upperLimit"
1681 TK_CLOSE_SQUARE@21..22 "]"
1682 TK_WHITESPACE@22..23 " "
1683 TK_CLOSE_CURLY_CURLY@23..25 "}}""#]],
1684 );
1685 }
1686
1687 #[test]
1688 fn parse_twig_variable_array_range_left_accessor_variable() {
1689 check_parse(
1690 r"{{ prices[upperLimit:] }}",
1691 expect![[r#"
1692 ROOT@0..25
1693 TWIG_VAR@0..25
1694 TK_OPEN_CURLY_CURLY@0..2 "{{"
1695 TWIG_EXPRESSION@2..22
1696 TWIG_INDEX_LOOKUP@2..22
1697 TWIG_OPERAND@2..9
1698 TWIG_LITERAL_NAME@2..9
1699 TK_WHITESPACE@2..3 " "
1700 TK_WORD@3..9 "prices"
1701 TK_OPEN_SQUARE@9..10 "["
1702 TWIG_INDEX_RANGE@10..21
1703 TWIG_EXPRESSION@10..20
1704 TWIG_LITERAL_NAME@10..20
1705 TK_WORD@10..20 "upperLimit"
1706 TK_COLON@20..21 ":"
1707 TK_CLOSE_SQUARE@21..22 "]"
1708 TK_WHITESPACE@22..23 " "
1709 TK_CLOSE_CURLY_CURLY@23..25 "}}""#]],
1710 );
1711 }
1712
1713 #[test]
1714 fn parse_twig_variable_array_index_missing_expression() {
1715 check_parse(
1716 r"{{ prices[] }}",
1717 expect![[r#"
1718 ROOT@0..14
1719 TWIG_VAR@0..14
1720 TK_OPEN_CURLY_CURLY@0..2 "{{"
1721 TWIG_EXPRESSION@2..11
1722 TWIG_INDEX_LOOKUP@2..11
1723 TWIG_OPERAND@2..9
1724 TWIG_LITERAL_NAME@2..9
1725 TK_WHITESPACE@2..3 " "
1726 TK_WORD@3..9 "prices"
1727 TK_OPEN_SQUARE@9..10 "["
1728 TWIG_INDEX@10..10
1729 TK_CLOSE_SQUARE@10..11 "]"
1730 TK_WHITESPACE@11..12 " "
1731 TK_CLOSE_CURLY_CURLY@12..14 "}}"
1732 error at 10..11: expected twig expression but found ]"#]],
1733 );
1734 }
1735
1736 #[test]
1737 fn parse_twig_variable_accessor_indexer_and_filter() {
1738 check_parse(
1739 r"{{ product.prices['eur'][0]|title }}",
1740 expect![[r#"
1741 ROOT@0..36
1742 TWIG_VAR@0..36
1743 TK_OPEN_CURLY_CURLY@0..2 "{{"
1744 TWIG_EXPRESSION@2..33
1745 TWIG_FILTER@2..33
1746 TWIG_OPERAND@2..27
1747 TWIG_INDEX_LOOKUP@2..27
1748 TWIG_OPERAND@2..24
1749 TWIG_INDEX_LOOKUP@2..24
1750 TWIG_OPERAND@2..17
1751 TWIG_ACCESSOR@2..17
1752 TWIG_OPERAND@2..10
1753 TWIG_LITERAL_NAME@2..10
1754 TK_WHITESPACE@2..3 " "
1755 TK_WORD@3..10 "product"
1756 TK_DOT@10..11 "."
1757 TWIG_OPERAND@11..17
1758 TWIG_LITERAL_NAME@11..17
1759 TK_WORD@11..17 "prices"
1760 TK_OPEN_SQUARE@17..18 "["
1761 TWIG_INDEX@18..23
1762 TWIG_EXPRESSION@18..23
1763 TWIG_LITERAL_STRING@18..23
1764 TK_SINGLE_QUOTES@18..19 "'"
1765 TWIG_LITERAL_STRING_INNER@19..22
1766 TK_WORD@19..22 "eur"
1767 TK_SINGLE_QUOTES@22..23 "'"
1768 TK_CLOSE_SQUARE@23..24 "]"
1769 TK_OPEN_SQUARE@24..25 "["
1770 TWIG_INDEX@25..26
1771 TWIG_EXPRESSION@25..26
1772 TWIG_LITERAL_NUMBER@25..26
1773 TK_NUMBER@25..26 "0"
1774 TK_CLOSE_SQUARE@26..27 "]"
1775 TK_SINGLE_PIPE@27..28 "|"
1776 TWIG_OPERAND@28..33
1777 TWIG_LITERAL_NAME@28..33
1778 TK_WORD@28..33 "title"
1779 TK_WHITESPACE@33..34 " "
1780 TK_CLOSE_CURLY_CURLY@34..36 "}}""#]],
1781 );
1782 }
1783
1784 #[test]
1785 fn parse_twig_variable_function_accessor() {
1786 check_parse(
1787 r"{{ product.prices('eur').gross }}",
1788 expect![[r#"
1789 ROOT@0..33
1790 TWIG_VAR@0..33
1791 TK_OPEN_CURLY_CURLY@0..2 "{{"
1792 TWIG_EXPRESSION@2..30
1793 TWIG_ACCESSOR@2..30
1794 TWIG_OPERAND@2..24
1795 TWIG_FUNCTION_CALL@2..24
1796 TWIG_OPERAND@2..17
1797 TWIG_ACCESSOR@2..17
1798 TWIG_OPERAND@2..10
1799 TWIG_LITERAL_NAME@2..10
1800 TK_WHITESPACE@2..3 " "
1801 TK_WORD@3..10 "product"
1802 TK_DOT@10..11 "."
1803 TWIG_OPERAND@11..17
1804 TWIG_LITERAL_NAME@11..17
1805 TK_WORD@11..17 "prices"
1806 TWIG_ARGUMENTS@17..24
1807 TK_OPEN_PARENTHESIS@17..18 "("
1808 TWIG_EXPRESSION@18..23
1809 TWIG_LITERAL_STRING@18..23
1810 TK_SINGLE_QUOTES@18..19 "'"
1811 TWIG_LITERAL_STRING_INNER@19..22
1812 TK_WORD@19..22 "eur"
1813 TK_SINGLE_QUOTES@22..23 "'"
1814 TK_CLOSE_PARENTHESIS@23..24 ")"
1815 TK_DOT@24..25 "."
1816 TWIG_OPERAND@25..30
1817 TWIG_LITERAL_NAME@25..30
1818 TK_WORD@25..30 "gross"
1819 TK_WHITESPACE@30..31 " "
1820 TK_CLOSE_CURLY_CURLY@31..33 "}}""#]],
1821 );
1822 }
1823
1824 #[test]
1825 fn parse_twig_variable_deep_function_accessor() {
1826 check_parse(
1827 r"{{ product.prices.gross('eur').gross }}",
1828 expect![[r#"
1829 ROOT@0..39
1830 TWIG_VAR@0..39
1831 TK_OPEN_CURLY_CURLY@0..2 "{{"
1832 TWIG_EXPRESSION@2..36
1833 TWIG_ACCESSOR@2..36
1834 TWIG_OPERAND@2..30
1835 TWIG_FUNCTION_CALL@2..30
1836 TWIG_OPERAND@2..23
1837 TWIG_ACCESSOR@2..23
1838 TWIG_OPERAND@2..17
1839 TWIG_ACCESSOR@2..17
1840 TWIG_OPERAND@2..10
1841 TWIG_LITERAL_NAME@2..10
1842 TK_WHITESPACE@2..3 " "
1843 TK_WORD@3..10 "product"
1844 TK_DOT@10..11 "."
1845 TWIG_OPERAND@11..17
1846 TWIG_LITERAL_NAME@11..17
1847 TK_WORD@11..17 "prices"
1848 TK_DOT@17..18 "."
1849 TWIG_OPERAND@18..23
1850 TWIG_LITERAL_NAME@18..23
1851 TK_WORD@18..23 "gross"
1852 TWIG_ARGUMENTS@23..30
1853 TK_OPEN_PARENTHESIS@23..24 "("
1854 TWIG_EXPRESSION@24..29
1855 TWIG_LITERAL_STRING@24..29
1856 TK_SINGLE_QUOTES@24..25 "'"
1857 TWIG_LITERAL_STRING_INNER@25..28
1858 TK_WORD@25..28 "eur"
1859 TK_SINGLE_QUOTES@28..29 "'"
1860 TK_CLOSE_PARENTHESIS@29..30 ")"
1861 TK_DOT@30..31 "."
1862 TWIG_OPERAND@31..36
1863 TWIG_LITERAL_NAME@31..36
1864 TK_WORD@31..36 "gross"
1865 TK_WHITESPACE@36..37 " "
1866 TK_CLOSE_CURLY_CURLY@37..39 "}}""#]],
1867 );
1868 }
1869
1870 #[test]
1871 fn parse_twig_function() {
1872 check_parse(
1873 r"{{ doIt() }}",
1874 expect![[r#"
1875 ROOT@0..12
1876 TWIG_VAR@0..12
1877 TK_OPEN_CURLY_CURLY@0..2 "{{"
1878 TWIG_EXPRESSION@2..9
1879 TWIG_FUNCTION_CALL@2..9
1880 TWIG_OPERAND@2..7
1881 TWIG_LITERAL_NAME@2..7
1882 TK_WHITESPACE@2..3 " "
1883 TK_WORD@3..7 "doIt"
1884 TWIG_ARGUMENTS@7..9
1885 TK_OPEN_PARENTHESIS@7..8 "("
1886 TK_CLOSE_PARENTHESIS@8..9 ")"
1887 TK_WHITESPACE@9..10 " "
1888 TK_CLOSE_CURLY_CURLY@10..12 "}}""#]],
1889 );
1890 }
1891
1892 #[test]
1893 fn parse_twig_function_arguments() {
1894 check_parse(
1895 r"{{ sum(1, 2) }}",
1896 expect![[r#"
1897 ROOT@0..15
1898 TWIG_VAR@0..15
1899 TK_OPEN_CURLY_CURLY@0..2 "{{"
1900 TWIG_EXPRESSION@2..12
1901 TWIG_FUNCTION_CALL@2..12
1902 TWIG_OPERAND@2..6
1903 TWIG_LITERAL_NAME@2..6
1904 TK_WHITESPACE@2..3 " "
1905 TK_WORD@3..6 "sum"
1906 TWIG_ARGUMENTS@6..12
1907 TK_OPEN_PARENTHESIS@6..7 "("
1908 TWIG_EXPRESSION@7..8
1909 TWIG_LITERAL_NUMBER@7..8
1910 TK_NUMBER@7..8 "1"
1911 TK_COMMA@8..9 ","
1912 TWIG_EXPRESSION@9..11
1913 TWIG_LITERAL_NUMBER@9..11
1914 TK_WHITESPACE@9..10 " "
1915 TK_NUMBER@10..11 "2"
1916 TK_CLOSE_PARENTHESIS@11..12 ")"
1917 TK_WHITESPACE@12..13 " "
1918 TK_CLOSE_CURLY_CURLY@13..15 "}}""#]],
1919 );
1920 }
1921
1922 #[test]
1923 fn parse_twig_function_named_arguments() {
1924 check_parse(
1925 r"{{ sum(a=1, b=2) }}",
1926 expect![[r#"
1927 ROOT@0..19
1928 TWIG_VAR@0..19
1929 TK_OPEN_CURLY_CURLY@0..2 "{{"
1930 TWIG_EXPRESSION@2..16
1931 TWIG_FUNCTION_CALL@2..16
1932 TWIG_OPERAND@2..6
1933 TWIG_LITERAL_NAME@2..6
1934 TK_WHITESPACE@2..3 " "
1935 TK_WORD@3..6 "sum"
1936 TWIG_ARGUMENTS@6..16
1937 TK_OPEN_PARENTHESIS@6..7 "("
1938 TWIG_NAMED_ARGUMENT@7..10
1939 TK_WORD@7..8 "a"
1940 TK_EQUAL@8..9 "="
1941 TWIG_EXPRESSION@9..10
1942 TWIG_LITERAL_NUMBER@9..10
1943 TK_NUMBER@9..10 "1"
1944 TK_COMMA@10..11 ","
1945 TWIG_NAMED_ARGUMENT@11..15
1946 TK_WHITESPACE@11..12 " "
1947 TK_WORD@12..13 "b"
1948 TK_EQUAL@13..14 "="
1949 TWIG_EXPRESSION@14..15
1950 TWIG_LITERAL_NUMBER@14..15
1951 TK_NUMBER@14..15 "2"
1952 TK_CLOSE_PARENTHESIS@15..16 ")"
1953 TK_WHITESPACE@16..17 " "
1954 TK_CLOSE_CURLY_CURLY@17..19 "}}""#]],
1955 );
1956 }
1957
1958 #[test]
1959 fn parse_twig_function_mixed_named_arguments() {
1960 check_parse(
1961 r"{{ sum(1, b=my_number) }}",
1962 expect![[r#"
1963 ROOT@0..25
1964 TWIG_VAR@0..25
1965 TK_OPEN_CURLY_CURLY@0..2 "{{"
1966 TWIG_EXPRESSION@2..22
1967 TWIG_FUNCTION_CALL@2..22
1968 TWIG_OPERAND@2..6
1969 TWIG_LITERAL_NAME@2..6
1970 TK_WHITESPACE@2..3 " "
1971 TK_WORD@3..6 "sum"
1972 TWIG_ARGUMENTS@6..22
1973 TK_OPEN_PARENTHESIS@6..7 "("
1974 TWIG_EXPRESSION@7..8
1975 TWIG_LITERAL_NUMBER@7..8
1976 TK_NUMBER@7..8 "1"
1977 TK_COMMA@8..9 ","
1978 TWIG_NAMED_ARGUMENT@9..21
1979 TK_WHITESPACE@9..10 " "
1980 TK_WORD@10..11 "b"
1981 TK_EQUAL@11..12 "="
1982 TWIG_EXPRESSION@12..21
1983 TWIG_LITERAL_NAME@12..21
1984 TK_WORD@12..21 "my_number"
1985 TK_CLOSE_PARENTHESIS@21..22 ")"
1986 TK_WHITESPACE@22..23 " "
1987 TK_CLOSE_CURLY_CURLY@23..25 "}}""#]],
1988 );
1989 }
1990
1991 #[test]
1992 fn parse_twig_function_nested_call() {
1993 check_parse(
1994 r"{{ sum(1, sin(1)) }}",
1995 expect![[r#"
1996 ROOT@0..20
1997 TWIG_VAR@0..20
1998 TK_OPEN_CURLY_CURLY@0..2 "{{"
1999 TWIG_EXPRESSION@2..17
2000 TWIG_FUNCTION_CALL@2..17
2001 TWIG_OPERAND@2..6
2002 TWIG_LITERAL_NAME@2..6
2003 TK_WHITESPACE@2..3 " "
2004 TK_WORD@3..6 "sum"
2005 TWIG_ARGUMENTS@6..17
2006 TK_OPEN_PARENTHESIS@6..7 "("
2007 TWIG_EXPRESSION@7..8
2008 TWIG_LITERAL_NUMBER@7..8
2009 TK_NUMBER@7..8 "1"
2010 TK_COMMA@8..9 ","
2011 TWIG_EXPRESSION@9..16
2012 TWIG_FUNCTION_CALL@9..16
2013 TWIG_OPERAND@9..13
2014 TWIG_LITERAL_NAME@9..13
2015 TK_WHITESPACE@9..10 " "
2016 TK_WORD@10..13 "sin"
2017 TWIG_ARGUMENTS@13..16
2018 TK_OPEN_PARENTHESIS@13..14 "("
2019 TWIG_EXPRESSION@14..15
2020 TWIG_LITERAL_NUMBER@14..15
2021 TK_NUMBER@14..15 "1"
2022 TK_CLOSE_PARENTHESIS@15..16 ")"
2023 TK_CLOSE_PARENTHESIS@16..17 ")"
2024 TK_WHITESPACE@17..18 " "
2025 TK_CLOSE_CURLY_CURLY@18..20 "}}""#]],
2026 );
2027 }
2028
2029 #[test]
2030 fn parse_twig_arrow_function_simple() {
2031 check_parse(
2032 r"{% set my_arrow_function = i => i % 2 %}",
2033 expect![[r#"
2034 ROOT@0..40
2035 TWIG_SET@0..40
2036 TWIG_SET_BLOCK@0..40
2037 TK_CURLY_PERCENT@0..2 "{%"
2038 TK_WHITESPACE@2..3 " "
2039 TK_SET@3..6 "set"
2040 TWIG_ASSIGNMENT@6..37
2041 TWIG_LITERAL_NAME@6..24
2042 TK_WHITESPACE@6..7 " "
2043 TK_WORD@7..24 "my_arrow_function"
2044 TK_WHITESPACE@24..25 " "
2045 TK_EQUAL@25..26 "="
2046 TWIG_EXPRESSION@26..37
2047 TWIG_ARROW_FUNCTION@26..37
2048 TWIG_ARGUMENTS@26..28
2049 TWIG_LITERAL_NAME@26..28
2050 TK_WHITESPACE@26..27 " "
2051 TK_WORD@27..28 "i"
2052 TK_WHITESPACE@28..29 " "
2053 TK_EQUAL_GREATER_THAN@29..31 "=>"
2054 TWIG_EXPRESSION@31..37
2055 TWIG_BINARY_EXPRESSION@31..37
2056 TWIG_EXPRESSION@31..33
2057 TWIG_LITERAL_NAME@31..33
2058 TK_WHITESPACE@31..32 " "
2059 TK_WORD@32..33 "i"
2060 TK_WHITESPACE@33..34 " "
2061 TK_PERCENT@34..35 "%"
2062 TWIG_EXPRESSION@35..37
2063 TWIG_LITERAL_NUMBER@35..37
2064 TK_WHITESPACE@35..36 " "
2065 TK_NUMBER@36..37 "2"
2066 TK_WHITESPACE@37..38 " "
2067 TK_PERCENT_CURLY@38..40 "%}""#]],
2068 );
2069 }
2070
2071 #[test]
2072 fn parse_twig_arrow_function_simple_brackets() {
2073 check_parse(
2074 r"{% set my_arrow_function = (i) => i % 2 %}",
2075 expect![[r#"
2076 ROOT@0..42
2077 TWIG_SET@0..42
2078 TWIG_SET_BLOCK@0..42
2079 TK_CURLY_PERCENT@0..2 "{%"
2080 TK_WHITESPACE@2..3 " "
2081 TK_SET@3..6 "set"
2082 TWIG_ASSIGNMENT@6..39
2083 TWIG_LITERAL_NAME@6..24
2084 TK_WHITESPACE@6..7 " "
2085 TK_WORD@7..24 "my_arrow_function"
2086 TK_WHITESPACE@24..25 " "
2087 TK_EQUAL@25..26 "="
2088 TWIG_EXPRESSION@26..39
2089 TWIG_ARROW_FUNCTION@26..39
2090 TWIG_ARGUMENTS@26..30
2091 TK_WHITESPACE@26..27 " "
2092 TK_OPEN_PARENTHESIS@27..28 "("
2093 TWIG_LITERAL_NAME@28..29
2094 TK_WORD@28..29 "i"
2095 TK_CLOSE_PARENTHESIS@29..30 ")"
2096 TK_WHITESPACE@30..31 " "
2097 TK_EQUAL_GREATER_THAN@31..33 "=>"
2098 TWIG_EXPRESSION@33..39
2099 TWIG_BINARY_EXPRESSION@33..39
2100 TWIG_EXPRESSION@33..35
2101 TWIG_LITERAL_NAME@33..35
2102 TK_WHITESPACE@33..34 " "
2103 TK_WORD@34..35 "i"
2104 TK_WHITESPACE@35..36 " "
2105 TK_PERCENT@36..37 "%"
2106 TWIG_EXPRESSION@37..39
2107 TWIG_LITERAL_NUMBER@37..39
2108 TK_WHITESPACE@37..38 " "
2109 TK_NUMBER@38..39 "2"
2110 TK_WHITESPACE@39..40 " "
2111 TK_PERCENT_CURLY@40..42 "%}""#]],
2112 );
2113 }
2114
2115 #[test]
2116 fn parse_twig_arrow_function_advanced() {
2117 check_parse(
2118 r"{% set my_arrow_function = (a, b) => a >= b %}",
2119 expect![[r#"
2120 ROOT@0..46
2121 TWIG_SET@0..46
2122 TWIG_SET_BLOCK@0..46
2123 TK_CURLY_PERCENT@0..2 "{%"
2124 TK_WHITESPACE@2..3 " "
2125 TK_SET@3..6 "set"
2126 TWIG_ASSIGNMENT@6..43
2127 TWIG_LITERAL_NAME@6..24
2128 TK_WHITESPACE@6..7 " "
2129 TK_WORD@7..24 "my_arrow_function"
2130 TK_WHITESPACE@24..25 " "
2131 TK_EQUAL@25..26 "="
2132 TWIG_EXPRESSION@26..43
2133 TWIG_ARROW_FUNCTION@26..43
2134 TWIG_ARGUMENTS@26..33
2135 TK_WHITESPACE@26..27 " "
2136 TK_OPEN_PARENTHESIS@27..28 "("
2137 TWIG_LITERAL_NAME@28..29
2138 TK_WORD@28..29 "a"
2139 TK_COMMA@29..30 ","
2140 TWIG_LITERAL_NAME@30..32
2141 TK_WHITESPACE@30..31 " "
2142 TK_WORD@31..32 "b"
2143 TK_CLOSE_PARENTHESIS@32..33 ")"
2144 TK_WHITESPACE@33..34 " "
2145 TK_EQUAL_GREATER_THAN@34..36 "=>"
2146 TWIG_EXPRESSION@36..43
2147 TWIG_BINARY_EXPRESSION@36..43
2148 TWIG_EXPRESSION@36..38
2149 TWIG_LITERAL_NAME@36..38
2150 TK_WHITESPACE@36..37 " "
2151 TK_WORD@37..38 "a"
2152 TK_WHITESPACE@38..39 " "
2153 TK_GREATER_THAN_EQUAL@39..41 ">="
2154 TWIG_EXPRESSION@41..43
2155 TWIG_LITERAL_NAME@41..43
2156 TK_WHITESPACE@41..42 " "
2157 TK_WORD@42..43 "b"
2158 TK_WHITESPACE@43..44 " "
2159 TK_PERCENT_CURLY@44..46 "%}""#]],
2160 );
2161 }
2162
2163 #[test]
2164 fn parse_twig_arrow_function_as_filer_argument() {
2165 check_parse(
2166 r"{% for item in crossSellings|filter(item => item.total > 0 and item.crossSelling.active == true) %} {{ item }} {% endfor %}",
2167 expect![[r#"
2168 ROOT@0..123
2169 TWIG_FOR@0..123
2170 TWIG_FOR_BLOCK@0..99
2171 TK_CURLY_PERCENT@0..2 "{%"
2172 TK_WHITESPACE@2..3 " "
2173 TK_FOR@3..6 "for"
2174 TWIG_LITERAL_NAME@6..11
2175 TK_WHITESPACE@6..7 " "
2176 TK_WORD@7..11 "item"
2177 TK_WHITESPACE@11..12 " "
2178 TK_IN@12..14 "in"
2179 TWIG_EXPRESSION@14..96
2180 TWIG_FILTER@14..96
2181 TWIG_OPERAND@14..28
2182 TWIG_LITERAL_NAME@14..28
2183 TK_WHITESPACE@14..15 " "
2184 TK_WORD@15..28 "crossSellings"
2185 TK_SINGLE_PIPE@28..29 "|"
2186 TWIG_OPERAND@29..96
2187 TWIG_LITERAL_NAME@29..35
2188 TK_WORD@29..35 "filter"
2189 TWIG_ARGUMENTS@35..96
2190 TK_OPEN_PARENTHESIS@35..36 "("
2191 TWIG_EXPRESSION@36..95
2192 TWIG_ARROW_FUNCTION@36..95
2193 TWIG_ARGUMENTS@36..40
2194 TWIG_LITERAL_NAME@36..40
2195 TK_WORD@36..40 "item"
2196 TK_WHITESPACE@40..41 " "
2197 TK_EQUAL_GREATER_THAN@41..43 "=>"
2198 TWIG_EXPRESSION@43..95
2199 TWIG_BINARY_EXPRESSION@43..95
2200 TWIG_BINARY_EXPRESSION@43..58
2201 TWIG_EXPRESSION@43..54
2202 TWIG_ACCESSOR@43..54
2203 TWIG_OPERAND@43..48
2204 TWIG_LITERAL_NAME@43..48
2205 TK_WHITESPACE@43..44 " "
2206 TK_WORD@44..48 "item"
2207 TK_DOT@48..49 "."
2208 TWIG_OPERAND@49..54
2209 TWIG_LITERAL_NAME@49..54
2210 TK_WORD@49..54 "total"
2211 TK_WHITESPACE@54..55 " "
2212 TK_GREATER_THAN@55..56 ">"
2213 TWIG_EXPRESSION@56..58
2214 TWIG_LITERAL_NUMBER@56..58
2215 TK_WHITESPACE@56..57 " "
2216 TK_NUMBER@57..58 "0"
2217 TK_WHITESPACE@58..59 " "
2218 TK_AND@59..62 "and"
2219 TWIG_EXPRESSION@62..95
2220 TWIG_BINARY_EXPRESSION@62..95
2221 TWIG_EXPRESSION@62..87
2222 TWIG_ACCESSOR@62..87
2223 TWIG_OPERAND@62..80
2224 TWIG_ACCESSOR@62..80
2225 TWIG_OPERAND@62..67
2226 TWIG_LITERAL_NAME@62..67
2227 TK_WHITESPACE@62..63 " "
2228 TK_WORD@63..67 "item"
2229 TK_DOT@67..68 "."
2230 TWIG_OPERAND@68..80
2231 TWIG_LITERAL_NAME@68..80
2232 TK_WORD@68..80 "crossSelling"
2233 TK_DOT@80..81 "."
2234 TWIG_OPERAND@81..87
2235 TWIG_LITERAL_NAME@81..87
2236 TK_WORD@81..87 "active"
2237 TK_WHITESPACE@87..88 " "
2238 TK_DOUBLE_EQUAL@88..90 "=="
2239 TWIG_EXPRESSION@90..95
2240 TWIG_LITERAL_BOOLEAN@90..95
2241 TK_WHITESPACE@90..91 " "
2242 TK_TRUE@91..95 "true"
2243 TK_CLOSE_PARENTHESIS@95..96 ")"
2244 TK_WHITESPACE@96..97 " "
2245 TK_PERCENT_CURLY@97..99 "%}"
2246 BODY@99..110
2247 TWIG_VAR@99..110
2248 TK_WHITESPACE@99..100 " "
2249 TK_OPEN_CURLY_CURLY@100..102 "{{"
2250 TWIG_EXPRESSION@102..107
2251 TWIG_LITERAL_NAME@102..107
2252 TK_WHITESPACE@102..103 " "
2253 TK_WORD@103..107 "item"
2254 TK_WHITESPACE@107..108 " "
2255 TK_CLOSE_CURLY_CURLY@108..110 "}}"
2256 TWIG_ENDFOR_BLOCK@110..123
2257 TK_WHITESPACE@110..111 " "
2258 TK_CURLY_PERCENT@111..113 "{%"
2259 TK_WHITESPACE@113..114 " "
2260 TK_ENDFOR@114..120 "endfor"
2261 TK_WHITESPACE@120..121 " "
2262 TK_PERCENT_CURLY@121..123 "%}""#]],
2263 );
2264 }
2265
2266 #[test]
2267 fn parse_twig_filter_arguments() {
2268 check_parse(
2269 r"{{ list|join(', ') }}",
2270 expect![[r#"
2271 ROOT@0..21
2272 TWIG_VAR@0..21
2273 TK_OPEN_CURLY_CURLY@0..2 "{{"
2274 TWIG_EXPRESSION@2..18
2275 TWIG_FILTER@2..18
2276 TWIG_OPERAND@2..7
2277 TWIG_LITERAL_NAME@2..7
2278 TK_WHITESPACE@2..3 " "
2279 TK_WORD@3..7 "list"
2280 TK_SINGLE_PIPE@7..8 "|"
2281 TWIG_OPERAND@8..18
2282 TWIG_LITERAL_NAME@8..12
2283 TK_WORD@8..12 "join"
2284 TWIG_ARGUMENTS@12..18
2285 TK_OPEN_PARENTHESIS@12..13 "("
2286 TWIG_EXPRESSION@13..17
2287 TWIG_LITERAL_STRING@13..17
2288 TK_SINGLE_QUOTES@13..14 "'"
2289 TWIG_LITERAL_STRING_INNER@14..16
2290 TK_COMMA@14..15 ","
2291 TK_WHITESPACE@15..16 " "
2292 TK_SINGLE_QUOTES@16..17 "'"
2293 TK_CLOSE_PARENTHESIS@17..18 ")"
2294 TK_WHITESPACE@18..19 " "
2295 TK_CLOSE_CURLY_CURLY@19..21 "}}""#]],
2296 );
2297 }
2298
2299 #[test]
2300 fn parse_twig_double_filter_arguments() {
2301 check_parse(
2302 r"{{ list|join(', ')|trim }}",
2303 expect![[r#"
2304 ROOT@0..26
2305 TWIG_VAR@0..26
2306 TK_OPEN_CURLY_CURLY@0..2 "{{"
2307 TWIG_EXPRESSION@2..23
2308 TWIG_FILTER@2..23
2309 TWIG_OPERAND@2..18
2310 TWIG_FILTER@2..18
2311 TWIG_OPERAND@2..7
2312 TWIG_LITERAL_NAME@2..7
2313 TK_WHITESPACE@2..3 " "
2314 TK_WORD@3..7 "list"
2315 TK_SINGLE_PIPE@7..8 "|"
2316 TWIG_OPERAND@8..18
2317 TWIG_LITERAL_NAME@8..12
2318 TK_WORD@8..12 "join"
2319 TWIG_ARGUMENTS@12..18
2320 TK_OPEN_PARENTHESIS@12..13 "("
2321 TWIG_EXPRESSION@13..17
2322 TWIG_LITERAL_STRING@13..17
2323 TK_SINGLE_QUOTES@13..14 "'"
2324 TWIG_LITERAL_STRING_INNER@14..16
2325 TK_COMMA@14..15 ","
2326 TK_WHITESPACE@15..16 " "
2327 TK_SINGLE_QUOTES@16..17 "'"
2328 TK_CLOSE_PARENTHESIS@17..18 ")"
2329 TK_SINGLE_PIPE@18..19 "|"
2330 TWIG_OPERAND@19..23
2331 TWIG_LITERAL_NAME@19..23
2332 TK_WORD@19..23 "trim"
2333 TK_WHITESPACE@23..24 " "
2334 TK_CLOSE_CURLY_CURLY@24..26 "}}""#]],
2335 );
2336 }
2337
2338 #[test]
2339 fn parse_twig_filter_after_string_with_named_argument() {
2340 check_parse(
2341 r#"{{ "now"|date('d/m/Y H:i', timezone="Europe/Paris") }}"#,
2342 expect![[r#"
2343 ROOT@0..54
2344 TWIG_VAR@0..54
2345 TK_OPEN_CURLY_CURLY@0..2 "{{"
2346 TWIG_EXPRESSION@2..51
2347 TWIG_FILTER@2..51
2348 TWIG_OPERAND@2..8
2349 TWIG_LITERAL_STRING@2..8
2350 TK_WHITESPACE@2..3 " "
2351 TK_DOUBLE_QUOTES@3..4 "\""
2352 TWIG_LITERAL_STRING_INNER@4..7
2353 TK_WORD@4..7 "now"
2354 TK_DOUBLE_QUOTES@7..8 "\""
2355 TK_SINGLE_PIPE@8..9 "|"
2356 TWIG_OPERAND@9..51
2357 TWIG_LITERAL_NAME@9..13
2358 TK_WORD@9..13 "date"
2359 TWIG_ARGUMENTS@13..51
2360 TK_OPEN_PARENTHESIS@13..14 "("
2361 TWIG_EXPRESSION@14..25
2362 TWIG_LITERAL_STRING@14..25
2363 TK_SINGLE_QUOTES@14..15 "'"
2364 TWIG_LITERAL_STRING_INNER@15..24
2365 TK_WORD@15..16 "d"
2366 TK_FORWARD_SLASH@16..17 "/"
2367 TK_WORD@17..18 "m"
2368 TK_FORWARD_SLASH@18..19 "/"
2369 TK_WORD@19..20 "Y"
2370 TK_WHITESPACE@20..21 " "
2371 TK_WORD@21..22 "H"
2372 TK_COLON@22..23 ":"
2373 TK_WORD@23..24 "i"
2374 TK_SINGLE_QUOTES@24..25 "'"
2375 TK_COMMA@25..26 ","
2376 TWIG_NAMED_ARGUMENT@26..50
2377 TK_WHITESPACE@26..27 " "
2378 TK_WORD@27..35 "timezone"
2379 TK_EQUAL@35..36 "="
2380 TWIG_EXPRESSION@36..50
2381 TWIG_LITERAL_STRING@36..50
2382 TK_DOUBLE_QUOTES@36..37 "\""
2383 TWIG_LITERAL_STRING_INNER@37..49
2384 TK_WORD@37..43 "Europe"
2385 TK_FORWARD_SLASH@43..44 "/"
2386 TK_WORD@44..49 "Paris"
2387 TK_DOUBLE_QUOTES@49..50 "\""
2388 TK_CLOSE_PARENTHESIS@50..51 ")"
2389 TK_WHITESPACE@51..52 " "
2390 TK_CLOSE_CURLY_CURLY@52..54 "}}""#]],
2391 );
2392 }
2393
2394 #[test]
2395 fn parse_twig_filter_within_binary_comparison() {
2396 check_parse(
2397 r"{{ users|length > 0 }}",
2398 expect![[r#"
2399 ROOT@0..22
2400 TWIG_VAR@0..22
2401 TK_OPEN_CURLY_CURLY@0..2 "{{"
2402 TWIG_EXPRESSION@2..19
2403 TWIG_BINARY_EXPRESSION@2..19
2404 TWIG_EXPRESSION@2..15
2405 TWIG_FILTER@2..15
2406 TWIG_OPERAND@2..8
2407 TWIG_LITERAL_NAME@2..8
2408 TK_WHITESPACE@2..3 " "
2409 TK_WORD@3..8 "users"
2410 TK_SINGLE_PIPE@8..9 "|"
2411 TWIG_OPERAND@9..15
2412 TWIG_LITERAL_NAME@9..15
2413 TK_WORD@9..15 "length"
2414 TK_WHITESPACE@15..16 " "
2415 TK_GREATER_THAN@16..17 ">"
2416 TWIG_EXPRESSION@17..19
2417 TWIG_LITERAL_NUMBER@17..19
2418 TK_WHITESPACE@17..18 " "
2419 TK_NUMBER@18..19 "0"
2420 TK_WHITESPACE@19..20 " "
2421 TK_CLOSE_CURLY_CURLY@20..22 "}}""#]],
2422 );
2423 }
2424
2425 #[test]
2426 fn parse_twig_include_function_call() {
2427 check_parse(
2428 r"{{ include('sections/articles/sidebar.html') }}",
2429 expect![[r#"
2430 ROOT@0..47
2431 TWIG_VAR@0..47
2432 TK_OPEN_CURLY_CURLY@0..2 "{{"
2433 TWIG_EXPRESSION@2..44
2434 TWIG_FUNCTION_CALL@2..44
2435 TWIG_OPERAND@2..10
2436 TWIG_LITERAL_NAME@2..10
2437 TK_WHITESPACE@2..3 " "
2438 TK_WORD@3..10 "include"
2439 TWIG_ARGUMENTS@10..44
2440 TK_OPEN_PARENTHESIS@10..11 "("
2441 TWIG_EXPRESSION@11..43
2442 TWIG_LITERAL_STRING@11..43
2443 TK_SINGLE_QUOTES@11..12 "'"
2444 TWIG_LITERAL_STRING_INNER@12..42
2445 TK_WORD@12..20 "sections"
2446 TK_FORWARD_SLASH@20..21 "/"
2447 TK_WORD@21..29 "articles"
2448 TK_FORWARD_SLASH@29..30 "/"
2449 TK_WORD@30..37 "sidebar"
2450 TK_DOT@37..38 "."
2451 TK_WORD@38..42 "html"
2452 TK_SINGLE_QUOTES@42..43 "'"
2453 TK_CLOSE_PARENTHESIS@43..44 ")"
2454 TK_WHITESPACE@44..45 " "
2455 TK_CLOSE_CURLY_CURLY@45..47 "}}""#]],
2456 );
2457 }
2458}