1#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
2#![deny(elided_lifetimes_in_paths)]
3#![deny(unreachable_pub)]
4
5use std::borrow::Cow;
6use std::cell::Cell;
7use std::env::current_dir;
8use std::ops::{Deref, DerefMut};
9use std::path::Path;
10use std::sync::Arc;
11use std::{fmt, str};
12
13use nom::branch::alt;
14use nom::bytes::complete::{escaped, is_not, tag, take_till, take_while_m_n};
15use nom::character::complete::{anychar, char, one_of, satisfy};
16use nom::combinator::{consumed, cut, fail, map, not, opt, recognize, value};
17use nom::error::{ErrorKind, FromExternalError};
18use nom::multi::{many0_count, many1};
19use nom::sequence::{delimited, pair, preceded, tuple};
20use nom::{AsChar, InputTakeAtPosition};
21
22pub mod expr;
23pub use expr::{Expr, Filter};
24mod memchr_splitter;
25pub mod node;
26pub use node::Node;
27
28mod target;
29pub use target::Target;
30#[cfg(test)]
31mod tests;
32
33mod _parsed {
34 use std::path::Path;
35 use std::sync::Arc;
36 use std::{fmt, mem};
37
38 use super::node::Node;
39 use super::{Ast, ParseError, Syntax};
40
41 pub struct Parsed {
42 // `source` must outlive `ast`, so `ast` must be declared before `source`
43 ast: Ast<'static>,
44 #[allow(dead_code)]
45 source: Arc<str>,
46 }
47
48 impl Parsed {
49 /// If `file_path` is `None`, it means the `source` is an inline template. Therefore, if
50 /// a parsing error occurs, we won't display the path as it wouldn't be useful.
51 pub fn new(
52 source: Arc<str>,
53 file_path: Option<Arc<Path>>,
54 syntax: &Syntax<'_>,
55 ) -> Result<Self, ParseError> {
56 // Self-referential borrowing: `self` will keep the source alive as `String`,
57 // internally we will transmute it to `&'static str` to satisfy the compiler.
58 // However, we only expose the nodes with a lifetime limited to `self`.
59 let src = unsafe { mem::transmute::<&str, &'static str>(source.as_ref()) };
60 let ast = Ast::from_str(src, file_path, syntax)?;
61 Ok(Self { ast, source })
62 }
63
64 // The return value's lifetime must be limited to `self` to uphold the unsafe invariant.
65 #[must_use]
66 pub fn nodes(&self) -> &[Node<'_>] {
67 &self.ast.nodes
68 }
69
70 #[must_use]
71 pub fn source(&self) -> &str {
72 &self.source
73 }
74 }
75
76 impl fmt::Debug for Parsed {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 f.debug_struct("Parsed")
79 .field("nodes", &self.ast.nodes)
80 .finish_non_exhaustive()
81 }
82 }
83
84 impl PartialEq for Parsed {
85 fn eq(&self, other: &Self) -> bool {
86 self.ast.nodes == other.ast.nodes
87 }
88 }
89
90 impl Default for Parsed {
91 fn default() -> Self {
92 Self {
93 ast: Ast::default(),
94 source: "".into(),
95 }
96 }
97 }
98}
99
100pub use _parsed::Parsed;
101
102#[derive(Debug, Default)]
103pub struct Ast<'a> {
104 nodes: Vec<Node<'a>>,
105}
106
107impl<'a> Ast<'a> {
108 /// If `file_path` is `None`, it means the `source` is an inline template. Therefore, if
109 /// a parsing error occurs, we won't display the path as it wouldn't be useful.
110 pub fn from_str(
111 src: &'a str,
112 file_path: Option<Arc<Path>>,
113 syntax: &Syntax<'_>,
114 ) -> Result<Self, ParseError> {
115 match Node::parse_template(src, &State::new(syntax)) {
116 Ok(("", nodes)) => Ok(Self { nodes }),
117 Ok(_) | Err(nom::Err::Incomplete(_)) => unreachable!(),
118 Err(
119 nom::Err::Error(ErrorContext { input, message, .. })
120 | nom::Err::Failure(ErrorContext { input, message, .. }),
121 ) => Err(ParseError {
122 message,
123 offset: src.len() - input.len(),
124 file_path,
125 }),
126 }
127 }
128
129 #[must_use]
130 pub fn nodes(&self) -> &[Node<'a>] {
131 &self.nodes
132 }
133}
134
135/// Struct used to wrap types with their associated "span" which is used when generating errors
136/// in the code generation.
137pub struct WithSpan<'a, T> {
138 inner: T,
139 span: &'a str,
140}
141
142impl<'a, T> WithSpan<'a, T> {
143 pub const fn new(inner: T, span: &'a str) -> Self {
144 Self { inner, span }
145 }
146
147 pub fn span(&self) -> &'a str {
148 self.span
149 }
150
151 pub fn deconstruct(self) -> (T, &'a str) {
152 let Self { inner: T, span: &'a str } = self;
153 (inner, span)
154 }
155}
156
157impl<'a, T> Deref for WithSpan<'a, T> {
158 type Target = T;
159
160 fn deref(&self) -> &Self::Target {
161 &self.inner
162 }
163}
164
165impl<'a, T> DerefMut for WithSpan<'a, T> {
166 fn deref_mut(&mut self) -> &mut Self::Target {
167 &mut self.inner
168 }
169}
170
171impl<'a, T: fmt::Debug> fmt::Debug for WithSpan<'a, T> {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 write!(f, "{:?}", self.inner)
174 }
175}
176
177impl<'a, T: Clone> Clone for WithSpan<'a, T> {
178 fn clone(&self) -> Self {
179 Self {
180 inner: self.inner.clone(),
181 span: self.span,
182 }
183 }
184}
185
186impl<'a, T: PartialEq> PartialEq for WithSpan<'a, T> {
187 fn eq(&self, other: &Self) -> bool {
188 // We never want to compare the span information.
189 self.inner == other.inner
190 }
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
194pub struct ParseError {
195 pub message: Option<Cow<'static, str>>,
196 pub offset: usize,
197 pub file_path: Option<Arc<Path>>,
198}
199
200impl std::error::Error for ParseError {}
201
202impl fmt::Display for ParseError {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 let ParseError {
205 message: &Option>,
206 file_path: &Option>,
207 offset: &usize,
208 } = self;
209
210 if let Some(message: &Cow<'static, str>) = message {
211 writeln!(f, "{message}")?;
212 }
213
214 let path: Option = file_pathOption<&Arc>
215 .as_ref()
216 .and_then(|path: &Arc| Some(strip_common(&current_dir().ok()?, path)));
217 match path {
218 Some(path: String) => write!(f, "failed to parse template source\n --> {path}@{offset}"),
219 None => write!(f, "failed to parse template source near offset {offset}"),
220 }
221 }
222}
223
224pub(crate) type ParseErr<'a> = nom::Err<ErrorContext<'a>>;
225pub(crate) type ParseResult<'a, T = &'a str> = Result<(&'a str, T), ParseErr<'a>>;
226
227/// This type is used to handle `nom` errors and in particular to add custom error messages.
228/// It used to generate `ParserError`.
229///
230/// It cannot be used to replace `ParseError` because it expects a generic, which would make
231/// `rinja`'s users experience less good (since this generic is only needed for `nom`).
232#[derive(Debug)]
233pub(crate) struct ErrorContext<'a> {
234 pub(crate) input: &'a str,
235 pub(crate) message: Option<Cow<'static, str>>,
236}
237
238impl<'a> ErrorContext<'a> {
239 fn unclosed(kind: &str, tag: &str, i: &'a str) -> Self {
240 Self::new(message:format!("unclosed {kind}, missing {tag:?}"), input:i)
241 }
242
243 fn new(message: impl Into<Cow<'static, str>>, input: &'a str) -> Self {
244 Self {
245 input,
246 message: Some(message.into()),
247 }
248 }
249}
250
251impl<'a> nom::error::ParseError<&'a str> for ErrorContext<'a> {
252 fn from_error_kind(input: &'a str, _code: ErrorKind) -> Self {
253 Self {
254 input,
255 message: None,
256 }
257 }
258
259 fn append(_: &'a str, _: ErrorKind, other: Self) -> Self {
260 other
261 }
262}
263
264impl<'a, E: std::fmt::Display> FromExternalError<&'a str, E> for ErrorContext<'a> {
265 fn from_external_error(input: &'a str, _kind: ErrorKind, e: E) -> Self {
266 Self {
267 input,
268 message: Some(Cow::Owned(e.to_string())),
269 }
270 }
271}
272
273impl<'a> From<ErrorContext<'a>> for nom::Err<ErrorContext<'a>> {
274 fn from(cx: ErrorContext<'a>) -> Self {
275 Self::Failure(cx)
276 }
277}
278
279fn is_ws(c: char) -> bool {
280 matches!(c, ' ' | '\t' | '\r' | '\n')
281}
282
283fn not_ws(c: char) -> bool {
284 !is_ws(c)
285}
286
287fn ws<'a, O>(
288 inner: impl FnMut(&'a str) -> ParseResult<'a, O>,
289) -> impl FnMut(&'a str) -> ParseResult<'a, O> {
290 delimited(first:take_till(not_ws), second:inner, third:take_till(cond:not_ws))
291}
292
293/// Skips input until `end` was found, but does not consume it.
294/// Returns tuple that would be returned when parsing `end`.
295fn skip_till<'a, 'b, O>(
296 candidate_finder: impl crate::memchr_splitter::Splitter,
297 end: impl FnMut(&'a str) -> ParseResult<'a, O>,
298) -> impl FnMut(&'a str) -> ParseResult<'a, (&'a str, O)> {
299 let mut next: impl FnMut(&'a str) -> Result<…, …> = alt((map(parser:end, f:Some), map(parser:anychar, |_| None)));
300 move |start: &'a str| {
301 let mut i: &'a str = start;
302 loop {
303 i = match candidate_finder.split(haystack:i) {
304 Some((_, j: &str)) => j,
305 None => return Err(nom::Err::Error(ErrorContext::new(message:"`end` not found`", input:i))),
306 };
307 i = match next(i)? {
308 (j: &'a str, Some(lookahead: O)) => return Ok((i, (j, lookahead))),
309 (j: &'a str, None) => j,
310 };
311 }
312 }
313}
314
315fn keyword<'a>(k: &'a str) -> impl FnMut(&'a str) -> ParseResult<'a> {
316 move |i: &'a str| -> ParseResult<'a> {
317 let (j: &str, v: &str) = identifier(input:i)?;
318 if k == v { Ok((j, v)) } else { fail(i) }
319 }
320}
321
322fn identifier(input: &str) -> ParseResult<'_> {
323 fn start(s: &str) -> ParseResult<'_> {
324 s.split_at_position1_complete(
325 |c| !(c.is_alpha() || c == '_' || c >= '\u{0080}'),
326 e:nom::error::ErrorKind::Alpha,
327 )
328 }
329
330 fn tail(s: &str) -> ParseResult<'_> {
331 s.split_at_position1_complete(
332 |c| !(c.is_alphanum() || c == '_' || c >= '\u{0080}'),
333 e:nom::error::ErrorKind::Alpha,
334 )
335 }
336
337 recognize(parser:pair(first:start, second:opt(tail)))(input)
338}
339
340fn bool_lit(i: &str) -> ParseResult<'_> {
341 alt((keyword("false"), keyword("true")))(i)
342}
343
344#[derive(Debug, Clone, Copy, PartialEq)]
345pub enum Num<'a> {
346 Int(&'a str, Option<IntKind>),
347 Float(&'a str, Option<FloatKind>),
348}
349
350fn num_lit<'a>(start: &'a str) -> ParseResult<'a, Num<'a>> {
351 fn num_lit_suffix<'a, T: Copy>(
352 kind: &'a str,
353 list: &[(&str, T)],
354 start: &'a str,
355 i: &'a str,
356 ) -> ParseResult<'a, T> {
357 let (i, suffix) = identifier(i)?;
358 if let Some(value) = list
359 .iter()
360 .copied()
361 .find_map(|(name, value)| (name == suffix).then_some(value))
362 {
363 Ok((i, value))
364 } else {
365 Err(nom::Err::Failure(ErrorContext::new(
366 format!("unknown {kind} suffix `{suffix}`"),
367 start,
368 )))
369 }
370 }
371
372 // Equivalent to <https://github.com/rust-lang/rust/blob/e3f909b2bbd0b10db6f164d466db237c582d3045/compiler/rustc_lexer/src/lib.rs#L587-L620>.
373 let int_with_base = pair(opt(char('-')), |i| {
374 let (i, (kind, base)) = consumed(preceded(
375 char('0'),
376 alt((
377 value(2, char('b')),
378 value(8, char('o')),
379 value(16, char('x')),
380 )),
381 ))(i)?;
382 match opt(separated_digits(base, false))(i)? {
383 (i, Some(_)) => Ok((i, ())),
384 (_, None) => Err(nom::Err::Failure(ErrorContext::new(
385 format!("expected digits after `{kind}`"),
386 start,
387 ))),
388 }
389 });
390
391 // Equivalent to <https://github.com/rust-lang/rust/blob/e3f909b2bbd0b10db6f164d466db237c582d3045/compiler/rustc_lexer/src/lib.rs#L626-L653>:
392 // no `_` directly after the decimal point `.`, or between `e` and `+/-`.
393 let float = |i: &'a str| -> ParseResult<'a, ()> {
394 let (i, has_dot) = opt(pair(char('.'), separated_digits(10, true)))(i)?;
395 let (i, has_exp) = opt(|i| {
396 let (i, (kind, op)) = pair(one_of("eE"), opt(one_of("+-")))(i)?;
397 match opt(separated_digits(10, op.is_none()))(i)? {
398 (i, Some(_)) => Ok((i, ())),
399 (_, None) => Err(nom::Err::Failure(ErrorContext::new(
400 format!("expected decimal digits, `+` or `-` after exponent `{kind}`"),
401 start,
402 ))),
403 }
404 })(i)?;
405 match (has_dot, has_exp) {
406 (Some(_), _) | (_, Some(())) => Ok((i, ())),
407 _ => fail(start),
408 }
409 };
410
411 let (i, num) = if let Ok((i, Some(num))) = opt(recognize(int_with_base))(start) {
412 let (i, suffix) = opt(|i| num_lit_suffix("integer", INTEGER_TYPES, start, i))(i)?;
413 (i, Num::Int(num, suffix))
414 } else {
415 let (i, (num, float)) = consumed(preceded(
416 pair(opt(char('-')), separated_digits(10, true)),
417 opt(float),
418 ))(start)?;
419 if float.is_some() {
420 let (i, suffix) = opt(|i| num_lit_suffix("float", FLOAT_TYPES, start, i))(i)?;
421 (i, Num::Float(num, suffix))
422 } else {
423 let (i, suffix) = opt(|i| num_lit_suffix("number", NUM_TYPES, start, i))(i)?;
424 match suffix {
425 Some(NumKind::Int(kind)) => (i, Num::Int(num, Some(kind))),
426 Some(NumKind::Float(kind)) => (i, Num::Float(num, Some(kind))),
427 None => (i, Num::Int(num, None)),
428 }
429 }
430 };
431 Ok((i, num))
432}
433
434/// Underscore separated digits of the given base, unless `start` is true this may start
435/// with an underscore.
436fn separated_digits(radix: u32, start: bool) -> impl Fn(&str) -> ParseResult<'_> {
437 move |i: &str| {
438 recognize(parser:tuple((
439 |i: &str| match start {
440 true => Ok((i, 0)),
441 false => many0_count(char('_'))(i),
442 },
443 satisfy(|ch: char| ch.is_digit(radix)),
444 many0_count(satisfy(|ch: char| ch == '_' || ch.is_digit(radix))),
445 )))(i)
446 }
447}
448
449#[derive(Clone, Copy, Debug, PartialEq)]
450pub enum StrPrefix {
451 Binary,
452 CLike,
453}
454
455impl StrPrefix {
456 #[must_use]
457 pub fn to_char(self) -> char {
458 match self {
459 Self::Binary => 'b',
460 Self::CLike => 'c',
461 }
462 }
463}
464
465impl fmt::Display for StrPrefix {
466 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
467 use std::fmt::Write;
468
469 f.write_char(self.to_char())
470 }
471}
472
473#[derive(Clone, Debug, PartialEq)]
474pub struct StrLit<'a> {
475 pub prefix: Option<StrPrefix>,
476 pub content: &'a str,
477}
478
479fn str_lit_without_prefix(i: &str) -> ParseResult<'_> {
480 let (i: &str, s: Option<&str>) = delimited(
481 first:char('"'),
482 second:opt(escaped(is_not("\\\""), '\\', anychar)),
483 third:char('"'),
484 )(i)?;
485 Ok((i, s.unwrap_or_default()))
486}
487
488fn str_lit(i: &str) -> Result<(&str, StrLit<'_>), ParseErr<'_>> {
489 let (i: &str, (prefix: Option, content: &str)) =
490 tuple((opt(alt((char('b'), char('c')))), str_lit_without_prefix))(i)?;
491 let prefix: Option = match prefix {
492 Some('b') => Some(StrPrefix::Binary),
493 Some('c') => Some(StrPrefix::CLike),
494 _ => None,
495 };
496 Ok((i, StrLit { prefix, content }))
497}
498
499#[derive(Clone, Copy, Debug, PartialEq)]
500pub enum CharPrefix {
501 Binary,
502}
503
504#[derive(Clone, Debug, PartialEq)]
505pub struct CharLit<'a> {
506 pub prefix: Option<CharPrefix>,
507 pub content: &'a str,
508}
509
510// Information about allowed character escapes is available at:
511// <https://doc.rust-lang.org/reference/tokens.html#character-literals>.
512fn char_lit(i: &str) -> Result<(&str, CharLit<'_>), ParseErr<'_>> {
513 let start = i;
514 let (i, (b_prefix, s)) = tuple((
515 opt(char('b')),
516 delimited(
517 char('\''),
518 opt(escaped(is_not("\\\'"), '\\', anychar)),
519 char('\''),
520 ),
521 ))(i)?;
522
523 let Some(s) = s else {
524 return Err(nom::Err::Failure(ErrorContext::new(
525 "empty character literal",
526 start,
527 )));
528 };
529 let Ok(("", c)) = Char::parse(s) else {
530 return Err(nom::Err::Failure(ErrorContext::new(
531 "invalid character",
532 start,
533 )));
534 };
535
536 let (nb, max_value, err1, err2) = match c {
537 Char::Literal | Char::Escaped => {
538 return Ok((i, CharLit {
539 prefix: b_prefix.map(|_| CharPrefix::Binary),
540 content: s,
541 }));
542 }
543 Char::AsciiEscape(nb) => (
544 nb,
545 // `0x7F` is the maximum value for a `\x` escaped character.
546 0x7F,
547 "invalid character in ascii escape",
548 "must be a character in the range [\\x00-\\x7f]",
549 ),
550 Char::UnicodeEscape(nb) => (
551 nb,
552 // `0x10FFFF` is the maximum value for a `\u` escaped character.
553 0x0010_FFFF,
554 "invalid character in unicode escape",
555 "unicode escape must be at most 10FFFF",
556 ),
557 };
558
559 let Ok(nb) = u32::from_str_radix(nb, 16) else {
560 return Err(nom::Err::Failure(ErrorContext::new(err1, start)));
561 };
562 if nb > max_value {
563 return Err(nom::Err::Failure(ErrorContext::new(err2, start)));
564 }
565
566 Ok((i, CharLit {
567 prefix: b_prefix.map(|_| CharPrefix::Binary),
568 content: s,
569 }))
570}
571
572/// Represents the different kinds of char declarations:
573enum Char<'a> {
574 /// Any character that is not escaped.
575 Literal,
576 /// An escaped character (like `\n`) which doesn't require any extra check.
577 Escaped,
578 /// Ascii escape (like `\x12`).
579 AsciiEscape(&'a str),
580 /// Unicode escape (like `\u{12}`).
581 UnicodeEscape(&'a str),
582}
583
584impl<'a> Char<'a> {
585 fn parse(i: &'a str) -> ParseResult<'a, Self> {
586 if i.chars().count() == 1 {
587 return Ok(("", Self::Literal));
588 }
589 map(
590 tuple((
591 char('\\'),
592 alt((
593 map(char('n'), |_| Self::Escaped),
594 map(char('r'), |_| Self::Escaped),
595 map(char('t'), |_| Self::Escaped),
596 map(char('\\'), |_| Self::Escaped),
597 map(char('0'), |_| Self::Escaped),
598 map(char('\''), |_| Self::Escaped),
599 // Not useful but supported by rust.
600 map(char('"'), |_| Self::Escaped),
601 map(
602 tuple((
603 char('x'),
604 take_while_m_n(2, 2, |c: char| c.is_ascii_hexdigit()),
605 )),
606 |(_, s)| Self::AsciiEscape(s),
607 ),
608 map(
609 tuple((
610 tag("u{"),
611 take_while_m_n(1, 6, |c: char| c.is_ascii_hexdigit()),
612 char('}'),
613 )),
614 |(_, s, _)| Self::UnicodeEscape(s),
615 ),
616 )),
617 )),
618 |(_, ch)| ch,
619 )(i)
620 }
621}
622
623enum PathOrIdentifier<'a> {
624 Path(Vec<&'a str>),
625 Identifier(&'a str),
626}
627
628fn path_or_identifier(i: &str) -> ParseResult<'_, PathOrIdentifier<'_>> {
629 let root = ws(opt(tag("::")));
630 let tail = opt(many1(preceded(ws(tag("::")), identifier)));
631
632 let (i, (root, start, rest)) = tuple((root, identifier, tail))(i)?;
633 let rest = rest.as_deref().unwrap_or_default();
634
635 // The returned identifier can be assumed to be path if:
636 // - it is an absolute path (starts with `::`), or
637 // - it has multiple components (at least one `::`), or
638 // - the first letter is uppercase
639 match (root, start, rest) {
640 (Some(_), start, tail) => {
641 let mut path = Vec::with_capacity(2 + tail.len());
642 path.push("");
643 path.push(start);
644 path.extend(rest);
645 Ok((i, PathOrIdentifier::Path(path)))
646 }
647 (None, name, []) if name.chars().next().map_or(true, char::is_lowercase) => {
648 Ok((i, PathOrIdentifier::Identifier(name)))
649 }
650 (None, start, tail) => {
651 let mut path = Vec::with_capacity(1 + tail.len());
652 path.push(start);
653 path.extend(rest);
654 Ok((i, PathOrIdentifier::Path(path)))
655 }
656 }
657}
658
659struct State<'a> {
660 syntax: &'a Syntax<'a>,
661 loop_depth: Cell<usize>,
662 level: Cell<Level>,
663}
664
665impl<'a> State<'a> {
666 fn new(syntax: &'a Syntax<'a>) -> State<'a> {
667 State {
668 syntax,
669 loop_depth: Cell::new(0),
670 level: Cell::new(Level::default()),
671 }
672 }
673
674 fn nest<'b, T, F: FnOnce(&'b str) -> ParseResult<'b, T>>(
675 &self,
676 i: &'b str,
677 callback: F,
678 ) -> ParseResult<'b, T> {
679 let prev_level = self.level.get();
680 let (_, level) = prev_level.nest(i)?;
681 self.level.set(level);
682 let ret = callback(i);
683 self.level.set(prev_level);
684 ret
685 }
686
687 fn tag_block_start<'i>(&self, i: &'i str) -> ParseResult<'i> {
688 tag(self.syntax.block_start)(i)
689 }
690
691 fn tag_block_end<'i>(&self, i: &'i str) -> ParseResult<'i> {
692 tag(self.syntax.block_end)(i)
693 }
694
695 fn tag_comment_start<'i>(&self, i: &'i str) -> ParseResult<'i> {
696 tag(self.syntax.comment_start)(i)
697 }
698
699 fn tag_comment_end<'i>(&self, i: &'i str) -> ParseResult<'i> {
700 tag(self.syntax.comment_end)(i)
701 }
702
703 fn tag_expr_start<'i>(&self, i: &'i str) -> ParseResult<'i> {
704 tag(self.syntax.expr_start)(i)
705 }
706
707 fn tag_expr_end<'i>(&self, i: &'i str) -> ParseResult<'i> {
708 tag(self.syntax.expr_end)(i)
709 }
710
711 fn enter_loop(&self) {
712 self.loop_depth.set(self.loop_depth.get() + 1);
713 }
714
715 fn leave_loop(&self) {
716 self.loop_depth.set(self.loop_depth.get() - 1);
717 }
718
719 fn is_in_loop(&self) -> bool {
720 self.loop_depth.get() > 0
721 }
722}
723
724#[derive(Default, Hash, PartialEq, Clone, Copy)]
725pub struct Syntax<'a>(InnerSyntax<'a>);
726
727// This abstraction ensures that the fields are readable, but not writable.
728#[derive(Hash, PartialEq, Clone, Copy)]
729pub struct InnerSyntax<'a> {
730 pub block_start: &'a str,
731 pub block_end: &'a str,
732 pub expr_start: &'a str,
733 pub expr_end: &'a str,
734 pub comment_start: &'a str,
735 pub comment_end: &'a str,
736}
737
738impl<'a> Deref for Syntax<'a> {
739 type Target = InnerSyntax<'a>;
740
741 #[inline]
742 fn deref(&self) -> &Self::Target {
743 &self.0
744 }
745}
746
747impl Default for InnerSyntax<'static> {
748 fn default() -> Self {
749 Self {
750 block_start: "{%",
751 block_end: "%}",
752 expr_start: "{{",
753 expr_end: "}}",
754 comment_start: "{#",
755 comment_end: "#}",
756 }
757 }
758}
759
760impl<'a> fmt::Debug for Syntax<'a> {
761 #[inline]
762 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
763 fmt_syntax(name:"Syntax", self, f)
764 }
765}
766
767impl<'a> fmt::Debug for InnerSyntax<'a> {
768 #[inline]
769 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
770 fmt_syntax(name:"InnerSyntax", self, f)
771 }
772}
773
774fn fmt_syntax(name: &str, inner: &InnerSyntax<'_>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
775 f&mut DebugStruct<'_, '_>.debug_struct(name)
776 .field("block_start", &inner.block_start)
777 .field("block_end", &inner.block_end)
778 .field("expr_start", &inner.expr_start)
779 .field("expr_end", &inner.expr_end)
780 .field("comment_start", &inner.comment_start)
781 .field(name:"comment_end", &inner.comment_end)
782 .finish()
783}
784
785#[derive(Debug, Default, Clone, Copy, Hash, PartialEq)]
786#[cfg_attr(feature = "config", derive(serde::Deserialize))]
787pub struct SyntaxBuilder<'a> {
788 pub name: &'a str,
789 pub block_start: Option<&'a str>,
790 pub block_end: Option<&'a str>,
791 pub expr_start: Option<&'a str>,
792 pub expr_end: Option<&'a str>,
793 pub comment_start: Option<&'a str>,
794 pub comment_end: Option<&'a str>,
795}
796
797impl<'a> SyntaxBuilder<'a> {
798 pub fn to_syntax(&self) -> Result<Syntax<'a>, String> {
799 let default = InnerSyntax::default();
800 let syntax = Syntax(InnerSyntax {
801 block_start: self.block_start.unwrap_or(default.block_start),
802 block_end: self.block_end.unwrap_or(default.block_end),
803 expr_start: self.expr_start.unwrap_or(default.expr_start),
804 expr_end: self.expr_end.unwrap_or(default.expr_end),
805 comment_start: self.comment_start.unwrap_or(default.comment_start),
806 comment_end: self.comment_end.unwrap_or(default.comment_end),
807 });
808
809 for (s, k, is_closing) in [
810 (syntax.block_start, "opening block", false),
811 (syntax.block_end, "closing block", true),
812 (syntax.expr_start, "opening expression", false),
813 (syntax.expr_end, "closing expression", true),
814 (syntax.comment_start, "opening comment", false),
815 (syntax.comment_end, "closing comment", true),
816 ] {
817 if s.len() < 2 {
818 return Err(format!(
819 "delimiters must be at least two characters long. \
820 The {k} delimiter ({s:?}) is too short",
821 ));
822 } else if s.chars().any(char::is_whitespace) {
823 return Err(format!(
824 "delimiters may not contain white spaces. \
825 The {k} delimiter ({s:?}) contains white spaces",
826 ));
827 } else if is_closing
828 && ['(', '-', '+', '~', '.', '>', '<', '&', '|', '!']
829 .contains(&s.chars().next().unwrap())
830 {
831 return Err(format!(
832 "closing delimiters may not start with operators. \
833 The {k} delimiter ({s:?}) starts with operator `{}`",
834 s.chars().next().unwrap(),
835 ));
836 }
837 }
838
839 for ((s1, k1), (s2, k2)) in [
840 (
841 (syntax.block_start, "block"),
842 (syntax.expr_start, "expression"),
843 ),
844 (
845 (syntax.block_start, "block"),
846 (syntax.comment_start, "comment"),
847 ),
848 (
849 (syntax.expr_start, "expression"),
850 (syntax.comment_start, "comment"),
851 ),
852 ] {
853 if s1.starts_with(s2) || s2.starts_with(s1) {
854 let (s1, k1, s2, k2) = match s1.len() < s2.len() {
855 true => (s1, k1, s2, k2),
856 false => (s2, k2, s1, k1),
857 };
858 return Err(format!(
859 "an opening delimiter may not be the prefix of another delimiter. \
860 The {k1} delimiter ({s1:?}) clashes with the {k2} delimiter ({s2:?})",
861 ));
862 }
863 }
864
865 Ok(syntax)
866 }
867}
868
869#[derive(Clone, Copy, Default)]
870pub(crate) struct Level(u8);
871
872impl Level {
873 fn nest(self, i: &str) -> ParseResult<'_, Level> {
874 if self.0 >= Self::MAX_DEPTH {
875 return Err(nom::Err::Failure(ErrorContext::new(
876 message:"your template code is too deeply nested, or last expression is too complex",
877 input:i,
878 )));
879 }
880
881 Ok((i, Level(self.0 + 1)))
882 }
883
884 const MAX_DEPTH: u8 = 128;
885}
886
887fn filter<'a>(
888 i: &'a str,
889 level: &mut Level,
890) -> ParseResult<'a, (&'a str, Option<Vec<WithSpan<'a, Expr<'a>>>>)> {
891 let (j: &'a str, _) = take_till(cond:not_ws)(i)?;
892 let had_spaces: bool = i.len() != j.len();
893 let (j: &'a str, _) = pair(first:char('|'), second:not(parser:char('|')))(j)?;
894
895 if !had_spaces {
896 *level = level.nest(i)?.1;
897 cut(parser:pair(
898 first:ws(identifier),
899 second:opt(|i: &str| Expr::arguments(i, *level, is_template_macro:false)),
900 ))(j)
901 } else {
902 Err(nom::Err::Failure(ErrorContext::new(
903 message:"the filter operator `|` must not be preceded by any whitespace characters\n\
904 the binary OR operator is called `bitor` in rinja",
905 input:i,
906 )))
907 }
908}
909
910/// Returns the common parts of two paths.
911///
912/// The goal of this function is to reduce the path length based on the `base` argument
913/// (generally the path where the program is running into). For example:
914///
915/// ```text
916/// current dir: /a/b/c
917/// path: /a/b/c/d/e.txt
918/// ```
919///
920/// `strip_common` will return `d/e.txt`.
921#[must_use]
922pub fn strip_common(base: &Path, path: &Path) -> String {
923 let path = match path.canonicalize() {
924 Ok(path) => path,
925 Err(_) => return path.display().to_string(),
926 };
927 let mut components_iter = path.components().peekable();
928
929 for current_path_component in base.components() {
930 let Some(path_component) = components_iter.peek() else {
931 return path.display().to_string();
932 };
933 if current_path_component != *path_component {
934 break;
935 }
936 components_iter.next();
937 }
938 let path_parts = components_iter
939 .map(|c| c.as_os_str().to_string_lossy())
940 .collect::<Vec<_>>();
941 if path_parts.is_empty() {
942 path.display().to_string()
943 } else {
944 path_parts.join(std::path::MAIN_SEPARATOR_STR)
945 }
946}
947
948#[derive(Debug, Clone, Copy, PartialEq, Eq)]
949pub enum IntKind {
950 I8,
951 I16,
952 I32,
953 I64,
954 I128,
955 Isize,
956 U8,
957 U16,
958 U32,
959 U64,
960 U128,
961 Usize,
962}
963
964#[derive(Debug, Clone, Copy, PartialEq, Eq)]
965pub enum FloatKind {
966 F16,
967 F32,
968 F64,
969 F128,
970}
971
972#[derive(Debug, Clone, Copy, PartialEq, Eq)]
973enum NumKind {
974 Int(IntKind),
975 Float(FloatKind),
976}
977
978/// Primitive integer types. Also used as number suffixes.
979const INTEGER_TYPES: &[(&str, IntKind)] = &[
980 ("i8", IntKind::I8),
981 ("i16", IntKind::I16),
982 ("i32", IntKind::I32),
983 ("i64", IntKind::I64),
984 ("i128", IntKind::I128),
985 ("isize", IntKind::Isize),
986 ("u8", IntKind::U8),
987 ("u16", IntKind::U16),
988 ("u32", IntKind::U32),
989 ("u64", IntKind::U64),
990 ("u128", IntKind::U128),
991 ("usize", IntKind::Usize),
992];
993
994/// Primitive floating point types. Also used as number suffixes.
995const FLOAT_TYPES: &[(&str, FloatKind)] = &[
996 ("f16", FloatKind::F16),
997 ("f32", FloatKind::F32),
998 ("f64", FloatKind::F64),
999 ("f128", FloatKind::F128),
1000];
1001
1002/// Primitive numeric types. Also used as number suffixes.
1003const NUM_TYPES: &[(&str, NumKind)] = &{
1004 let mut list: [(&'static str, NumKind); 16] = [("", NumKind::Int(IntKind::I8)); INTEGER_TYPES.len() + FLOAT_TYPES.len()];
1005 let mut i: usize = 0;
1006 let mut o: usize = 0;
1007 while i < INTEGER_TYPES.len() {
1008 let (name: &str, value: IntKind) = INTEGER_TYPES[i];
1009 list[o] = (name, NumKind::Int(value));
1010 i += 1;
1011 o += 1;
1012 }
1013 let mut i: usize = 0;
1014 while i < FLOAT_TYPES.len() {
1015 let (name: &str, value: FloatKind) = FLOAT_TYPES[i];
1016 list[o] = (name, NumKind::Float(value));
1017 i += 1;
1018 o += 1;
1019 }
1020 list
1021};
1022
1023/// Complete list of named primitive types.
1024const PRIMITIVE_TYPES: &[&str] = &{
1025 let mut list: [&'static str; 17] = [""; NUM_TYPES.len() + 1];
1026 let mut i: usize = 0;
1027 let mut o: usize = 0;
1028 while i < NUM_TYPES.len() {
1029 list[o] = NUM_TYPES[i].0;
1030 i += 1;
1031 o += 1;
1032 }
1033 list[o] = "bool";
1034 list
1035};
1036
1037#[cfg(not(windows))]
1038#[cfg(test)]
1039mod test {
1040 use std::path::Path;
1041
1042 use super::*;
1043
1044 #[test]
1045 fn test_strip_common() {
1046 // Full path is returned instead of empty when the entire path is in common.
1047 assert_eq!(strip_common(Path::new("home"), Path::new("home")), "home");
1048
1049 let cwd = std::env::current_dir().expect("current_dir failed");
1050
1051 // We need actual existing paths for `canonicalize` to work, so let's do that.
1052 let entry = cwd
1053 .read_dir()
1054 .expect("read_dir failed")
1055 .filter_map(std::result::Result::ok)
1056 .find(|f| f.path().is_file())
1057 .expect("no entry");
1058
1059 // Since they have the complete path in common except for the folder entry name, it should
1060 // return only the folder entry name.
1061 assert_eq!(
1062 strip_common(&cwd, &entry.path()),
1063 entry.file_name().to_string_lossy()
1064 );
1065
1066 // In this case it cannot canonicalize `/a/b/c` so it returns the path as is.
1067 assert_eq!(strip_common(&cwd, Path::new("/a/b/c")), "/a/b/c");
1068 }
1069
1070 #[test]
1071 fn test_num_lit() {
1072 // Should fail.
1073 assert!(num_lit(".").is_err());
1074 // Should succeed.
1075 assert_eq!(
1076 num_lit("1.2E-02").unwrap(),
1077 ("", Num::Float("1.2E-02", None))
1078 );
1079 assert_eq!(num_lit("4e3").unwrap(), ("", Num::Float("4e3", None)),);
1080 assert_eq!(num_lit("4e+_3").unwrap(), ("", Num::Float("4e+_3", None)),);
1081 // Not supported because Rust wants a number before the `.`.
1082 assert!(num_lit(".1").is_err());
1083 assert!(num_lit(".1E-02").is_err());
1084 // A `_` directly after the `.` denotes a field.
1085 assert_eq!(num_lit("1._0").unwrap(), ("._0", Num::Int("1", None)));
1086 assert_eq!(num_lit("1_.0").unwrap(), ("", Num::Float("1_.0", None)));
1087 // Not supported (voluntarily because of `1..` syntax).
1088 assert_eq!(num_lit("1.").unwrap(), (".", Num::Int("1", None)));
1089 assert_eq!(num_lit("1_.").unwrap(), (".", Num::Int("1_", None)));
1090 assert_eq!(num_lit("1_2.").unwrap(), (".", Num::Int("1_2", None)));
1091 // Numbers with suffixes
1092 assert_eq!(
1093 num_lit("-1usize").unwrap(),
1094 ("", Num::Int("-1", Some(IntKind::Usize)))
1095 );
1096 assert_eq!(
1097 num_lit("123_f32").unwrap(),
1098 ("", Num::Float("123_", Some(FloatKind::F32)))
1099 );
1100 assert_eq!(
1101 num_lit("1_.2_e+_3_f64|into_isize").unwrap(),
1102 (
1103 "|into_isize",
1104 Num::Float("1_.2_e+_3_", Some(FloatKind::F64))
1105 )
1106 );
1107 assert_eq!(
1108 num_lit("4e3f128").unwrap(),
1109 ("", Num::Float("4e3", Some(FloatKind::F128))),
1110 );
1111 }
1112
1113 #[test]
1114 fn test_char_lit() {
1115 let lit = |s: &'static str| crate::CharLit {
1116 prefix: None,
1117 content: s,
1118 };
1119
1120 assert_eq!(char_lit("'a'").unwrap(), ("", lit("a")));
1121 assert_eq!(char_lit("'字'").unwrap(), ("", lit("字")));
1122
1123 // Escaped single characters.
1124 assert_eq!(char_lit("'\\\"'").unwrap(), ("", lit("\\\"")));
1125 assert_eq!(char_lit("'\\''").unwrap(), ("", lit("\\'")));
1126 assert_eq!(char_lit("'\\t'").unwrap(), ("", lit("\\t")));
1127 assert_eq!(char_lit("'\\n'").unwrap(), ("", lit("\\n")));
1128 assert_eq!(char_lit("'\\r'").unwrap(), ("", lit("\\r")));
1129 assert_eq!(char_lit("'\\0'").unwrap(), ("", lit("\\0")));
1130 // Escaped ascii characters (up to `0x7F`).
1131 assert_eq!(char_lit("'\\x12'").unwrap(), ("", lit("\\x12")));
1132 assert_eq!(char_lit("'\\x02'").unwrap(), ("", lit("\\x02")));
1133 assert_eq!(char_lit("'\\x6a'").unwrap(), ("", lit("\\x6a")));
1134 assert_eq!(char_lit("'\\x7F'").unwrap(), ("", lit("\\x7F")));
1135 // Escaped unicode characters (up to `0x10FFFF`).
1136 assert_eq!(char_lit("'\\u{A}'").unwrap(), ("", lit("\\u{A}")));
1137 assert_eq!(char_lit("'\\u{10}'").unwrap(), ("", lit("\\u{10}")));
1138 assert_eq!(char_lit("'\\u{aa}'").unwrap(), ("", lit("\\u{aa}")));
1139 assert_eq!(char_lit("'\\u{10FFFF}'").unwrap(), ("", lit("\\u{10FFFF}")));
1140
1141 // Check with `b` prefix.
1142 assert_eq!(
1143 char_lit("b'a'").unwrap(),
1144 ("", crate::CharLit {
1145 prefix: Some(crate::CharPrefix::Binary),
1146 content: "a"
1147 })
1148 );
1149
1150 // Should fail.
1151 assert!(char_lit("''").is_err());
1152 assert!(char_lit("'\\o'").is_err());
1153 assert!(char_lit("'\\x'").is_err());
1154 assert!(char_lit("'\\x1'").is_err());
1155 assert!(char_lit("'\\x80'").is_err());
1156 assert!(char_lit("'\\u'").is_err());
1157 assert!(char_lit("'\\u{}'").is_err());
1158 assert!(char_lit("'\\u{110000}'").is_err());
1159 }
1160
1161 #[test]
1162 fn test_str_lit() {
1163 assert_eq!(
1164 str_lit(r#"b"hello""#).unwrap(),
1165 ("", StrLit {
1166 prefix: Some(StrPrefix::Binary),
1167 content: "hello"
1168 })
1169 );
1170 assert_eq!(
1171 str_lit(r#"c"hello""#).unwrap(),
1172 ("", StrLit {
1173 prefix: Some(StrPrefix::CLike),
1174 content: "hello"
1175 })
1176 );
1177 assert!(str_lit(r#"d"hello""#).is_err());
1178 }
1179}
1180