tor_netdoc/parse/
rules.rs

1//! Keywords for interpreting items and rules for validating them.
2
3use crate::parse::keyword::Keyword;
4use crate::parse::tokenize::Item;
5use crate::{NetdocErrorKind as EK, Result};
6
7/// May an Item take an object?
8#[derive(Copy, Clone)]
9enum ObjKind {
10    /// No object is allowed.
11    NoObj,
12    /// An object is required.
13    RequireObj,
14    /// An object is optional.
15    ObjOk,
16}
17
18/// A set of restrictions to place on Items for a single keyword.
19///
20/// These are built by the TokenFmtBuilder API.
21#[derive(Clone)]
22pub(crate) struct TokenFmt<T: Keyword> {
23    /// Which keyword is being restricted?
24    kwd: T,
25    /// If present, a lower bound on how many arguments may be present.
26    min_args: Option<usize>,
27    /// If present, an upper bound on how many arguments may be present.
28    max_args: Option<usize>,
29    /// If true, then at least one of this Item must appear.
30    required: bool,
31    /// If false, then no more than one this Item may appear.
32    may_repeat: bool,
33    /// May this Item have an object? Must it?
34    obj: ObjKind,
35}
36
37impl<T: Keyword> TokenFmt<T> {
38    /// Return the keyword that this rule restricts.
39    pub(crate) fn kwd(&self) -> T {
40        self.kwd
41    }
42    /// Check whether a single Item matches this TokenFmt rule, with respect
43    /// to its number of arguments.
44    fn item_matches_args(&self, item: &Item<'_, T>) -> Result<()> {
45        let n_args = item.n_args();
46        if let Some(max) = self.max_args {
47            if n_args > max {
48                return Err(EK::TooManyArguments
49                    .with_msg(self.kwd.to_str())
50                    .at_pos(item.pos()));
51            }
52        }
53        if let Some(min) = self.min_args {
54            if n_args < min {
55                return Err(EK::TooFewArguments
56                    .with_msg(self.kwd.to_str())
57                    .at_pos(item.pos()));
58            }
59        }
60        Ok(())
61    }
62
63    /// Check whether a single Item matches a TokenFmt rule, with respect
64    /// to its object's presence and type.
65    fn item_matches_obj(&self, item: &Item<'_, T>) -> Result<()> {
66        match (&self.obj, item.has_obj()) {
67            (ObjKind::NoObj, true) => Err(EK::UnexpectedObject
68                .with_msg(self.kwd.to_str())
69                .at_pos(item.pos())),
70            (ObjKind::RequireObj, false) => Err(EK::MissingObject
71                .with_msg(self.kwd.to_str())
72                .at_pos(item.pos())),
73            (_, _) => Ok(()),
74        }
75    }
76
77    /// Check whether a single item has the right number of arguments
78    /// and object.
79    pub(crate) fn check_item(&self, item: &Item<'_, T>) -> Result<()> {
80        self.item_matches_args(item)?;
81        self.item_matches_obj(item)
82    }
83
84    /// Check whether this kind of item may appear this many times.
85    pub(crate) fn check_multiplicity(&self, items: &[Item<'_, T>]) -> Result<()> {
86        match items.len() {
87            0 => {
88                if self.required {
89                    return Err(EK::MissingToken.with_msg(self.kwd.to_str()));
90                }
91            }
92            1 => (),
93            _ => {
94                if !self.may_repeat {
95                    return Err(EK::DuplicateToken
96                        .with_msg(self.kwd.to_str())
97                        .at_pos(items[1].pos()));
98                }
99            }
100        }
101        Ok(())
102    }
103}
104
105/// Represents a TokenFmt under construction.
106///
107/// To construct a rule, create this type with Keyword::rule(), then
108/// call method on it to set its fields, and then pass it to
109/// SectionRules::add().
110///
111/// # Example
112///
113/// ```ignore
114/// // There must be exactly one "ROUTER" entry, with 5 or more arguments.
115/// section_rules.add(D.rule().required().args(5..));
116/// ```
117///
118/// TODO: I'd rather have this be pub(crate), but I haven't figured out
119/// how to make that work.  There are complicated cascading side-effects.
120pub struct TokenFmtBuilder<T: Keyword>(TokenFmt<T>);
121
122impl<T: Keyword> From<TokenFmtBuilder<T>> for TokenFmt<T> {
123    fn from(builder: TokenFmtBuilder<T>) -> Self {
124        builder.0
125    }
126}
127
128impl<T: Keyword> TokenFmtBuilder<T> {
129    /// Make a new TokenFmtBuilder with default behavior.
130    ///
131    /// (By default, all arguments are allowed, the Item may appear 0
132    /// or 1 times, and it may not take an object.)
133    pub(crate) fn new(t: T) -> Self {
134        Self(TokenFmt {
135            kwd: t,
136            min_args: None,
137            max_args: None,
138            required: false,
139            may_repeat: false,
140            obj: ObjKind::NoObj,
141        })
142    }
143
144    /// Indicate that this Item is required.
145    ///
146    /// By default, no item is required.
147    pub(crate) fn required(self) -> Self {
148        Self(TokenFmt {
149            required: true,
150            ..self.0
151        })
152    }
153
154    /// Indicate that this Item may appear more than once.
155    ///
156    /// By default, items may not repeat.
157    pub(crate) fn may_repeat(self) -> Self {
158        Self(TokenFmt {
159            may_repeat: true,
160            ..self.0
161        })
162    }
163
164    /// Indicate that this Item takes no arguments.
165    ///
166    /// By default, items may take any number of arguments.
167    pub(crate) fn no_args(self) -> Self {
168        Self(TokenFmt {
169            max_args: Some(0),
170            ..self.0
171        })
172    }
173    /// Indicate that this item takes a certain number of arguments.
174    ///
175    /// The number of arguments is provided as a range, like `5..`.
176    pub(crate) fn args<R>(self, r: R) -> Self
177    where
178        R: std::ops::RangeBounds<usize>,
179    {
180        use std::ops::Bound::*;
181        let min_args = match r.start_bound() {
182            Included(x) => Some(*x),
183            Excluded(x) => Some(*x + 1),
184            Unbounded => None,
185        };
186        let max_args = match r.end_bound() {
187            Included(x) => Some(*x),
188            Excluded(x) => Some(*x - 1),
189            Unbounded => None,
190        };
191        Self(TokenFmt {
192            min_args,
193            max_args,
194            ..self.0
195        })
196    }
197    /// Indicate that this token takes an optional object.
198    ///
199    /// By default, objects are not allowed.
200    pub(crate) fn obj_optional(self) -> Self {
201        Self(TokenFmt {
202            obj: ObjKind::ObjOk,
203            ..self.0
204        })
205    }
206    /// Indicate that this token takes an required object.
207    ///
208    /// By default, objects are not allowed.
209    pub(crate) fn obj_required(self) -> Self {
210        Self(TokenFmt {
211            obj: ObjKind::RequireObj,
212            ..self.0
213        })
214    }
215}