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>>;