1use crate::parse::keyword::Keyword;
17use crate::parse::rules::*;
18use crate::parse::tokenize::*;
19use crate::{NetdocErrorKind as EK, Result};
20
21use educe::Educe;
22
23#[derive(Clone)]
28pub(crate) struct SectionRules<T: Keyword> {
29 rules: Vec<Option<TokenFmt<T>>>,
38}
39
40#[derive(Clone, Educe)]
42#[educe(Default)]
43struct TokVal<'a, K: Keyword>(Vec<Item<'a, K>>);
44
45impl<'a, K: Keyword> TokVal<'a, K> {
46 fn none() -> Self {
48 Default::default()
49 }
50 fn count(&self) -> usize {
52 self.0.len()
53 }
54 fn first(&self) -> Option<&Item<'a, K>> {
56 self.0.first()
57 }
58 fn singleton(&self) -> Option<&Item<'a, K>> {
60 match &*self.0 {
61 [x] => Some(x),
62 _ => None,
63 }
64 }
65 fn as_slice(&self) -> &[Item<'a, K>] {
67 &self.0
68 }
69 fn last(&self) -> Option<&Item<'a, K>> {
71 self.0.last()
72 }
73}
74
75pub(crate) struct Section<'a, T: Keyword> {
80 v: Vec<TokVal<'a, T>>,
82 first: Option<T>,
85 last: Option<T>,
88}
89
90impl<'a, T: Keyword> Section<'a, T> {
91 fn new() -> Self {
93 let n = T::n_vals();
94 let mut v = Vec::with_capacity(n);
95 v.resize(n, TokVal::none());
96 Section {
97 v,
98 first: None,
99 last: None,
100 }
101 }
102 fn tokval(&self, t: T) -> &TokVal<'a, T> {
104 let idx = t.idx();
105 &self.v[idx]
106 }
107 pub(crate) fn slice(&self, t: T) -> &[Item<'a, T>] {
109 self.tokval(t).as_slice()
110 }
111 pub(crate) fn get(&self, t: T) -> Option<&Item<'a, T>> {
113 self.tokval(t).singleton()
114 }
115 pub(crate) fn required(&self, t: T) -> Result<&Item<'a, T>> {
121 self.get(t)
122 .ok_or_else(|| EK::MissingToken.with_msg(t.to_str()))
123 }
124 pub(crate) fn maybe<'b>(&'b self, t: T) -> MaybeItem<'b, 'a, T> {
129 MaybeItem::from_option(self.get(t))
130 }
131 pub(crate) fn first_item(&self) -> Option<&Item<'a, T>> {
134 match self.first {
135 None => None,
136 Some(t) => self.tokval(t).first(),
137 }
138 }
139 pub(crate) fn last_item(&self) -> Option<&Item<'a, T>> {
142 match self.last {
143 None => None,
144 Some(t) => self.tokval(t).last(),
145 }
146 }
147 fn add_tok(&mut self, t: T, item: Item<'a, T>) {
151 let idx = Keyword::idx(t);
152 if idx >= self.v.len() {
153 self.v.resize(idx + 1, TokVal::none());
154 }
155 self.v[idx].0.push(item);
156 if self.first.is_none() {
157 self.first = Some(t);
158 }
159 self.last = Some(t);
160 }
161}
162
163#[derive(Clone)]
165pub(crate) struct SectionRulesBuilder<T: Keyword> {
166 strict: bool,
168 rules: SectionRules<T>,
170}
171
172impl<T: Keyword> SectionRulesBuilder<T> {
173 pub(crate) fn add(&mut self, t: TokenFmtBuilder<T>) {
177 let rule: TokenFmt<_> = t.into();
178 let idx = rule.kwd().idx();
179 assert!(self.rules.rules[idx].is_none());
180 self.rules.rules[idx] = Some(rule);
181 }
182
183 pub(crate) fn reject_unrecognized(&mut self) {
188 self.strict = true;
189 }
190
191 pub(crate) fn build(self) -> SectionRules<T> {
198 let unrecognized_idx = T::unrecognized().idx();
199 assert!(
200 self.strict || self.rules.rules[unrecognized_idx].is_some(),
201 "BUG: Section has to handle UNRECOGNIZED tokens explicitly."
202 );
203 self.rules
204 }
205}
206
207impl<T: Keyword> SectionRules<T> {
208 pub(crate) fn builder() -> SectionRulesBuilder<T> {
212 let n = T::n_vals();
213 let mut rules = Vec::with_capacity(n);
214 rules.resize(n, None);
215 SectionRulesBuilder {
216 strict: false,
217 rules: SectionRules { rules },
218 }
219 }
220
221 fn parse_unverified<'a, I>(&self, tokens: I, section: &mut Section<'a, T>) -> Result<()>
227 where
228 I: Iterator<Item = Result<Item<'a, T>>>,
229 {
230 for item in tokens {
231 let item = item?;
232
233 let tok = item.kwd();
234 let tok_idx = tok.idx();
235 if let Some(rule) = &self.rules[tok_idx] {
236 assert!(rule.kwd() == tok);
238 section.add_tok(tok, item);
239 rule.check_multiplicity(section.v[tok_idx].as_slice())?;
240 } else {
241 return Err(EK::UnexpectedToken
243 .with_msg(tok.to_str())
244 .at_pos(item.pos()));
245 }
246 }
247 Ok(())
248 }
249
250 fn validate(&self, s: &Section<'_, T>) -> Result<()> {
253 assert_eq!(s.v.len(), self.rules.len());
255
256 for (rule, t) in self.rules.iter().zip(s.v.iter()) {
259 match rule {
260 None => {
261 if t.count() > 0 {
263 unreachable!(
264 "This item should have been rejected earlier, in parse_unverified()"
265 );
266 }
267 }
268 Some(rule) => {
269 rule.check_multiplicity(t.as_slice())?;
271 for item in t.as_slice() {
273 rule.check_item(item)?;
274 }
275 }
276 }
277 }
278
279 Ok(())
280 }
281
282 fn validate_objects(&self, s: &Section<'_, T>, kwd: T) -> Result<()> {
287 for item in s.slice(kwd).iter() {
288 let _ = item.obj_raw()?;
289 }
290 Ok(())
291 }
292
293 pub(crate) fn parse<'a, I>(&self, tokens: I) -> Result<Section<'a, T>>
295 where
296 I: Iterator<Item = Result<Item<'a, T>>>,
297 {
298 let mut section = Section::new();
299 self.parse_unverified(tokens, &mut section)?;
300 self.validate(§ion)?;
301 self.validate_objects(§ion, T::unrecognized())?;
302 self.validate_objects(§ion, T::ann_unrecognized())?;
303 Ok(section)
304 }
305}
306
307#[cfg(test)]
308mod test {
309 #![allow(clippy::bool_assert_comparison)]
311 #![allow(clippy::clone_on_copy)]
312 #![allow(clippy::dbg_macro)]
313 #![allow(clippy::mixed_attributes_style)]
314 #![allow(clippy::print_stderr)]
315 #![allow(clippy::print_stdout)]
316 #![allow(clippy::single_char_pattern)]
317 #![allow(clippy::unwrap_used)]
318 #![allow(clippy::unchecked_time_subtraction)]
319 #![allow(clippy::useless_vec)]
320 #![allow(clippy::needless_pass_by_value)]
321 #![allow(clippy::string_slice)] use super::SectionRules;
324 use crate::parse::keyword::Keyword;
325 use crate::parse::macros::test::Fruit;
326 use crate::parse::tokenize::{Item, NetDocReader};
327 use crate::{Error, NetdocErrorKind as EK, Result};
328 use std::sync::LazyLock;
329
330 static FRUIT_SALAD: LazyLock<SectionRules<Fruit>> = LazyLock::new(|| {
332 use Fruit::*;
333 let mut rules = SectionRules::builder();
334 rules.add(ANN_TASTY.rule().required().args(1..=1));
335 rules.add(ORANGE.rule().args(1..));
336 rules.add(STONEFRUIT.rule().may_repeat());
337 rules.add(GUAVA.rule().obj_optional());
338 rules.add(LEMON.rule().no_args().obj_required());
339 rules.reject_unrecognized();
340 rules.build()
341 });
342
343 #[test]
344 fn parse_section() -> Result<()> {
345 use Fruit::*;
346 let s = "\
347@tasty yes
348orange soda
349cherry cobbler
350cherry pie
351plum compote
352guava fresh from 7 trees
353-----BEGIN GUAVA MANIFESTO-----
354VGhlIGd1YXZhIGVtb2ppIGlzIG5vdCBjdXJyZW50bHkgc3VwcG9ydGVkIGluI
355HVuaWNvZGUgMTMuMC4gTGV0J3MgZmlnaHQgYWdhaW5zdCBhbnRpLWd1YXZhIG
356JpYXMu
357-----END GUAVA MANIFESTO-----
358lemon
359-----BEGIN LEMON-----
3608J+Niw==
361-----END LEMON-----
362";
363 let r: NetDocReader<'_, Fruit> = NetDocReader::new(s).unwrap();
364 let sec = FRUIT_SALAD.parse(r).unwrap();
365
366 assert_eq!(sec.required(ANN_TASTY)?.arg(0), Some("yes"));
367
368 assert!(sec.get(ORANGE).is_some());
369 assert_eq!(sec.get(ORANGE).unwrap().args_as_str(), "soda");
370
371 let stonefruit_slice = sec.slice(STONEFRUIT);
372 assert_eq!(stonefruit_slice.len(), 3);
373 let kwds: Vec<&str> = stonefruit_slice.iter().map(Item::kwd_str).collect();
374 assert_eq!(kwds, &["cherry", "cherry", "plum"]);
375
376 assert_eq!(sec.maybe(GUAVA).args_as_str(), Some("fresh from 7 trees"));
377 assert_eq!(sec.maybe(GUAVA).parse_arg::<u32>(2).unwrap(), Some(7));
378 assert!(sec.maybe(GUAVA).parse_arg::<u32>(1).is_err());
379
380 assert_eq!(sec.get(GUAVA).unwrap().obj("GUAVA MANIFESTO").unwrap(),
382 &b"The guava emoji is not currently supported in unicode 13.0. Let's fight against anti-guava bias."[..]);
383 assert!(matches!(
384 sec.get(ORANGE)
385 .unwrap()
386 .obj("ORANGE MANIFESTO")
387 .unwrap_err()
388 .netdoc_error_kind(),
389 EK::MissingObject ));
391
392 let maybe_banana = sec.maybe(BANANA);
394 assert!(maybe_banana.parse_arg::<u32>(3).unwrap().is_none()); let maybe_guava = sec.maybe(GUAVA);
396 assert_eq!(maybe_guava.parse_arg::<u32>(2).unwrap(), Some(7));
397
398 assert_eq!(
399 sec.get(ANN_TASTY).unwrap() as *const Item<'_, _>,
400 sec.first_item().unwrap() as *const Item<'_, _>
401 );
402
403 assert_eq!(
404 sec.get(LEMON).unwrap() as *const Item<'_, _>,
405 sec.last_item().unwrap() as *const Item<'_, _>
406 );
407
408 Ok(())
409 }
410
411 #[test]
412 fn rejected() {
413 use crate::Pos;
414 fn check(s: &str, e: &Error) {
415 let r: NetDocReader<'_, Fruit> = NetDocReader::new(s).unwrap();
416 let res = FRUIT_SALAD.parse(r);
417 assert!(res.is_err());
418 assert_eq!(&res.err().unwrap().within(s), e);
419 }
420
421 check(
423 "orange foo\nfoobar x\n@tasty yes\n",
424 &EK::UnexpectedToken
425 .with_msg("<unrecognized>")
426 .at_pos(Pos::from_line(2, 1)),
427 );
428
429 check(
431 "@tasty yes\norange foo\norange bar\n",
432 &EK::DuplicateToken
433 .with_msg("orange")
434 .at_pos(Pos::from_line(3, 1)),
435 );
436
437 check("orange foo\n", &EK::MissingToken.with_msg("@tasty"));
439
440 check(
442 "@tasty nope\norange\n",
443 &EK::TooFewArguments
444 .with_msg("orange")
445 .at_pos(Pos::from_line(2, 1)),
446 );
447 check(
449 "@tasty yup indeed\norange normal\n",
450 &EK::TooManyArguments
451 .with_msg("@tasty")
452 .at_pos(Pos::from_line(1, 1)),
453 );
454
455 check(
457 "@tasty yes\nlemon\norange no\n",
458 &EK::MissingObject
459 .with_msg("lemon")
460 .at_pos(Pos::from_line(2, 1)),
461 );
462
463 check(
465 "@tasty yes\norange no\n-----BEGIN ORANGE-----\naaa\n-----END ORANGE-----\n",
466 &EK::UnexpectedObject
467 .with_msg("orange")
468 .at_pos(Pos::from_line(2, 1)),
469 );
470 }
471}