ftml/parsing/collect/generic.rs
1/*
2 * parsing/collect/generic.rs
3 *
4 * ftml - Library to parse Wikidot text
5 * Copyright (C) 2019-2025 Wikijump Team
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21use super::prelude::*;
22
23/// Generic function to parse upcoming tokens until conditions are met.
24///
25/// Each handled token can then processed in some manner, in accordance
26/// to the passed closure.
27///
28/// The conditions for how to consume tokens are passed as arguments,
29/// which are explained below.
30///
31/// Mutable parser reference:
32/// * `parser`
33///
34/// The rule we're parsing for:
35/// * `rule`
36///
37/// The conditions we should end iteration on:
38/// If one of these is true, we will return success.
39/// * `close_conditions`
40///
41/// The conditions we should abort on:
42/// If one of these is true, we will return failure.
43/// * `invalid_conditions`
44///
45/// If one of the failures is activated, then this `ParseErrorKind`
46/// will be returned. If `None` is provided, then `ParseErrorKind::RuleFailed` is used.
47/// * `error_kind`
48///
49/// The closure we should execute each time a token extraction is reached:
50/// If the return value is `Err(_)` then collection is aborted and that error
51/// is bubbled up.
52/// * `process`
53///
54/// This will proceed until a closing condition is found, an abort is found,
55/// or the end of the input is reached.
56///
57/// It is up to the caller to save whatever result they need while running
58/// in the closure.
59///
60/// The final token from the collection, one prior to the now-current token,
61/// is returned.
62pub fn collect<'p, 'r, 't, F>(
63 parser: &'p mut Parser<'r, 't>,
64 rule: Rule,
65 close_conditions: &[ParseCondition],
66 invalid_conditions: &[ParseCondition],
67 error_kind: Option<ParseErrorKind>,
68 mut process: F,
69) -> ParseResult<'r, 't, &'r ExtractedToken<'t>>
70where
71 F: FnMut(&mut Parser<'r, 't>) -> ParseResult<'r, 't, ()>,
72{
73 debug!("Trying to collect tokens for rule {}", rule.name());
74
75 let mut errors = Vec::new();
76 let mut paragraph_safe = true;
77
78 loop {
79 // Check current token state to decide how to proceed.
80 //
81 // * End the collection, return elements
82 // * Fail the collection, invalid token
83 // * Continue the collection, consume to make a new element
84
85 // See if the container has ended
86 if parser.evaluate_any(close_conditions) {
87 trace!(
88 "Found ending condition, returning collected elements (token {})",
89 parser.current().token.name(),
90 );
91
92 let last = parser.current();
93 if parser.current().token != Token::InputEnd {
94 parser.step()?;
95 }
96
97 return ok!(paragraph_safe; last, errors);
98 }
99
100 // See if the container should be aborted
101 if parser.evaluate_any(invalid_conditions) {
102 trace!(
103 "Found invalid token, aborting container attempt (token {})",
104 parser.current().token.name(),
105 );
106
107 return Err(parser.make_err(error_kind.unwrap_or(ParseErrorKind::RuleFailed)));
108 }
109
110 // See if we've hit the end
111 if parser.current().token == Token::InputEnd {
112 trace!("Found end of input, aborting");
113 return Err(parser.make_err(ParseErrorKind::EndOfInput));
114 }
115
116 // Process token(s).
117 let old_remaining = parser.remaining();
118 process(parser)?.chain(&mut errors, &mut paragraph_safe);
119
120 // If the pointer hasn't moved, we step one token.
121 if parser.same_pointer(old_remaining) {
122 parser.step()?;
123 }
124 }
125}