Skip to main content

ftml/parsing/rule/
mod.rs

1/*
2 * parsing/rule/mod.rs
3 *
4 * ftml - Library to parse Wikidot text
5 * Copyright (C) 2019-2026 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 parser_state = parser.get_mutable_state();
71        let mut sub_parser = parser.clone_with_rule(self);
72        let result = (self.try_consume_fn)(&mut sub_parser);
73
74        match result {
75            // Rule succeeded, ensure that changes from the subparser are persisted.
76            Ok(ref output) => {
77                // First, ensure there aren't any partial elements in the result.
78                output.check_partials(parser)?;
79
80                // Now, finally save the parser state since it succeeded.
81                parser.update(&sub_parser);
82            }
83
84            // Rule failed, ensure that any changes are rolled back.
85            //
86            // While normally discarding the subparser is sufficient,
87            // some annoying mutable fields are
88            Err(_) => parser.reset_mutable_state(parser_state),
89        }
90
91        result
92    }
93}
94
95impl Debug for Rule {
96    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
97        f.debug_struct("Rule")
98            .field("name", &self.name)
99            .field("position", &self.position)
100            .field("try_consume_fn", &(self.try_consume_fn as *const ()))
101            .finish()
102    }
103}
104
105/// The enum describing what requirements a rule has regarding lines.
106#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, PartialEq, Eq)]
107pub enum LineRequirement {
108    /// This rule does not care where it is in a line.
109    Any,
110
111    /// This rule may only activate when it is at the start of a line.
112    ///
113    /// This includes situations which are not technically line breaks,
114    /// such as start of input and paragraph breaks.
115    StartOfLine,
116}
117
118/// The function type for actually trying to consume tokens
119pub type TryConsumeFn = for<'p, 'r, 't> fn(
120    parser: &'p mut Parser<'r, 't>,
121) -> ParseResult<'r, 't, Elements<'t>>;