1use crate::{
2 Location, Span, Spanned,
3 markmap::{Entry, MarkMap},
4 yacc::{
5 YaccGrammarError, YaccGrammarErrorKind, YaccKind, YaccOriginalActionKind, parser::SpansKind,
6 },
7};
8use regex::{Regex, RegexBuilder};
9use std::{error::Error, fmt, sync::LazyLock};
10
11#[derive(Debug, Clone)]
18#[doc(hidden)]
19pub struct HeaderError<T> {
20 pub kind: HeaderErrorKind,
21 pub locations: Vec<T>,
22}
23
24impl<T: fmt::Debug> Error for HeaderError<T> {}
25impl<T> fmt::Display for HeaderError<T> {
26 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27 write!(f, "{}", self.kind)
28 }
29}
30
31impl From<HeaderError<Span>> for YaccGrammarError {
32 fn from(e: HeaderError<Span>) -> YaccGrammarError {
33 YaccGrammarError {
34 kind: YaccGrammarErrorKind::Header(e.kind, e.spanskind()),
35 spans: e.locations,
36 }
37 }
38}
39
40impl Spanned for HeaderError<Span> {
41 fn spans(&self) -> &[Span] {
42 self.locations.as_slice()
43 }
44 fn spanskind(&self) -> SpansKind {
45 self.spanskind()
46 }
47}
48
49#[derive(Debug, PartialEq)]
52#[doc(hidden)]
53pub struct HeaderValue<T>(pub T, pub Value<T>);
54
55impl From<HeaderValue<Span>> for HeaderValue<Location> {
56 fn from(hv: HeaderValue<Span>) -> HeaderValue<Location> {
57 HeaderValue(hv.0.into(), hv.1.into())
58 }
59}
60
61#[derive(Debug, Eq, PartialEq, Copy, Clone)]
62#[non_exhaustive]
63#[doc(hidden)]
64pub enum HeaderErrorKind {
65 MissingGrmtoolsSection,
66 IllegalName,
67 ExpectedToken(char),
68 UnexpectedToken(char, &'static str),
69 DuplicateEntry,
70 InvalidEntry(&'static str),
71 ConversionError(&'static str, &'static str),
72}
73
74impl fmt::Display for HeaderErrorKind {
75 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
76 let s = match self {
77 HeaderErrorKind::MissingGrmtoolsSection => "Missing %grmtools section",
78 HeaderErrorKind::IllegalName => "Illegal name",
79 HeaderErrorKind::ExpectedToken(c) => &format!("Expected token: '{}'", c),
80 HeaderErrorKind::UnexpectedToken(c, hint) => {
81 &format!("Unxpected token: '{}', {} ", c, hint)
82 }
83 HeaderErrorKind::InvalidEntry(s) => &format!("Invalid entry: '{}'", s),
84 HeaderErrorKind::DuplicateEntry => "Duplicate Entry",
85 HeaderErrorKind::ConversionError(t, err_str) => {
86 &format!("Converting header value to type '{}': {}", t, err_str)
87 }
88 };
89 write!(f, "{}", s)
90 }
91}
92
93impl<T> HeaderError<T> {
94 pub fn spanskind(&self) -> SpansKind {
96 match self.kind {
97 HeaderErrorKind::DuplicateEntry => SpansKind::DuplicationError,
98 _ => SpansKind::Error,
99 }
100 }
101}
102
103#[derive(Debug, Eq, PartialEq)]
120#[doc(hidden)]
121pub struct Namespaced<T> {
122 pub namespace: Option<(String, T)>,
123 pub member: (String, T),
124}
125
126#[derive(Debug, Eq, PartialEq)]
127#[doc(hidden)]
128pub enum Setting<T> {
129 Unitary(Namespaced<T>),
131 Constructor {
135 ctor: Namespaced<T>,
136 arg: Namespaced<T>,
137 },
138 Num(u64, T),
139 String(String, T),
140 Array(Vec<Setting<T>>, T, T),
142}
143
144#[doc(hidden)]
146pub struct GrmtoolsSectionParser<'input> {
147 src: &'input str,
148 required: bool,
149}
150
151#[derive(Debug, Eq, PartialEq)]
156#[doc(hidden)]
157pub enum Value<T> {
158 Flag(bool, T),
159 Setting(Setting<T>),
160}
161
162impl From<Setting<Span>> for Setting<Location> {
163 fn from(s: Setting<Span>) -> Setting<Location> {
164 match s {
165 Setting::Unitary(Namespaced {
166 namespace,
167 member: (m, ml),
168 }) => Setting::Unitary(Namespaced {
169 namespace: namespace.map(|(n, nl)| (n, nl.into())),
170 member: (m, ml.into()),
171 }),
172 Setting::Constructor {
173 ctor:
174 Namespaced {
175 namespace: ctor_ns,
176 member: (ctor_m, ctor_ml),
177 },
178 arg:
179 Namespaced {
180 namespace: arg_ns,
181 member: (arg_m, arg_ml),
182 },
183 } => Setting::Constructor {
184 ctor: Namespaced {
185 namespace: ctor_ns.map(|(ns, ns_l)| (ns, ns_l.into())),
186 member: (ctor_m, ctor_ml.into()),
187 },
188 arg: Namespaced {
189 namespace: arg_ns.map(|(ns, ns_l)| (ns, ns_l.into())),
190 member: (arg_m, arg_ml.into()),
191 },
192 },
193 Setting::Num(num, num_loc) => Setting::Num(num, num_loc.into()),
194 Setting::String(s, str_loc) => Setting::String(s, str_loc.into()),
195 Setting::Array(mut xs, arr_open_loc, arr_close_loc) => Setting::Array(
196 xs.drain(..).map(|x| x.into()).collect(),
197 arr_open_loc.into(),
198 arr_close_loc.into(),
199 ),
200 }
201 }
202}
203
204impl From<Value<Span>> for Value<Location> {
205 fn from(v: Value<Span>) -> Value<Location> {
206 match v {
207 Value::Flag(flag, u) => Value::Flag(flag, u.into()),
208 Value::Setting(s) => Value::Setting(s.into()),
209 }
210 }
211}
212
213impl<T> Value<T> {
214 pub fn primary_location(&self) -> &T {
215 match self {
216 Value::Flag(_, loc) => loc,
217 Value::Setting(setting) => setting.primary_location(),
218 }
219 }
220}
221
222impl<T> Setting<T> {
223 fn primary_location(&self) -> &T {
224 match self {
225 Self::Constructor { arg, .. } => arg.primary_location(),
226 Self::Unitary(ns) => ns.primary_location(),
227 Self::Array(_, start_loc, _) => start_loc,
228 Self::Num(_, loc) | Self::String(_, loc) => loc,
229 }
230 }
231}
232
233impl<T> Namespaced<T> {
234 fn primary_location(&self) -> &T {
235 &self.member.1
236 }
237}
238
239static RE_LEADING_WS: LazyLock<Regex> =
240 LazyLock::new(|| Regex::new(r"^[\p{Pattern_White_Space}]*").unwrap());
241static RE_NAME: LazyLock<Regex> = LazyLock::new(|| {
242 RegexBuilder::new(r"^[A-Z][A-Z_]*")
243 .case_insensitive(true)
244 .build()
245 .unwrap()
246});
247static RE_DIGITS: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^[0-9]+").unwrap());
248static RE_STRING: LazyLock<Regex> = LazyLock::new(|| Regex::new(r#"^\"(\\.|[^"\\])*\""#).unwrap());
249
250const MAGIC: &str = "%grmtools";
251
252fn add_duplicate_occurrence<T: Eq + PartialEq + Clone>(
253 errs: &mut Vec<HeaderError<T>>,
254 kind: HeaderErrorKind,
255 orig_loc: T,
256 dup_loc: T,
257) {
258 if !errs.iter_mut().any(|e| {
259 if e.kind == kind && e.locations[0] == orig_loc {
260 e.locations.push(dup_loc.clone());
261 true
262 } else {
263 false
264 }
265 }) {
266 errs.push(HeaderError {
267 kind,
268 locations: vec![orig_loc, dup_loc],
269 });
270 }
271}
272
273impl<'input> GrmtoolsSectionParser<'input> {
274 fn parse_setting(&'_ self, mut i: usize) -> Result<(Setting<Span>, usize), HeaderError<Span>> {
275 i = self.parse_ws(i);
276 match RE_DIGITS.find(&self.src[i..]) {
277 Some(m) => {
278 let num_span = Span::new(i + m.start(), i + m.end());
279 let num_str = &self.src[num_span.start()..num_span.end()];
280 let num = str::parse::<u64>(num_str).unwrap();
282 let val = Setting::Num(num, num_span);
283 i = self.parse_ws(num_span.end());
284 Ok((val, i))
285 }
286 None => match RE_STRING.find(&self.src[i..]) {
287 Some(m) => {
288 let end = i + m.end();
289 let str_span = Span::new(i + m.start() + 1, end - 1);
291 let str = &self.src[str_span.start()..str_span.end()];
292 let setting = Setting::String(str.to_string(), str_span);
293 i = self.parse_ws(end);
295 Ok((setting, i))
296 }
297 None => {
298 if let Some(mut j) = self.lookahead_is("[", i) {
299 let mut vals = Vec::new();
300 let open_pos = j;
301
302 loop {
303 j = self.parse_ws(j);
304 if let Some(end_pos) = self.lookahead_is("]", j) {
305 return Ok((
306 Setting::Array(
307 vals,
308 Span::new(i, open_pos),
309 Span::new(j, end_pos),
310 ),
311 end_pos,
312 ));
313 }
314 if let Ok((val, k)) = self.parse_setting(j) {
315 vals.push(val);
316 j = self.parse_ws(k);
317 }
318 if let Some(k) = self.lookahead_is(",", j) {
319 j = k
320 }
321 }
322 } else {
323 let (path_val, j) = self.parse_namespaced(i)?;
324 i = self.parse_ws(j);
325 if let Some(j) = self.lookahead_is("(", i) {
326 let (arg, j) = self.parse_namespaced(j)?;
327 i = self.parse_ws(j);
328 if let Some(j) = self.lookahead_is(")", i) {
329 i = self.parse_ws(j);
330 Ok((
331 Setting::Constructor {
332 ctor: path_val,
333 arg,
334 },
335 i,
336 ))
337 } else {
338 Err(HeaderError {
339 kind: HeaderErrorKind::ExpectedToken(')'),
340 locations: vec![Span::new(i, i)],
341 })
342 }
343 } else {
344 Ok((Setting::Unitary(path_val), i))
345 }
346 }
347 }
348 },
349 }
350 }
351
352 pub fn parse_key_value(
353 &'_ self,
354 mut i: usize,
355 ) -> Result<(String, Span, Value<Span>, usize), HeaderError<Span>> {
356 if let Some(j) = self.lookahead_is("!", i) {
357 let (flag_name, k) = self.parse_name(j)?;
358 Ok((
359 flag_name,
360 Span::new(j, k),
361 Value::Flag(false, Span::new(i, k)),
362 self.parse_ws(k),
363 ))
364 } else {
365 let (key_name, j) = self.parse_name(i)?;
366 let key_span = Span::new(i, j);
367 i = self.parse_ws(j);
368 if let Some(j) = self.lookahead_is(":", i) {
369 let (val, j) = self.parse_setting(j)?;
370 Ok((key_name, key_span, Value::Setting(val), j))
371 } else {
372 Ok((key_name, key_span, Value::Flag(true, key_span), i))
373 }
374 }
375 }
376
377 fn parse_namespaced(
378 &self,
379 mut i: usize,
380 ) -> Result<(Namespaced<Span>, usize), HeaderError<Span>> {
381 let (name, j) = self.parse_name(i)?;
383 let name_span = Span::new(i, j);
384 i = self.parse_ws(j);
385 if let Some(j) = self.lookahead_is("::", i) {
386 i = self.parse_ws(j);
387 let (member_val, j) = self.parse_name(i)?;
388 let member_val_span = Span::new(i, j);
389 i = self.parse_ws(j);
390 Ok((
391 Namespaced {
392 namespace: Some((name, name_span)),
393 member: (member_val, member_val_span),
394 },
395 i,
396 ))
397 } else {
398 Ok((
399 Namespaced {
400 namespace: None,
401 member: (name, name_span),
402 },
403 i,
404 ))
405 }
406 }
407
408 pub fn new(src: &'input str, required: bool) -> Self {
418 Self { src, required }
419 }
420
421 #[allow(clippy::type_complexity)]
422 pub fn parse(&'_ self) -> Result<(Header<Span>, usize), Vec<HeaderError<Span>>> {
423 let mut errs = Vec::new();
424 if let Some(mut i) = self.lookahead_is(MAGIC, self.parse_ws(0)) {
425 let mut ret = Header::new();
426 i = self.parse_ws(i);
427 let section_start_pos = i;
428 if let Some(j) = self.lookahead_is("{", i) {
429 i = self.parse_ws(j);
430 while self.lookahead_is("}", i).is_none() && i < self.src.len() {
431 let (key, key_loc, val, j) = match self.parse_key_value(i) {
432 Ok((key, key_loc, val, pos)) => (key, key_loc, val, pos),
433 Err(e) => {
434 errs.push(e);
435 return Err(errs);
436 }
437 };
438 match ret.entry(key) {
439 Entry::Occupied(orig) => {
440 let HeaderValue(orig_loc, _): &HeaderValue<Span> = orig.get();
441 add_duplicate_occurrence(
442 &mut errs,
443 HeaderErrorKind::DuplicateEntry,
444 *orig_loc,
445 key_loc,
446 )
447 }
448 Entry::Vacant(entry) => {
449 entry.insert(HeaderValue(key_loc, val));
450 }
451 }
452 if let Some(j) = self.lookahead_is(",", j) {
453 i = self.parse_ws(j);
454 continue;
455 } else {
456 i = self.parse_ws(j);
457 break;
458 }
459 }
460 if let Some(j) = self.lookahead_is("*", i) {
461 errs.push(HeaderError {
462 kind: HeaderErrorKind::UnexpectedToken(
463 '*',
464 "perhaps this is a glob, in which case it requires string quoting.",
465 ),
466 locations: vec![Span::new(i, j)],
467 });
468 Err(errs)
469 } else if let Some(i) = self.lookahead_is("}", i) {
470 if errs.is_empty() {
471 Ok((ret, i))
472 } else {
473 Err(errs)
474 }
475 } else {
476 errs.push(HeaderError {
477 kind: HeaderErrorKind::ExpectedToken('}'),
478 locations: vec![Span::new(section_start_pos, i)],
479 });
480 Err(errs)
481 }
482 } else {
483 errs.push(HeaderError {
484 kind: HeaderErrorKind::ExpectedToken('{'),
485 locations: vec![Span::new(i, i)],
486 });
487 Err(errs)
488 }
489 } else if self.required {
490 errs.push(HeaderError {
491 kind: HeaderErrorKind::MissingGrmtoolsSection,
492 locations: vec![Span::new(0, 0)],
493 });
494 Err(errs)
495 } else {
496 Ok((Header::new(), 0))
497 }
498 }
499
500 fn parse_name(&self, i: usize) -> Result<(String, usize), HeaderError<Span>> {
501 match RE_NAME.find(&self.src[i..]) {
502 Some(m) => {
503 assert_eq!(m.start(), 0);
504 Ok((
505 self.src[i..i + m.end()].to_string().to_lowercase(),
506 i + m.end(),
507 ))
508 }
509 None => {
510 if self.src[i..].starts_with("*") {
511 Err(HeaderError {
512 kind: HeaderErrorKind::UnexpectedToken(
513 '*',
514 "perhaps this is a glob, in which case it requires string quoting.",
515 ),
516 locations: vec![Span::new(i, i)],
517 })
518 } else {
519 Err(HeaderError {
520 kind: HeaderErrorKind::IllegalName,
521 locations: vec![Span::new(i, i)],
522 })
523 }
524 }
525 }
526 }
527
528 fn lookahead_is(&self, s: &'static str, i: usize) -> Option<usize> {
529 if self.src[i..].starts_with(s) {
530 Some(i + s.len())
531 } else {
532 None
533 }
534 }
535
536 fn parse_ws(&self, i: usize) -> usize {
537 RE_LEADING_WS
538 .find(&self.src[i..])
539 .map(|m| m.end() + i)
540 .unwrap_or(i)
541 }
542}
543
544#[doc(hidden)]
546pub type Header<T> = MarkMap<String, HeaderValue<T>>;
547
548impl TryFrom<YaccKind> for Value<Location> {
549 type Error = HeaderError<Location>;
550 fn try_from(kind: YaccKind) -> Result<Value<Location>, HeaderError<Location>> {
551 let from_loc = Location::Other("From<YaccKind>".to_string());
552 Ok(match kind {
553 YaccKind::Grmtools => Value::Setting(Setting::Unitary(Namespaced {
554 namespace: Some(("yacckind".to_string(), from_loc.clone())),
555 member: ("grmtools".to_string(), from_loc),
556 })),
557 YaccKind::Eco => Value::Setting(Setting::Unitary(Namespaced {
558 namespace: Some(("yacckind".to_string(), from_loc.clone())),
559 member: ("eco".to_string(), from_loc),
560 })),
561 YaccKind::Original(action_kind) => Value::Setting(Setting::Constructor {
562 ctor: Namespaced {
563 namespace: Some(("yacckind".to_string(), from_loc.clone())),
564 member: ("original".to_string(), from_loc.clone()),
565 },
566 arg: match action_kind {
567 YaccOriginalActionKind::NoAction => Namespaced {
568 namespace: Some(("yaccoriginalactionkind".to_string(), from_loc.clone())),
569 member: ("noaction".to_string(), from_loc),
570 },
571 YaccOriginalActionKind::UserAction => Namespaced {
572 namespace: Some(("yaccoriginalactionkind".to_string(), from_loc.clone())),
573 member: ("useraction".to_string(), from_loc),
574 },
575 YaccOriginalActionKind::GenericParseTree => Namespaced {
576 namespace: Some(("yaccoriginalactionkind".to_string(), from_loc.clone())),
577 member: ("genericparsetree".to_string(), from_loc),
578 },
579 },
580 }),
581 })
582 }
583}
584
585impl<T: Clone> TryFrom<&Value<T>> for YaccKind {
586 type Error = HeaderError<T>;
587 fn try_from(value: &Value<T>) -> Result<YaccKind, HeaderError<T>> {
588 let mut err_locs = Vec::new();
589 match value {
590 Value::Setting(Setting::Unitary(Namespaced {
591 namespace,
592 member: (yk_value, yk_value_loc),
593 })) => {
594 if let Some((ns, ns_loc)) = namespace
595 && ns != "yacckind"
596 {
597 err_locs.push(ns_loc.clone());
598 }
599 let yacckinds = [
600 ("grmtools".to_string(), YaccKind::Grmtools),
601 ("eco".to_string(), YaccKind::Eco),
602 ];
603 let yk_found = yacckinds
604 .iter()
605 .find_map(|(yk_str, yk)| (yk_str == yk_value).then_some(yk));
606 if let Some(yk) = yk_found {
607 if err_locs.is_empty() {
608 Ok(*yk)
609 } else {
610 Err(HeaderError {
611 kind: HeaderErrorKind::InvalidEntry("yacckind"),
612 locations: err_locs,
613 })
614 }
615 } else {
616 err_locs.push(yk_value_loc.clone());
617 Err(HeaderError {
618 kind: HeaderErrorKind::InvalidEntry("yacckind"),
619 locations: err_locs,
620 })
621 }
622 }
623 Value::Setting(Setting::Constructor {
624 ctor:
625 Namespaced {
626 namespace: yk_namespace,
627 member: (yk_str, yk_loc),
628 },
629 arg:
630 Namespaced {
631 namespace: ak_namespace,
632 member: (ak_str, ak_loc),
633 },
634 }) => {
635 if let Some((yk_ns, yk_ns_loc)) = yk_namespace
636 && yk_ns != "yacckind"
637 {
638 err_locs.push(yk_ns_loc.clone());
639 }
640
641 if yk_str != "original" {
642 err_locs.push(yk_loc.clone());
643 }
644
645 if let Some((ak_ns, ak_ns_loc)) = ak_namespace
646 && ak_ns != "yaccoriginalactionkind"
647 {
648 err_locs.push(ak_ns_loc.clone());
649 }
650 let actionkinds = [
651 ("noaction", YaccOriginalActionKind::NoAction),
652 ("useraction", YaccOriginalActionKind::UserAction),
653 ("genericparsetree", YaccOriginalActionKind::GenericParseTree),
654 ];
655 let yk_found = actionkinds.iter().find_map(|(actionkind_str, actionkind)| {
656 (ak_str == actionkind_str).then_some(YaccKind::Original(*actionkind))
657 });
658
659 if let Some(yk) = yk_found {
660 if err_locs.is_empty() {
661 Ok(yk)
662 } else {
663 Err(HeaderError {
664 kind: HeaderErrorKind::InvalidEntry("yacckind"),
665 locations: err_locs,
666 })
667 }
668 } else {
669 err_locs.push(ak_loc.clone());
670 Err(HeaderError {
671 kind: HeaderErrorKind::InvalidEntry("yacckind"),
672 locations: err_locs,
673 })
674 }
675 }
676 val => Err(HeaderError {
677 kind: HeaderErrorKind::InvalidEntry("yacckind"),
678 locations: vec![val.primary_location().clone()],
679 }),
680 }
681 }
682}
683
684#[cfg(test)]
685mod test {
686 use super::*;
687
688 #[test]
689 fn test_header_missing_curly_bracket() {
690 let srcs = [
691 "%grmtools { a",
692 "%grmtools { a, b",
693 "%grmtools { a, b,",
694 "%grmtools { yacckind",
695 "%grmtools { yacckind:",
696 "%grmtools { yacckind: GrmTools",
697 "%grmtools { yacckind: GrmTools,",
698 r#"%grmtools { test_files: ""#,
699 r#"%grmtools { test_files: "test"#,
700 r#"%grmtools { test_files: "test""#,
701 r#"%grmtools { test_files: "test","#,
702 "%grmtools { !flag",
703 "%grmtools { !flag,",
704 ];
705 for src in srcs {
706 for flag in [true, false] {
707 let parser = GrmtoolsSectionParser::new(src, flag);
708 let res = parser.parse();
709 assert!(res.is_err());
710 }
711 }
712 }
713
714 #[test]
715 fn test_header_missing_curly_bracket_empty() {
716 let src = "%grmtools {";
717 for flag in [true, false] {
718 let parser = GrmtoolsSectionParser::new(src, flag);
719 let res = parser.parse();
720 assert!(res.is_err());
721 }
722 }
723
724 #[test]
725 fn test_header_missing_curly_bracket_invalid() {
726 let src = "%grmtools {####";
727 for flag in [true, false] {
728 let parser = GrmtoolsSectionParser::new(src, flag);
729 let res = parser.parse();
730 assert!(res.is_err());
731 }
732 }
733
734 #[test]
735 fn test_header_duplicates() {
736 let src = "%grmtools {dupe, !dupe, dupe: test}";
737 for flag in [true, false] {
738 let parser = GrmtoolsSectionParser::new(src, flag);
739 let res = parser.parse();
740 let errs = res.unwrap_err();
741 assert_eq!(errs.len(), 1);
742 assert_eq!(errs[0].kind, HeaderErrorKind::DuplicateEntry);
743 assert_eq!(errs[0].locations.len(), 3);
744 }
745 }
746
747 #[test]
748 fn test_unquoted_globs() {
749 let srcs = [
750 "%grmtools {test_files: *.test,}",
751 "%grmtools {test_files: foo*.test,}",
752 ];
753 for src in srcs {
754 let parser = GrmtoolsSectionParser::new(src, true);
755 let res = parser.parse();
756 let errs = res.unwrap_err();
757 assert_eq!(errs.len(), 1);
758 match errs[0] {
759 HeaderError {
760 kind: HeaderErrorKind::UnexpectedToken('*', _),
761 locations: _,
762 } => (),
763 _ => panic!("Expected glob specific error"),
764 }
765 }
766 }
767}