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::{collections::HashMap, 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
93#[doc(hidden)]
95pub struct GrmtoolsSectionParser<'input> {
96 src: &'input str,
97 required: bool,
98}
99
100#[derive(Debug, Clone, Eq, PartialEq, Hash)]
110pub enum Value<T> {
111 String(String, T),
112 Num(u64, T),
113 Bool(bool, T),
114 Array(Vec<Value<T>>, T),
115 Namespaced(String, T),
123}
124
125impl From<Value<Span>> for Value<Location> {
126 fn from(it: Value<Span>) -> Value<Location> {
127 use Value as GV;
128 match it {
129 GV::String(v, span) => GV::String(v, Location::Span(span)),
130 GV::Num(v, span) => GV::Num(v, Location::Span(span)),
131 GV::Bool(v, span) => GV::Bool(v, Location::Span(span)),
132 GV::Array(mut v, span) => GV::Array(
133 v.drain(..).map(|val| val.into()).collect::<Vec<_>>(),
134 Location::Span(span),
135 ),
136 GV::Namespaced(v, span) => GV::Namespaced(v, Location::Span(span)),
137 }
138 }
139}
140
141impl<T> HeaderError<T> {
142 pub fn spanskind(&self) -> SpansKind {
144 match self.kind {
145 HeaderErrorKind::DuplicateEntry => SpansKind::DuplicationError,
146 _ => SpansKind::Error,
147 }
148 }
149}
150
151static RE_LEADING_WS: LazyLock<Regex> =
152 LazyLock::new(|| Regex::new(r"^[\p{Pattern_White_Space}]*").unwrap());
153static RE_NAME: LazyLock<Regex> = LazyLock::new(|| {
154 RegexBuilder::new(r"^[A-Z][A-Z_\.]*")
155 .case_insensitive(true)
156 .build()
157 .unwrap()
158});
159#[doc(hidden)]
160pub static RE_CRATE_DOT: LazyLock<Regex> = LazyLock::new(|| {
161 RegexBuilder::new(r"^[A-Z][A-Z_]*\.")
162 .case_insensitive(true)
163 .build()
164 .unwrap()
165});
166static RE_DIGITS: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^[0-9]+").unwrap());
167static RE_STRING: LazyLock<Regex> = LazyLock::new(|| Regex::new(r#"^\"(\\.|[^"\\])*\""#).unwrap());
168#[doc(hidden)]
169pub static CRATE_KEY_MAP: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
170 let mut map = HashMap::new();
171 let cfgrammar = ["yacckind"];
172 let lrpar = ["recoverer", "test_files", "serialisation_format"];
173
174 let lrlex = ["lexerkind", "allow_wholeline_comments", "posix_escapes"];
175 let regex = [
176 "case_insensitive",
177 "dot_matches_new_line",
178 "multi_line",
179 "octal",
180 "swap_greed",
181 "ignore_whitespace",
182 "unicode",
183 "size_limit",
184 "dfa_size_limit",
185 "nest_limit",
186 ];
187 for s in cfgrammar {
188 map.insert(s, "cfgrammar");
189 }
190 for s in lrpar {
191 map.insert(s, "lrpar");
192 }
193 for s in lrlex {
194 map.insert(s, "lrlex");
195 }
196 for s in regex {
197 map.insert(s, "regex");
198 }
199 map
200});
201
202const MAGIC: &str = "%grmtools";
203
204fn add_duplicate_occurrence<T: Eq + PartialEq + Clone>(
205 errs: &mut Vec<HeaderError<T>>,
206 kind: HeaderErrorKind,
207 orig_loc: T,
208 dup_loc: T,
209) {
210 if !errs.iter_mut().any(|e| {
211 if e.kind == kind && e.locations[0] == orig_loc {
212 e.locations.push(dup_loc.clone());
213 true
214 } else {
215 false
216 }
217 }) {
218 errs.push(HeaderError {
219 kind,
220 locations: vec![orig_loc, dup_loc],
221 });
222 }
223}
224
225impl<'input> GrmtoolsSectionParser<'input> {
226 fn parse_value(&'_ self, mut i: usize) -> Result<(Value<Span>, usize), HeaderError<Span>> {
227 i = self.parse_ws(i);
228 match RE_DIGITS.find(&self.src[i..]) {
229 Some(m) => {
230 let num_span = Span::new(i + m.start(), i + m.end());
231 let num_str = &self.src[num_span.start()..num_span.end()];
232 let num = str::parse::<u64>(num_str).unwrap();
234 let val = Value::Num(num, num_span);
235 i = self.parse_ws(num_span.end());
236 Ok((val, i))
237 }
238 None => match RE_STRING.find(&self.src[i..]) {
239 Some(m) => {
240 let end = i + m.end();
241 let str_span = Span::new(i + m.start() + 1, end - 1);
243 let str = &self.src[str_span.start()..str_span.end()];
244 let setting = Value::String(str.to_string(), str_span);
245 i = self.parse_ws(end);
247 Ok((setting, i))
248 }
249 None => {
250 if let Some(mut j) = self.lookahead_is("[", i) {
251 let mut vals = Vec::new();
252 loop {
253 j = self.parse_ws(j);
254 if let Some(end_pos) = self.lookahead_is("]", j) {
255 return Ok((Value::Array(vals, Span::new(i, end_pos)), end_pos));
256 }
257 if let Ok((val, k)) = self.parse_value(j) {
258 vals.push(val);
259 j = self.parse_ws(k);
260 }
261 if let Some(k) = self.lookahead_is(",", j) {
262 j = k
263 }
264 }
265 } else {
266 let ((path_val, path_span), j) = self.parse_namespaced(i)?;
267 i = self.parse_ws(j);
268 if let Some(j) = self.lookahead_is("(", i) {
269 let ((arg, _), j) = self.parse_namespaced(j)?;
270 i = self.parse_ws(j);
271 if let Some(j) = self.lookahead_is(")", i) {
272 i = self.parse_ws(j);
273 let span = Span::new(path_span.start(), j);
274 Ok(((Value::Namespaced(format!("{path_val}({arg})"), span)), i))
275 } else {
276 Err(HeaderError {
277 kind: HeaderErrorKind::ExpectedToken(')'),
278 locations: vec![Span::new(i, i)],
279 })
280 }
281 } else {
282 Ok((Value::Namespaced(path_val, path_span), i))
283 }
284 }
285 }
286 },
287 }
288 }
289
290 pub fn parse_key_value(
291 &'_ self,
292 mut i: usize,
293 ) -> Result<(String, Span, Value<Span>, usize), HeaderError<Span>> {
294 if let Some(j) = self.lookahead_is("!", i) {
295 let (flag_name, k) = self.parse_name(j)?;
296 Ok((
297 flag_name,
298 Span::new(j, k),
299 Value::Bool(false, Span::new(i, k)),
300 self.parse_ws(k),
301 ))
302 } else {
303 let (key_name, j) = self.parse_name(i)?;
304 let key_span = Span::new(i, j);
305 i = self.parse_ws(j);
306 if let Some(j) = self.lookahead_is(":", i) {
307 let (val, j) = self.parse_value(j)?;
308 Ok((key_name, key_span, val, j))
309 } else {
310 Ok((key_name, key_span, Value::Bool(true, key_span), i))
311 }
312 }
313 }
314
315 fn parse_namespaced(&self, mut i: usize) -> Result<((String, Span), usize), HeaderError<Span>> {
316 let (name, j) = self.parse_name(i)?;
318 let name_span = Span::new(i, j);
319 i = self.parse_ws(j);
320 if let Some(j) = self.lookahead_is("::", i) {
321 i = self.parse_ws(j);
322 let (member_val, j) = self.parse_name(i)?;
323 let member_val_span = Span::new(i, j);
324 i = self.parse_ws(j);
325 let span = Span::new(name_span.start(), member_val_span.end());
326 Ok(((format!("{name}::{member_val}"), span), i))
327 } else {
328 Ok(((name, name_span), i))
329 }
330 }
331
332 pub fn new(src: &'input str, required: bool) -> Self {
342 Self { src, required }
343 }
344
345 #[allow(clippy::type_complexity)]
346 pub fn parse(&'_ self) -> Result<(Header<Span>, usize), Vec<HeaderError<Span>>> {
347 let mut errs = Vec::new();
348 if let Some(mut i) = self.lookahead_is(MAGIC, self.parse_ws(0)) {
349 let mut ret = Header::new();
350 i = self.parse_ws(i);
351 let section_start_pos = i;
352 if let Some(j) = self.lookahead_is("{", i) {
353 i = self.parse_ws(j);
354 while self.lookahead_is("}", i).is_none() && i < self.src.len() {
355 let (key, key_loc, val, j) = match self.parse_key_value(i) {
356 Ok((key, key_loc, val, pos)) => {
357 let key = if !RE_CRATE_DOT.is_match(&key) {
358 if let Some(crate_name) = CRATE_KEY_MAP.get(key.as_str()) {
359 format!("{crate_name}.{key}")
360 } else {
361 key
362 }
363 } else {
364 key
365 };
366 (key, key_loc, val, pos)
367 }
368 Err(e) => {
369 errs.push(e);
370 return Err(errs);
371 }
372 };
373 match ret.entry(key) {
374 Entry::Occupied(orig) => {
375 let HeaderValue(orig_loc, _): &HeaderValue<Span> = orig.get();
376 add_duplicate_occurrence(
377 &mut errs,
378 HeaderErrorKind::DuplicateEntry,
379 *orig_loc,
380 key_loc,
381 )
382 }
383 Entry::Vacant(entry) => {
384 entry.insert(HeaderValue(key_loc, val));
385 }
386 }
387 if let Some(j) = self.lookahead_is(",", j) {
388 i = self.parse_ws(j);
389 continue;
390 } else {
391 i = self.parse_ws(j);
392 break;
393 }
394 }
395 if let Some(j) = self.lookahead_is("*", i) {
396 errs.push(HeaderError {
397 kind: HeaderErrorKind::UnexpectedToken(
398 '*',
399 "perhaps this is a glob, in which case it requires string quoting.",
400 ),
401 locations: vec![Span::new(i, j)],
402 });
403 Err(errs)
404 } else if let Some(i) = self.lookahead_is("}", i) {
405 if errs.is_empty() {
406 Ok((ret, i))
407 } else {
408 Err(errs)
409 }
410 } else {
411 errs.push(HeaderError {
412 kind: HeaderErrorKind::ExpectedToken('}'),
413 locations: vec![Span::new(section_start_pos, i)],
414 });
415 Err(errs)
416 }
417 } else {
418 errs.push(HeaderError {
419 kind: HeaderErrorKind::ExpectedToken('{'),
420 locations: vec![Span::new(i, i)],
421 });
422 Err(errs)
423 }
424 } else if self.required {
425 errs.push(HeaderError {
426 kind: HeaderErrorKind::MissingGrmtoolsSection,
427 locations: vec![Span::new(0, 0)],
428 });
429 Err(errs)
430 } else {
431 Ok((Header::new(), 0))
432 }
433 }
434
435 fn parse_name(&self, i: usize) -> Result<(String, usize), HeaderError<Span>> {
436 match RE_NAME.find(&self.src[i..]) {
437 Some(m) => {
438 assert_eq!(m.start(), 0);
439 Ok((self.src[i..i + m.end()].to_string(), i + m.end()))
440 }
441 None => {
442 if self.src[i..].starts_with("*") {
443 Err(HeaderError {
444 kind: HeaderErrorKind::UnexpectedToken(
445 '*',
446 "perhaps this is a glob, in which case it requires string quoting.",
447 ),
448 locations: vec![Span::new(i, i)],
449 })
450 } else {
451 Err(HeaderError {
452 kind: HeaderErrorKind::IllegalName,
453 locations: vec![Span::new(i, i)],
454 })
455 }
456 }
457 }
458 }
459
460 fn lookahead_is(&self, s: &'static str, i: usize) -> Option<usize> {
461 if self.src[i..].starts_with(s) {
462 Some(i + s.len())
463 } else {
464 None
465 }
466 }
467
468 fn parse_ws(&self, i: usize) -> usize {
469 RE_LEADING_WS
470 .find(&self.src[i..])
471 .map(|m| m.end() + i)
472 .unwrap_or(i)
473 }
474}
475
476#[doc(hidden)]
478pub type Header<T> = MarkMap<String, HeaderValue<T>>;
479
480impl TryFrom<YaccKind> for Value<Location> {
481 type Error = HeaderError<Location>;
482 fn try_from(kind: YaccKind) -> Result<Value<Location>, HeaderError<Location>> {
483 let from_loc = Location::Other("From<YaccKind>".to_string());
484 Ok(Value::Namespaced(format!("YaccKind::{kind:?}"), from_loc))
485 }
486}
487
488impl<T: Clone> TryFrom<&Value<T>> for YaccKind {
489 type Error = HeaderError<T>;
490 fn try_from(value: &Value<T>) -> Result<YaccKind, HeaderError<T>> {
491 match value {
492 Value::Namespaced(kind, loc) => match kind.as_str() {
493 "YaccKind::Grmtools" | "Grmtools" => Ok(YaccKind::Grmtools),
494 "YaccKind::Eco" | "Eco" => Ok(YaccKind::Eco),
495 "YaccKind::Original(UserAction)"
496 | "Original(UserAction)"
497 | "Original(YaccOriginalActionKind::UserAction)"
498 | "YaccKind::Original(YaccOriginalActionKind::UserAction)" => {
499 Ok(YaccKind::Original(YaccOriginalActionKind::UserAction))
500 }
501 "YaccKind::Original(NoAction)"
502 | "Original(NoAction)"
503 | "Original(YaccOriginalActionKind::NoAction)"
504 | "YaccKind::Original(YaccOriginalActionKind::NoAction)" => {
505 Ok(YaccKind::Original(YaccOriginalActionKind::NoAction))
506 }
507 "YaccKind::Original(GenericParseTree)"
508 | "Original(GenericParseTree)"
509 | "Original(YaccOriginalActionKind::GenericParseTree)"
510 | "YaccKind::Original(YaccOriginalActionKind::GenericParseTree)" => {
511 Ok(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
512 }
513 _ => Err(HeaderError {
514 kind: HeaderErrorKind::InvalidEntry("cfgrammar.yacckind"),
515 locations: vec![loc.clone()],
516 }),
517 },
518 val => Err(HeaderError {
519 kind: HeaderErrorKind::InvalidEntry("cfgrammar.yacckind"),
520 locations: vec![val.primary_location().clone()],
521 }),
522 }
523 }
524}
525
526impl<T> Value<T> {
527 #[doc(hidden)]
528 pub fn primary_location(&self) -> &T {
529 match self {
530 Self::Array(_, loc)
531 | Self::Bool(_, loc)
532 | Self::Num(_, loc)
533 | Self::Namespaced(_, loc)
534 | Self::String(_, loc) => loc,
535 }
536 }
537}
538
539#[cfg(test)]
540mod test {
541 use super::*;
542
543 #[test]
544 fn test_header_missing_curly_bracket() {
545 let srcs = [
546 "%grmtools { a",
547 "%grmtools { a, b",
548 "%grmtools { a, b,",
549 "%grmtools { yacckind",
550 "%grmtools { yacckind:",
551 "%grmtools { yacckind: GrmTools",
552 "%grmtools { yacckind: GrmTools,",
553 r#"%grmtools { test_files: ""#,
554 r#"%grmtools { test_files: "test"#,
555 r#"%grmtools { test_files: "test""#,
556 r#"%grmtools { test_files: "test","#,
557 "%grmtools { !flag",
558 "%grmtools { !flag,",
559 ];
560 for src in srcs {
561 for flag in [true, false] {
562 let parser = GrmtoolsSectionParser::new(src, flag);
563 let res = parser.parse();
564 assert!(res.is_err());
565 }
566 }
567 }
568
569 #[test]
570 fn test_header_missing_curly_bracket_empty() {
571 let src = "%grmtools {";
572 for flag in [true, false] {
573 let parser = GrmtoolsSectionParser::new(src, flag);
574 let res = parser.parse();
575 assert!(res.is_err());
576 }
577 }
578
579 #[test]
580 fn test_header_missing_curly_bracket_invalid() {
581 let src = "%grmtools {####";
582 for flag in [true, false] {
583 let parser = GrmtoolsSectionParser::new(src, flag);
584 let res = parser.parse();
585 assert!(res.is_err());
586 }
587 }
588
589 #[test]
590 fn test_header_duplicates() {
591 let src = "%grmtools {dupe, !dupe, dupe: test}";
592 for flag in [true, false] {
593 let parser = GrmtoolsSectionParser::new(src, flag);
594 let res = parser.parse();
595 let errs = res.unwrap_err();
596 assert_eq!(errs.len(), 1);
597 assert_eq!(errs[0].kind, HeaderErrorKind::DuplicateEntry);
598 assert_eq!(errs[0].locations.len(), 3);
599 }
600 }
601
602 #[test]
603 fn test_unquoted_globs() {
604 let srcs = [
605 "%grmtools {test_files: *.test,}",
606 "%grmtools {test_files: foo*.test,}",
607 ];
608 for src in srcs {
609 let parser = GrmtoolsSectionParser::new(src, true);
610 let res = parser.parse();
611 let errs = res.unwrap_err();
612 assert_eq!(errs.len(), 1);
613 match errs[0] {
614 HeaderError {
615 kind: HeaderErrorKind::UnexpectedToken('*', _),
616 locations: _,
617 } => (),
618 _ => panic!("Expected glob specific error"),
619 }
620 }
621 }
622}