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}