1use crate::expression::{
2 comma_expressions, expr_in_braces, expr_inside_parens, expression,
3 input_to_str, rust_name,
4};
5use crate::parseresult::PResult;
6use crate::spacelike::{comment_tail, spacelike};
7use nom::branch::alt;
8use nom::bytes::complete::is_not;
9use nom::bytes::complete::tag;
10use nom::character::complete::char;
11use nom::combinator::{map, map_res, opt, recognize, value};
12use nom::error::context;
13use nom::multi::{many0, many_till, separated_list0};
14use nom::sequence::{delimited, pair, preceded, terminated};
15use nom::Parser as _;
16use std::fmt::{self, Display, Write};
17
18#[derive(Debug, PartialEq, Eq)]
19pub enum TemplateExpression {
20 Comment,
21 Text {
22 text: String,
23 },
24 Expression {
25 expr: String,
26 },
27 ForLoop {
28 name: String,
29 expr: String,
30 body: Vec<TemplateExpression>,
31 },
32 IfBlock {
33 expr: String,
34 body: Vec<TemplateExpression>,
35 else_body: Option<Vec<TemplateExpression>>,
36 },
37 MatchBlock {
38 expr: String,
39 arms: Vec<(String, Vec<TemplateExpression>)>,
40 },
41 CallTemplate {
42 name: String,
43 args: Vec<TemplateArgument>,
44 },
45}
46
47#[derive(Debug, PartialEq, Eq)]
48pub enum TemplateArgument {
49 Rust(String),
50 Body(Vec<TemplateExpression>),
51}
52
53impl Display for TemplateArgument {
54 fn fmt(&self, out: &mut fmt::Formatter) -> Result<(), fmt::Error> {
55 match *self {
56 TemplateArgument::Rust(ref s) => out.write_str(s),
57 TemplateArgument::Body(ref v) if v.is_empty() => {
58 out.write_str("|_| Ok(())")
59 }
60 TemplateArgument::Body(ref v) => {
61 out.write_str("#[allow(clippy::used_underscore_binding)] |mut _ructe_out_| {\n")?;
62 for b in v {
63 b.write_code(out)?;
64 }
65 out.write_str("Ok(())\n}\n")
66 }
67 }
68 }
69}
70
71impl TemplateExpression {
72 pub fn text(text: &str) -> Self {
73 TemplateExpression::Text {
74 text: text.to_string(),
75 }
76 }
77 pub fn write_code(&self, out: &mut impl Write) -> fmt::Result {
78 match *self {
79 TemplateExpression::Comment => Ok(()),
80 TemplateExpression::Text { ref text } if text.is_ascii() => {
81 writeln!(out, "_ructe_out_.write_all(b{text:?})?;")
82 }
83 TemplateExpression::Text { ref text } => {
84 writeln!(out, "_ructe_out_.write_all({text:?}.as_bytes())?;")
85 }
86 TemplateExpression::Expression { ref expr } => {
87 writeln!(out, "{expr}.to_html(_ructe_out_.by_ref())?;")
88 }
89 TemplateExpression::ForLoop {
90 ref name,
91 ref expr,
92 ref body,
93 } => {
94 writeln!(out, "for {name} in {expr} {{")?;
95 for b in body {
96 b.write_code(out)?;
97 }
98 out.write_str("}\n")
99 }
100 TemplateExpression::IfBlock {
101 ref expr,
102 ref body,
103 ref else_body,
104 } => {
105 writeln!(out, "if {expr} {{")?;
106 for b in body {
107 b.write_code(out)?;
108 }
109 out.write_str("}")?;
110 match else_body.as_deref() {
111 Some([e @ TemplateExpression::IfBlock { .. }]) => {
112 out.write_str(" else ")?;
113 e.write_code(out)
114 }
115 Some(body) => {
116 out.write_str(" else {\n")?;
117 for b in body {
118 b.write_code(out)?;
119 }
120 out.write_str("}\n")
121 }
122 None => out.write_char('\n'),
123 }
124 }
125 TemplateExpression::MatchBlock { ref expr, ref arms } => {
126 write!(out, "match {expr} {{")?;
127 for (expr, body) in arms {
128 write!(out, "\n {expr} => {{")?;
129 for b in body {
130 b.write_code(out)?;
131 }
132 write!(out, "}}")?;
133 }
134 writeln!(out, "\n}}")
135 }
136 TemplateExpression::CallTemplate { ref name, ref args } => {
137 write!(out, "{name}(_ructe_out_.by_ref()",)?;
138 for arg in args {
139 write!(out, ", {arg}")?;
140 }
141 writeln!(out, ")?;")
142 }
143 }
144 }
145}
146
147pub fn template_expression(input: &[u8]) -> PResult<TemplateExpression> {
148 match opt(preceded(
149 char('@'),
150 alt((
151 tag("*"),
152 tag(":"),
153 tag("@"),
154 tag("{"),
155 tag("}"),
156 tag("("),
157 terminated(alt((tag("if"), tag("for"), tag("match"))), tag(" ")),
158 value(&b""[..], tag("")),
159 )),
160 ))
161 .parse(input)?
162 {
163 (i, Some(b":")) => map(
164 pair(
165 rust_name,
166 delimited(
167 char('('),
168 separated_list0(
169 terminated(tag(","), spacelike),
170 template_argument,
171 ),
172 char(')'),
173 ),
174 ),
175 |(name, args)| TemplateExpression::CallTemplate {
176 name: name.to_string(),
177 args,
178 },
179 )
180 .parse(i),
181 (i, Some(b"@")) => Ok((i, TemplateExpression::text("@"))),
182 (i, Some(b"{")) => Ok((i, TemplateExpression::text("{"))),
183 (i, Some(b"}")) => Ok((i, TemplateExpression::text("}"))),
184 (i, Some(b"*")) => {
185 map(comment_tail, |()| TemplateExpression::Comment).parse(i)
186 }
187 (i, Some(b"if")) => if2(i),
188 (i, Some(b"for")) => map(
189 (
190 for_variable,
191 delimited(
192 terminated(
193 context("Expected \"in\"", tag("in")),
194 spacelike,
195 ),
196 context("Expected iterable expression", loop_expression),
197 spacelike,
198 ),
199 context("Error in loop block:", template_block),
200 ),
201 |(name, expr, body)| TemplateExpression::ForLoop {
202 name,
203 expr,
204 body,
205 },
206 )
207 .parse(i),
208 (i, Some(b"match")) => context(
209 "Error in match expression:",
210 map(
211 (
212 delimited(spacelike, expression, spacelike),
213 preceded(
214 char('{'),
215 map(
216 many_till(
217 context(
218 "Error in match arm starting here:",
219 pair(
220 delimited(
221 spacelike,
222 map(expression, String::from),
223 spacelike,
224 ),
225 preceded(
226 terminated(tag("=>"), spacelike),
227 template_block,
228 ),
229 ),
230 ),
231 preceded(spacelike, char('}')),
232 ),
233 |(arms, _end)| arms,
234 ),
235 ),
236 ),
237 |(expr, arms)| TemplateExpression::MatchBlock {
238 expr: expr.to_string(),
239 arms,
240 },
241 ),
242 )
243 .parse(i),
244 (i, Some(b"(")) => {
245 map(terminated(expr_inside_parens, tag(")")), |expr| {
246 TemplateExpression::Expression {
247 expr: format!("({expr})"),
248 }
249 })
250 .parse(i)
251 }
252 (i, Some(b"")) => {
253 map(expression, |expr| TemplateExpression::Expression {
254 expr: expr.to_string(),
255 })
256 .parse(i)
257 }
258 (_i, Some(_)) => unreachable!(),
259 (i, None) => map(map_res(is_not("@{}"), input_to_str), |text| {
260 TemplateExpression::Text {
261 text: text.to_string(),
262 }
263 })
264 .parse(i),
265 }
266}
267
268fn if2(input: &[u8]) -> PResult<TemplateExpression> {
269 context(
270 "Error in conditional expression:",
271 map(
272 (
273 delimited(spacelike, cond_expression, spacelike),
274 template_block,
275 opt(preceded(
276 delimited(spacelike, tag("else"), spacelike),
277 alt((
278 preceded(tag("if"), map(if2, |e| vec![e])),
279 template_block,
280 )),
281 )),
282 ),
283 |(expr, body, else_body)| TemplateExpression::IfBlock {
284 expr,
285 body,
286 else_body,
287 },
288 ),
289 )
290 .parse(input)
291}
292
293fn for_variable(input: &[u8]) -> PResult<String> {
294 delimited(
295 spacelike,
296 context(
297 "Expected loop variable name or destructuring tuple",
298 alt((
299 map(
300 map_res(
301 recognize(preceded(rust_name, opt(expr_in_braces))),
302 input_to_str,
303 ),
304 String::from,
305 ),
306 map(
307 pair(
308 opt(char('&')),
309 delimited(char('('), comma_expressions, char(')')),
310 ),
311 |(pre, args)| {
312 format!("{}({})", pre.map_or("", |_| "&"), args)
313 },
314 ),
315 )),
316 ),
317 spacelike,
318 )
319 .parse(input)
320}
321
322fn template_block(input: &[u8]) -> PResult<Vec<TemplateExpression>> {
323 preceded(
324 char('{'),
325 map(
326 many_till(
327 context(
328 "Error in expression starting here:",
329 template_expression,
330 ),
331 char('}'),
332 ),
333 |(block, _end)| block,
334 ),
335 )
336 .parse(input)
337}
338
339fn template_argument(input: &[u8]) -> PResult<TemplateArgument> {
340 alt((
341 map(
342 delimited(
343 char('{'),
344 many0(template_expression),
345 terminated(char('}'), spacelike),
346 ),
347 TemplateArgument::Body,
348 ),
349 map(map(expression, String::from), TemplateArgument::Rust),
350 ))
351 .parse(input)
352}
353
354fn cond_expression(input: &[u8]) -> PResult<String> {
355 match opt(tag("let")).parse(input)? {
356 (i, Some(b"let")) => map(
357 pair(
358 preceded(
359 spacelike,
360 context(
361 "Expected LHS expression in let binding",
362 expression,
363 ),
364 ),
365 preceded(
366 delimited(spacelike, char('='), spacelike),
367 context(
368 "Expected RHS expression in let binding",
369 expression,
370 ),
371 ),
372 ),
373 |(lhs, rhs)| format!("let {lhs} = {rhs}"),
374 )
375 .parse(i),
376 (_i, Some(_)) => unreachable!(),
377 (i, None) => map(
378 context("Expected expression", logic_expression),
379 String::from,
380 )
381 .parse(i),
382 }
383}
384
385fn loop_expression(input: &[u8]) -> PResult<String> {
386 map(
387 map_res(
388 recognize(terminated(
389 expression,
390 opt(preceded(
391 terminated(tag(".."), opt(char('='))),
392 expression,
393 )),
394 )),
395 input_to_str,
396 ),
397 String::from,
398 )
399 .parse(input)
400}
401
402fn logic_expression(input: &[u8]) -> PResult<&str> {
403 map_res(
404 recognize((
405 opt(terminated(char('!'), spacelike)),
406 expression,
407 opt(pair(
408 rel_operator,
409 context("Expected expression", logic_expression),
410 )),
411 )),
412 input_to_str,
413 )
414 .parse(input)
415}
416
417fn rel_operator(input: &[u8]) -> PResult<&str> {
418 map_res(
419 delimited(
420 spacelike,
421 context(
422 "Expected relational operator",
423 alt((
424 tag("!="),
425 tag("&&"),
426 tag("<="),
427 tag("<"),
428 tag("=="),
429 tag(">="),
430 tag(">"),
431 tag("||"),
432 )),
433 ),
434 spacelike,
435 ),
436 input_to_str,
437 )
438 .parse(input)
439}
440
441#[cfg(test)]
442mod test {
443 use super::super::parseresult::show_errors;
444 use super::*;
445
446 #[test]
447 fn for_variable_simple() {
448 assert_eq!(
449 for_variable(b"foo").unwrap(),
450 (&b""[..], "foo".to_string())
451 )
452 }
453
454 #[test]
455 fn for_variable_tuple() {
456 assert_eq!(
457 for_variable(b"(foo, bar)").unwrap(),
458 (&b""[..], "(foo, bar)".to_string())
459 )
460 }
461
462 #[test]
463 fn for_variable_tuple_ref() {
464 assert_eq!(
465 for_variable(b"&(foo, bar)").unwrap(),
466 (&b""[..], "&(foo, bar)".to_string())
467 )
468 }
469
470 #[test]
471 fn for_variable_struct() {
472 assert_eq!(
473 for_variable(b"MyStruct{foo, bar}").unwrap(),
474 (&b""[..], "MyStruct{foo, bar}".to_string())
475 )
476 }
477
478 #[test]
479 fn call_simple() {
480 assert_eq!(
481 template_expression(b"@foo()"),
482 Ok((
483 &b""[..],
484 TemplateExpression::Expression {
485 expr: "foo()".to_string(),
486 },
487 ))
488 )
489 }
490
491 #[test]
493 fn call_empty_str() {
494 assert_eq!(
495 template_expression(b"@foo(\"\")"),
496 Ok((
497 &b""[..],
498 TemplateExpression::Expression {
499 expr: "foo(\"\")".to_string(),
500 },
501 ))
502 )
503 }
504
505 #[test]
506 fn if_boolean_var() {
507 assert_eq!(
508 template_expression(b"@if cond { something }"),
509 Ok((
510 &b""[..],
511 TemplateExpression::IfBlock {
512 expr: "cond".to_string(),
513 body: vec![TemplateExpression::text(" something ")],
514 else_body: None,
515 }
516 ))
517 )
518 }
519
520 #[test]
521 fn if_let() {
522 assert_eq!(
523 template_expression(b"@if let Some(x) = x { something }"),
524 Ok((
525 &b""[..],
526 TemplateExpression::IfBlock {
527 expr: "let Some(x) = x".to_string(),
528 body: vec![TemplateExpression::text(" something ")],
529 else_body: None,
530 }
531 ))
532 )
533 }
534
535 #[test]
536 fn if_let_2() {
537 assert_eq!(
538 template_expression(b"@if let Some((x, y)) = x { something }"),
539 Ok((
540 &b""[..],
541 TemplateExpression::IfBlock {
542 expr: "let Some((x, y)) = x".to_string(),
543 body: vec![TemplateExpression::text(" something ")],
544 else_body: None,
545 }
546 ))
547 )
548 }
549
550 #[test]
551 fn if_let_3() {
552 assert_eq!(
553 template_expression(
554 b"@if let Some(p) = Uri::borrow_from(&state) { something }"
555 ),
556 Ok((
557 &b""[..],
558 TemplateExpression::IfBlock {
559 expr: "let Some(p) = Uri::borrow_from(&state)"
560 .to_string(),
561 body: vec![TemplateExpression::text(" something ")],
562 else_body: None,
563 }
564 ))
565 )
566 }
567
568 #[test]
569 fn if_let_struct() {
570 assert_eq!(
571 template_expression(
572 b"@if let Struct{x, y} = variable { something }"
573 ),
574 Ok((
575 &b""[..],
576 TemplateExpression::IfBlock {
577 expr: "let Struct{x, y} = variable".to_string(),
578 body: vec![TemplateExpression::text(" something ")],
579 else_body: None,
580 }
581 ))
582 )
583 }
584
585 #[test]
586 fn if_compare() {
587 assert_eq!(
588 template_expression(b"@if x == 17 { something }"),
589 Ok((
590 &b""[..],
591 TemplateExpression::IfBlock {
592 expr: "x == 17".to_string(),
593 body: vec![TemplateExpression::text(" something ")],
594 else_body: None,
595 }
596 ))
597 )
598 }
599
600 #[test]
602 fn if_compare_empty_string() {
603 assert_eq!(
606 template_expression(b"@if x == \"\" { something }"),
607 Ok((
608 &b""[..],
609 TemplateExpression::IfBlock {
610 expr: "x == \"\"".to_string(),
611 body: vec![TemplateExpression::text(" something ")],
612 else_body: None,
613 }
614 ))
615 )
616 }
617
618 #[test]
619 fn if_complex_logig() {
620 assert_eq!(
621 template_expression(b"@if x == 17 || y && z() { something }"),
622 Ok((
623 &b""[..],
624 TemplateExpression::IfBlock {
625 expr: "x == 17 || y && z()".to_string(),
626 body: vec![TemplateExpression::text(" something ")],
627 else_body: None,
628 }
629 ))
630 )
631 }
632 #[test]
633 fn if_missing_conditional() {
634 assert_eq!(
635 expression_error(b"@if { oops }"),
636 ": 1:@if { oops }\n\
637 : ^ Error in conditional expression:\n\
638 : 1:@if { oops }\n\
639 : ^ Expected expression\n\
640 : 1:@if { oops }\n\
641 : ^ Expected rust expression\n"
642 )
643 }
644
645 #[test]
646 fn if_bad_let() {
647 assert_eq!(
648 expression_error(b"@if let foo { oops }"),
649 ": 1:@if let foo { oops }\n\
650 : ^ Error in conditional expression:\n\
651 : 1:@if let foo { oops }\n\
652 : ^ Expected \'=\'\n"
653 )
654 }
655
656 #[test]
657 fn for_in_struct() {
658 assert_eq!(
659 template_expression(
660 b"@for Struct{x, y} in structs { something }"
661 ),
662 Ok((
663 &b""[..],
664 TemplateExpression::ForLoop {
665 name: "Struct{x, y}".to_string(),
666 expr: "structs".to_string(),
667 body: vec![TemplateExpression::text(" something ")],
668 }
669 ))
670 )
671 }
672
673 #[test]
674 fn for_missing_in() {
675 assert_eq!(
677 expression_error(b"@for what ever { hello }"),
678 ": 1:@for what ever { hello }\n\
679 : ^ Expected \"in\"\n"
680 )
681 }
682
683 fn expression_error(input: &[u8]) -> String {
684 let mut buf = Vec::new();
685 if let Err(error) = template_expression(input) {
686 show_errors(&mut buf, input, &error, ":");
687 }
688 String::from_utf8(buf).unwrap()
689 }
690}