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}