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