1use crate::ast::{BinOp, CompareOp, Expr, MacroParam, UnaryOp};
6use crate::errors::{Result, RunjucksError};
7use crate::parser::split::split_top_level_commas;
8use nom::branch::alt;
9use nom::character::complete::{char, digit1};
10use nom::combinator::{all_consuming, map_res, opt, recognize};
11use nom::IResult;
12use nom::Parser;
13use serde_json::{json, Value};
14
15type CallKwArgs = Vec<(String, Expr)>;
17type CallArgParts = (Vec<Expr>, CallKwArgs);
19
20fn trim_start(s: &str) -> &str {
21 s.trim_start()
22}
23
24fn keyword_boundary(s: &str, kw_len: usize) -> bool {
26 s.as_bytes()
27 .get(kw_len)
28 .is_none_or(|&b| !b.is_ascii_alphanumeric() && b != b'_')
29}
30
31fn parse_keyword<'a>(input: &'a str, kw: &str) -> Option<&'a str> {
32 let input = trim_start(input);
33 if input.starts_with(kw) && keyword_boundary(input, kw.len()) {
34 Some(&input[kw.len()..])
35 } else {
36 None
37 }
38}
39
40fn parse_string(input: &str) -> IResult<&str, String> {
41 let input = trim_start(input);
42 let mut chars = input.chars();
43 let quote = match chars.next() {
44 Some('"') => '"',
45 Some('\'') => '\'',
46 _ => {
47 return Err(nom::Err::Error(nom::error::Error::new(
48 input,
49 nom::error::ErrorKind::Tag,
50 )));
51 }
52 };
53 let rest = &input[quote.len_utf8()..];
54 let mut out = String::new();
55 let mut i = 0usize;
56 let rest_bytes = rest.as_bytes();
57 while i < rest_bytes.len() {
58 let c = rest[i..].chars().next().unwrap();
59 if c == quote {
60 return Ok((&rest[i + c.len_utf8()..], out));
61 }
62 if c == '\\' {
63 i += 1;
64 let Some(esc) = rest.get(i..).and_then(|s| s.chars().next()) else {
65 return Err(nom::Err::Failure(nom::error::Error::new(
66 input,
67 nom::error::ErrorKind::Escaped,
68 )));
69 };
70 match esc {
71 'n' => out.push('\n'),
72 'r' => out.push('\r'),
73 't' => out.push('\t'),
74 '\\' => out.push('\\'),
75 q if q == quote => out.push(q),
76 _ => out.push(esc),
77 }
78 i += esc.len_utf8();
79 continue;
80 }
81 out.push(c);
82 i += c.len_utf8();
83 }
84 Err(nom::Err::Failure(nom::error::Error::new(
85 input,
86 nom::error::ErrorKind::Tag,
87 )))
88}
89
90fn parse_number(input: &str) -> IResult<&str, Value> {
91 let input = trim_start(input);
92 map_res(
93 recognize((
94 opt(char('-')),
95 alt((recognize((digit1, char('.'), digit1)), digit1)),
96 )),
97 |s: &str| -> std::result::Result<Value, ()> {
98 if s.contains('.') {
99 s.parse::<f64>().map(|x| json!(x)).map_err(|_| ())
100 } else if let Ok(n) = s.parse::<i64>() {
101 Ok(json!(n))
102 } else {
103 s.parse::<f64>().map(|x| json!(x)).map_err(|_| ())
104 }
105 },
106 )
107 .parse(input)
108}
109
110fn parse_bool_or_none(input: &str) -> IResult<&str, Value> {
111 let input = trim_start(input);
112 if input.starts_with("true") && keyword_boundary(input, 4) {
113 return Ok((&input[4..], json!(true)));
114 }
115 if input.starts_with("false") && keyword_boundary(input, 5) {
116 return Ok((&input[5..], json!(false)));
117 }
118 if input.starts_with("none") && keyword_boundary(input, 4) {
119 return Ok((&input[4..], Value::Null));
120 }
121 if input.starts_with("null") && keyword_boundary(input, 4) {
122 return Ok((&input[4..], Value::Null));
123 }
124 Err(nom::Err::Error(nom::error::Error::new(
125 input,
126 nom::error::ErrorKind::Tag,
127 )))
128}
129
130fn parse_identifier(input: &str) -> IResult<&str, String> {
131 let input = trim_start(input);
132 let mut chars = input.chars();
133 let first = match chars.next() {
134 Some(c) if c.is_ascii_alphabetic() || c == '_' => c,
135 _ => {
136 return Err(nom::Err::Error(nom::error::Error::new(
137 input,
138 nom::error::ErrorKind::Tag,
139 )));
140 }
141 };
142 let len_first = first.len_utf8();
143 let take = input[len_first..]
144 .chars()
145 .take_while(|c| c.is_ascii_alphanumeric() || *c == '_')
146 .map(|c| c.len_utf8())
147 .sum::<usize>();
148 let end = len_first + take;
149 Ok((&input[end..], input[..end].to_string()))
150}
151
152fn parse_filter_name(input: &str) -> IResult<&str, String> {
154 let (mut rest, mut name) = parse_identifier(input)?;
155 loop {
156 let r = trim_start(rest);
157 if let Some(r2) = r.strip_prefix('.') {
158 let (r3, part) = parse_identifier(trim_start(r2))?;
159 name.push('.');
160 name.push_str(&part);
161 rest = r3;
162 } else {
163 break;
164 }
165 }
166 Ok((rest, name))
167}
168
169fn simple_ident_str(s: &str) -> bool {
170 let mut ch = s.chars();
171 let Some(first) = ch.next() else {
172 return false;
173 };
174 if !first.is_ascii_alphabetic() && first != '_' {
175 return false;
176 }
177 ch.all(|c| c.is_ascii_alphanumeric() || c == '_')
178}
179
180fn split_call_kw_seg(seg: &str) -> Option<(&str, &str)> {
182 let seg = seg.trim();
183 let bytes = seg.as_bytes();
184 let mut depth = 0i32;
185 let mut in_string: Option<u8> = None;
186 let mut escaped = false;
187 let mut i = 0usize;
188 while i < bytes.len() {
189 let c = bytes[i];
190 if let Some(q) = in_string {
191 if escaped {
192 escaped = false;
193 i += 1;
194 continue;
195 }
196 if c == b'\\' {
197 escaped = true;
198 i += 1;
199 continue;
200 }
201 if c == q {
202 in_string = None;
203 }
204 i += 1;
205 continue;
206 }
207 if c == b'"' || c == b'\'' {
208 in_string = Some(c);
209 i += 1;
210 continue;
211 }
212 match c {
213 b'(' => depth += 1,
214 b')' => depth -= 1,
215 b'=' if depth == 0 => {
216 if i + 1 < bytes.len() && bytes[i + 1] == b'=' {
217 i += 2;
218 continue;
219 }
220 if i > 0 && bytes[i - 1] == b'=' {
221 i += 1;
222 continue;
223 }
224 let left = seg[..i].trim();
225 let right = seg[i + 1..].trim();
226 if simple_ident_str(left) {
227 return Some((left, right));
228 }
229 }
230 _ => {}
231 }
232 i += 1;
233 }
234 None
235}
236
237fn split_call_inner_rest(rest_after_open_paren: &str) -> IResult<&str, &str> {
239 let s = trim_start(rest_after_open_paren);
240 let bytes = s.as_bytes();
241 let mut depth = 0i32;
242 let mut in_string: Option<u8> = None;
243 let mut escaped = false;
244 let mut i = 0usize;
245 while i < bytes.len() {
246 let c = bytes[i];
247 if let Some(q) = in_string {
248 if escaped {
249 escaped = false;
250 i += 1;
251 continue;
252 }
253 if c == b'\\' {
254 escaped = true;
255 i += 1;
256 continue;
257 }
258 if c == q {
259 in_string = None;
260 }
261 i += 1;
262 continue;
263 }
264 if c == b'"' || c == b'\'' {
265 in_string = Some(c);
266 i += 1;
267 continue;
268 }
269 match c {
270 b'(' => depth += 1,
271 b')' if depth == 0 => {
272 return Ok((&s[i + 1..], &s[..i]));
273 }
274 b')' => depth -= 1,
275 _ => {}
276 }
277 i += 1;
278 }
279 Err(nom::Err::Failure(nom::error::Error::new(
280 s,
281 nom::error::ErrorKind::Tag,
282 )))
283}
284
285fn parse_call_argument_list_inner(inner: &str) -> Result<CallArgParts> {
286 let inner = inner.trim();
287 if inner.is_empty() {
288 return Ok((vec![], vec![]));
289 }
290 let segs = split_top_level_commas(inner);
291 let mut pos = Vec::new();
292 let mut kw = Vec::new();
293 for seg in segs {
294 let seg = seg.trim();
295 if seg.is_empty() {
296 continue;
297 }
298 if let Some((name, rhs)) = split_call_kw_seg(seg) {
299 kw.push((name.to_string(), parse_expression(rhs)?));
300 } else {
301 pos.push(parse_expression(seg)?);
302 }
303 }
304 Ok((pos, kw))
305}
306
307fn parse_call_argument_list(input: &str) -> IResult<&str, CallArgParts> {
308 let input = trim_start(input);
309 if let Some(r) = input.strip_prefix(')') {
310 return Ok((r, (vec![], vec![])));
311 }
312 let (rest, inner) = split_call_inner_rest(input)?;
313 match parse_call_argument_list_inner(inner) {
314 Ok(pair) => Ok((rest, pair)),
315 Err(_) => Err(nom::Err::Failure(nom::error::Error::new(
316 inner,
317 nom::error::ErrorKind::Verify,
318 ))),
319 }
320}
321
322fn bracket_content_end(s: &str) -> Option<usize> {
324 let mut depth = 0i32;
325 for (i, c) in s.char_indices() {
326 match c {
327 '[' => depth += 1,
328 ']' => {
329 if depth == 0 {
330 return Some(i);
331 }
332 depth -= 1;
333 }
334 _ => {}
335 }
336 }
337 None
338}
339
340fn has_top_level_colon(body: &str) -> bool {
341 let mut d_paren = 0i32;
342 let mut d_bracket = 0i32;
343 for c in body.chars() {
344 match c {
345 '(' => d_paren += 1,
346 ')' => d_paren -= 1,
347 '[' => d_bracket += 1,
348 ']' => d_bracket -= 1,
349 ':' if d_paren == 0 && d_bracket == 0 => return true,
350 _ => {}
351 }
352 }
353 false
354}
355
356fn split_top_level_colon(body: &str) -> Vec<&str> {
357 let mut parts = Vec::new();
358 let mut start = 0usize;
359 let mut d_paren = 0i32;
360 let mut d_bracket = 0i32;
361 for (i, c) in body.char_indices() {
362 match c {
363 '(' => d_paren += 1,
364 ')' => d_paren -= 1,
365 '[' => d_bracket += 1,
366 ']' => d_bracket -= 1,
367 ':' if d_paren == 0 && d_bracket == 0 => {
368 parts.push(body[start..i].trim());
369 start = i + 1;
370 }
371 _ => {}
372 }
373 }
374 parts.push(body[start..].trim());
375 parts
376}
377
378fn parse_optional_slice_segment(
379 seg: &str,
380) -> std::result::Result<Option<Expr>, nom::Err<nom::error::Error<&str>>> {
381 let seg = seg.trim();
382 if seg.is_empty() {
383 return Ok(None);
384 }
385 all_consuming(parse_inline_if)
386 .parse(seg)
387 .map(|(_, e)| Some(e))
388}
389
390fn parse_subscript(input: &str) -> IResult<&str, Expr> {
391 let end = bracket_content_end(input).ok_or_else(|| {
392 nom::Err::Failure(nom::error::Error::new(input, nom::error::ErrorKind::Tag))
393 })?;
394 let body = trim_start(&input[..end]);
395 let rest = &input[end + 1..];
396
397 if !has_top_level_colon(body) {
398 let (_, e) = all_consuming(parse_inline_if).parse(body)?;
399 return Ok((rest, e));
400 }
401
402 let segs = split_top_level_colon(body);
403 if segs.len() > 3 {
404 return Err(nom::Err::Failure(nom::error::Error::new(
405 body,
406 nom::error::ErrorKind::TooLarge,
407 )));
408 }
409 let start_e = parse_optional_slice_segment(segs.first().copied().unwrap_or(""))?;
410 let stop_e = parse_optional_slice_segment(segs.get(1).copied().unwrap_or(""))?;
411 let step_e = parse_optional_slice_segment(segs.get(2).copied().unwrap_or(""))?;
412 let start = start_e.map(Box::new);
413 let stop = stop_e.map(Box::new);
414 let step = step_e.map(Box::new);
415 Ok((rest, Expr::Slice { start, stop, step }))
416}
417
418fn parse_postfix(input: &str, mut node: Expr) -> IResult<&str, Expr> {
419 let mut rest = input;
420 loop {
421 let r = trim_start(rest);
422 if let Some(r2) = r.strip_prefix('.') {
423 let (r3, attr) = parse_identifier(trim_start(r2))?;
424 node = Expr::GetAttr {
425 base: Box::new(node),
426 attr,
427 };
428 rest = r3;
429 continue;
430 }
431 if let Some(r2) = r.strip_prefix('[') {
432 let (r4, idx) = parse_subscript(trim_start(r2))?;
433 node = Expr::GetItem {
434 base: Box::new(node),
435 index: Box::new(idx),
436 };
437 rest = r4;
438 continue;
439 }
440 if let Some(r2) = r.strip_prefix('(') {
441 let (r3, (args, kwargs)) = parse_call_argument_list(r2)?;
442 node = Expr::Call {
443 callee: Box::new(node),
444 args,
445 kwargs,
446 };
447 rest = r3;
448 continue;
449 }
450 break;
451 }
452 Ok((rest, node))
453}
454
455fn parse_list_literal(input: &str) -> IResult<&str, Expr> {
456 let input = trim_start(input);
457 let after = input.strip_prefix('[').ok_or_else(|| {
458 nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Tag))
459 })?;
460 let mut rest = trim_start(after);
461 if let Some(r) = rest.strip_prefix(']') {
462 return Ok((r, Expr::List(vec![])));
463 }
464 let mut items = Vec::new();
465 loop {
466 let (r, e) = parse_inline_if(rest)?;
467 items.push(e);
468 let r = trim_start(r);
469 if let Some(r2) = r.strip_prefix(']') {
470 return Ok((r2, Expr::List(items)));
471 }
472 let r = r.strip_prefix(',').ok_or_else(|| {
473 nom::Err::Failure(nom::error::Error::new(r, nom::error::ErrorKind::Tag))
474 })?;
475 rest = trim_start(r);
476 }
477}
478
479fn parse_dict_literal(input: &str) -> IResult<&str, Expr> {
480 let input = trim_start(input);
481 let after = input.strip_prefix('{').ok_or_else(|| {
482 nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Tag))
483 })?;
484 let mut rest = trim_start(after);
485 if let Some(r) = rest.strip_prefix('}') {
486 return Ok((r, Expr::Dict(vec![])));
487 }
488 let mut pairs = Vec::new();
489 loop {
490 let (r, k) = parse_inline_if(rest)?;
491 let r = trim_start(r);
492 let r = r.strip_prefix(':').ok_or_else(|| {
493 nom::Err::Failure(nom::error::Error::new(r, nom::error::ErrorKind::Tag))
494 })?;
495 let (r, v) = parse_inline_if(trim_start(r))?;
496 pairs.push((k, v));
497 let r = trim_start(r);
498 if let Some(r2) = r.strip_prefix('}') {
499 return Ok((r2, Expr::Dict(pairs)));
500 }
501 let r = r.strip_prefix(',').ok_or_else(|| {
502 nom::Err::Failure(nom::error::Error::new(r, nom::error::ErrorKind::Tag))
503 })?;
504 rest = trim_start(r);
505 }
506}
507
508fn parse_atom(input: &str) -> IResult<&str, Expr> {
509 let input = trim_start(input);
510 if let Some(after) = input.strip_prefix('(') {
511 let (rest, e) = parse_inline_if(after)?;
512 let rest = trim_start(rest);
513 let r = rest.strip_prefix(')').ok_or_else(|| {
514 nom::Err::Failure(nom::error::Error::new(rest, nom::error::ErrorKind::Tag))
515 })?;
516 return Ok((r, e));
517 }
518 if input.starts_with('[') {
519 return parse_list_literal(input);
520 }
521 if input.starts_with('{') {
522 return parse_dict_literal(input);
523 }
524 if let Ok((rest, v)) = parse_string(input) {
525 return Ok((rest, Expr::Literal(json!(v))));
526 }
527 if let Ok((rest, v)) = parse_bool_or_none(input) {
528 return Ok((rest, Expr::Literal(v)));
529 }
530 if let Ok((rest, v)) = parse_number(input) {
531 return Ok((rest, Expr::Literal(v)));
532 }
533 if let Ok((rest, rx)) = parse_regex_literal(input) {
534 return Ok((rest, rx));
535 }
536 let (rest, name) = parse_identifier(input)?;
537 Ok((rest, Expr::Variable(name)))
538}
539
540fn parse_regex_literal(input: &str) -> IResult<&str, Expr> {
542 let input = trim_start(input);
543 let Some(after_r_slash) = input.strip_prefix("r/") else {
544 return Err(nom::Err::Error(nom::error::Error::new(
545 input,
546 nom::error::ErrorKind::Tag,
547 )));
548 };
549 let mut prev = None::<char>;
550 let mut body = String::new();
551 let mut end_slash = None::<usize>;
552 for (i, c) in after_r_slash.char_indices() {
553 if c == '/' && prev != Some('\\') {
554 end_slash = Some(i);
555 break;
556 }
557 body.push(c);
558 prev = Some(c);
559 }
560 let end_idx = end_slash.ok_or_else(|| {
561 nom::Err::Failure(nom::error::Error::new(input, nom::error::ErrorKind::Eof))
562 })?;
563 let after_slash = &after_r_slash[end_idx + 1..];
564 let mut flags = String::new();
565 for ch in after_slash.chars() {
566 if matches!(ch, 'g' | 'i' | 'm' | 'y') {
567 flags.push(ch);
568 } else {
569 break;
570 }
571 }
572 let rest = &after_slash[flags.len()..];
573 Ok((
574 rest,
575 Expr::RegexLiteral {
576 pattern: body,
577 flags,
578 },
579 ))
580}
581
582fn parse_atom_with_postfix(input: &str) -> IResult<&str, Expr> {
583 let (rest, atom) = parse_atom(input)?;
584 parse_postfix(rest, atom)
585}
586
587fn parse_filter_chain(input: &str, mut node: Expr) -> IResult<&str, Expr> {
588 let mut rest = input;
589 loop {
590 let r = trim_start(rest);
591 if !r.starts_with('|') {
592 break;
593 }
594 let after = &r[1..];
595 let after = trim_start(after);
596 let (r2, name) = parse_filter_name(after)?;
597 let after_name = trim_start(r2);
598 let (r3, (extra_args, filter_kw)) = if let Some(inner) = after_name.strip_prefix('(') {
599 parse_call_argument_list(inner)?
600 } else {
601 (after_name, (vec![], vec![]))
602 };
603 if !filter_kw.is_empty() {
604 return Err(nom::Err::Failure(nom::error::Error::new(
605 after_name,
606 nom::error::ErrorKind::Verify,
607 )));
608 }
609 node = Expr::Filter {
610 name,
611 input: Box::new(node),
612 args: extra_args,
613 };
614 rest = r3;
615 }
616 Ok((rest, node))
617}
618
619fn parse_unary_no_filters(input: &str) -> IResult<&str, Expr> {
620 let t = trim_start(input);
621 if let Some(rest) = parse_keyword(t, "not") {
622 let (rest, e) = parse_unary_no_filters(rest)?;
623 return Ok((
624 rest,
625 Expr::Unary {
626 op: UnaryOp::Not,
627 expr: Box::new(e),
628 },
629 ));
630 }
631 if t.starts_with('-')
632 && t.as_bytes()
633 .get(1)
634 .is_some_and(|b| b.is_ascii_digit() || *b == b'.')
635 {
636 let (rest, v) = parse_number(t)?;
637 return Ok((rest, Expr::Literal(v)));
638 }
639 if let Some(rest) = t.strip_prefix('-') {
640 let (rest, e) = parse_unary_no_filters(rest)?;
641 return Ok((
642 rest,
643 Expr::Unary {
644 op: UnaryOp::Neg,
645 expr: Box::new(e),
646 },
647 ));
648 }
649 if let Some(rest) = t.strip_prefix('+') {
650 let (rest, e) = parse_unary_no_filters(rest)?;
651 return Ok((
652 rest,
653 Expr::Unary {
654 op: UnaryOp::Pos,
655 expr: Box::new(e),
656 },
657 ));
658 }
659 parse_atom_with_postfix(input)
660}
661
662fn parse_unary(input: &str) -> IResult<&str, Expr> {
663 let (rest, e) = parse_unary_no_filters(input)?;
664 parse_filter_chain(rest, e)
665}
666
667fn parse_pow(input: &str) -> IResult<&str, Expr> {
668 let (mut rest, mut acc) = parse_unary(input)?;
669 loop {
670 let r = trim_start(rest);
671 if let Some(r2) = r.strip_prefix("**") {
672 rest = r2;
673 let (r2, rhs) = parse_unary(rest)?;
674 rest = r2;
675 acc = Expr::Binary {
676 op: BinOp::Pow,
677 left: Box::new(acc),
678 right: Box::new(rhs),
679 };
680 continue;
681 }
682 break;
683 }
684 Ok((rest, acc))
685}
686
687fn parse_mod(input: &str) -> IResult<&str, Expr> {
688 let (mut rest, mut acc) = parse_pow(input)?;
689 loop {
690 let r = trim_start(rest);
691 if let Some(r2) = r.strip_prefix('%') {
692 if r2.starts_with('%') {
693 break;
694 }
695 let (r3, rhs) = parse_pow(r2)?;
696 rest = r3;
697 acc = Expr::Binary {
698 op: BinOp::Mod,
699 left: Box::new(acc),
700 right: Box::new(rhs),
701 };
702 continue;
703 }
704 break;
705 }
706 Ok((rest, acc))
707}
708
709fn parse_floor_div(input: &str) -> IResult<&str, Expr> {
710 let (mut rest, mut acc) = parse_mod(input)?;
711 loop {
712 let r = trim_start(rest);
713 if let Some(r2) = r.strip_prefix("//") {
714 let (r3, rhs) = parse_mod(r2)?;
715 rest = r3;
716 acc = Expr::Binary {
717 op: BinOp::FloorDiv,
718 left: Box::new(acc),
719 right: Box::new(rhs),
720 };
721 continue;
722 }
723 break;
724 }
725 Ok((rest, acc))
726}
727
728fn parse_div(input: &str) -> IResult<&str, Expr> {
729 let (mut rest, mut acc) = parse_floor_div(input)?;
730 loop {
731 let r = trim_start(rest);
732 if let Some(r2) = r.strip_prefix('/') {
733 if r2.starts_with('/') {
734 break;
735 }
736 let (r3, rhs) = parse_floor_div(r2)?;
737 rest = r3;
738 acc = Expr::Binary {
739 op: BinOp::Div,
740 left: Box::new(acc),
741 right: Box::new(rhs),
742 };
743 continue;
744 }
745 break;
746 }
747 Ok((rest, acc))
748}
749
750fn parse_mul(input: &str) -> IResult<&str, Expr> {
751 let (mut rest, mut acc) = parse_div(input)?;
752 loop {
753 let r = trim_start(rest);
754 if let Some(r2) = r.strip_prefix('*') {
755 if r2.starts_with('*') {
756 break;
757 }
758 let (r3, rhs) = parse_div(r2)?;
759 rest = r3;
760 acc = Expr::Binary {
761 op: BinOp::Mul,
762 left: Box::new(acc),
763 right: Box::new(rhs),
764 };
765 continue;
766 }
767 break;
768 }
769 Ok((rest, acc))
770}
771
772fn parse_sub(input: &str) -> IResult<&str, Expr> {
773 let (mut rest, mut acc) = parse_mul(input)?;
774 loop {
775 let r = trim_start(rest);
776 if let Some(r2) = r.strip_prefix('-') {
777 let (r3, rhs) = parse_mul(r2)?;
778 rest = r3;
779 acc = Expr::Binary {
780 op: BinOp::Sub,
781 left: Box::new(acc),
782 right: Box::new(rhs),
783 };
784 continue;
785 }
786 break;
787 }
788 Ok((rest, acc))
789}
790
791fn parse_add(input: &str) -> IResult<&str, Expr> {
792 let (mut rest, mut acc) = parse_sub(input)?;
793 loop {
794 let r = trim_start(rest);
795 if let Some(r2) = r.strip_prefix('+') {
796 let (r3, rhs) = parse_sub(r2)?;
797 rest = r3;
798 acc = Expr::Binary {
799 op: BinOp::Add,
800 left: Box::new(acc),
801 right: Box::new(rhs),
802 };
803 continue;
804 }
805 break;
806 }
807 Ok((rest, acc))
808}
809
810fn parse_concat(input: &str) -> IResult<&str, Expr> {
811 let (mut rest, mut acc) = parse_add(input)?;
812 loop {
813 let r = trim_start(rest);
814 if let Some(r2) = r.strip_prefix('~') {
815 let (r3, rhs) = parse_add(r2)?;
816 rest = r3;
817 acc = Expr::Binary {
818 op: BinOp::Concat,
819 left: Box::new(acc),
820 right: Box::new(rhs),
821 };
822 continue;
823 }
824 break;
825 }
826 Ok((rest, acc))
827}
828
829fn parse_compare_op(rest: &str) -> Option<(CompareOp, usize)> {
830 if rest.starts_with("===") {
831 Some((CompareOp::StrictEq, 3))
832 } else if rest.starts_with("!==") {
833 Some((CompareOp::StrictNe, 3))
834 } else if rest.starts_with("==") {
835 Some((CompareOp::Eq, 2))
836 } else if rest.starts_with("!=") {
837 Some((CompareOp::Ne, 2))
838 } else if rest.starts_with("<=") {
839 Some((CompareOp::Le, 2))
840 } else if rest.starts_with(">=") {
841 Some((CompareOp::Ge, 2))
842 } else if rest.starts_with('<') {
843 Some((CompareOp::Lt, 1))
844 } else if rest.starts_with('>') {
845 Some((CompareOp::Gt, 1))
846 } else {
847 None
848 }
849}
850
851fn parse_compare(input: &str) -> IResult<&str, Expr> {
852 let (mut rest, head) = parse_concat(input)?;
853 let mut rest_vec: Vec<(CompareOp, Expr)> = Vec::new();
854 loop {
855 let r = trim_start(rest);
856 if let Some((op, len)) = parse_compare_op(r) {
857 let after = &r[len..];
858 let (r2, rhs) = parse_concat(after)?;
859 rest = r2;
860 rest_vec.push((op, rhs));
861 continue;
862 }
863 break;
864 }
865 if rest_vec.is_empty() {
866 Ok((rest, head))
867 } else {
868 Ok((
869 rest,
870 Expr::Compare {
871 head: Box::new(head),
872 rest: rest_vec,
873 },
874 ))
875 }
876}
877
878fn parse_is(input: &str) -> IResult<&str, Expr> {
879 let (mut rest, mut acc) = parse_compare(input)?;
880 loop {
881 let r = trim_start(rest);
882 if let Some(r2) = parse_keyword(r, "is") {
883 let mut after = trim_start(r2);
884 let mut negated = false;
885 if let Some(r3) = parse_keyword(after, "not") {
886 negated = true;
887 after = trim_start(r3);
888 }
889 let (r3, rhs) = parse_compare(after)?;
890 rest = r3;
891 let node = Expr::Binary {
892 op: BinOp::Is,
893 left: Box::new(acc),
894 right: Box::new(rhs),
895 };
896 acc = if negated {
897 Expr::Unary {
898 op: UnaryOp::Not,
899 expr: Box::new(node),
900 }
901 } else {
902 node
903 };
904 continue;
905 }
906 break;
907 }
908 Ok((rest, acc))
909}
910
911fn parse_in(input: &str) -> IResult<&str, Expr> {
912 let (mut rest, mut acc) = parse_is(input)?;
913 loop {
914 let r = trim_start(rest);
915 let (invert, after_not) = if let Some(r2) = parse_keyword(r, "not") {
916 let r3 = trim_start(r2);
917 if let Some(r4) = parse_keyword(r3, "in") {
918 (true, r4)
919 } else {
920 break;
921 }
922 } else if let Some(r2) = parse_keyword(r, "in") {
923 (false, r2)
924 } else {
925 break;
926 };
927 let (r2, rhs) = parse_is(after_not)?;
928 rest = r2;
929 let mut node = Expr::Binary {
930 op: BinOp::In,
931 left: Box::new(acc),
932 right: Box::new(rhs),
933 };
934 if invert {
935 node = Expr::Unary {
936 op: UnaryOp::Not,
937 expr: Box::new(node),
938 };
939 }
940 acc = node;
941 }
942 Ok((rest, acc))
943}
944
945fn parse_and(input: &str) -> IResult<&str, Expr> {
946 let (mut rest, mut acc) = parse_in(input)?;
947 loop {
948 let r = trim_start(rest);
949 if let Some(r2) = parse_keyword(r, "and") {
950 let (r3, rhs) = parse_in(r2)?;
951 rest = r3;
952 acc = Expr::Binary {
953 op: BinOp::And,
954 left: Box::new(acc),
955 right: Box::new(rhs),
956 };
957 continue;
958 }
959 break;
960 }
961 Ok((rest, acc))
962}
963
964fn parse_or(input: &str) -> IResult<&str, Expr> {
965 let (mut rest, mut acc) = parse_and(input)?;
966 loop {
967 let r = trim_start(rest);
968 if let Some(r2) = parse_keyword(r, "or") {
969 let (r3, rhs) = parse_and(r2)?;
970 rest = r3;
971 acc = Expr::Binary {
972 op: BinOp::Or,
973 left: Box::new(acc),
974 right: Box::new(rhs),
975 };
976 continue;
977 }
978 break;
979 }
980 Ok((rest, acc))
981}
982
983pub(crate) fn parse_macro_param_segment(seg: &str) -> Result<MacroParam> {
984 let seg = seg.trim();
985 if seg.is_empty() {
986 return Err(RunjucksError::new("empty macro parameter"));
987 }
988 if let Some((name, rhs)) = split_call_kw_seg(seg) {
989 Ok(MacroParam {
990 name: name.to_string(),
991 default: Some(parse_expression(rhs)?),
992 })
993 } else if simple_ident_str(seg) {
994 Ok(MacroParam {
995 name: seg.to_string(),
996 default: None,
997 })
998 } else {
999 Err(RunjucksError::new(format!(
1000 "invalid macro parameter `{seg}` (expected `name` or `name = default`)"
1001 )))
1002 }
1003}
1004
1005pub(crate) fn parse_inline_if(input: &str) -> IResult<&str, Expr> {
1006 let (rest, first) = parse_or(input)?;
1007 let r = trim_start(rest);
1008 if let Some(r2) = parse_keyword(r, "if") {
1009 let (r3, cond) = parse_or(r2)?;
1010 let r3 = trim_start(r3);
1011 let (rest, else_expr) = if let Some(r4) = parse_keyword(r3, "else") {
1012 let (r5, e) = parse_or(r4)?;
1013 (r5, Some(e))
1014 } else {
1015 (r3, None)
1016 };
1017 return Ok((
1018 rest,
1019 Expr::InlineIf {
1020 cond: Box::new(cond),
1021 then_expr: Box::new(first),
1022 else_expr: else_expr.map(Box::new),
1023 },
1024 ));
1025 }
1026 Ok((rest, first))
1027}
1028
1029pub fn parse_expression(source: &str) -> Result<Expr> {
1031 let s = source.trim();
1032 if s.is_empty() {
1033 return Err(RunjucksError::new(
1034 "empty expression inside `{{ }}` is not allowed",
1035 ));
1036 }
1037 match all_consuming(parse_inline_if).parse(s) {
1038 Ok((_, expr)) => Ok(expr),
1039 Err(e) => Err(RunjucksError::new(format!("expression parse error: {e}"))),
1040 }
1041}
1042
1043#[cfg(test)]
1044mod tests {
1045 use super::*;
1046 use crate::ast::BinOp;
1047
1048 #[test]
1049 fn precedence_mul_before_add() {
1050 let e = parse_expression("2 + 3 * 4").unwrap();
1051 match e {
1052 Expr::Binary {
1053 op: BinOp::Add,
1054 left,
1055 right,
1056 } => {
1057 assert!(matches!(*left, Expr::Literal(_)));
1058 assert!(matches!(*right, Expr::Binary { op: BinOp::Mul, .. }));
1059 }
1060 _ => panic!("unexpected {:?}", e),
1061 }
1062 }
1063}