Skip to main content

cfgrammar/
header.rs

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/// An error regarding the `%grmtools` header section.
12///
13/// It could be any of:
14///
15/// * An error during parsing the section.
16/// * An error resulting from a value in the section having an invalid value.
17#[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// This is essentially a tuple that needs a newtype so we can implement `From` for it.
50// Thus we aren't worried about it being `pub`.
51#[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/// Parser for the `%grmtools` section
94#[doc(hidden)]
95pub struct GrmtoolsSectionParser<'input> {
96    src: &'input str,
97    required: bool,
98}
99
100/// The value contained within a `Header`
101///
102/// To be useful across diverse crates this types fields are largely limited to types derived from `core::` types.
103/// like booleans, numeric types, and string values. The exception to this is `Namespaced` which still must
104///  be able to be marshalled through a string value.
105///
106/// The generic parameter `T` indicates the source of the value, for values read from a `%grmtools` section
107/// this should be a `Span`. For other values which stem from command line arguments, or a programmatic interface
108/// this should be a `Location`.
109#[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    /// A Rust like value, with an optional type namespace.
116    /// For instance, you can omit the optional prefix `YaccKind::`
117    /// specifying `YaccKind::Grmtools` or `Grmtools`.
118    ///
119    /// Further examples of `Namespaced` values:
120    /// * YaccKind::Original(UserAction) or YaccKind::Original(YaccOriginalActionKind::UserAction)
121    /// * Original(UserAction) or Original(YaccOriginalActionKind::UserAction)
122    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    /// Returns the [SpansKind] associated with this error.
143    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                // If the above regex matches we expect this to succeed.
233                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                    // Trim the leading and trailing quotes.
242                    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                    // After the trailing quotes.
246                    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        // Either a name alone, or a type::name.
317        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    /// Parses any `%grmtools` section at the beginning of `src`.
333    /// If `required` is true, the parse function will
334    /// return an error if the `%grmtools` section is
335    /// missing.
336    ///
337    /// If required is set and the section is empty, no error will be
338    /// produced. If a caller requires a value they should
339    /// produce an error that specifies the required value.
340    ///
341    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/// A data structure representation of the %grmtools section.
477#[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}