diff --git a/cfgrammar/src/lib/header.rs b/cfgrammar/src/lib/header.rs index a75012623..f4c2482fc 100644 --- a/cfgrammar/src/lib/header.rs +++ b/cfgrammar/src/lib/header.rs @@ -1,6 +1,8 @@ use crate::{ markmap::{Entry, MarkMap}, - yacc::{ParserError, YaccKind, YaccOriginalActionKind}, + yacc::{ + parser::SpansKind, YaccGrammarError, YaccGrammarErrorKind, YaccKind, YaccOriginalActionKind, + }, Location, Span, }; use lazy_static::lazy_static; @@ -14,21 +16,37 @@ use std::{error::Error, fmt}; /// * An error during parsing the section. /// * An error resulting from a value in the section having an invalid value. #[derive(Debug, Clone)] -pub struct HeaderError { +#[doc(hidden)] +pub struct HeaderError { pub kind: HeaderErrorKind, - pub locations: Vec, + pub locations: Vec, } -impl Error for HeaderError {} -impl fmt::Display for HeaderError { +impl Error for HeaderError {} +impl fmt::Display for HeaderError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.kind) } } -impl From for ParserError { - fn from(e: HeaderError) -> ParserError { - ParserError::HeaderError(e) +impl From> for YaccGrammarError { + fn from(e: HeaderError) -> YaccGrammarError { + YaccGrammarError { + kind: YaccGrammarErrorKind::Header(e.kind, e.spanskind()), + spans: e.locations, + } + } +} + +// This is essentially a tuple that needs a newtype so we can implement `From` for it. +// Thus we aren't worried about it being `pub`. +#[derive(Debug, PartialEq)] +#[doc(hidden)] +pub struct HeaderValue(pub T, pub Value); + +impl From> for HeaderValue { + fn from(hv: HeaderValue) -> HeaderValue { + HeaderValue(hv.0.into(), hv.1.into()) } } @@ -60,6 +78,16 @@ impl fmt::Display for HeaderErrorKind { } } +impl HeaderError { + /// Returns the [SpansKind] associated with this error. + pub fn spanskind(&self) -> SpansKind { + match self.kind { + HeaderErrorKind::DuplicateEntry => SpansKind::DuplicationError, + _ => SpansKind::Error, + } + } +} + /// Indicates a value prefixed by an optional namespace. /// `Foo::Bar` with optional `Foo` specified being /// ```rust,ignore @@ -78,24 +106,24 @@ impl fmt::Display for HeaderErrorKind { /// ``` #[derive(Debug, Eq, PartialEq)] #[doc(hidden)] -pub struct Namespaced { - pub namespace: Option<(String, Location)>, - pub member: (String, Location), +pub struct Namespaced { + pub namespace: Option<(String, T)>, + pub member: (String, T), } #[derive(Debug, Eq, PartialEq)] #[doc(hidden)] -pub enum Setting { +pub enum Setting { /// A value like `YaccKind::Grmtools` - Unitary(Namespaced), + Unitary(Namespaced), /// A value like `YaccKind::Original(UserActions)`. /// In that example the field ctor would be: `Namespaced { namespace: "YaccKind", member: "Original" }`. /// The field would be `Namespaced{ None, UserActions }`. Constructor { - ctor: Namespaced, - arg: Namespaced, + ctor: Namespaced, + arg: Namespaced, }, - Num(u64, Location), + Num(u64, T), } /// Parser for the `%grmtools` section @@ -111,9 +139,48 @@ pub struct GrmtoolsSectionParser<'input> { /// like booleans, numeric types, and string values. #[derive(Debug, Eq, PartialEq)] #[doc(hidden)] -pub enum Value { - Flag(bool, Location), - Setting(Setting), +pub enum Value { + Flag(bool, T), + Setting(Setting), +} + +impl From> for Value { + fn from(v: Value) -> Value { + match v { + Value::Flag(flag, u) => Value::Flag(flag, u.into()), + Value::Setting(s) => Value::Setting(match s { + Setting::Unitary(Namespaced { + namespace, + member: (m, ml), + }) => Setting::Unitary(Namespaced { + namespace: namespace.map(|(n, nl)| (n, nl.into())), + member: (m, ml.into()), + }), + Setting::Constructor { + ctor: + Namespaced { + namespace: ctor_ns, + member: (ctor_m, ctor_ml), + }, + arg: + Namespaced { + namespace: arg_ns, + member: (arg_m, arg_ml), + }, + } => Setting::Constructor { + ctor: Namespaced { + namespace: ctor_ns.map(|(ns, ns_l)| (ns, ns_l.into())), + member: (ctor_m, ctor_ml.into()), + }, + arg: Namespaced { + namespace: arg_ns.map(|(ns, ns_l)| (ns, ns_l.into())), + member: (arg_m, arg_ml.into()), + }, + }, + Setting::Num(num, num_loc) => Setting::Num(num, num_loc.into()), + }), + } + } } lazy_static! { @@ -127,11 +194,11 @@ lazy_static! { const MAGIC: &str = "%grmtools"; -fn add_duplicate_occurrence( - errs: &mut Vec, +fn add_duplicate_occurrence( + errs: &mut Vec>, kind: HeaderErrorKind, - orig_loc: Location, - dup_loc: Location, + orig_loc: T, + dup_loc: T, ) { if !errs.iter_mut().any(|e| { if e.kind == kind && e.locations[0] == orig_loc { @@ -152,18 +219,18 @@ impl<'input> GrmtoolsSectionParser<'input> { pub fn parse_value( &'_ self, mut i: usize, - ) -> Result<(String, Location, Value, usize), HeaderError> { + ) -> Result<(String, Span, Value, usize), HeaderError> { if let Some(j) = self.lookahead_is("!", i) { let (flag_name, k) = self.parse_name(j)?; Ok(( flag_name, - Location::Span(Span::new(j, k)), - Value::Flag(false, Location::Span(Span::new(i, k))), + Span::new(j, k), + Value::Flag(false, Span::new(i, k)), self.parse_ws(k), )) } else { let (key_name, j) = self.parse_name(i)?; - let key_span = Location::Span(Span::new(i, j)); + let key_span = Span::new(i, j); i = self.parse_ws(j); if let Some(j) = self.lookahead_is(":", i) { i = self.parse_ws(j); @@ -173,7 +240,7 @@ impl<'input> GrmtoolsSectionParser<'input> { let num_str = &self.src[num_span.start()..num_span.end()]; // If the above regex matches we expect this to succeed. let num = str::parse::(num_str).unwrap(); - let val = Setting::Num(num, Location::Span(num_span)); + let val = Setting::Num(num, num_span); i = self.parse_ws(num_span.end()); Ok((key_name, key_span, Value::Setting(val), i)) } @@ -197,7 +264,7 @@ impl<'input> GrmtoolsSectionParser<'input> { } else { Err(HeaderError { kind: HeaderErrorKind::ExpectedToken(')'), - locations: vec![Location::Span(Span::new(i, i))], + locations: vec![Span::new(i, i)], }) } } else { @@ -211,20 +278,23 @@ impl<'input> GrmtoolsSectionParser<'input> { } } } else { - Ok((key_name, key_span.clone(), Value::Flag(true, key_span), i)) + Ok((key_name, key_span, Value::Flag(true, key_span), i)) } } } - fn parse_namespaced(&self, mut i: usize) -> Result<(Namespaced, usize), HeaderError> { + fn parse_namespaced( + &self, + mut i: usize, + ) -> Result<(Namespaced, usize), HeaderError> { // Either a name alone, or a namespace which will be followed by a member. let (name, j) = self.parse_name(i)?; - let name_span = Location::Span(Span::new(i, j)); + let name_span = Span::new(i, j); i = self.parse_ws(j); if let Some(j) = self.lookahead_is("::", i) { i = self.parse_ws(j); let (member_val, j) = self.parse_name(i)?; - let member_val_span = Location::Span(Span::new(i, j)); + let member_val_span = Span::new(i, j); i = self.parse_ws(j); Ok(( Namespaced { @@ -258,7 +328,7 @@ impl<'input> GrmtoolsSectionParser<'input> { } #[allow(clippy::type_complexity)] - pub fn parse(&'_ self) -> Result<(Header, usize), Vec> { + pub fn parse(&'_ self) -> Result<(Header, usize), Vec>> { let mut errs = Vec::new(); if let Some(mut i) = self.lookahead_is(MAGIC, self.parse_ws(0)) { let mut ret = Header::new(); @@ -276,16 +346,16 @@ impl<'input> GrmtoolsSectionParser<'input> { }; match ret.entry(key) { Entry::Occupied(orig) => { - let (orig_loc, _): &(Location, Value) = orig.get(); + let HeaderValue(orig_loc, _): &HeaderValue = orig.get(); add_duplicate_occurrence( &mut errs, HeaderErrorKind::DuplicateEntry, - orig_loc.clone(), + *orig_loc, key_loc, ) } Entry::Vacant(entry) => { - entry.insert((key_loc, val)); + entry.insert(HeaderValue(key_loc, val)); } } if let Some(j) = self.lookahead_is(",", j) { @@ -305,24 +375,21 @@ impl<'input> GrmtoolsSectionParser<'input> { } else { errs.push(HeaderError { kind: HeaderErrorKind::ExpectedToken('}'), - locations: vec![Location::Span(Span::new( - section_start_pos, - self.src.len(), - ))], + locations: vec![Span::new(section_start_pos, self.src.len())], }); Err(errs) } } else { errs.push(HeaderError { kind: HeaderErrorKind::ExpectedToken('{'), - locations: vec![Location::Span(Span::new(i, i))], + locations: vec![Span::new(i, i)], }); Err(errs) } } else if self.required { errs.push(HeaderError { kind: HeaderErrorKind::MissingGrmtoolsSection, - locations: vec![Location::Span(Span::new(0, 0))], + locations: vec![Span::new(0, 0)], }); Err(errs) } else { @@ -330,7 +397,7 @@ impl<'input> GrmtoolsSectionParser<'input> { } } - fn parse_name(&self, i: usize) -> Result<(String, usize), HeaderError> { + fn parse_name(&self, i: usize) -> Result<(String, usize), HeaderError> { match RE_NAME.find(&self.src[i..]) { Some(m) => { assert_eq!(m.start(), 0); @@ -341,7 +408,7 @@ impl<'input> GrmtoolsSectionParser<'input> { } None => Err(HeaderError { kind: HeaderErrorKind::IllegalName, - locations: vec![Location::Span(Span::new(i, i))], + locations: vec![Span::new(i, i)], }), } } @@ -363,11 +430,12 @@ impl<'input> GrmtoolsSectionParser<'input> { } /// A data structure representation of the %grmtools section. -pub type Header = MarkMap; +#[doc(hidden)] +pub type Header = MarkMap>; -impl TryFrom for Value { - type Error = HeaderError; - fn try_from(kind: YaccKind) -> Result { +impl TryFrom for Value { + type Error = HeaderError; + fn try_from(kind: YaccKind) -> Result, HeaderError> { let from_loc = Location::Other("From".to_string()); Ok(match kind { YaccKind::Grmtools => Value::Setting(Setting::Unitary(Namespaced { @@ -402,9 +470,9 @@ impl TryFrom for Value { } } -impl TryFrom<&Value> for YaccKind { - type Error = HeaderError; - fn try_from(value: &Value) -> Result { +impl TryFrom<&Value> for YaccKind { + type Error = HeaderError; + fn try_from(value: &Value) -> Result> { let mut err_locs = Vec::new(); match value { Value::Flag(_, loc) => Err(HeaderError { diff --git a/cfgrammar/src/lib/markmap.rs b/cfgrammar/src/lib/markmap.rs index dd7fdbb7e..6cde6bad7 100644 --- a/cfgrammar/src/lib/markmap.rs +++ b/cfgrammar/src/lib/markmap.rs @@ -1,25 +1,28 @@ use std::borrow::Borrow; - -// MarkMap is a key value data structure that uses an API similar to that of -// `std::collections::HashMap` and `std::collections::BTreeMap`. -// -// The current implementation is based on a sorted `Vec` is not optimized for -// storing large number of items. -// -// On top of the familiar `std::collections` API it has a few additions: -// -// * Marking a key with a condition. -// * Marking a key with a merge behavior. -// * A merge operator. -// -// The current *conditions* are [`Used`](MarkMap::mark_used) and [`Required`](MarkMap::mark_required). -// -// The available merge behaviors are [`Theirs`](MergeBehavior::Theirs), [`Ours`](MergeBehavior::Ours), -// and [`MutuallyExclusive`](MergeBehavior::MutuallyExclusive). -// -// Merge behaviors configure how the merge operator handles cases where both `MarkMaps` being merged -// contain a particular key. +use std::error::Error; +use std::fmt; + +/// MarkMap is a key value data structure that uses an API similar to that of +/// `std::collections::HashMap` and `std::collections::BTreeMap`. +/// +/// The current implementation is based on a sorted `Vec` is not optimized for +/// storing large number of items. +/// +/// On top of the familiar `std::collections` API it has a few additions: +/// +/// * Marking a key with a condition. +/// * Marking a key with a merge behavior. +/// * A merge operator. +/// +/// The current *conditions* are [`Used`](MarkMap::mark_used) and [`Required`](MarkMap::mark_required). +/// +/// The available merge behaviors are [`Theirs`](MergeBehavior::Theirs), [`Ours`](MergeBehavior::Ours), +/// and [`MutuallyExclusive`](MergeBehavior::MutuallyExclusive). +/// +/// Merge behaviors configure how the merge operator handles cases where both `MarkMaps` being merged +/// contain a particular key. #[derive(Debug, PartialEq, Eq)] +#[doc(hidden)] pub struct MarkMap { contents: Vec<(K, u16, Option)>, } @@ -47,6 +50,18 @@ pub enum MergeError { Exclusivity(K, V), } +impl Error for MergeError {} + +impl fmt::Display for MergeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Exclusivity(k, _) => { + write!(f, "Merge behavior forbids overriding key: {:?}", k) + } + } + } +} + /// A view into a single entry in a `MarkMap`, which may either be vacant or occupied. #[doc(hidden)] pub enum Entry<'a, K, V> { @@ -205,7 +220,6 @@ impl MarkMap { } /// Inserts a `key` `value` pair. - #[doc(hidden)] pub fn insert(&mut self, key: K, val: V) -> Option { let pos = self.contents.binary_search_by(|(k, _, _)| k.cmp(&key)); match pos { @@ -232,7 +246,6 @@ impl MarkMap { } /// Marks `key` as used. - #[doc(hidden)] pub fn mark_used(&mut self, key: &K) { let pos = self.contents.binary_search_by(|(k, _, _)| k.cmp(key)); match pos { @@ -249,7 +262,6 @@ impl MarkMap { } /// Marks `key` as a required value. - #[doc(hidden)] pub fn mark_required(&mut self, key: &K) { let pos = self.contents.binary_search_by(|(k, _, _)| k.cmp(key)); match pos { @@ -266,7 +278,6 @@ impl MarkMap { } /// Returns whether `key` is required. - #[doc(hidden)] pub fn is_required(&self, key: &K) -> bool { let pos = self.contents.binary_search_by(|(k, _, _)| k.cmp(key)); match pos { @@ -276,7 +287,6 @@ impl MarkMap { } /// Sets the merge behavior for `key`. - #[doc(hidden)] pub fn set_merge_behavior(&mut self, key: &K, mb: MergeBehavior) { let pos = self.contents.binary_search_by(|(k, _, _)| k.cmp(key)); match pos { @@ -298,7 +308,6 @@ impl MarkMap { } /// Returns a `Some(value)` associated with `key` if present otherwise `None`. - #[doc(hidden)] pub fn get(&self, key: &Q) -> Option<&V> where K: Borrow, @@ -315,7 +324,6 @@ impl MarkMap { } /// Returns true if the `MarkMap` contains `key` otherwise false. - #[doc(hidden)] pub fn contains_key(&self, key: &Q) -> bool where K: Borrow, @@ -325,7 +333,6 @@ impl MarkMap { } /// Removes `key` from the `MarkMap` and returns the previous value when present. - #[doc(hidden)] pub fn remove(&mut self, key: &K) -> Option { let pos = self.contents.binary_search_by(|(k, _, _)| k.cmp(key)); match pos { @@ -335,7 +342,6 @@ impl MarkMap { } /// Returns an `Entry` for `key`. - #[doc(hidden)] pub fn entry(&mut self, key: K) -> Entry { let pos = self.contents.binary_search_by(|(k, _, _)| k.cmp(&key)); match pos { @@ -368,8 +374,10 @@ impl MarkMap { /// /// For the behavior of exclusive or mark the behavior as also `Mark::Required`, then after merge call `missing()` /// to check all required values. - #[doc(hidden)] - pub fn merge_from(&mut self, mut other: Self) -> Result<(), MergeError>> { + pub fn merge_from(&mut self, mut other: MarkMap) -> Result<(), MergeError>> + where + U: Into, + { for (their_key, their_mark, their_val) in other.contents.drain(..) { let pos = self.contents.binary_search_by(|x| x.0.cmp(&their_key)); match pos { @@ -388,21 +396,21 @@ impl MarkMap { if my_val.is_some() && their_val.is_some() { return Err(MergeError::Exclusivity( their_key, - Box::new(their_val.unwrap()), + Box::new(their_val.unwrap().into()), )); } else if my_val.is_none() { - *my_val = their_val; + *my_val = their_val.map(|u| u.into()); } } else if merge_behavior == theirs_mark && their_val.is_some() || merge_behavior == ours_mark && my_val.is_none() { *my_mark = their_mark; - *my_val = their_val; + *my_val = their_val.map(|u| u.into()); } } Err(pos) => { self.contents - .insert(pos, (their_key, their_mark, their_val)); + .insert(pos, (their_key, their_mark, their_val.map(|u| u.into()))); } } } @@ -410,7 +418,6 @@ impl MarkMap { } /// Returns whether `key` has been marked as used. - #[doc(hidden)] pub fn is_used(&self, key: &Q) -> bool where K: Borrow, @@ -427,7 +434,6 @@ impl MarkMap { } /// Returns a `Vec` containing all the keys that are not marked as used. - #[doc(hidden)] pub fn unused(&self) -> Vec { let mut ret = Vec::new(); for (k, mark, v) in &self.contents { @@ -441,7 +447,6 @@ impl MarkMap { /// Returns a `Vec` containing all the keys that are marked as required, /// but have values that are not present in the `MarkMap`. - #[doc(hidden)] pub fn missing(&self) -> Vec<&K> { let mut ret = Vec::new(); for (k, mark, v) in &self.contents { @@ -454,7 +459,6 @@ impl MarkMap { } /// Returns an `Iterator` over all the keys of the `MarkMap`. - #[doc(hidden)] pub fn keys(&self) -> Keys<'_, K, V> { Keys { pos: 0, map: self } } @@ -696,7 +700,7 @@ mod test { fn test_merge_empty() { { let mut ours: MarkMap<&str, &str> = MarkMap::new(); - let theirs = MarkMap::new(); + let theirs: MarkMap<&str, &str> = MarkMap::new(); assert!(ours.merge_from(theirs).is_ok()); } { diff --git a/cfgrammar/src/lib/mod.rs b/cfgrammar/src/lib/mod.rs index ca750d014..3d1664606 100644 --- a/cfgrammar/src/lib/mod.rs +++ b/cfgrammar/src/lib/mod.rs @@ -56,13 +56,13 @@ use bincode::{Decode, Encode}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[doc(hidden)] pub mod header; mod idxnewtype; +#[doc(hidden)] pub mod markmap; pub mod newlinecache; pub mod span; -#[cfg(test)] -pub mod test_utils; pub mod yacc; pub use newlinecache::NewlineCache; diff --git a/cfgrammar/src/lib/span.rs b/cfgrammar/src/lib/span.rs index 679d817ad..5cdeb7d2c 100644 --- a/cfgrammar/src/lib/span.rs +++ b/cfgrammar/src/lib/span.rs @@ -75,3 +75,9 @@ pub enum Location { CommandLine, Other(String), } + +impl From for Location { + fn from(span: Span) -> Location { + Location::Span(span) + } +} diff --git a/cfgrammar/src/lib/test_utils.rs b/cfgrammar/src/lib/test_utils.rs deleted file mode 100644 index 863127bfc..000000000 --- a/cfgrammar/src/lib/test_utils.rs +++ /dev/null @@ -1,25 +0,0 @@ -#[allow(unused)] -pub use crate::header::{Header, Value}; -pub use crate::markmap::{Entry, MergeBehavior}; -pub use crate::Location; -#[macro_export] -#[cfg(test)] -macro_rules! header_for_yacckind { - ($yk:expr) => {{ - let mut header = Header::new(); - match header.entry("yacckind".to_string()) { - Entry::Occupied(_) => unreachable!("Header should be empty"), - Entry::Vacant(v) => { - let mut o = v.insert_entry(( - Location::Other("Testsuite".to_string()), - Value::try_from($yk).unwrap(), - )); - o.mark_required(); - o.set_merge_behavior(MergeBehavior::Ours); - } - }; - header - }}; -} - -pub use header_for_yacckind; diff --git a/cfgrammar/src/lib/yacc/ast.rs b/cfgrammar/src/lib/yacc/ast.rs index 04676e5c9..e713bbf40 100644 --- a/cfgrammar/src/lib/yacc/ast.rs +++ b/cfgrammar/src/lib/yacc/ast.rs @@ -1,22 +1,26 @@ use std::{ collections::{HashMap, HashSet}, fmt, + str::FromStr, }; use indexmap::{IndexMap, IndexSet}; use super::{ - parser::YaccParser, ParserError, Precedence, YaccGrammarError, YaccGrammarErrorKind, - YaccGrammarWarning, YaccGrammarWarningKind, YaccKind, + parser::YaccParser, Precedence, YaccGrammarError, YaccGrammarErrorKind, YaccGrammarWarning, + YaccGrammarWarningKind, YaccKind, }; -use crate::{header::Header, Span}; +use crate::{ + header::{GrmtoolsSectionParser, HeaderError, HeaderErrorKind, HeaderValue}, + Span, +}; /// Contains a `GrammarAST` structure produced from a grammar source file. /// As well as any errors which occurred during the construction of the AST. pub struct ASTWithValidityInfo { - yacc_kind: Option, + yacc_kind: YaccKind, ast: GrammarAST, - errs: Vec, + errs: Vec, } impl ASTWithValidityInfo { @@ -24,18 +28,17 @@ impl ASTWithValidityInfo { /// encountered during the construction of it. The `ASTWithValidityInfo` can be /// then unused to construct a `YaccGrammar`, which will either produce an /// `Ok(YaccGrammar)` or an `Err` which includes these errors. - pub fn new(header: &mut Header, s: &str) -> Self { + /// + /// This function ignores the `%grmtools` section entirely, assuming that the caller has + /// already extracted the `YaccKind` if any. + pub fn new(yacc_kind: YaccKind, s: &str) -> Self { let mut errs = Vec::new(); - let (yacc_kind, ast) = { - let mut yp = YaccParser::new(header, s.to_string()); + let ast = { + let mut yp = YaccParser::new(yacc_kind, s); yp.parse().map_err(|e| errs.extend(e)).ok(); - let (yacc_kind, mut ast) = yp.build(); - if yacc_kind.is_some() { - ast.complete_and_validate() - .map_err(|e| errs.push(e.into())) - .ok(); - } - (yacc_kind, ast) + let mut ast = yp.build(); + ast.complete_and_validate().map_err(|e| errs.push(e)).ok(); + ast }; ASTWithValidityInfo { ast, @@ -59,16 +62,49 @@ impl ASTWithValidityInfo { } /// Returns the `YaccKind` that was used to parse the `GrammarAST`. - pub fn yacc_kind(&self) -> Option { + pub fn yacc_kind(&self) -> YaccKind { self.yacc_kind } /// Returns all errors which were encountered during AST construction. - pub fn errors(&self) -> &[ParserError] { + pub fn errors(&self) -> &[YaccGrammarError] { self.errs.as_slice() } } +impl FromStr for ASTWithValidityInfo { + type Err = Vec; + /// Parses the `%grmtools section` expecting it to contain a `yacckind` entry. + fn from_str(src: &str) -> Result> { + let mut errs = Vec::new(); + let (header, _) = GrmtoolsSectionParser::new(src, true) + .parse() + .map_err(|mut errs| errs.drain(..).map(|e| e.into()).collect::>())?; + if let Some(HeaderValue(_, yk_val)) = header.get("yacckind") { + let yacc_kind = YaccKind::try_from(yk_val).map_err(|e| vec![e.into()])?; + let ast = { + // We don't want to strip off the header so that span's will be correct. + let mut yp = YaccParser::new(yacc_kind, src); + yp.parse().map_err(|e| errs.extend(e)).ok(); + let mut ast = yp.build(); + ast.complete_and_validate().map_err(|e| errs.push(e)).ok(); + ast + }; + Ok(ASTWithValidityInfo { + ast, + errs, + yacc_kind, + }) + } else { + Err(vec![HeaderError { + kind: HeaderErrorKind::InvalidEntry("yacckind"), + locations: vec![Span::new(0, 0)], + } + .into()]) + } + } +} + /// An AST representing a grammar. This is built up gradually: when it is finished, the /// `complete_and_validate` must be called exactly once in order to finish the set-up. At that /// point, any further mutations made to the struct lead to undefined behaviour. diff --git a/cfgrammar/src/lib/yacc/firsts.rs b/cfgrammar/src/lib/yacc/firsts.rs index 44dafca8a..937a99d97 100644 --- a/cfgrammar/src/lib/yacc/firsts.rs +++ b/cfgrammar/src/lib/yacc/firsts.rs @@ -147,7 +147,6 @@ mod test { super::{YaccGrammar, YaccKind, YaccOriginalActionKind}, YaccFirsts, }; - use crate::test_utils::*; use num_traits::{AsPrimitive, PrimInt, Unsigned}; fn has( @@ -182,7 +181,7 @@ mod test { #[test] fn test_first() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start C %token c d @@ -204,7 +203,7 @@ mod test { #[test] fn test_first_no_subsequent_rules() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start C %token c d @@ -222,7 +221,7 @@ mod test { #[test] fn test_first_epsilon() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start A %token a b c @@ -243,7 +242,7 @@ mod test { #[test] fn test_last_epsilon() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start A %token b c @@ -263,7 +262,7 @@ mod test { #[test] fn test_first_no_multiples() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start A %token b c @@ -279,7 +278,7 @@ mod test { fn eco_grammar() -> YaccGrammar { YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start S %token a b c d f @@ -310,7 +309,7 @@ mod test { #[test] fn test_first_from_eco_bug() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start E %token a b c d e f diff --git a/cfgrammar/src/lib/yacc/follows.rs b/cfgrammar/src/lib/yacc/follows.rs index 962f26ce7..68c179231 100644 --- a/cfgrammar/src/lib/yacc/follows.rs +++ b/cfgrammar/src/lib/yacc/follows.rs @@ -119,7 +119,6 @@ mod test { super::{YaccGrammar, YaccKind, YaccOriginalActionKind}, YaccFollows, }; - use crate::test_utils::*; use num_traits::{AsPrimitive, PrimInt, Unsigned}; fn has( @@ -151,7 +150,7 @@ mod test { fn test_follow() { // Adapted from p2 of https://www.cs.uaf.edu/~cs331/notes/FirstFollow.pdf let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start E %% @@ -175,7 +174,7 @@ mod test { fn test_follow2() { // Adapted from https://www.l2f.inesc-id.pt/~david/w/pt/Top-Down_Parsing/Exercise_5:_Test_2010/07/01 let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start A %% @@ -198,7 +197,7 @@ mod test { #[test] fn test_follow3() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start S %% @@ -215,7 +214,7 @@ mod test { #[test] fn test_follow_corchuelo() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start E %% diff --git a/cfgrammar/src/lib/yacc/grammar.rs b/cfgrammar/src/lib/yacc/grammar.rs index a1f3f2f9c..fe9def8a2 100644 --- a/cfgrammar/src/lib/yacc/grammar.rs +++ b/cfgrammar/src/lib/yacc/grammar.rs @@ -1,5 +1,5 @@ #![allow(clippy::derive_partial_eq_without_eq)] -use std::{cell::RefCell, collections::HashMap, fmt::Write}; +use std::{cell::RefCell, collections::HashMap, fmt::Write, str::FromStr}; #[cfg(feature = "bincode")] use bincode::{Decode, Encode}; @@ -8,8 +8,14 @@ use num_traits::{AsPrimitive, PrimInt, Unsigned}; use serde::{Deserialize, Serialize}; use vob::Vob; -use super::{ast, firsts::YaccFirsts, follows::YaccFollows, parser::YaccGrammarResult, YaccKind}; -use crate::{header::Header, PIdx, RIdx, SIdx, Span, Symbol, TIdx}; +use super::{ + ast, + firsts::YaccFirsts, + follows::YaccFollows, + parser::{YaccGrammarError, YaccGrammarResult}, + YaccKind, +}; +use crate::{PIdx, RIdx, SIdx, Span, Symbol, TIdx}; const START_RULE: &str = "^"; const IMPLICIT_RULE: &str = "~"; @@ -176,8 +182,19 @@ where // create the start rule ourselves (without relying on user input), this is a safe assumption. impl YaccGrammar { - pub fn new(header: &mut Header, s: &str) -> YaccGrammarResult { - YaccGrammar::new_with_storaget(header, s) + pub fn new(yk: YaccKind, s: &str) -> YaccGrammarResult { + YaccGrammar::new_with_storaget(yk, s) + } +} + +impl FromStr for YaccGrammar +where + usize: AsPrimitive, +{ + type Err = Vec; + fn from_str(s: &str) -> YaccGrammarResult { + let ast_validation = ast::ASTWithValidityInfo::from_str(s)?; + Self::new_from_ast_with_validity_info(&ast_validation) } } @@ -192,8 +209,8 @@ where /// As we're compiling the `YaccGrammar`, we add a new start rule (which we'll refer to as `^`, /// though the actual name is a fresh name that is guaranteed to be unique) that references the /// user defined start rule. - pub fn new_with_storaget(header: &mut Header, s: &str) -> YaccGrammarResult { - let ast_validation = ast::ASTWithValidityInfo::new(header, s); + pub fn new_with_storaget(yk: YaccKind, s: &str) -> YaccGrammarResult { + let ast_validation = ast::ASTWithValidityInfo::new(yk, s); Self::new_from_ast_with_validity_info(&ast_validation) } @@ -235,10 +252,7 @@ where let implicit_rule; let implicit_start_rule; - match ast_validation - .yacc_kind() - .expect("is_valid() ensures Some(yacc_kind)") - { + match ast_validation.yacc_kind() { YaccKind::Original(_) | YaccKind::Grmtools => { implicit_rule = None; implicit_start_rule = None; @@ -1122,8 +1136,9 @@ mod test { super::{AssocKind, Precedence, YaccGrammar, YaccKind, YaccOriginalActionKind}, rule_max_costs, rule_min_costs, IMPLICIT_RULE, IMPLICIT_START_RULE, }; - use crate::{test_utils::*, PIdx, RIdx, Span, Symbol, TIdx}; + use crate::{PIdx, RIdx, Span, Symbol, TIdx}; use std::collections::HashMap; + use std::str::FromStr; macro_rules! bslice { () => ( @@ -1142,7 +1157,7 @@ mod test { #[test] fn test_minimal() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), "%start R %token T %% R: 'T';", ) .unwrap(); @@ -1173,7 +1188,7 @@ mod test { #[test] fn test_rule_ref() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), "%start R %token T %% R : S; S: 'T';", ) .unwrap(); @@ -1202,7 +1217,7 @@ mod test { #[rustfmt::skip] fn test_long_prod() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), "%start R %token T1 T2 %% R : S 'T1' S; S: 'T2';" ).unwrap(); @@ -1233,7 +1248,7 @@ mod test { #[test] fn test_prods_rules() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start A %% @@ -1256,7 +1271,7 @@ mod test { #[rustfmt::skip] fn test_left_right_nonassoc_precs() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start Expr %right '=' @@ -1289,7 +1304,7 @@ mod test { #[rustfmt::skip] fn test_prec_override() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start expr %left '+' '-' @@ -1317,7 +1332,7 @@ mod test { #[rustfmt::skip] fn test_implicit_tokens_rewrite() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Eco), + YaccKind::Eco, " %implicit_tokens ws1 ws2 %start S @@ -1393,7 +1408,7 @@ mod test { #[rustfmt::skip] fn test_has_path() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start A %% @@ -1420,7 +1435,7 @@ mod test { #[rustfmt::skip] fn test_rule_min_costs() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start A %% @@ -1443,7 +1458,7 @@ mod test { #[test] fn test_min_sentences() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start A %% @@ -1491,7 +1506,7 @@ mod test { #[rustfmt::skip] fn test_rule_max_costs1() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start A %% @@ -1515,7 +1530,7 @@ mod test { #[rustfmt::skip] fn test_rule_max_costs2() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start A %% @@ -1537,7 +1552,7 @@ mod test { fn test_out_of_order_productions() { // Example taken from p54 of Locally least-cost error repair in LR parsers, Carl Cerecke let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start S %% @@ -1568,11 +1583,8 @@ mod test { #[test] fn test_token_spans() { let src = "%%\nAB: 'a' | 'foo';"; - let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::NoAction)), - src, - ) - .unwrap(); + let grm = + YaccGrammar::new(YaccKind::Original(YaccOriginalActionKind::NoAction), src).unwrap(); let token_map = grm.tokens_map(); let a_tidx = token_map.get("a"); let foo_tidx = token_map.get("foo"); @@ -1596,15 +1608,87 @@ mod test { AB: A AB | B ';' AB; %% "; - let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::NoAction)), - src, - ) - .unwrap(); + let grm = + YaccGrammar::new(YaccKind::Original(YaccOriginalActionKind::NoAction), src).unwrap(); let token_map = grm.tokens_map(); let c_tidx = token_map.get("c").unwrap(); assert_eq!(grm.token_name(*c_tidx), Some("c")); let c_span = grm.token_span(*c_tidx).unwrap(); assert_eq!(&src[c_span.start()..c_span.end()], "c"); } + + #[test] + fn test_grmtools_section_yacckinds() { + let srcs = [ + "%grmtools{yacckind: Original(NoAction)} + %% + Start: ;", + "%grmtools{yacckind: YaccKind::Original(GenericParseTree)} + %% + Start: ;", + "%grmtools{yacckind: YaccKind::Original(yaccoriginalactionkind::useraction)} + %actiontype () + %% + Start: ;", + "%grmtools{yacckind: Original(YACCOriginalActionKind::NoAction)} + %% + Start: ;", + "%grmtools{yacckind: YaccKind::Grmtools} + %% + Start -> () : ;", + ]; + for src in srcs { + YaccGrammar::::from_str(src).unwrap(); + } + } + + #[test] + fn test_grmtools_section_invalid_yacckinds() { + let srcs = [ + "%grmtools{yacckind: Foo}", + "%grmtools{yacckind: YaccKind::Foo}", + "%grmtools{yacckindof: YaccKind::Grmtools}", + "%grmtools{yacckindof: Grmtools}", + "%grmtools{yacckindof: YaccKindFoo::Foo}", + "%grmtools{yacckind: Foo::Grmtools}", + "%grmtools{yacckind: YaccKind::Original}", + "%grmtools{yacckind: YaccKind::OriginalFoo}", + "%grmtools{yacckind: YaccKind::Original()}", + "%grmtools{yacckind: YaccKind::Original(Foo)}", + "%grmtools{yacckind: YaccKind::Original(YaccOriginalActionKind)}", + "%grmtools{yacckind: YaccKind::Original(YaccOriginalActionKind::Foo)}", + "%grmtools{yacckind: YaccKind::Original(Foo::NoActions)}", + "%grmtools{yacckind: YaccKind::Original(Foo::NoActionsBar)}", + ]; + + for src in srcs { + let s = format!("{}\n%%\nStart();\n", src); + assert!(YaccGrammar::::from_str(&s).is_err()); + } + } + + #[test] + fn test_grmtools_section_commas() { + // We can't actually test much here, because + // We don't have a second value to test. + // + // `RecoveryKind` seemed like an option for an additional value to allow, + // but that is part of `lrpar` which cfgrammar doesn't depend upon. + let src = r#" + %grmtools{ + yacckind: YaccKind::Grmtools, + } + %% + Start -> () : ; + "#; + YaccGrammar::::from_str(src).unwrap(); + let src = r#" + %grmtools{ + yacckind: YaccKind::Grmtools + } + %% + Start -> () : ; + "#; + YaccGrammar::::from_str(src).unwrap(); + } } diff --git a/cfgrammar/src/lib/yacc/mod.rs b/cfgrammar/src/lib/yacc/mod.rs index 36832adb1..68367525e 100644 --- a/cfgrammar/src/lib/yacc/mod.rs +++ b/cfgrammar/src/lib/yacc/mod.rs @@ -8,10 +8,7 @@ pub mod parser; pub use self::{ grammar::{AssocKind, Precedence, SentenceGenerator, YaccGrammar}, - parser::{ - ParserError, YaccGrammarError, YaccGrammarErrorKind, YaccGrammarWarning, - YaccGrammarWarningKind, - }, + parser::{YaccGrammarError, YaccGrammarErrorKind, YaccGrammarWarning, YaccGrammarWarningKind}, }; use proc_macro2::TokenStream; use quote::quote; diff --git a/cfgrammar/src/lib/yacc/parser.rs b/cfgrammar/src/lib/yacc/parser.rs index b671e1caa..943a5703b 100644 --- a/cfgrammar/src/lib/yacc/parser.rs +++ b/cfgrammar/src/lib/yacc/parser.rs @@ -15,39 +15,11 @@ use std::{ }; use crate::{ - header::{GrmtoolsSectionParser, Header, HeaderError, HeaderErrorKind}, - markmap::MergeError, + header::{GrmtoolsSectionParser, HeaderErrorKind}, Span, Spanned, }; -pub type YaccGrammarResult = Result>; - -#[derive(Debug, Clone)] -#[non_exhaustive] -pub enum ParserError { - YaccGrammarError(YaccGrammarError), - HeaderError(HeaderError), -} - -impl Error for ParserError {} -impl fmt::Display for ParserError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}", - match self { - Self::YaccGrammarError(e) => e.to_string(), - Self::HeaderError(e) => e.to_string(), - } - ) - } -} - -impl From for ParserError { - fn from(e: YaccGrammarError) -> ParserError { - ParserError::YaccGrammarError(e) - } -} +pub type YaccGrammarResult = Result>; use super::{ ast::{GrammarAST, Symbol}, @@ -98,6 +70,7 @@ pub enum YaccGrammarErrorKind { InvalidGrmtoolsSectionEntry, DuplicateGrmtoolsSectionEntry, MissingGrmtoolsSection, + Header(HeaderErrorKind, SpansKind), } /// Any error from the Yacc parser returns an instance of this struct. @@ -195,6 +168,7 @@ impl fmt::Display for YaccGrammarErrorKind { YaccGrammarErrorKind::InvalidYaccKindNamespace => "Invalid yacc kind namespace", YaccGrammarErrorKind::InvalidActionKind => "Invalid action kind", YaccGrammarErrorKind::InvalidActionKindNamespace => "Invalid action kind namespace", + YaccGrammarErrorKind::Header(hk, _) => &format!("Error in '%grmtools' {}", hk), }; write!(f, "{}", s) } @@ -260,6 +234,7 @@ impl Spanned for YaccGrammarWarning { } /// Indicates how to interpret the spans of an error. +#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[non_exhaustive] pub enum SpansKind { /// The first span is the first occurrence, and a span for each subsequent occurrence. @@ -320,14 +295,14 @@ impl Spanned for YaccGrammarError { | YaccGrammarErrorKind::DuplicateActiontypeDeclaration | YaccGrammarErrorKind::DuplicateGrmtoolsSectionEntry | YaccGrammarErrorKind::DuplicateEPP => SpansKind::DuplicationError, + YaccGrammarErrorKind::Header(_, spanskind) => spanskind, } } } pub(crate) struct YaccParser<'a> { - header: &'a mut Header, - yacc_kind: Option, - src: String, + yacc_kind: YaccKind, + src: &'a str, num_newlines: usize, ast: GrammarAST, global_actiontype: Option<(String, Span)>, @@ -362,10 +337,9 @@ fn add_duplicate_occurrence( /// The actual parser is intended to be entirely opaque from outside users. impl YaccParser<'_> { - pub(crate) fn new(header: &mut Header, src: String) -> YaccParser { + pub(crate) fn new(yacc_kind: YaccKind, src: &str) -> YaccParser { YaccParser { - header, - yacc_kind: None, + yacc_kind, src, num_newlines: 0, ast: GrammarAST::new(), @@ -375,59 +349,17 @@ impl YaccParser<'_> { pub(crate) fn parse(&mut self) -> YaccGrammarResult { let mut errs = Vec::new(); + let (_, pos) = GrmtoolsSectionParser::new(self.src, false) + .parse() + .map_err(|mut errs| errs.drain(..).map(|e| e.into()).collect::>())?; // We pass around an index into the *bytes* of self.src. We guarantee that at all times // this points to the beginning of a UTF-8 character (since multibyte characters exist, not // every byte within the string is also a valid character). - let header_parser = GrmtoolsSectionParser::new(&self.src, false); - let result = match header_parser.parse() { - Ok((parsed_header, i)) => { - match self.header.merge_from(parsed_header) { - Err(MergeError::Exclusivity(key, boxed_val)) => { - let (their_loc, _) = boxed_val.as_ref(); - let (header_loc, _) = self.header.get(&key).unwrap(); - errs.push( - HeaderError { - kind: HeaderErrorKind::DuplicateEntry, - locations: vec![their_loc.clone(), header_loc.clone()], - } - .into(), - ) - } - Ok(()) => (), - } - self.update_yacckind(i) - } - Err(es) => { - errs.extend(es.iter().cloned().map(ParserError::from)); - return Err(errs); - } - }; - if result.is_ok() && self.yacc_kind.is_none() { - errs.push( - YaccGrammarError { - kind: YaccGrammarErrorKind::InvalidYaccKind, - spans: vec![Span::new(0, 0)], - } - .into(), - ); - return Err(errs); - } - let mut y_errs = Vec::new(); - let mut result = self.parse_declarations( - match result { - Ok(i) => i, - Err(e) => { - errs.push(e.into()); - return Err(errs); - } - }, - &mut y_errs, - ); - errs.extend(y_errs.drain(..).map(ParserError::from)); + let mut result = self.parse_declarations(pos, &mut errs); result = self.parse_rules(match result { Ok(i) => i, Err(e) => { - errs.push(e.into()); + errs.push(e); return Err(errs); } }); @@ -435,44 +367,24 @@ impl YaccParser<'_> { match result { Ok(i) => i, Err(e) => { - errs.push(e.into()); + errs.push(e); return Err(errs); } }, - &mut y_errs, + &mut errs, ); - errs.extend(y_errs.drain(..).map(ParserError::from)); match result { Ok(i) if errs.is_empty() => Ok(i), Err(e) => { - errs.push(e.into()); + errs.push(e); Err(errs) } _ => Err(errs), } } - fn update_yacckind(&mut self, i: usize) -> Result { - use crate::markmap::Entry; - use crate::Location; - match self.header.entry("yacckind".to_string()) { - Entry::Occupied(mut o) => { - o.mark_used(); - let (_, val) = o.get(); - self.yacc_kind = Some(YaccKind::try_from(val)?); - } - Entry::Vacant(_) => { - return Err(HeaderError { - kind: HeaderErrorKind::InvalidEntry("yacckind"), - locations: vec![Location::Span(Span::new(i, i))], - }); - } - } - Ok(i) - } - - pub(crate) fn build(self) -> (Option, GrammarAST) { - (self.yacc_kind, self.ast) + pub(crate) fn build(self) -> GrammarAST { + self.ast } fn parse_declarations( @@ -497,7 +409,7 @@ impl YaccParser<'_> { } continue; } - if let Some(YaccKind::Original(_)) = self.yacc_kind { + if let YaccKind::Original(_) = self.yacc_kind { if let Some(j) = self.lookahead_is("%actiontype", i) { i = self.parse_ws(j, false)?; let (j, n) = self.parse_to_eol(i)?; @@ -658,7 +570,7 @@ impl YaccParser<'_> { i = self.parse_ws(j, true)?; continue; } - if let Some(YaccKind::Eco) = self.yacc_kind { + if let YaccKind::Eco = self.yacc_kind { if let Some(j) = self.lookahead_is("%implicit_tokens", i) { i = self.parse_ws(j, false)?; let num_newlines = self.num_newlines; @@ -755,8 +667,7 @@ impl YaccParser<'_> { self.ast.start = Some((rn.clone(), span)); } match self.yacc_kind { - None => unreachable!("Concrete YaccKind resolved at this point"), - Some(YaccKind::Original(_)) | Some(YaccKind::Eco) => { + YaccKind::Original(_) | YaccKind::Eco => { if self.ast.get_rule(&rn).is_none() { self.ast.add_rule( (rn.clone(), span), @@ -765,7 +676,7 @@ impl YaccParser<'_> { } i = j; } - Some(YaccKind::Grmtools) => { + YaccKind::Grmtools => { i = self.parse_ws(j, true)?; if let Some(j) = self.lookahead_is("->", i) { i = j; @@ -1158,16 +1069,14 @@ mod test { ast::{GrammarAST, Production, Symbol}, AssocKind, Precedence, YaccKind, YaccOriginalActionKind, }, - ParserError, Span, Spanned, YaccGrammarErrorKind, YaccParser, + Span, Spanned, YaccGrammarError, YaccGrammarErrorKind, YaccParser, }; - use crate::test_utils::*; use std::collections::HashSet; - fn parse(yacc_kind: YaccKind, s: &str) -> Result> { - let mut header = header_for_yacckind!(yacc_kind); - let mut yp = YaccParser::new(&mut header, s.to_string()); + fn parse(yacc_kind: YaccKind, s: &str) -> Result> { + let mut yp = YaccParser::new(yacc_kind, s); yp.parse()?; - Ok(yp.build().1) + Ok(yp.build()) } fn rule(n: &str) -> Symbol { @@ -1221,7 +1130,7 @@ mod test { ); } - impl ErrorsHelper for Result> { + impl ErrorsHelper for Result> { #[track_caller] fn expect_error_at_line(self, src: &str, kind: YaccGrammarErrorKind, line: usize) { let errs = self @@ -1230,11 +1139,6 @@ mod test { .expect_err("Parsed ok while expecting error"); assert_eq!(errs.len(), 1); let e = &errs[0]; - let e = match e { - ParserError::YaccGrammarError(e) => e, - _ => panic!("Expected YaccGrammarError {}", e), - }; - assert_eq!(e.kind, kind); assert_eq!(line_of_offset(src, e.spans()[0].start()), line); assert_eq!(e.spans.len(), 1); @@ -1264,10 +1168,6 @@ mod test { .expect_err("Parsed ok while expecting error"); assert_eq!(errs.len(), 1); let e = &errs[0]; - let e = match e { - ParserError::YaccGrammarError(e) => e, - _ => panic!("Expected YaccGrammarError {}", e), - }; assert_eq!(e.kind, kind); assert_eq!( e.spans() @@ -1289,16 +1189,6 @@ mod test { expected: &mut dyn Iterator)>, ) { let errs = self.expect_err("Parsed ok while expecting error"); - let y_errs = errs - .iter() - .cloned() - .map(|e| match e { - ParserError::YaccGrammarError(e) => e, - e => panic!("Expected YaccGrammarError got: '{}'", e), - }) - .collect::>(); - - let errs = y_errs; for e in &errs { // Check that it is valid to slice the source with the spans. for span in e.spans() { @@ -2836,88 +2726,6 @@ B"; parse(YaccKind::Original(YaccOriginalActionKind::NoAction), src).unwrap(); } - #[test] - fn test_grmtools_section_yacckinds() { - let srcs = [ - "%grmtools{yacckind: Original(NoAction)} - %% - Start: ;", - "%grmtools{yacckind: YaccKind::Original(GenericParseTree)} - %% - Start: ;", - "%grmtools{yacckind: YaccKind::Original(yaccoriginalactionkind::useraction)} - %actiontype () - %% - Start: ;", - "%grmtools{yacckind: Original(YACCOriginalActionKind::NoAction)} - %% - Start: ;", - "%grmtools{yacckind: YaccKind::Grmtools} - %% - Start -> () : ;", - ]; - for src in srcs { - YaccParser::new(&mut Header::new(), src.to_string()) - .parse() - .unwrap(); - } - } - - #[test] - fn test_grmtools_section_invalid_yacckinds() { - let srcs = [ - "%grmtools{yacckind: Foo}", - "%grmtools{yacckind: YaccKind::Foo}", - "%grmtools{yacckindof: YaccKind::Grmtools}", - "%grmtools{yacckindof: Grmtools}", - "%grmtools{yacckindof: YaccKindFoo::Foo}", - "%grmtools{yacckind: Foo::Grmtools}", - "%grmtools{yacckind: YaccKind::Original}", - "%grmtools{yacckind: YaccKind::OriginalFoo}", - "%grmtools{yacckind: YaccKind::Original()}", - "%grmtools{yacckind: YaccKind::Original(Foo)}", - "%grmtools{yacckind: YaccKind::Original(YaccOriginalActionKind)}", - "%grmtools{yacckind: YaccKind::Original(YaccOriginalActionKind::Foo)}", - "%grmtools{yacckind: YaccKind::Original(Foo::NoActions)}", - "%grmtools{yacckind: YaccKind::Original(Foo::NoActionsBar)}", - ]; - - for src in srcs { - let s = format!("{}\n%%\nStart();\n", src); - let parse_result = YaccParser::new(&mut Header::new(), s.to_string()).parse(); - assert!(parse_result.is_err()); - } - } - - #[test] - fn test_grmtools_section_commas() { - // We can't actually test much here, because - // We don't have a second value to test. - // - // `RecoveryKind` seemed like an option for an additional value to allow, - // but that is part of `lrpar` which cfgrammar doesn't depend upon. - let src = r#" - %grmtools{ - yacckind: YaccKind::Grmtools, - } - %% - Start -> () : ; - "#; - YaccParser::new(&mut Header::new(), src.to_string()) - .parse() - .unwrap(); - let src = r#" - %grmtools{ - yacckind: YaccKind::Grmtools - } - %% - Start -> () : ; - "#; - YaccParser::new(&mut Header::new(), src.to_string()) - .parse() - .unwrap(); - } - #[test] fn test_empty_production_spans_issue_473() { let empty_prod_conflicts = [ diff --git a/lrlex/src/lib/ctbuilder.rs b/lrlex/src/lib/ctbuilder.rs index cd4aec811..6df63438f 100644 --- a/lrlex/src/lib/ctbuilder.rs +++ b/lrlex/src/lib/ctbuilder.rs @@ -16,7 +16,10 @@ use std::{ use bincode::Encode; use cfgrammar::{ - header::{GrmtoolsSectionParser, HeaderError, HeaderErrorKind, Namespaced, Setting, Value}, + header::{ + GrmtoolsSectionParser, HeaderError, HeaderErrorKind, HeaderValue, Namespaced, Setting, + Value, + }, newlinecache::NewlineCache, Spanned, }; @@ -41,9 +44,9 @@ pub enum LexerKind { LRNonStreamingLexer, } -impl TryFrom<&Value> for LexerKind { - type Error = cfgrammar::header::HeaderError; - fn try_from(it: &Value) -> Result { +impl TryFrom<&Value> for LexerKind { + type Error = cfgrammar::header::HeaderError; + fn try_from(it: &Value) -> Result { match it { Value::Flag(_, loc) => Err(HeaderError { kind: HeaderErrorKind::ConversionError( @@ -451,7 +454,7 @@ where let lexerkind = match self.lexerkind { Some(lexerkind) => lexerkind, None => { - if let Some((_, lk_val)) = header.get("lexerkind") { + if let Some(HeaderValue(_, lk_val)) = header.get("lexerkind") { LexerKind::try_from(lk_val)? } else { LexerKind::LRNonStreamingLexer diff --git a/lrpar/cttests/src/grmtools_section.test b/lrpar/cttests/src/grmtools_section.test index bceff4b75..6b81020c1 100644 --- a/lrpar/cttests/src/grmtools_section.test +++ b/lrpar/cttests/src/grmtools_section.test @@ -3,31 +3,31 @@ grammar: | %token MAGIC IDENT NUM %epp MAGIC "%grmtools" %% - start -> Result> + start -> Result, Vec>> : MAGIC '{' contents '}' { $3 } ; - contents -> Result> + contents -> Result, Vec>> : %empty { Ok(Header::new()) } | val_seq comma_opt { $1 } ; - val_seq -> Result> + val_seq -> Result, Vec>> : valbind { let ((key, key_loc), val) = $1; - let mut ret = Header::new(); + let mut ret = Header::::new(); match ret.entry(key) { Entry::Occupied(orig) => { - let (orig_loc, _): &(Location, Value) = orig.get(); + let HeaderValue(orig_loc, _) : &HeaderValue = orig.get(); // One difference between the manually written parser and this // is we don't try return multiple errors, or coalesce them. return Err(vec![HeaderError { kind: HeaderErrorKind::DuplicateEntry, - locations: vec![orig_loc.clone(), key_loc] + locations: vec![*orig_loc, key_loc] }]); } Entry::Vacant(entry) => { - entry.insert((key_loc, val)); + entry.insert(HeaderValue(key_loc, val)); } } Ok(ret) @@ -37,29 +37,29 @@ grammar: | let mut ret = $1?; match ret.entry(key) { Entry::Occupied(orig) => { - let (orig_loc, _) = orig.get(); + let HeaderValue(orig_loc, _): &HeaderValue = orig.get(); // One difference between the manually written parser and this // is we don't try return multiple errors, or coalesce them. return Err(vec![HeaderError { kind: HeaderErrorKind::DuplicateEntry, - locations: vec![orig_loc.clone(), key_loc] + locations: vec![*orig_loc, key_loc] }]); } Entry::Vacant(entry) => { - entry.insert((key_loc, val)); + entry.insert(HeaderValue(key_loc, val)); } } Ok(ret) } ; - namespaced -> Namespaced + namespaced -> Namespaced : IDENT { let ident_span = $1.as_ref().unwrap().span(); let ident = $lexer.span_str(ident_span).to_string().to_lowercase(); Namespaced{ namespace: None, - member: (ident, Location::Span(ident_span)) + member: (ident, ident_span) } } | IDENT '::' IDENT { @@ -69,37 +69,37 @@ grammar: | let ident_span = $3.as_ref().unwrap().span(); let ident = $lexer.span_str(ident_span).to_string().to_lowercase(); Namespaced { - namespace: Some((namespace, Location::Span(namespace_span))), - member: (ident, Location::Span(ident_span)) + namespace: Some((namespace, namespace_span)), + member: (ident, ident_span) } } ; - valbind -> ((String, Location), Value) + valbind -> ((String, Span), Value) : IDENT ':' val { let key_span = $1.as_ref().unwrap().span(); let key = $lexer.span_str(key_span).to_string().to_lowercase(); - ((key, Location::Span(key_span)), Value::Setting($3)) + ((key, key_span), Value::Setting($3)) } | IDENT { let key_span = $1.as_ref().unwrap().span(); let key = $lexer.span_str(key_span).to_string().to_lowercase(); - ((key, Location::Span(key_span)), Value::Flag(true, Location::Span(key_span))) + ((key, key_span), Value::Flag(true, key_span)) } | '!' IDENT { let bang_span = $1.as_ref().unwrap().span(); let key_span = $2.as_ref().unwrap().span(); let key = $lexer.span_str(key_span).to_string().to_lowercase(); - ((key, Location::Span(key_span)), Value::Flag(false, Location::Span(Span::new(bang_span.start(), key_span.end())))) + ((key, key_span), Value::Flag(false, Span::new(bang_span.start(), key_span.end()))) } ; - val -> Setting + val -> Setting : namespaced { Setting::Unitary($1) } | NUM { let num_span = $1.as_ref().unwrap().span(); let n = str::parse::($lexer.span_str(num_span)); - Setting::Num(n.expect("convertible"), Location::Span(num_span)) + Setting::Num(n.expect("convertible"), num_span) } | namespaced '(' namespaced ')' { Setting::Constructor{ctor: $1, arg: $3} } ; @@ -114,7 +114,6 @@ grammar: | use cfgrammar::{ Span, - Location, header::{ Value, Setting, @@ -122,6 +121,7 @@ grammar: | HeaderErrorKind, Namespaced, Header, + HeaderValue, }, markmap::Entry, }; diff --git a/lrpar/src/lib/ctbuilder.rs b/lrpar/src/lib/ctbuilder.rs index ec97d9725..ea14a80fc 100644 --- a/lrpar/src/lib/ctbuilder.rs +++ b/lrpar/src/lib/ctbuilder.rs @@ -17,10 +17,10 @@ use std::{ use crate::{LexerTypes, RecoveryKind}; use bincode::{decode_from_slice, encode_to_vec, Decode, Encode}; use cfgrammar::{ - header::{Header, Value}, + header::{GrmtoolsSectionParser, Header, HeaderValue, Value}, markmap::{Entry, MergeBehavior}, newlinecache::NewlineCache, - yacc::{ast::ASTWithValidityInfo, ParserError, YaccGrammar, YaccKind, YaccOriginalActionKind}, + yacc::{ast::ASTWithValidityInfo, YaccGrammar, YaccKind, YaccOriginalActionKind}, Location, RIdx, Spanned, Symbol, }; use filetime::FileTime; @@ -499,8 +499,10 @@ where Some(YaccKind::Eco) => panic!("Eco compile-time grammar generation not supported."), Some(yk) => { let yk_value = Value::try_from(yk)?; - let mut o = - v.insert_entry((Location::Other("CTParserBuilder".to_string()), yk_value)); + let mut o = v.insert_entry(HeaderValue( + Location::Other("CTParserBuilder".to_string()), + yk_value, + )); o.set_merge_behavior(MergeBehavior::Ours); } None => { @@ -512,9 +514,11 @@ where match header.entry("recoverer".to_string()) { Entry::Occupied(_) => unreachable!(), Entry::Vacant(v) => { - let rk_value: Value = Value::try_from(recoverer)?; - let mut o = - v.insert_entry((Location::Other("CTParserBuilder".to_string()), rk_value)); + let rk_value: Value = Value::try_from(recoverer)?; + let mut o = v.insert_entry(HeaderValue( + Location::Other("CTParserBuilder".to_string()), + rk_value, + )); o.set_merge_behavior(MergeBehavior::Ours); } } @@ -530,9 +534,32 @@ where let inc = read_to_string(grmp).map_err(|e| format!("When reading '{}': {e}", grmp.display()))?; - let ast_validation = ASTWithValidityInfo::new(&mut header, &inc); + let parsed_header = GrmtoolsSectionParser::new(&inc, false).parse(); + if let Err(errs) = parsed_header { + return Err(format!( + "Error parsing `%grmtools` section:\n{}", + errs.iter() + .map(|e| e.to_string()) + .collect::>() + .join("\n") + ))?; + } + let (parsed_header, _) = parsed_header.unwrap(); + header.merge_from(parsed_header)?; + self.yacckind = header + .get("yacckind") + .map(|HeaderValue(_, val)| val) + .map(YaccKind::try_from) + .transpose()?; + header.mark_used(&"yacckind".to_string()); + let ast_validation = if let Some(yk) = self.yacckind { + ASTWithValidityInfo::new(yk, &inc) + } else { + Err("Missing 'yacckind'".to_string())? + }; + header.mark_used(&"recoverer".to_string()); - let rk_val = header.get("recoverer").map(|(_, rk_val)| rk_val); + let rk_val = header.get("recoverer").map(|HeaderValue(_, rk_val)| rk_val); if let Some(rk_val) = rk_val { self.recoverer = Some(RecoveryKind::try_from(rk_val)?); @@ -540,8 +567,7 @@ where // Fallback to the default recoverykind. self.recoverer = Some(RecoveryKind::CPCTPlus); } - - self.yacckind = ast_validation.yacc_kind(); + self.yacckind = Some(ast_validation.yacc_kind()); let warnings = ast_validation.ast().warnings(); let loc_fmt = |err_str, loc, inc: &str, line_cache: &NewlineCache| match loc { Location::Span(span) => { @@ -563,15 +589,6 @@ where let spanned_fmt = |x: &dyn Spanned, inc: &str, line_cache: &NewlineCache| { loc_fmt(x.to_string(), Location::Span(x.spans()[0]), inc, line_cache) }; - let perror_fmt = |e: &ParserError, inc: &str, line_cache: &NewlineCache| match e { - ParserError::YaccGrammarError(e) => spanned_fmt(e, inc, line_cache), - ParserError::HeaderError(e) => { - loc_fmt(e.to_string(), e.locations[0].clone(), inc, line_cache) - } - _ => { - format!("Unrecognized error: {}", e) - } - }; let res = YaccGrammar::::new_from_ast_with_validity_info(&ast_validation); let grm = match res { @@ -615,13 +632,13 @@ where format!( "\n\t{}", errs.iter() - .map(|e| perror_fmt(e, &inc, &line_cache)) + .map(|e| spanned_fmt(e, &inc, &line_cache)) .chain(warnings.iter().map(|w| spanned_fmt(w, &inc, &line_cache))) .collect::>() .join("\n\t") ) } else { - perror_fmt(errs.first().unwrap(), &inc, &line_cache) + spanned_fmt(errs.first().unwrap(), &inc, &line_cache) }))?; } }; @@ -635,11 +652,15 @@ where if !unused_keys.is_empty() { return Err(format!("Unused keys in header: {}", unused_keys.join(", ")).into()); } - let missing_keys = header.missing(); + let missing_keys = header + .missing() + .iter() + .map(|s| s.as_str()) + .collect::>(); if !missing_keys.is_empty() { return Err(format!( "Required values were missing from the header: {}", - unused_keys.join(", ") + missing_keys.join(", ") ) .into()); } diff --git a/lrpar/src/lib/parser.rs b/lrpar/src/lib/parser.rs index 1530d9c6e..40be99f78 100644 --- a/lrpar/src/lib/parser.rs +++ b/lrpar/src/lib/parser.rs @@ -14,7 +14,7 @@ use std::time::{Duration, Instant}; use web_time::{Duration, Instant}; use cactus::Cactus; -use cfgrammar::{header::Value, yacc::YaccGrammar, RIdx, Span, TIdx}; +use cfgrammar::{header::Value, span::Location, yacc::YaccGrammar, RIdx, Span, TIdx}; use lrtable::{Action, StIdx, StateTable}; use num_traits::{AsPrimitive, PrimInt, Unsigned}; use proc_macro2::TokenStream; @@ -629,9 +629,9 @@ pub enum RecoveryKind { None, } -impl TryFrom for Value { - type Error = cfgrammar::header::HeaderError; - fn try_from(rk: RecoveryKind) -> Result { +impl TryFrom for Value { + type Error = cfgrammar::header::HeaderError; + fn try_from(rk: RecoveryKind) -> Result, Self::Error> { use cfgrammar::{ header::{Namespaced, Setting}, Location, @@ -650,9 +650,9 @@ impl TryFrom for Value { } } -impl TryFrom<&Value> for RecoveryKind { - type Error = cfgrammar::header::HeaderError; - fn try_from(rk: &Value) -> Result { +impl TryFrom<&Value> for RecoveryKind { + type Error = cfgrammar::header::HeaderError; + fn try_from(rk: &Value) -> Result { use cfgrammar::header::{HeaderError, HeaderErrorKind, Namespaced, Setting}; match rk { @@ -1064,7 +1064,7 @@ pub(crate) mod test { use super::*; use crate::{ - test_utils::{header_for_yacckind, TestLexError, TestLexeme, TestLexerTypes}, + test_utils::{TestLexError, TestLexeme, TestLexerTypes}, Lexeme, Lexer, }; @@ -1105,7 +1105,7 @@ pub(crate) mod test { >, ) { let grm = YaccGrammar::::new_with_storaget( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), grms, ) .unwrap(); diff --git a/lrpar/src/lib/test_utils.rs b/lrpar/src/lib/test_utils.rs index 5dd18fe56..bf306a47a 100644 --- a/lrpar/src/lib/test_utils.rs +++ b/lrpar/src/lib/test_utils.rs @@ -87,29 +87,3 @@ impl fmt::Display for TestLexError { unreachable!(); } } - -#[macro_export] -#[cfg(test)] -macro_rules! header_for_yacckind { - ($yk:expr) => {{ - use cfgrammar::header::{Header, Value}; - use cfgrammar::markmap::{Entry, MergeBehavior}; - use cfgrammar::Location; - - let mut header = Header::new(); - match header.entry("yacckind".to_string()) { - Entry::Occupied(_) => unreachable!("Header should be empty"), - Entry::Vacant(v) => { - let mut o = v.insert_entry(( - Location::Other("Testsuite".to_string()), - Value::try_from($yk).unwrap(), - )); - o.mark_required(); - o.set_merge_behavior(MergeBehavior::Ours); - } - }; - header - }}; -} - -pub use header_for_yacckind; diff --git a/lrtable/src/lib/itemset.rs b/lrtable/src/lib/itemset.rs index f7200d710..514742ac6 100644 --- a/lrtable/src/lib/itemset.rs +++ b/lrtable/src/lib/itemset.rs @@ -158,7 +158,6 @@ where #[cfg(test)] mod test { - use crate::test_utils::*; use cfgrammar::{ yacc::{YaccGrammar, YaccKind, YaccOriginalActionKind}, SIdx, Symbol, @@ -173,7 +172,7 @@ mod test { fn test_dragon_grammar() { // From http://binarysculpting.com/2012/02/04/computing-lr1-closure/ let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start S %% @@ -201,7 +200,7 @@ mod test { fn eco_grammar() -> YaccGrammar { YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start S %token a b c d f @@ -252,7 +251,7 @@ mod test { // aSb fn grammar3() -> YaccGrammar { YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start S %token a b c d diff --git a/lrtable/src/lib/mod.rs b/lrtable/src/lib/mod.rs index 1ec5615a6..0cafb464c 100644 --- a/lrtable/src/lib/mod.rs +++ b/lrtable/src/lib/mod.rs @@ -16,8 +16,6 @@ mod itemset; mod pager; mod stategraph; pub mod statetable; -#[cfg(test)] -pub mod test_utils; pub use crate::{ stategraph::StateGraph, diff --git a/lrtable/src/lib/pager.rs b/lrtable/src/lib/pager.rs index ea674370e..c4efdf21d 100644 --- a/lrtable/src/lib/pager.rs +++ b/lrtable/src/lib/pager.rs @@ -398,7 +398,6 @@ where mod test { use vob::Vob; - use crate::test_utils::*; use crate::{pager::pager_stategraph, stategraph::state_exists, StIdx}; use cfgrammar::{ yacc::{YaccGrammar, YaccKind, YaccOriginalActionKind}, @@ -441,7 +440,7 @@ mod test { // aSb fn grammar3() -> YaccGrammar { YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start S %token a b c d @@ -520,7 +519,7 @@ mod test { // Pager grammar fn grammar_pager() -> YaccGrammar { YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start X %% diff --git a/lrtable/src/lib/stategraph.rs b/lrtable/src/lib/stategraph.rs index f04a1e9a2..bfc011775 100644 --- a/lrtable/src/lib/stategraph.rs +++ b/lrtable/src/lib/stategraph.rs @@ -248,7 +248,6 @@ pub(crate) fn state_exists( #[cfg(test)] mod test { - use crate::test_utils::*; use crate::{pager::pager_stategraph, StIdx}; use cfgrammar::{ yacc::{YaccGrammar, YaccKind, YaccOriginalActionKind}, @@ -260,7 +259,7 @@ mod test { fn test_statetable_core() { // Taken from p13 of https://link.springer.com/article/10.1007/s00236-010-0115-6 let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start A %% diff --git a/lrtable/src/lib/statetable.rs b/lrtable/src/lib/statetable.rs index 53ded4a31..dff34a334 100644 --- a/lrtable/src/lib/statetable.rs +++ b/lrtable/src/lib/statetable.rs @@ -617,7 +617,6 @@ fn resolve_shift_reduce( #[cfg(test)] mod test { use super::{Action, StateTable, StateTableError, StateTableErrorKind}; - use crate::test_utils::*; use crate::{pager::pager_stategraph, StIdx}; use cfgrammar::{ yacc::{ @@ -633,7 +632,7 @@ mod test { fn test_statetable() { // Taken from p19 of www.cs.umd.edu/~mvz/cmsc430-s07/M10lr.pdf let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start Expr %% @@ -714,7 +713,7 @@ mod test { #[test] #[rustfmt::skip] fn test_default_reduce_reduce() { - let grm = YaccGrammar::new(&mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " + let grm = YaccGrammar::new(YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start A %% A : B 'x' | C 'x' 'x'; @@ -738,7 +737,7 @@ mod test { #[test] #[rustfmt::skip] fn test_default_shift_reduce() { - let grm = YaccGrammar::new(&mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " + let grm = YaccGrammar::new(YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start Expr %% Expr : Expr '+' Expr @@ -768,7 +767,7 @@ mod test { #[rustfmt::skip] fn test_conflict_resolution() { // Example taken from p54 of Locally least-cost error repair in LR parsers, Carl Cerecke - let grm = YaccGrammar::new(&mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " + let grm = YaccGrammar::new(YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start S %% S: A 'c' 'd' @@ -793,7 +792,7 @@ mod test { #[test] #[rustfmt::skip] fn test_left_associativity() { - let grm = YaccGrammar::new(&mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " + let grm = YaccGrammar::new(YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start Expr %left '+' %left '*' @@ -832,7 +831,7 @@ mod test { #[test] #[rustfmt::skip] fn test_left_right_associativity() { - let grm = &YaccGrammar::new(&mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " + let grm = &YaccGrammar::new(YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start Expr %right '=' %left '+' @@ -888,7 +887,7 @@ mod test { #[test] #[rustfmt::skip] fn test_left_right_nonassoc_associativity() { - let grm = YaccGrammar::new(&mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " + let grm = YaccGrammar::new(YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start Expr %right '=' %left '+' @@ -963,7 +962,7 @@ mod test { #[test] fn conflicts() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start A %% @@ -1000,7 +999,7 @@ C : 'a'; #[test] fn reduce_reduce_conflict() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start S %% @@ -1034,7 +1033,7 @@ X: '1' ; Y: '2' ; #[test] fn accept_reduce_conflict() { let grm = YaccGrammar::new( - &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + YaccKind::Original(YaccOriginalActionKind::GenericParseTree), " %start D %% @@ -1057,7 +1056,7 @@ D : D; fn test_accept_reduce_conflict_spans1() { let src = "%% S: S | ;"; - let yk = &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::NoAction)); + let yk = YaccKind::Original(YaccOriginalActionKind::NoAction); let ast_validity = ASTWithValidityInfo::new(yk, src); let grm = YaccGrammar::::new_from_ast_with_validity_info(&ast_validity).unwrap(); let sg = pager_stategraph(&grm); @@ -1109,7 +1108,7 @@ S: S | ;"; let src = "%% S: T | ; T: S | ;"; - let yk = &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::NoAction)); + let yk = YaccKind::Original(YaccOriginalActionKind::NoAction); let ast_validity = ASTWithValidityInfo::new(yk, src); let grm = YaccGrammar::::new_from_ast_with_validity_info(&ast_validity).unwrap(); let sg = pager_stategraph(&grm); diff --git a/lrtable/src/lib/test_utils.rs b/lrtable/src/lib/test_utils.rs deleted file mode 100644 index 7fa4f5168..000000000 --- a/lrtable/src/lib/test_utils.rs +++ /dev/null @@ -1,25 +0,0 @@ -pub use cfgrammar::header::{Header, Value}; -pub use cfgrammar::markmap::{Entry, MergeBehavior}; -pub use cfgrammar::Location; - -#[macro_export] -#[cfg(test)] -macro_rules! header_for_yacckind { - ($yk:expr) => {{ - let mut header = Header::new(); - match header.entry("yacckind".to_string()) { - Entry::Occupied(_) => unreachable!("Header should be empty"), - Entry::Vacant(v) => { - let mut o = v.insert_entry(( - Location::Other("Testsuite".to_string()), - Value::try_from($yk).unwrap(), - )); - o.mark_required(); - o.set_merge_behavior(MergeBehavior::Ours); - } - }; - header - }}; -} - -pub use header_for_yacckind; diff --git a/nimbleparse/src/main.rs b/nimbleparse/src/main.rs index 756bc242b..0cb9d1d8c 100644 --- a/nimbleparse/src/main.rs +++ b/nimbleparse/src/main.rs @@ -1,9 +1,9 @@ mod diagnostics; use crate::diagnostics::*; use cfgrammar::{ - header::{Header, Value}, + header::{GrmtoolsSectionParser, Header, HeaderValue, Value}, markmap::Entry, - yacc::{ast::ASTWithValidityInfo, ParserError, YaccGrammar, YaccKind, YaccOriginalActionKind}, + yacc::{ast::ASTWithValidityInfo, YaccGrammar, YaccKind, YaccOriginalActionKind}, Location, Span, }; use getopts::Options; @@ -124,7 +124,7 @@ fn main() { ); header.insert( "recoverer".to_string(), - ( + HeaderValue( Location::CommandLine, Value::try_from(match &*s.to_lowercase() { "cpctplus" => RecoveryKind::CPCTPlus, @@ -143,7 +143,7 @@ fn main() { match matches.opt_str("y") { None => {} Some(s) => { - entry.insert(( + entry.insert(HeaderValue( Location::CommandLine, Value::try_from(match &*s.to_lowercase() { "eco" => YaccKind::Eco, @@ -176,8 +176,36 @@ fn main() { let yacc_y_path = PathBuf::from(&matches.free[1]); let yacc_src = read_file(&yacc_y_path); - let ast_validation = ASTWithValidityInfo::new(&mut header, &yacc_src); - let recoverykind = if let Some((_, rk_val)) = header.get("recoverer") { + let yk_val = header.get("yacckind"); + if yk_val.is_none() { + let parsed_header = GrmtoolsSectionParser::new(&yacc_src, true).parse(); + match parsed_header { + Ok((parsed_header, _)) => { + header + .merge_from(parsed_header) + .expect("Specified merge behavior cannot fail"); + } + Err(errs) => { + eprintln!( + "Error(s) parsing `%grmtools` section:\n {}\n", + errs.iter() + .map(|e| e.to_string()) + .collect::>() + .join("\n") + ); + std::process::exit(1); + } + } + } + let yk_val = header.get("yacckind"); + if yk_val.is_none() { + eprintln!("yacckind not specified in the %grmtools section of the grammar or via the '-y' parameter"); + std::process::exit(1); + } + let HeaderValue(_, yk_val) = yk_val.unwrap(); + let yacc_kind = YaccKind::try_from(yk_val).unwrap_or(YaccKind::Grmtools); + let ast_validation = ASTWithValidityInfo::new(yacc_kind, &yacc_src); + let recoverykind = if let Some(HeaderValue(_, rk_val)) = header.get("recoverer") { match RecoveryKind::try_from(rk_val) { Err(e) => { eprintln!("{e}"); @@ -208,17 +236,7 @@ fn main() { let formatter = SpannedDiagnosticFormatter::new(&yacc_src, &yacc_y_path).unwrap(); eprintln!("{ERROR}{}", formatter.file_location_msg("", None)); for e in errs { - match e { - ParserError::YaccGrammarError(e) => { - eprintln!("{}", indent(&formatter.format_error(e).to_string(), " ")); - } - ParserError::HeaderError(e) => { - eprintln!("{}", indent(&e.to_string(), " ")); - } - e => { - eprintln!("{}", indent(&e.to_string(), " ")); - } - } + eprintln!("{}", indent(&e.to_string(), " ")); } eprintln!("{WARNING}{}", formatter.file_location_msg("", None)); for w in warnings {