ftml/parsing/rule/
mod.rs

1/*
2 * parsing/rule/mod.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::*;
22use crate::parsing::Parser;
23use std::fmt::{self, Debug};
24
25mod mapping;
26
27pub mod impls;
28
29pub use self::mapping::get_rules_for_token;
30
31/// Defines a rule that can possibly match tokens and return an `Element`.
32#[derive(Copy, Clone)]
33pub struct Rule {
34    /// The name for this rule, in kebab-case.
35    ///
36    /// It must be globally unique.
37    name: &'static str,
38
39    /// What requirements this rule needs regarding its position in a line.
40    position: LineRequirement,
41
42    /// The consumption attempt function for this rule.
43    try_consume_fn: TryConsumeFn,
44}
45
46impl Rule {
47    #[inline]
48    pub fn name(self) -> &'static str {
49        self.name
50    }
51
52    #[inline]
53    pub fn try_consume<'r, 't>(
54        self,
55        parser: &mut Parser<'r, 't>,
56    ) -> ParseResult<'r, 't, Elements<'t>> {
57        debug!("Trying to consume for parse rule {}", self.name);
58
59        // Check that the line position matches what the rule wants.
60        match self.position {
61            LineRequirement::Any => (),
62            LineRequirement::StartOfLine => {
63                if !parser.start_of_line() {
64                    return Err(parser.make_err(ParseErrorKind::NotStartOfLine));
65                }
66            }
67        }
68
69        // Fork parser and try running the rule.
70        let mut sub_parser = parser.clone_with_rule(self);
71        let result = (self.try_consume_fn)(&mut sub_parser);
72
73        if let Ok(ref output) = result {
74            // First, ensure there aren't any partial elements in the result.
75            output.check_partials(parser)?;
76
77            // Now, finally save the parser state since it succeeded.
78            parser.update(&sub_parser);
79        }
80
81        result
82    }
83}
84
85impl Debug for Rule {
86    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
87        f.debug_struct("Rule")
88            .field("name", &self.name)
89            .field("try_consume_fn", &(self.try_consume_fn as *const ()))
90            .finish()
91    }
92}
93
94/// The enum describing what requirements a rule has regarding lines.
95#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, PartialEq, Eq)]
96pub enum LineRequirement {
97    /// This rule does not care where it is in a line.
98    Any,
99
100    /// This rule may only activate when it is at the start of a line.
101    ///
102    /// This includes situations which are not technically line breaks,
103    /// such as start of input and paragraph breaks.
104    StartOfLine,
105}
106
107/// The function type for actually trying to consume tokens
108pub type TryConsumeFn = for<'p, 'r, 't> fn(
109    parser: &'p mut Parser<'r, 't>,
110) -> ParseResult<'r, 't, Elements<'t>>;