1 | use std::str; |
2 | |
3 | use nom::branch::alt; |
4 | use nom::bytes::complete::{tag, take_till}; |
5 | use nom::character::complete::{anychar, char}; |
6 | use nom::combinator::{ |
7 | complete, consumed, cut, eof, fail, map, map_opt, not, opt, peek, recognize, value, |
8 | }; |
9 | use nom::multi::{many0, separated_list0, separated_list1}; |
10 | use nom::sequence::{delimited, pair, preceded, tuple}; |
11 | |
12 | use crate::memchr_splitter::{Splitter1, Splitter2, Splitter3}; |
13 | use crate::{ |
14 | ErrorContext, Expr, Filter, ParseResult, State, Target, WithSpan, filter, identifier, is_ws, |
15 | keyword, not_ws, skip_till, str_lit_without_prefix, ws, |
16 | }; |
17 | |
18 | #[derive (Debug, PartialEq)] |
19 | pub enum Node<'a> { |
20 | Lit(WithSpan<'a, Lit<'a>>), |
21 | Comment(WithSpan<'a, Comment<'a>>), |
22 | Expr(Ws, WithSpan<'a, Expr<'a>>), |
23 | Call(WithSpan<'a, Call<'a>>), |
24 | Let(WithSpan<'a, Let<'a>>), |
25 | If(WithSpan<'a, If<'a>>), |
26 | Match(WithSpan<'a, Match<'a>>), |
27 | Loop(Box<WithSpan<'a, Loop<'a>>>), |
28 | Extends(WithSpan<'a, Extends<'a>>), |
29 | BlockDef(WithSpan<'a, BlockDef<'a>>), |
30 | Include(WithSpan<'a, Include<'a>>), |
31 | Import(WithSpan<'a, Import<'a>>), |
32 | Macro(WithSpan<'a, Macro<'a>>), |
33 | Raw(WithSpan<'a, Raw<'a>>), |
34 | Break(WithSpan<'a, Ws>), |
35 | Continue(WithSpan<'a, Ws>), |
36 | FilterBlock(WithSpan<'a, FilterBlock<'a>>), |
37 | } |
38 | |
39 | impl<'a> Node<'a> { |
40 | pub(super) fn parse_template(i: &'a str, s: &State<'_>) -> ParseResult<'a, Vec<Self>> { |
41 | let (i, result) = match complete(|i| Self::many(i, s))(i) { |
42 | Ok((i, result)) => (i, result), |
43 | Err(err) => { |
44 | if let nom::Err::Error(err) | nom::Err::Failure(err) = &err { |
45 | if err.message.is_none() { |
46 | opt(|i| unexpected_tag(i, s))(err.input)?; |
47 | } |
48 | } |
49 | return Err(err); |
50 | } |
51 | }; |
52 | let (i, _) = opt(|i| unexpected_tag(i, s))(i)?; |
53 | let (i, is_eof) = opt(eof)(i)?; |
54 | if is_eof.is_none() { |
55 | return Err(nom::Err::Failure(ErrorContext::new( |
56 | "cannot parse entire template \n\ |
57 | you should never encounter this error \n\ |
58 | please report this error to <https://github.com/rinja-rs/rinja/issues>" , |
59 | i, |
60 | ))); |
61 | } |
62 | Ok((i, result)) |
63 | } |
64 | |
65 | fn many(i: &'a str, s: &State<'_>) -> ParseResult<'a, Vec<Self>> { |
66 | many0(alt(( |
67 | map(|i| Lit::parse(i, s), Self::Lit), |
68 | map(|i| Comment::parse(i, s), Self::Comment), |
69 | |i| Self::expr(i, s), |
70 | |i| Self::parse(i, s), |
71 | )))(i) |
72 | } |
73 | |
74 | fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
75 | #[inline ] |
76 | fn wrap<'a, T>( |
77 | func: impl FnOnce(T) -> Node<'a>, |
78 | result: ParseResult<'a, T>, |
79 | ) -> ParseResult<'a, Node<'a>> { |
80 | result.map(|(i, n)| (i, func(n))) |
81 | } |
82 | |
83 | let start = i; |
84 | let (j, tag) = preceded( |
85 | |i| s.tag_block_start(i), |
86 | peek(preceded( |
87 | pair(opt(Whitespace::parse), take_till(not_ws)), |
88 | identifier, |
89 | )), |
90 | )(i)?; |
91 | |
92 | let func = match tag { |
93 | "call" => |i, s| wrap(Self::Call, Call::parse(i, s)), |
94 | "let" | "set" => |i, s| wrap(Self::Let, Let::parse(i, s)), |
95 | "if" => |i, s| wrap(Self::If, If::parse(i, s)), |
96 | "for" => |i, s| wrap(|n| Self::Loop(Box::new(n)), Loop::parse(i, s)), |
97 | "match" => |i, s| wrap(Self::Match, Match::parse(i, s)), |
98 | "extends" => |i, _s| wrap(Self::Extends, Extends::parse(i)), |
99 | "include" => |i, _s| wrap(Self::Include, Include::parse(i)), |
100 | "import" => |i, _s| wrap(Self::Import, Import::parse(i)), |
101 | "block" => |i, s| wrap(Self::BlockDef, BlockDef::parse(i, s)), |
102 | "macro" => |i, s| wrap(Self::Macro, Macro::parse(i, s)), |
103 | "raw" => |i, s| wrap(Self::Raw, Raw::parse(i, s)), |
104 | "break" => |i, s| Self::r#break(i, s), |
105 | "continue" => |i, s| Self::r#continue(i, s), |
106 | "filter" => |i, s| wrap(Self::FilterBlock, FilterBlock::parse(i, s)), |
107 | _ => return fail(start), |
108 | }; |
109 | |
110 | let (i, node) = s.nest(j, |i| func(i, s))?; |
111 | |
112 | let (i, closed) = cut_node( |
113 | None, |
114 | alt((value(true, |i| s.tag_block_end(i)), value(false, ws(eof)))), |
115 | )(i)?; |
116 | match closed { |
117 | true => Ok((i, node)), |
118 | false => Err(ErrorContext::unclosed("block" , s.syntax.block_end, start).into()), |
119 | } |
120 | } |
121 | |
122 | fn r#break(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
123 | let mut p = tuple(( |
124 | opt(Whitespace::parse), |
125 | ws(keyword("break" )), |
126 | opt(Whitespace::parse), |
127 | )); |
128 | let (j, (pws, _, nws)) = p(i)?; |
129 | if !s.is_in_loop() { |
130 | return Err(nom::Err::Failure(ErrorContext::new( |
131 | "you can only `break` inside a `for` loop" , |
132 | i, |
133 | ))); |
134 | } |
135 | Ok((j, Self::Break(WithSpan::new(Ws(pws, nws), i)))) |
136 | } |
137 | |
138 | fn r#continue(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
139 | let mut p = tuple(( |
140 | opt(Whitespace::parse), |
141 | ws(keyword("continue" )), |
142 | opt(Whitespace::parse), |
143 | )); |
144 | let (j, (pws, _, nws)) = p(i)?; |
145 | if !s.is_in_loop() { |
146 | return Err(nom::Err::Failure(ErrorContext::new( |
147 | "you can only `continue` inside a `for` loop" , |
148 | i, |
149 | ))); |
150 | } |
151 | Ok((j, Self::Continue(WithSpan::new(Ws(pws, nws), i)))) |
152 | } |
153 | |
154 | fn expr(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
155 | let start = i; |
156 | let (i, (pws, expr)) = preceded( |
157 | |i| s.tag_expr_start(i), |
158 | cut_node( |
159 | None, |
160 | pair( |
161 | opt(Whitespace::parse), |
162 | ws(|i| Expr::parse(i, s.level.get())), |
163 | ), |
164 | ), |
165 | )(i)?; |
166 | |
167 | let (i, (nws, closed)) = cut_node( |
168 | None, |
169 | pair( |
170 | opt(Whitespace::parse), |
171 | alt((value(true, |i| s.tag_expr_end(i)), value(false, ws(eof)))), |
172 | ), |
173 | )(i)?; |
174 | match closed { |
175 | true => Ok((i, Self::Expr(Ws(pws, nws), expr))), |
176 | false => Err(ErrorContext::unclosed("expression" , s.syntax.expr_end, start).into()), |
177 | } |
178 | } |
179 | |
180 | #[must_use ] |
181 | pub fn span(&self) -> &str { |
182 | match self { |
183 | Self::Lit(span) => span.span, |
184 | Self::Comment(span) => span.span, |
185 | Self::Expr(_, span) => span.span, |
186 | Self::Call(span) => span.span, |
187 | Self::Let(span) => span.span, |
188 | Self::If(span) => span.span, |
189 | Self::Match(span) => span.span, |
190 | Self::Loop(span) => span.span, |
191 | Self::Extends(span) => span.span, |
192 | Self::BlockDef(span) => span.span, |
193 | Self::Include(span) => span.span, |
194 | Self::Import(span) => span.span, |
195 | Self::Macro(span) => span.span, |
196 | Self::Raw(span) => span.span, |
197 | Self::Break(span) => span.span, |
198 | Self::Continue(span) => span.span, |
199 | Self::FilterBlock(span) => span.span, |
200 | } |
201 | } |
202 | } |
203 | |
204 | fn cut_node<'a, O>( |
205 | kind: Option<&'static str>, |
206 | inner: impl FnMut(&'a str) -> ParseResult<'a, O>, |
207 | ) -> impl FnMut(&'a str) -> ParseResult<'a, O> { |
208 | let mut inner: impl FnMut(&'a str) -> Result<…, …> = cut(parser:inner); |
209 | move |i: &'a str| { |
210 | let result: Result<(&'a str, O), Err>> = inner(i); |
211 | if let Err(nom::Err::Failure(err: &ErrorContext<'a>) | nom::Err::Error(err: &ErrorContext<'a>)) = &result { |
212 | if err.message.is_none() { |
213 | opt(|i: &str| unexpected_raw_tag(kind, i))(err.input)?; |
214 | } |
215 | } |
216 | result |
217 | } |
218 | } |
219 | |
220 | fn unexpected_tag<'a>(i: &'a str, s: &State<'_>) -> ParseResult<'a, ()> { |
221 | value( |
222 | (), |
223 | parser:tuple(( |
224 | |i: &str| s.tag_block_start(i), |
225 | opt(Whitespace::parse), |
226 | |i: &str| unexpected_raw_tag(kind:None, i), |
227 | )), |
228 | )(i) |
229 | } |
230 | |
231 | fn unexpected_raw_tag<'a>(kind: Option<&'static str>, i: &'a str) -> ParseResult<'a, ()> { |
232 | let (_, tag: &str) = ws(inner:identifier)(i)?; |
233 | let msg: String = match tag { |
234 | "end" | "elif" | "else" | "when" => match kind { |
235 | Some(kind: &'static str) => { |
236 | format!("node ` {tag}` was not expected in the current context: ` {kind}` block" ) |
237 | } |
238 | None => format!("node ` {tag}` was not expected in the current context" ), |
239 | }, |
240 | tag: &str if tag.starts_with("end" ) => format!("unexpected closing tag ` {tag}`" ), |
241 | tag: &str => format!("unknown node ` {tag}`" ), |
242 | }; |
243 | Err(nom::Err::Failure(ErrorContext::new(message:msg, input:i))) |
244 | } |
245 | |
246 | #[derive (Debug, PartialEq)] |
247 | pub struct When<'a> { |
248 | pub ws: Ws, |
249 | pub target: Vec<Target<'a>>, |
250 | pub nodes: Vec<Node<'a>>, |
251 | } |
252 | |
253 | impl<'a> When<'a> { |
254 | fn r#else(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { |
255 | let start = i; |
256 | let mut p = tuple(( |
257 | |i| s.tag_block_start(i), |
258 | opt(Whitespace::parse), |
259 | ws(keyword("else" )), |
260 | cut_node( |
261 | Some("match-else" ), |
262 | tuple(( |
263 | opt(Whitespace::parse), |
264 | |i| s.tag_block_end(i), |
265 | cut_node(Some("match-else" ), |i| Node::many(i, s)), |
266 | )), |
267 | ), |
268 | )); |
269 | let (i, (_, pws, _, (nws, _, nodes))) = p(i)?; |
270 | Ok(( |
271 | i, |
272 | WithSpan::new( |
273 | Self { |
274 | ws: Ws(pws, nws), |
275 | target: vec![Target::Placeholder("_" )], |
276 | nodes, |
277 | }, |
278 | start, |
279 | ), |
280 | )) |
281 | } |
282 | |
283 | #[allow (clippy::self_named_constructors)] |
284 | fn when(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { |
285 | let start = i; |
286 | let endwhen = map( |
287 | consumed(ws(pair( |
288 | delimited( |
289 | |i| s.tag_block_start(i), |
290 | opt(Whitespace::parse), |
291 | ws(keyword("endwhen" )), |
292 | ), |
293 | cut_node( |
294 | Some("match-endwhen" ), |
295 | tuple(( |
296 | opt(Whitespace::parse), |
297 | |i| s.tag_block_end(i), |
298 | many0(value((), ws(|i| Comment::parse(i, s)))), |
299 | )), |
300 | ), |
301 | ))), |
302 | |(span, (pws, _))| { |
303 | // A comment node is used to pass the whitespace suppressing information to the |
304 | // generator. This way we don't have to fix up the next `when` node or the closing |
305 | // `endmatch`. Any whitespaces after `endwhen` are to be suppressed. Actually, they |
306 | // don't wind up in the AST anyway. |
307 | Node::Comment(WithSpan::new( |
308 | Comment { |
309 | ws: Ws(pws, Some(Whitespace::Suppress)), |
310 | content: "" , |
311 | }, |
312 | span, |
313 | )) |
314 | }, |
315 | ); |
316 | let mut p = tuple(( |
317 | |i| s.tag_block_start(i), |
318 | opt(Whitespace::parse), |
319 | ws(keyword("when" )), |
320 | cut_node( |
321 | Some("match-when" ), |
322 | tuple(( |
323 | separated_list1(char('|' ), ws(|i| Target::parse(i, s))), |
324 | opt(Whitespace::parse), |
325 | |i| s.tag_block_end(i), |
326 | cut_node(Some("match-when" ), |i| Node::many(i, s)), |
327 | opt(endwhen), |
328 | )), |
329 | ), |
330 | )); |
331 | let (i, (_, pws, _, (target, nws, _, mut nodes, endwhen))) = p(i)?; |
332 | if let Some(endwhen) = endwhen { |
333 | nodes.push(endwhen); |
334 | } |
335 | Ok(( |
336 | i, |
337 | WithSpan::new( |
338 | Self { |
339 | ws: Ws(pws, nws), |
340 | target, |
341 | nodes, |
342 | }, |
343 | start, |
344 | ), |
345 | )) |
346 | } |
347 | } |
348 | |
349 | #[derive (Debug, PartialEq)] |
350 | pub struct Cond<'a> { |
351 | pub ws: Ws, |
352 | pub cond: Option<CondTest<'a>>, |
353 | pub nodes: Vec<Node<'a>>, |
354 | } |
355 | |
356 | impl<'a> Cond<'a> { |
357 | fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { |
358 | let start = i; |
359 | let (i, (_, pws, cond, nws, _, nodes)) = tuple(( |
360 | |i| s.tag_block_start(i), |
361 | opt(Whitespace::parse), |
362 | alt(( |
363 | preceded(ws(keyword("else" )), opt(|i| CondTest::parse(i, s))), |
364 | preceded( |
365 | ws(keyword("elif" )), |
366 | cut_node(Some("if-elif" ), map(|i| CondTest::parse_cond(i, s), Some)), |
367 | ), |
368 | )), |
369 | opt(Whitespace::parse), |
370 | cut_node(Some("if" ), |i| s.tag_block_end(i)), |
371 | cut_node(Some("if" ), |i| Node::many(i, s)), |
372 | ))(i)?; |
373 | Ok(( |
374 | i, |
375 | WithSpan::new( |
376 | Self { |
377 | ws: Ws(pws, nws), |
378 | cond, |
379 | nodes, |
380 | }, |
381 | start, |
382 | ), |
383 | )) |
384 | } |
385 | } |
386 | |
387 | #[derive (Debug, PartialEq)] |
388 | pub struct CondTest<'a> { |
389 | pub target: Option<Target<'a>>, |
390 | pub expr: WithSpan<'a, Expr<'a>>, |
391 | pub contains_bool_lit_or_is_defined: bool, |
392 | } |
393 | |
394 | impl<'a> CondTest<'a> { |
395 | fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
396 | preceded( |
397 | ws(keyword("if" )), |
398 | cut_node(Some("if" ), |i| Self::parse_cond(i, s)), |
399 | )(i) |
400 | } |
401 | |
402 | fn parse_cond(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
403 | let (i, (target, expr)) = pair( |
404 | opt(delimited( |
405 | ws(alt((keyword("let" ), keyword("set" )))), |
406 | ws(|i| Target::parse(i, s)), |
407 | ws(char('=' )), |
408 | )), |
409 | ws(|i| Expr::parse(i, s.level.get())), |
410 | )(i)?; |
411 | let contains_bool_lit_or_is_defined = expr.contains_bool_lit_or_is_defined(); |
412 | Ok((i, Self { |
413 | target, |
414 | expr, |
415 | contains_bool_lit_or_is_defined, |
416 | })) |
417 | } |
418 | } |
419 | |
420 | #[derive (Clone, Copy, Debug, PartialEq)] |
421 | pub enum Whitespace { |
422 | Preserve, |
423 | Suppress, |
424 | Minimize, |
425 | } |
426 | |
427 | impl Whitespace { |
428 | fn parse(i: &str) -> ParseResult<'_, Self> { |
429 | map_opt(parser:anychar, Self::parse_char)(i) |
430 | } |
431 | |
432 | fn parse_char(c: char) -> Option<Self> { |
433 | match c { |
434 | '+' => Some(Self::Preserve), |
435 | '-' => Some(Self::Suppress), |
436 | '~' => Some(Self::Minimize), |
437 | _ => None, |
438 | } |
439 | } |
440 | } |
441 | |
442 | fn check_block_start<'a>( |
443 | i: &'a str, |
444 | start: &'a str, |
445 | s: &State<'_>, |
446 | node: &str, |
447 | expected: &str, |
448 | ) -> ParseResult<'a> { |
449 | if i.is_empty() { |
450 | return Err(nom::Err::Failure(ErrorContext::new( |
451 | message:format!("expected ` {expected}` to terminate ` {node}` node, found nothing" ), |
452 | input:start, |
453 | ))); |
454 | } |
455 | s.tag_block_start(i) |
456 | } |
457 | |
458 | #[derive (Debug, PartialEq)] |
459 | pub struct Loop<'a> { |
460 | pub ws1: Ws, |
461 | pub var: Target<'a>, |
462 | pub iter: WithSpan<'a, Expr<'a>>, |
463 | pub cond: Option<WithSpan<'a, Expr<'a>>>, |
464 | pub body: Vec<Node<'a>>, |
465 | pub ws2: Ws, |
466 | pub else_nodes: Vec<Node<'a>>, |
467 | pub ws3: Ws, |
468 | } |
469 | |
470 | impl<'a> Loop<'a> { |
471 | fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { |
472 | fn content<'a>(i: &'a str, s: &State<'_>) -> ParseResult<'a, Vec<Node<'a>>> { |
473 | s.enter_loop(); |
474 | let result = Node::many(i, s); |
475 | s.leave_loop(); |
476 | result |
477 | } |
478 | |
479 | let start = i; |
480 | let if_cond = preceded( |
481 | ws(keyword("if" )), |
482 | cut_node(Some("for-if" ), ws(|i| Expr::parse(i, s.level.get()))), |
483 | ); |
484 | |
485 | let else_block = |i| { |
486 | let mut p = preceded( |
487 | ws(keyword("else" )), |
488 | cut_node( |
489 | Some("for-else" ), |
490 | tuple(( |
491 | opt(Whitespace::parse), |
492 | delimited( |
493 | |i| s.tag_block_end(i), |
494 | |i| Node::many(i, s), |
495 | |i| s.tag_block_start(i), |
496 | ), |
497 | opt(Whitespace::parse), |
498 | )), |
499 | ), |
500 | ); |
501 | let (i, (pws, nodes, nws)) = p(i)?; |
502 | Ok((i, (pws, nodes, nws))) |
503 | }; |
504 | |
505 | let body_and_end = |i| { |
506 | let (i, (body, (_, pws, else_block, _, nws))) = cut_node( |
507 | Some("for" ), |
508 | tuple(( |
509 | |i| content(i, s), |
510 | cut_node( |
511 | Some("for" ), |
512 | tuple(( |
513 | |i| check_block_start(i, start, s, "for" , "endfor" ), |
514 | opt(Whitespace::parse), |
515 | opt(else_block), |
516 | end_node("for" , "endfor" ), |
517 | opt(Whitespace::parse), |
518 | )), |
519 | ), |
520 | )), |
521 | )(i)?; |
522 | Ok((i, (body, pws, else_block, nws))) |
523 | }; |
524 | |
525 | let mut p = tuple(( |
526 | opt(Whitespace::parse), |
527 | ws(keyword("for" )), |
528 | cut_node( |
529 | Some("for" ), |
530 | tuple(( |
531 | ws(|i| Target::parse(i, s)), |
532 | ws(keyword("in" )), |
533 | cut_node( |
534 | Some("for" ), |
535 | tuple(( |
536 | ws(|i| Expr::parse(i, s.level.get())), |
537 | opt(if_cond), |
538 | opt(Whitespace::parse), |
539 | |i| s.tag_block_end(i), |
540 | body_and_end, |
541 | )), |
542 | ), |
543 | )), |
544 | ), |
545 | )); |
546 | let (i, (pws1, _, (var, _, (iter, cond, nws1, _, (body, pws2, else_block, nws2))))) = p(i)?; |
547 | let (nws3, else_nodes, pws3) = else_block.unwrap_or_default(); |
548 | Ok(( |
549 | i, |
550 | WithSpan::new( |
551 | Self { |
552 | ws1: Ws(pws1, nws1), |
553 | var, |
554 | iter, |
555 | cond, |
556 | body, |
557 | ws2: Ws(pws2, nws3), |
558 | else_nodes, |
559 | ws3: Ws(pws3, nws2), |
560 | }, |
561 | start, |
562 | ), |
563 | )) |
564 | } |
565 | } |
566 | |
567 | #[derive (Debug, PartialEq)] |
568 | pub struct Macro<'a> { |
569 | pub ws1: Ws, |
570 | pub name: &'a str, |
571 | pub args: Vec<&'a str>, |
572 | pub nodes: Vec<Node<'a>>, |
573 | pub ws2: Ws, |
574 | } |
575 | |
576 | impl<'a> Macro<'a> { |
577 | fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { |
578 | fn parameters(i: &str) -> ParseResult<'_, Vec<&str>> { |
579 | delimited( |
580 | ws(char('(' )), |
581 | separated_list0(char(',' ), ws(identifier)), |
582 | tuple((opt(ws(char(',' ))), char(')' ))), |
583 | )(i) |
584 | } |
585 | |
586 | let start_s = i; |
587 | let mut start = tuple(( |
588 | opt(Whitespace::parse), |
589 | ws(keyword("macro" )), |
590 | cut_node( |
591 | Some("macro" ), |
592 | tuple(( |
593 | ws(identifier), |
594 | opt(ws(parameters)), |
595 | opt(Whitespace::parse), |
596 | |i| s.tag_block_end(i), |
597 | )), |
598 | ), |
599 | )); |
600 | let (j, (pws1, _, (name, params, nws1, _))) = start(i)?; |
601 | if is_rust_keyword(name) { |
602 | return Err(nom::Err::Failure(ErrorContext::new( |
603 | format!("' {name}' is not a valid name for a macro" ), |
604 | i, |
605 | ))); |
606 | } |
607 | |
608 | let mut end = cut_node( |
609 | Some("macro" ), |
610 | tuple(( |
611 | |i| Node::many(i, s), |
612 | cut_node( |
613 | Some("macro" ), |
614 | tuple(( |
615 | |i| check_block_start(i, start_s, s, "macro" , "endmacro" ), |
616 | opt(Whitespace::parse), |
617 | end_node("macro" , "endmacro" ), |
618 | cut_node( |
619 | Some("macro" ), |
620 | preceded( |
621 | opt(|before| { |
622 | let (after, end_name) = ws(identifier)(before)?; |
623 | check_end_name(before, after, name, end_name, "macro" ) |
624 | }), |
625 | opt(Whitespace::parse), |
626 | ), |
627 | ), |
628 | )), |
629 | ), |
630 | )), |
631 | ); |
632 | let (i, (contents, (_, pws2, _, nws2))) = end(j)?; |
633 | |
634 | Ok(( |
635 | i, |
636 | WithSpan::new( |
637 | Self { |
638 | ws1: Ws(pws1, nws1), |
639 | name, |
640 | args: params.unwrap_or_default(), |
641 | nodes: contents, |
642 | ws2: Ws(pws2, nws2), |
643 | }, |
644 | start_s, |
645 | ), |
646 | )) |
647 | } |
648 | } |
649 | |
650 | #[derive (Debug, PartialEq)] |
651 | pub struct FilterBlock<'a> { |
652 | pub ws1: Ws, |
653 | pub filters: Filter<'a>, |
654 | pub nodes: Vec<Node<'a>>, |
655 | pub ws2: Ws, |
656 | } |
657 | |
658 | impl<'a> FilterBlock<'a> { |
659 | fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { |
660 | let mut level = s.level.get(); |
661 | let start_s = i; |
662 | let mut start = tuple(( |
663 | opt(Whitespace::parse), |
664 | ws(keyword("filter" )), |
665 | cut_node( |
666 | Some("filter" ), |
667 | tuple(( |
668 | ws(identifier), |
669 | opt(|i| Expr::arguments(i, s.level.get(), false)), |
670 | many0(|i| { |
671 | filter(i, &mut level).map(|(j, (name, params))| (j, (name, params, i))) |
672 | }), |
673 | ws(|i| Ok((i, ()))), |
674 | opt(Whitespace::parse), |
675 | |i| s.tag_block_end(i), |
676 | )), |
677 | ), |
678 | )); |
679 | let (i, (pws1, _, (filter_name, params, extra_filters, (), nws1, _))) = start(i)?; |
680 | |
681 | let mut arguments = params.unwrap_or_default(); |
682 | arguments.insert(0, WithSpan::new(Expr::FilterSource, start_s)); |
683 | let mut filters = Filter { |
684 | name: filter_name, |
685 | arguments, |
686 | }; |
687 | for (filter_name, args, span) in extra_filters { |
688 | filters = Filter { |
689 | name: filter_name, |
690 | arguments: { |
691 | let mut args = args.unwrap_or_default(); |
692 | args.insert(0, WithSpan::new(Expr::Filter(filters), span)); |
693 | args |
694 | }, |
695 | }; |
696 | } |
697 | |
698 | let mut end = cut_node( |
699 | Some("filter" ), |
700 | tuple(( |
701 | |i| Node::many(i, s), |
702 | cut_node( |
703 | Some("filter" ), |
704 | tuple(( |
705 | |i| check_block_start(i, start_s, s, "filter" , "endfilter" ), |
706 | opt(Whitespace::parse), |
707 | end_node("filter" , "endfilter" ), |
708 | opt(Whitespace::parse), |
709 | )), |
710 | ), |
711 | )), |
712 | ); |
713 | let (i, (nodes, (_, pws2, _, nws2))) = end(i)?; |
714 | |
715 | Ok(( |
716 | i, |
717 | WithSpan::new( |
718 | Self { |
719 | ws1: Ws(pws1, nws1), |
720 | filters, |
721 | nodes, |
722 | ws2: Ws(pws2, nws2), |
723 | }, |
724 | start_s, |
725 | ), |
726 | )) |
727 | } |
728 | } |
729 | |
730 | #[derive (Debug, PartialEq)] |
731 | pub struct Import<'a> { |
732 | pub ws: Ws, |
733 | pub path: &'a str, |
734 | pub scope: &'a str, |
735 | } |
736 | |
737 | impl<'a> Import<'a> { |
738 | fn parse(i: &'a str) -> ParseResult<'a, WithSpan<'a, Self>> { |
739 | let start = i; |
740 | let mut p = tuple(( |
741 | opt(Whitespace::parse), |
742 | ws(keyword("import" )), |
743 | cut_node( |
744 | Some("import" ), |
745 | tuple(( |
746 | ws(str_lit_without_prefix), |
747 | ws(keyword("as" )), |
748 | cut_node(Some("import" ), pair(ws(identifier), opt(Whitespace::parse))), |
749 | )), |
750 | ), |
751 | )); |
752 | let (i, (pws, _, (path, _, (scope, nws)))) = p(i)?; |
753 | Ok(( |
754 | i, |
755 | WithSpan::new( |
756 | Self { |
757 | ws: Ws(pws, nws), |
758 | path, |
759 | scope, |
760 | }, |
761 | start, |
762 | ), |
763 | )) |
764 | } |
765 | } |
766 | |
767 | #[derive (Debug, PartialEq)] |
768 | pub struct Call<'a> { |
769 | pub ws: Ws, |
770 | pub scope: Option<&'a str>, |
771 | pub name: &'a str, |
772 | pub args: Vec<WithSpan<'a, Expr<'a>>>, |
773 | } |
774 | |
775 | impl<'a> Call<'a> { |
776 | fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { |
777 | let start = i; |
778 | let mut p = tuple(( |
779 | opt(Whitespace::parse), |
780 | ws(keyword("call" )), |
781 | cut_node( |
782 | Some("call" ), |
783 | tuple(( |
784 | opt(tuple((ws(identifier), ws(tag("::" ))))), |
785 | ws(identifier), |
786 | opt(ws(|nested| Expr::arguments(nested, s.level.get(), true))), |
787 | opt(Whitespace::parse), |
788 | )), |
789 | ), |
790 | )); |
791 | let (i, (pws, _, (scope, name, args, nws))) = p(i)?; |
792 | let scope = scope.map(|(scope, _)| scope); |
793 | let args = args.unwrap_or_default(); |
794 | Ok(( |
795 | i, |
796 | WithSpan::new( |
797 | Self { |
798 | ws: Ws(pws, nws), |
799 | scope, |
800 | name, |
801 | args, |
802 | }, |
803 | start, |
804 | ), |
805 | )) |
806 | } |
807 | } |
808 | |
809 | #[derive (Debug, PartialEq)] |
810 | pub struct Match<'a> { |
811 | pub ws1: Ws, |
812 | pub expr: WithSpan<'a, Expr<'a>>, |
813 | pub arms: Vec<WithSpan<'a, When<'a>>>, |
814 | pub ws2: Ws, |
815 | } |
816 | |
817 | impl<'a> Match<'a> { |
818 | fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { |
819 | let start = i; |
820 | let mut p = tuple(( |
821 | opt(Whitespace::parse), |
822 | ws(keyword("match" )), |
823 | cut_node( |
824 | Some("match" ), |
825 | tuple(( |
826 | ws(|i| Expr::parse(i, s.level.get())), |
827 | opt(Whitespace::parse), |
828 | |i| s.tag_block_end(i), |
829 | cut_node( |
830 | Some("match" ), |
831 | tuple(( |
832 | ws(many0(ws(value((), |i| Comment::parse(i, s))))), |
833 | many0(|i| When::when(i, s)), |
834 | cut_node( |
835 | Some("match" ), |
836 | tuple(( |
837 | opt(|i| When::r#else(i, s)), |
838 | cut_node( |
839 | Some("match" ), |
840 | tuple(( |
841 | ws(|i| { |
842 | check_block_start(i, start, s, "match" , "endmatch" ) |
843 | }), |
844 | opt(Whitespace::parse), |
845 | end_node("match" , "endmatch" ), |
846 | opt(Whitespace::parse), |
847 | )), |
848 | ), |
849 | )), |
850 | ), |
851 | )), |
852 | ), |
853 | )), |
854 | ), |
855 | )); |
856 | let (i, (pws1, _, (expr, nws1, _, (_, mut arms, (else_arm, (_, pws2, _, nws2)))))) = p(i)?; |
857 | |
858 | if let Some(arm) = else_arm { |
859 | arms.push(arm); |
860 | } |
861 | if arms.is_empty() { |
862 | return Err(nom::Err::Failure(ErrorContext::new( |
863 | "`match` nodes must contain at least one `when` node and/or an `else` case" , |
864 | start, |
865 | ))); |
866 | } |
867 | |
868 | Ok(( |
869 | i, |
870 | WithSpan::new( |
871 | Self { |
872 | ws1: Ws(pws1, nws1), |
873 | expr, |
874 | arms, |
875 | ws2: Ws(pws2, nws2), |
876 | }, |
877 | start, |
878 | ), |
879 | )) |
880 | } |
881 | } |
882 | |
883 | #[derive (Debug, PartialEq)] |
884 | pub struct BlockDef<'a> { |
885 | pub ws1: Ws, |
886 | pub name: &'a str, |
887 | pub nodes: Vec<Node<'a>>, |
888 | pub ws2: Ws, |
889 | } |
890 | |
891 | impl<'a> BlockDef<'a> { |
892 | fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { |
893 | let start_s = i; |
894 | let mut start = tuple(( |
895 | opt(Whitespace::parse), |
896 | ws(keyword("block" )), |
897 | cut_node( |
898 | Some("block" ), |
899 | tuple((ws(identifier), opt(Whitespace::parse), |i| { |
900 | s.tag_block_end(i) |
901 | })), |
902 | ), |
903 | )); |
904 | let (i, (pws1, _, (name, nws1, _))) = start(i)?; |
905 | |
906 | let mut end = cut_node( |
907 | Some("block" ), |
908 | tuple(( |
909 | |i| Node::many(i, s), |
910 | cut_node( |
911 | Some("block" ), |
912 | tuple(( |
913 | |i| check_block_start(i, start_s, s, "block" , "endblock" ), |
914 | opt(Whitespace::parse), |
915 | end_node("block" , "endblock" ), |
916 | cut_node( |
917 | Some("block" ), |
918 | tuple(( |
919 | opt(|before| { |
920 | let (after, end_name) = ws(identifier)(before)?; |
921 | check_end_name(before, after, name, end_name, "block" ) |
922 | }), |
923 | opt(Whitespace::parse), |
924 | )), |
925 | ), |
926 | )), |
927 | ), |
928 | )), |
929 | ); |
930 | let (i, (nodes, (_, pws2, _, (_, nws2)))) = end(i)?; |
931 | |
932 | Ok(( |
933 | i, |
934 | WithSpan::new( |
935 | BlockDef { |
936 | ws1: Ws(pws1, nws1), |
937 | name, |
938 | nodes, |
939 | ws2: Ws(pws2, nws2), |
940 | }, |
941 | start_s, |
942 | ), |
943 | )) |
944 | } |
945 | } |
946 | |
947 | fn check_end_name<'a>( |
948 | before: &'a str, |
949 | after: &'a str, |
950 | name: &'a str, |
951 | end_name: &'a str, |
952 | kind: &str, |
953 | ) -> ParseResult<'a> { |
954 | if name == end_name { |
955 | return Ok((after, end_name)); |
956 | } |
957 | |
958 | Err(nom::Err::Failure(ErrorContext::new( |
959 | message:match name.is_empty() && !end_name.is_empty() { |
960 | true => format!("unexpected name ` {end_name}` in `end {kind}` tag for unnamed ` {kind}`" ), |
961 | false => format!("expected name ` {name}` in `end {kind}` tag, found ` {end_name}`" ), |
962 | }, |
963 | input:before, |
964 | ))) |
965 | } |
966 | |
967 | #[derive (Debug, PartialEq)] |
968 | pub struct Lit<'a> { |
969 | pub lws: &'a str, |
970 | pub val: &'a str, |
971 | pub rws: &'a str, |
972 | } |
973 | |
974 | impl<'a> Lit<'a> { |
975 | fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { |
976 | let start = i; |
977 | let (i, ()) = not(eof)(i)?; |
978 | |
979 | let candidate_finder = Splitter3::new( |
980 | s.syntax.block_start, |
981 | s.syntax.comment_start, |
982 | s.syntax.expr_start, |
983 | ); |
984 | let p_start = alt(( |
985 | tag(s.syntax.block_start), |
986 | tag(s.syntax.comment_start), |
987 | tag(s.syntax.expr_start), |
988 | )); |
989 | |
990 | let (i, content) = opt(recognize(skip_till(candidate_finder, p_start)))(i)?; |
991 | let (i, content) = match content { |
992 | Some("" ) => { |
993 | // {block,comment,expr}_start follows immediately. |
994 | return fail(i); |
995 | } |
996 | Some(content) => (i, content), |
997 | None => ("" , i), // there is no {block,comment,expr}_start: take everything |
998 | }; |
999 | Ok((i, WithSpan::new(Self::split_ws_parts(content), start))) |
1000 | } |
1001 | |
1002 | pub(crate) fn split_ws_parts(s: &'a str) -> Self { |
1003 | let trimmed_start = s.trim_start_matches(is_ws); |
1004 | let len_start = s.len() - trimmed_start.len(); |
1005 | let trimmed = trimmed_start.trim_end_matches(is_ws); |
1006 | Self { |
1007 | lws: &s[..len_start], |
1008 | val: trimmed, |
1009 | rws: &trimmed_start[trimmed.len()..], |
1010 | } |
1011 | } |
1012 | } |
1013 | |
1014 | #[derive (Debug, PartialEq)] |
1015 | pub struct Raw<'a> { |
1016 | pub ws1: Ws, |
1017 | pub lit: Lit<'a>, |
1018 | pub ws2: Ws, |
1019 | } |
1020 | |
1021 | impl<'a> Raw<'a> { |
1022 | fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { |
1023 | let start = i; |
1024 | let endraw = tuple(( |
1025 | |i| s.tag_block_start(i), |
1026 | opt(Whitespace::parse), |
1027 | ws(keyword("endraw" )), // sic: ignore `{% end %}` in raw blocks |
1028 | opt(Whitespace::parse), |
1029 | peek(|i| s.tag_block_end(i)), |
1030 | )); |
1031 | |
1032 | let mut p = tuple(( |
1033 | opt(Whitespace::parse), |
1034 | ws(keyword("raw" )), |
1035 | cut_node( |
1036 | Some("raw" ), |
1037 | tuple(( |
1038 | opt(Whitespace::parse), |
1039 | |i| s.tag_block_end(i), |
1040 | consumed(skip_till(Splitter1::new(s.syntax.block_start), endraw)), |
1041 | )), |
1042 | ), |
1043 | )); |
1044 | |
1045 | let (_, (pws1, _, (nws1, _, (contents, (i, (_, pws2, _, nws2, _)))))) = p(i)?; |
1046 | let lit = Lit::split_ws_parts(contents); |
1047 | let ws1 = Ws(pws1, nws1); |
1048 | let ws2 = Ws(pws2, nws2); |
1049 | Ok((i, WithSpan::new(Self { ws1, lit, ws2 }, start))) |
1050 | } |
1051 | } |
1052 | |
1053 | #[derive (Debug, PartialEq)] |
1054 | pub struct Let<'a> { |
1055 | pub ws: Ws, |
1056 | pub var: Target<'a>, |
1057 | pub val: Option<WithSpan<'a, Expr<'a>>>, |
1058 | } |
1059 | |
1060 | impl<'a> Let<'a> { |
1061 | fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { |
1062 | let start = i; |
1063 | let mut p = tuple(( |
1064 | opt(Whitespace::parse), |
1065 | ws(alt((keyword("let" ), keyword("set" )))), |
1066 | cut_node( |
1067 | Some("let" ), |
1068 | tuple(( |
1069 | ws(|i| Target::parse(i, s)), |
1070 | opt(preceded( |
1071 | ws(char('=' )), |
1072 | ws(|i| Expr::parse(i, s.level.get())), |
1073 | )), |
1074 | opt(Whitespace::parse), |
1075 | )), |
1076 | ), |
1077 | )); |
1078 | let (i, (pws, _, (var, val, nws))) = p(i)?; |
1079 | |
1080 | Ok(( |
1081 | i, |
1082 | WithSpan::new( |
1083 | Let { |
1084 | ws: Ws(pws, nws), |
1085 | var, |
1086 | val, |
1087 | }, |
1088 | start, |
1089 | ), |
1090 | )) |
1091 | } |
1092 | } |
1093 | |
1094 | #[derive (Debug, PartialEq)] |
1095 | pub struct If<'a> { |
1096 | pub ws: Ws, |
1097 | pub branches: Vec<WithSpan<'a, Cond<'a>>>, |
1098 | } |
1099 | |
1100 | impl<'a> If<'a> { |
1101 | fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { |
1102 | let start = i; |
1103 | let mut p = tuple(( |
1104 | opt(Whitespace::parse), |
1105 | |i| CondTest::parse(i, s), |
1106 | cut_node( |
1107 | Some("if" ), |
1108 | tuple(( |
1109 | opt(Whitespace::parse), |
1110 | |i| s.tag_block_end(i), |
1111 | cut_node( |
1112 | Some("if" ), |
1113 | tuple(( |
1114 | |i| Node::many(i, s), |
1115 | many0(|i| Cond::parse(i, s)), |
1116 | cut_node( |
1117 | Some("if" ), |
1118 | tuple(( |
1119 | |i| check_block_start(i, start, s, "if" , "endif" ), |
1120 | opt(Whitespace::parse), |
1121 | end_node("if" , "endif" ), |
1122 | opt(Whitespace::parse), |
1123 | )), |
1124 | ), |
1125 | )), |
1126 | ), |
1127 | )), |
1128 | ), |
1129 | )); |
1130 | |
1131 | let (i, (pws1, cond, (nws1, _, (nodes, elifs, (_, pws2, _, nws2))))) = p(i)?; |
1132 | let mut branches = vec![WithSpan::new( |
1133 | Cond { |
1134 | ws: Ws(pws1, nws1), |
1135 | cond: Some(cond), |
1136 | nodes, |
1137 | }, |
1138 | start, |
1139 | )]; |
1140 | branches.extend(elifs); |
1141 | |
1142 | Ok(( |
1143 | i, |
1144 | WithSpan::new( |
1145 | Self { |
1146 | ws: Ws(pws2, nws2), |
1147 | branches, |
1148 | }, |
1149 | start, |
1150 | ), |
1151 | )) |
1152 | } |
1153 | } |
1154 | |
1155 | #[derive (Debug, PartialEq)] |
1156 | pub struct Include<'a> { |
1157 | pub ws: Ws, |
1158 | pub path: &'a str, |
1159 | } |
1160 | |
1161 | impl<'a> Include<'a> { |
1162 | fn parse(i: &'a str) -> ParseResult<'a, WithSpan<'a, Self>> { |
1163 | let start: &'a str = i; |
1164 | let mut p: impl FnMut(&str) -> Result<…, …> = tuple(( |
1165 | opt(Whitespace::parse), |
1166 | ws(inner:keyword("include" )), |
1167 | cut_node( |
1168 | kind:Some("include" ), |
1169 | inner:pair(first:ws(str_lit_without_prefix), second:opt(Whitespace::parse)), |
1170 | ), |
1171 | )); |
1172 | let (i: &str, (pws: Option, _, (path: &str, nws: Option))) = p(i)?; |
1173 | Ok(( |
1174 | i, |
1175 | WithSpan::new( |
1176 | Self { |
1177 | ws: Ws(pws, nws), |
1178 | path, |
1179 | }, |
1180 | span:start, |
1181 | ), |
1182 | )) |
1183 | } |
1184 | } |
1185 | |
1186 | #[derive (Debug, PartialEq)] |
1187 | pub struct Extends<'a> { |
1188 | pub path: &'a str, |
1189 | } |
1190 | |
1191 | impl<'a> Extends<'a> { |
1192 | fn parse(i: &'a str) -> ParseResult<'a, WithSpan<'a, Self>> { |
1193 | let start: &'a str = i; |
1194 | |
1195 | let (i: &str, (pws: Option, _, (path: &str, nws: Option))) = tuple(( |
1196 | opt(Whitespace::parse), |
1197 | ws(inner:keyword("extends" )), |
1198 | cut_node( |
1199 | kind:Some("extends" ), |
1200 | inner:pair(first:ws(str_lit_without_prefix), second:opt(Whitespace::parse)), |
1201 | ), |
1202 | ))(i)?; |
1203 | match (pws, nws) { |
1204 | (None, None) => Ok((i, WithSpan::new(Self { path }, span:start))), |
1205 | (_, _) => Err(nom::Err::Failure(ErrorContext::new( |
1206 | message:"whitespace control is not allowed on `extends`" , |
1207 | input:start, |
1208 | ))), |
1209 | } |
1210 | } |
1211 | } |
1212 | |
1213 | #[derive (Debug, PartialEq)] |
1214 | pub struct Comment<'a> { |
1215 | pub ws: Ws, |
1216 | pub content: &'a str, |
1217 | } |
1218 | |
1219 | impl<'a> Comment<'a> { |
1220 | fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { |
1221 | #[derive (Debug, Clone, Copy)] |
1222 | enum Tag { |
1223 | Open, |
1224 | Close, |
1225 | } |
1226 | |
1227 | fn tag<'a>(i: &'a str, s: &State<'_>) -> ParseResult<'a, Tag> { |
1228 | alt(( |
1229 | value(Tag::Open, |i| s.tag_comment_start(i)), |
1230 | value(Tag::Close, |i| s.tag_comment_end(i)), |
1231 | ))(i) |
1232 | } |
1233 | |
1234 | fn content<'a>(mut i: &'a str, s: &State<'_>) -> ParseResult<'a> { |
1235 | let mut depth = 0usize; |
1236 | let start = i; |
1237 | loop { |
1238 | let splitter = Splitter2::new(s.syntax.comment_start, s.syntax.comment_end); |
1239 | let (k, tag) = opt(skip_till(splitter, |i| tag(i, s)))(i)?; |
1240 | let Some((j, tag)) = tag else { |
1241 | return Err( |
1242 | ErrorContext::unclosed("comment" , s.syntax.comment_end, start).into(), |
1243 | ); |
1244 | }; |
1245 | match tag { |
1246 | Tag::Open => match depth.checked_add(1) { |
1247 | Some(new_depth) => depth = new_depth, |
1248 | None => { |
1249 | return Err(nom::Err::Failure(ErrorContext::new( |
1250 | "too deeply nested comments" , |
1251 | start, |
1252 | ))); |
1253 | } |
1254 | }, |
1255 | Tag::Close => match depth.checked_sub(1) { |
1256 | Some(new_depth) => depth = new_depth, |
1257 | None => return Ok((j, &start[..start.len() - k.len()])), |
1258 | }, |
1259 | } |
1260 | i = j; |
1261 | } |
1262 | } |
1263 | |
1264 | let start = i; |
1265 | let (i, content) = preceded( |
1266 | |i| s.tag_comment_start(i), |
1267 | cut_node(Some("comment" ), |i| content(i, s)), |
1268 | )(i)?; |
1269 | |
1270 | let mut ws = Ws(None, None); |
1271 | if content.len() == 1 && matches!(content, "-" | "+" | "~" ) { |
1272 | return Err(nom::Err::Failure(ErrorContext::new( |
1273 | format!( |
1274 | "ambiguous whitespace stripping \n\ |
1275 | use ` {}{content} {content}{}` to apply the same whitespace stripping on both \ |
1276 | sides" , |
1277 | s.syntax.comment_start, s.syntax.comment_end, |
1278 | ), |
1279 | start, |
1280 | ))); |
1281 | } else if content.len() >= 2 { |
1282 | ws.0 = Whitespace::parse_char(content.chars().next().unwrap_or_default()); |
1283 | ws.1 = Whitespace::parse_char(content.chars().next_back().unwrap_or_default()); |
1284 | } |
1285 | |
1286 | Ok((i, WithSpan::new(Self { ws, content }, start))) |
1287 | } |
1288 | } |
1289 | |
1290 | /// First field is "minus/plus sign was used on the left part of the item". |
1291 | /// |
1292 | /// Second field is "minus/plus sign was used on the right part of the item". |
1293 | #[derive (Clone, Copy, Debug, PartialEq)] |
1294 | pub struct Ws(pub Option<Whitespace>, pub Option<Whitespace>); |
1295 | |
1296 | fn end_node<'a, 'g: 'a>( |
1297 | node: &'g str, |
1298 | expected: &'g str, |
1299 | ) -> impl Fn(&'a str) -> ParseResult<'a> + 'g { |
1300 | move |start: &'a str| { |
1301 | let (i: &str, actual: &str) = ws(inner:identifier)(start)?; |
1302 | if actual == expected { |
1303 | Ok((i, actual)) |
1304 | } else if actual.starts_with("end" ) { |
1305 | Err(nom::Err::Failure(ErrorContext::new( |
1306 | message:format!("expected ` {expected}` to terminate ` {node}` node, found ` {actual}`" ), |
1307 | input:start, |
1308 | ))) |
1309 | } else { |
1310 | fail(start) |
1311 | } |
1312 | } |
1313 | } |
1314 | |
1315 | #[doc (hidden)] |
1316 | pub const MAX_KW_LEN: usize = 8; |
1317 | const MAX_REPL_LEN: usize = MAX_KW_LEN + 2; |
1318 | #[doc (hidden)] |
1319 | pub const KWS: &[&[[u8; MAX_REPL_LEN]]] = { |
1320 | // FIXME: Replace `u8` with `[core:ascii::Char; MAX_REPL_LEN]` once |
1321 | // <https://github.com/rust-lang/rust/issues/110998> is stable. |
1322 | |
1323 | const KW2: &[[u8; MAX_REPL_LEN]] = &[ |
1324 | *b"r#as______" , |
1325 | *b"r#do______" , |
1326 | *b"r#fn______" , |
1327 | *b"r#if______" , |
1328 | *b"r#in______" , |
1329 | ]; |
1330 | const KW3: &[[u8; MAX_REPL_LEN]] = &[ |
1331 | *b"r#box_____" , |
1332 | *b"r#dyn_____" , |
1333 | *b"r#for_____" , |
1334 | *b"r#let_____" , |
1335 | *b"r#mod_____" , |
1336 | *b"r#mut_____" , |
1337 | *b"r#pub_____" , |
1338 | *b"r#ref_____" , |
1339 | *b"r#try_____" , |
1340 | *b"r#use_____" , |
1341 | ]; |
1342 | const KW4: &[[u8; MAX_REPL_LEN]] = &[ |
1343 | *b"r#else____" , |
1344 | *b"r#enum____" , |
1345 | *b"r#impl____" , |
1346 | *b"r#move____" , |
1347 | *b"r#priv____" , |
1348 | *b"r#true____" , |
1349 | *b"r#type____" , |
1350 | ]; |
1351 | const KW5: &[[u8; MAX_REPL_LEN]] = &[ |
1352 | *b"r#async___" , |
1353 | *b"r#await___" , |
1354 | *b"r#break___" , |
1355 | *b"r#const___" , |
1356 | *b"r#crate___" , |
1357 | *b"r#false___" , |
1358 | *b"r#final___" , |
1359 | *b"r#macro___" , |
1360 | *b"r#match___" , |
1361 | *b"r#trait___" , |
1362 | *b"r#where___" , |
1363 | *b"r#while___" , |
1364 | *b"r#yield___" , |
1365 | ]; |
1366 | const KW6: &[[u8; MAX_REPL_LEN]] = &[ |
1367 | *b"r#become__" , |
1368 | *b"r#extern__" , |
1369 | *b"r#return__" , |
1370 | *b"r#static__" , |
1371 | *b"r#struct__" , |
1372 | *b"r#typeof__" , |
1373 | *b"r#unsafe__" , |
1374 | ]; |
1375 | const KW7: &[[u8; MAX_REPL_LEN]] = &[*b"r#unsized_" , *b"r#virtual_" ]; |
1376 | const KW8: &[[u8; MAX_REPL_LEN]] = &[*b"r#abstract" , *b"r#continue" , *b"r#override" ]; |
1377 | |
1378 | &[&[], &[], KW2, KW3, KW4, KW5, KW6, KW7, KW8] |
1379 | }; |
1380 | |
1381 | // These ones are only used in the parser, hence why they're private. |
1382 | const KWS_EXTRA: &[&[[u8; MAX_REPL_LEN]]] = { |
1383 | const KW4: &[[u8; MAX_REPL_LEN]] = &[*b"r#loop____" , *b"r#self____" , *b"r#Self____" ]; |
1384 | const KW5: &[[u8; MAX_REPL_LEN]] = &[*b"r#super___" , *b"r#union___" ]; |
1385 | |
1386 | &[&[], &[], &[], &[], KW4, KW5, &[], &[], &[]] |
1387 | }; |
1388 | |
1389 | fn is_rust_keyword(ident: &str) -> bool { |
1390 | fn is_rust_keyword_inner( |
1391 | kws: &[&[[u8; MAX_REPL_LEN]]], |
1392 | padded_ident: &[u8; MAX_KW_LEN], |
1393 | ident_len: usize, |
1394 | ) -> bool { |
1395 | // Since the individual buckets are quite short, a linear search is faster than a binary search. |
1396 | kwsIter<'_, [u8; 10]>[ident_len] |
1397 | .iter() |
1398 | .any(|&probe: [u8; 10]| padded_ident == &probe[2..]) |
1399 | } |
1400 | if ident.len() > MAX_KW_LEN { |
1401 | return false; |
1402 | } |
1403 | let ident_len: usize = ident.len(); |
1404 | |
1405 | let mut padded_ident: [u8; 8] = [b'_' ; MAX_KW_LEN]; |
1406 | padded_ident[..ident.len()].copy_from_slice(src:ident.as_bytes()); |
1407 | |
1408 | is_rust_keyword_inner(KWS, &padded_ident, ident_len) |
1409 | || is_rust_keyword_inner(KWS_EXTRA, &padded_ident, ident_len) |
1410 | } |
1411 | |
1412 | #[cfg (test)] |
1413 | mod kws_tests { |
1414 | use super::{KWS, KWS_EXTRA, MAX_REPL_LEN, is_rust_keyword}; |
1415 | |
1416 | fn ensure_utf8_inner(entry: &[&[[u8; MAX_REPL_LEN]]]) { |
1417 | for kws in entry { |
1418 | for kw in *kws { |
1419 | assert!(std::str::from_utf8(kw).is_ok(), "not UTF-8: {kw:?}" ); |
1420 | } |
1421 | } |
1422 | } |
1423 | |
1424 | // Ensure that all strings are UTF-8, because we use `from_utf8_unchecked()` further down. |
1425 | #[test ] |
1426 | fn ensure_utf8() { |
1427 | assert_eq!(KWS.len(), KWS_EXTRA.len()); |
1428 | ensure_utf8_inner(KWS); |
1429 | ensure_utf8_inner(KWS_EXTRA); |
1430 | } |
1431 | |
1432 | #[test ] |
1433 | fn test_is_rust_keyword() { |
1434 | assert!(is_rust_keyword("super" )); |
1435 | assert!(is_rust_keyword("become" )); |
1436 | assert!(!is_rust_keyword("supeeeer" )); |
1437 | assert!(!is_rust_keyword("sur" )); |
1438 | } |
1439 | } |
1440 | |