diff --git a/cfgrammar/src/lib/header.rs b/cfgrammar/src/lib/header.rs index dfdd058e3..a75012623 100644 --- a/cfgrammar/src/lib/header.rs +++ b/cfgrammar/src/lib/header.rs @@ -1,15 +1,38 @@ -use crate::Span; +use crate::{ + markmap::{Entry, MarkMap}, + yacc::{ParserError, YaccKind, YaccOriginalActionKind}, + Location, Span, +}; use lazy_static::lazy_static; use regex::{Regex, RegexBuilder}; -use std::collections::{hash_map::Entry, HashMap}; +use std::{error::Error, fmt}; -#[derive(Debug)] +/// An error regarding the `%grmtools` header section. +/// +/// It could be any of: +/// +/// * 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 { pub kind: HeaderErrorKind, - pub spans: Vec, + pub locations: Vec, } -#[derive(Debug, Eq, PartialEq)] +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) + } +} + +#[derive(Debug, Eq, PartialEq, Copy, Clone)] #[non_exhaustive] #[doc(hidden)] pub enum HeaderErrorKind { @@ -17,7 +40,26 @@ pub enum HeaderErrorKind { IllegalName, ExpectedToken(char), DuplicateEntry, + InvalidEntry(&'static str), + ConversionError(&'static str, &'static str), +} + +impl fmt::Display for HeaderErrorKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match self { + HeaderErrorKind::MissingGrmtoolsSection => "Missing %grmtools section", + HeaderErrorKind::IllegalName => "Illegal name", + HeaderErrorKind::ExpectedToken(c) => &format!("Expected token: '{}", c), + HeaderErrorKind::InvalidEntry(s) => &format!("Invalid entry: '{}'", s), + HeaderErrorKind::DuplicateEntry => "Duplicate Entry", + HeaderErrorKind::ConversionError(t, err_str) => { + &format!("Converting header value to type '{}': {}", t, err_str) + } + }; + write!(f, "{}", s) + } } + /// Indicates a value prefixed by an optional namespace. /// `Foo::Bar` with optional `Foo` specified being /// ```rust,ignore @@ -35,9 +77,10 @@ pub enum HeaderErrorKind { /// } /// ``` #[derive(Debug, Eq, PartialEq)] +#[doc(hidden)] pub struct Namespaced { - pub namespace: Option<(String, Span)>, - pub member: (String, Span), + pub namespace: Option<(String, Location)>, + pub member: (String, Location), } #[derive(Debug, Eq, PartialEq)] @@ -52,7 +95,7 @@ pub enum Setting { ctor: Namespaced, arg: Namespaced, }, - Num(u64, Span), + Num(u64, Location), } /// Parser for the `%grmtools` section @@ -62,9 +105,14 @@ pub struct GrmtoolsSectionParser<'input> { required: bool, } +/// The value contained within a `Header` +/// +/// To be useful across diverse crates this types fields are limited to types derived from `core::` types. +/// like booleans, numeric types, and string values. #[derive(Debug, Eq, PartialEq)] +#[doc(hidden)] pub enum Value { - Flag(bool), + Flag(bool, Location), Setting(Setting), } @@ -82,12 +130,12 @@ const MAGIC: &str = "%grmtools"; fn add_duplicate_occurrence( errs: &mut Vec, kind: HeaderErrorKind, - orig_span: Span, - dup_span: Span, + orig_loc: Location, + dup_loc: Location, ) { if !errs.iter_mut().any(|e| { - if e.kind == kind && e.spans[0] == orig_span { - e.spans.push(dup_span); + if e.kind == kind && e.locations[0] == orig_loc { + e.locations.push(dup_loc.clone()); true } else { false @@ -95,7 +143,7 @@ fn add_duplicate_occurrence( }) { errs.push(HeaderError { kind, - spans: vec![orig_span, dup_span], + locations: vec![orig_loc, dup_loc], }); } } @@ -104,18 +152,18 @@ impl<'input> GrmtoolsSectionParser<'input> { pub fn parse_value( &'_ self, mut i: usize, - ) -> Result<(String, Span, Value, usize), HeaderError> { - if let Some(i) = self.lookahead_is("!", i) { - let (flag_name, j) = self.parse_name(i)?; + ) -> Result<(String, Location, Value, usize), HeaderError> { + if let Some(j) = self.lookahead_is("!", i) { + let (flag_name, k) = self.parse_name(j)?; Ok(( flag_name, - Span::new(i, j), - Value::Flag(false), - self.parse_ws(j), + Location::Span(Span::new(j, k)), + Value::Flag(false, Location::Span(Span::new(i, k))), + self.parse_ws(k), )) } else { let (key_name, j) = self.parse_name(i)?; - let key_span = Span::new(i, j); + let key_span = Location::Span(Span::new(i, j)); i = self.parse_ws(j); if let Some(j) = self.lookahead_is(":", i) { i = self.parse_ws(j); @@ -125,7 +173,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, num_span); + let val = Setting::Num(num, Location::Span(num_span)); i = self.parse_ws(num_span.end()); Ok((key_name, key_span, Value::Setting(val), i)) } @@ -149,7 +197,7 @@ impl<'input> GrmtoolsSectionParser<'input> { } else { Err(HeaderError { kind: HeaderErrorKind::ExpectedToken(')'), - spans: vec![Span::new(i, i)], + locations: vec![Location::Span(Span::new(i, i))], }) } } else { @@ -163,7 +211,7 @@ impl<'input> GrmtoolsSectionParser<'input> { } } } else { - Ok((key_name, key_span, Value::Flag(true), i)) + Ok((key_name, key_span.clone(), Value::Flag(true, key_span), i)) } } } @@ -171,12 +219,12 @@ impl<'input> GrmtoolsSectionParser<'input> { 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 = Span::new(i, j); + let name_span = Location::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 = Span::new(i, j); + let member_val_span = Location::Span(Span::new(i, j)); i = self.parse_ws(j); Ok(( Namespaced { @@ -210,17 +258,17 @@ impl<'input> GrmtoolsSectionParser<'input> { } #[allow(clippy::type_complexity)] - pub fn parse(&'_ self) -> Result<(HashMap, 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 = HashMap::new(); + let mut ret = Header::new(); i = self.parse_ws(i); let section_start_pos = i; if let Some(j) = self.lookahead_is("{", i) { i = self.parse_ws(j); while self.lookahead_is("}", i).is_none() && i < self.src.len() { - let (key, key_span, val, j) = match self.parse_value(i) { - Ok((key, key_span, val, pos)) => (key, key_span, val, pos), + let (key, key_loc, val, j) = match self.parse_value(i) { + Ok((key, key_loc, val, pos)) => (key, key_loc, val, pos), Err(e) => { errs.push(e); return Err(errs); @@ -228,16 +276,16 @@ impl<'input> GrmtoolsSectionParser<'input> { }; match ret.entry(key) { Entry::Occupied(orig) => { - let (orig_span, _) = orig.get(); + let (orig_loc, _): &(Location, Value) = orig.get(); add_duplicate_occurrence( &mut errs, HeaderErrorKind::DuplicateEntry, - *orig_span, - key_span, + orig_loc.clone(), + key_loc, ) } Entry::Vacant(entry) => { - entry.insert((key_span, val)); + entry.insert((key_loc, val)); } } if let Some(j) = self.lookahead_is(",", j) { @@ -257,25 +305,28 @@ impl<'input> GrmtoolsSectionParser<'input> { } else { errs.push(HeaderError { kind: HeaderErrorKind::ExpectedToken('}'), - spans: vec![Span::new(section_start_pos, self.src.len())], + locations: vec![Location::Span(Span::new( + section_start_pos, + self.src.len(), + ))], }); Err(errs) } } else { errs.push(HeaderError { kind: HeaderErrorKind::ExpectedToken('{'), - spans: vec![Span::new(i, i)], + locations: vec![Location::Span(Span::new(i, i))], }); Err(errs) } } else if self.required { errs.push(HeaderError { kind: HeaderErrorKind::MissingGrmtoolsSection, - spans: vec![Span::new(0, 0)], + locations: vec![Location::Span(Span::new(0, 0))], }); Err(errs) } else { - Ok((HashMap::new(), 0)) + Ok((Header::new(), 0)) } } @@ -290,7 +341,7 @@ impl<'input> GrmtoolsSectionParser<'input> { } None => Err(HeaderError { kind: HeaderErrorKind::IllegalName, - spans: vec![Span::new(i, i)], + locations: vec![Location::Span(Span::new(i, i))], }), } } @@ -311,6 +362,155 @@ impl<'input> GrmtoolsSectionParser<'input> { } } +/// A data structure representation of the %grmtools section. +pub type Header = MarkMap; + +impl TryFrom for Value { + type Error = HeaderError; + fn try_from(kind: YaccKind) -> Result { + let from_loc = Location::Other("From".to_string()); + Ok(match kind { + YaccKind::Grmtools => Value::Setting(Setting::Unitary(Namespaced { + namespace: Some(("yacckind".to_string(), from_loc.clone())), + member: ("grmtools".to_string(), from_loc), + })), + YaccKind::Eco => Value::Setting(Setting::Unitary(Namespaced { + namespace: Some(("yacckind".to_string(), from_loc.clone())), + member: ("eco".to_string(), from_loc), + })), + YaccKind::Original(action_kind) => Value::Setting(Setting::Constructor { + ctor: Namespaced { + namespace: Some(("yacckind".to_string(), from_loc.clone())), + member: ("original".to_string(), from_loc.clone()), + }, + arg: match action_kind { + YaccOriginalActionKind::NoAction => Namespaced { + namespace: Some(("yaccoriginalactionkind".to_string(), from_loc.clone())), + member: ("noaction".to_string(), from_loc), + }, + YaccOriginalActionKind::UserAction => Namespaced { + namespace: Some(("yaccoriginalactionkind".to_string(), from_loc.clone())), + member: ("useraction".to_string(), from_loc), + }, + YaccOriginalActionKind::GenericParseTree => Namespaced { + namespace: Some(("yaccoriginalactionkind".to_string(), from_loc.clone())), + member: ("genericparsetree".to_string(), from_loc), + }, + }, + }), + }) + } +} + +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 { + kind: HeaderErrorKind::ConversionError( + "From", + "Cannot convert boolean to YaccKind", + ), + locations: vec![loc.clone()], + }), + Value::Setting(Setting::Num(_, loc)) => Err(HeaderError { + kind: HeaderErrorKind::ConversionError( + "From", + "Cannot convert number to YaccKind", + ), + locations: vec![loc.clone()], + }), + Value::Setting(Setting::Unitary(Namespaced { + namespace, + member: (yk_value, yk_value_loc), + })) => { + if let Some((ns, ns_loc)) = namespace { + if ns != "yacckind" { + err_locs.push(ns_loc.clone()); + } + } + let yacckinds = [ + ("grmtools".to_string(), YaccKind::Grmtools), + ("eco".to_string(), YaccKind::Eco), + ]; + let yk_found = yacckinds + .iter() + .find_map(|(yk_str, yk)| (yk_str == yk_value).then_some(yk)); + if let Some(yk) = yk_found { + if err_locs.is_empty() { + Ok(*yk) + } else { + Err(HeaderError { + kind: HeaderErrorKind::InvalidEntry("yacckind"), + locations: err_locs, + }) + } + } else { + err_locs.push(yk_value_loc.clone()); + Err(HeaderError { + kind: HeaderErrorKind::InvalidEntry("yacckind"), + locations: err_locs, + }) + } + } + Value::Setting(Setting::Constructor { + ctor: + Namespaced { + namespace: yk_namespace, + member: (yk_str, yk_loc), + }, + arg: + Namespaced { + namespace: ak_namespace, + member: (ak_str, ak_loc), + }, + }) => { + if let Some((yk_ns, yk_ns_loc)) = yk_namespace { + if yk_ns != "yacckind" { + err_locs.push(yk_ns_loc.clone()); + } + } + + if yk_str != "original" { + err_locs.push(yk_loc.clone()); + } + + if let Some((ak_ns, ak_ns_loc)) = ak_namespace { + if ak_ns != "yaccoriginalactionkind" { + err_locs.push(ak_ns_loc.clone()); + } + } + let actionkinds = [ + ("noaction", YaccOriginalActionKind::NoAction), + ("useraction", YaccOriginalActionKind::UserAction), + ("genericparsetree", YaccOriginalActionKind::GenericParseTree), + ]; + let yk_found = actionkinds.iter().find_map(|(actionkind_str, actionkind)| { + (ak_str == actionkind_str).then_some(YaccKind::Original(*actionkind)) + }); + + if let Some(yk) = yk_found { + if err_locs.is_empty() { + Ok(yk) + } else { + Err(HeaderError { + kind: HeaderErrorKind::InvalidEntry("yacckind"), + locations: err_locs, + }) + } + } else { + err_locs.push(ak_loc.clone()); + Err(HeaderError { + kind: HeaderErrorKind::InvalidEntry("yacckind"), + locations: err_locs, + }) + } + } + } + } +} + #[cfg(test)] mod test { use super::*; @@ -354,7 +554,7 @@ mod test { let errs = res.unwrap_err(); assert_eq!(errs.len(), 1); assert_eq!(errs[0].kind, HeaderErrorKind::DuplicateEntry); - assert_eq!(errs[0].spans.len(), 3); + assert_eq!(errs[0].locations.len(), 3); } } } diff --git a/cfgrammar/src/lib/markmap.rs b/cfgrammar/src/lib/markmap.rs new file mode 100644 index 000000000..060f1ae03 --- /dev/null +++ b/cfgrammar/src/lib/markmap.rs @@ -0,0 +1,831 @@ +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. +#[derive(Debug, PartialEq, Eq)] +pub struct MarkMap { + contents: Vec<(K, u16, Option)>, +} + +/// Defines the merge behavior for a single key in the markmap. +#[repr(u8)] +#[derive(Clone, Copy)] +#[doc(hidden)] +pub enum MergeBehavior { + /// The value in `self` takes precedence. + Theirs = 1 << 0, + /// The value in `other` takes precedence. + Ours = 1 << 1, + /// It is an error if the key is present in both `MarkMaps`. + /// This is the default if no merge behavior is set. + MutuallyExclusive = 1 << 2, +} + +/// Conflicting values were present while merging, and the +/// merge behavior did not specify a means to resolve them. +#[derive(Debug)] +#[doc(hidden)] +pub enum MergeError { + // Contains the key which was present in both, and the value which was present in `Theirs`. + Exclusivity(K, V), +} + +/// A view into a single entry in a `MarkMap`, which may either be vacant or occupied. +#[doc(hidden)] +pub enum Entry<'a, K, V> { + Occupied(OccupiedEntry<'a, K, V>), + Vacant(VacantEntry<'a, K, V>), +} + +#[repr(u8)] +#[derive(Clone, Copy)] +#[doc(hidden)] +enum Mark { + // There are some other interesting marks that could be added based on row polymorphic records. + // Marks such as `Prohibited` could be used to prove disjointedness. + Used, + Required, + MergeBehavior(MergeBehavior), +} + +impl Mark { + fn repr(self) -> u16 { + match self { + Self::Used => 1 << 0, + Self::Required => 1 << 1, + Self::MergeBehavior(mb) => (1 << 2) | ((mb as u16) << 8), + } + } +} + +impl<'a, K: Ord + Clone, V> VacantEntry<'a, K, V> { + /// Inserts a value into a vacant entry. + /// Returns a mutable reference to value inserted. + pub fn insert(self, val: V) -> &'a mut V { + match self.pos { + Ok(pos) => { + self.map.contents[pos].2 = Some(val); + self.map.contents[pos].2.as_mut().unwrap() + } + Err(pos) => { + self.map.contents.insert(pos, (self.key, 0, Some(val))); + self.map.contents[pos].2.as_mut().unwrap() + } + } + } + + /// Inserts a value into a vacant entry. + /// Returns an occupied entry. + pub fn insert_entry(self, val: V) -> OccupiedEntry<'a, K, V> { + match self.pos { + Ok(pos) => { + self.map.contents[pos].2 = Some(val); + OccupiedEntry { pos, map: self.map } + } + Err(pos) => { + self.map.contents.insert(pos, (self.key, 0, Some(val))); + OccupiedEntry { pos, map: self.map } + } + } + } + + /// Inserts the key into the `MarkMap`. + /// + /// If you want to insert a value into the `MarkMap` use `insert` or `insert_entry` instead. + /// This function can be used if you want to mark a vacant key as `required` or `used`. + pub fn occupied_entry(self) -> OccupiedEntry<'a, K, V> { + match self.pos { + Ok(pos) => { + self.map.contents[pos].2 = None; + OccupiedEntry { pos, map: self.map } + } + Err(pos) => { + self.map.contents.insert(pos, (self.key, 0, None)); + OccupiedEntry { pos, map: self.map } + } + } + } + + /// Returns the key associated with this entry. + pub fn key(&self) -> &K { + &self.key + } +} + +impl OccupiedEntry<'_, K, V> { + /// Returns the value associated with this entry. + pub fn get(&self) -> &V { + self.map.contents[self.pos].2.as_ref().unwrap() + } + + /// Sets the value of the entry, and returns the entry’s old value. + pub fn insert(self, val: V) -> V { + let v: Option = self.map.contents[self.pos].2.take(); + self.map.contents[self.pos].2 = Some(val); + v.unwrap() + } + + /// Gets the mark associated with the key of this entry. + pub fn get_mark(&self) -> u16 { + self.map.contents[self.pos].1 + } + + /// Marks the key associated with this entry as used. + pub fn mark_used(&mut self) { + let repr = self.map.contents[self.pos].1 | Mark::Used.repr(); + self.map.contents[self.pos].1 = repr; + } + + /// Returns whether the key associated with this entry is used. + pub fn is_used(&self) -> bool { + self.map.contents[self.pos].1 & Mark::Used.repr() != 0 + } + + /// Marks the key associated with this entry as required. + pub fn mark_required(&mut self) { + let repr = self.map.contents[self.pos].1 | Mark::Required.repr(); + self.map.contents[self.pos].1 = repr; + } + + /// Returns whether the key associated with this entry is required. + pub fn is_required(&self) -> bool { + self.map.contents[self.pos].1 & Mark::Required.repr() != 0 + } + + /// Sets the merge behavior for the key associated with this entry. + pub fn set_merge_behavior(&mut self, mb: MergeBehavior) { + let mut repr = self.map.contents[self.pos].1; + let merge_reprs = Mark::MergeBehavior(MergeBehavior::MutuallyExclusive).repr() + | Mark::MergeBehavior(MergeBehavior::Ours).repr() + | Mark::MergeBehavior(MergeBehavior::Theirs).repr(); + // Zap just the MergeBehavior bits. + repr ^= repr & merge_reprs; + repr |= Mark::MergeBehavior(mb).repr(); + self.map.contents[self.pos].1 = repr; + } +} + +/// A view into an occupied entry in a `MarkMap`. It is part of the `Entry` enum. +#[doc(hidden)] +pub struct OccupiedEntry<'a, K, V> { + pos: usize, + map: &'a mut MarkMap, +} + +/// A view into a vacant entry in a `MarkMap`. It is part of the `Entry` enum. +#[doc(hidden)] +pub struct VacantEntry<'a, K, V> { + pos: Result, + key: K, + map: &'a mut MarkMap, +} + +impl MarkMap { + /// Returns a new `MarkMap`. + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + MarkMap { contents: vec![] } + } + + /// 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 { + Ok(pos) => { + let ret = self.contents[pos].2.take(); + self.contents[pos].2 = Some(val); + ret + } + Err(pos) => { + self.contents.insert(pos, (key, 0, Some(val))); + None + } + } + } + + /// Gets the raw mark value, for testing purposes. + #[cfg(test)] + pub(crate) fn get_mark(&mut self, key: &K) -> Option { + let pos = self.contents.binary_search_by(|(k, _, _)| k.cmp(key)); + match pos { + Ok(pos) => Some(self.contents[pos].1), + Err(_) => None, + } + } + + /// 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 { + Ok(pos) => { + let mut repr = self.contents[pos].1; + repr |= Mark::Used.repr(); + self.contents[pos].1 = repr; + } + Err(pos) => { + self.contents + .insert(pos, (key.to_owned(), Mark::Used.repr(), None)); + } + } + } + + /// 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 { + Ok(pos) => { + let mut repr = self.contents[pos].1; + repr |= Mark::Required.repr(); + self.contents[pos].1 = repr; + } + Err(pos) => { + self.contents + .insert(pos, (key.to_owned(), Mark::Required.repr(), None)); + } + } + } + + /// 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 { + Ok(pos) => self.contents[pos].1 & Mark::Required.repr() != 0, + _ => false, + } + } + + /// 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 { + Ok(pos) => { + let mut repr = self.contents[pos].1; + let merge_reprs = Mark::MergeBehavior(MergeBehavior::MutuallyExclusive).repr() + | Mark::MergeBehavior(MergeBehavior::Ours).repr() + | Mark::MergeBehavior(MergeBehavior::Theirs).repr(); + // Zap just the MergeBehavior bits. + repr ^= repr & merge_reprs; + repr |= Mark::MergeBehavior(mb).repr(); + self.contents[pos].1 = repr; + } + Err(pos) => { + self.contents + .insert(pos, (key.to_owned(), Mark::MergeBehavior(mb).repr(), None)); + } + } + } + + /// Returns a `Some(value)` associated with `key` if present otherwise `None`. + #[doc(hidden)] + pub fn get(&self, key: &Q) -> Option<&V> + where + K: Borrow, + Q: Ord + ?Sized, + { + let pos = self.contents.binary_search_by(|(k, _, _)| { + let q: &Q = k.borrow(); + q.cmp(key) + }); + match pos { + Err(_) => None, + Ok(pos) => self.contents[pos].2.as_ref(), + } + } + + /// Returns true if the `MarkMap` contains `key` otherwise false. + #[doc(hidden)] + pub fn contains_key(&self, key: &Q) -> bool + where + K: Borrow, + Q: Ord + ?Sized, + { + self.get(key).is_some() + } + + /// 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 { + Err(_) => None, + Ok(pos) => self.contents.remove(pos).2, + } + } + + /// 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 { + Err(pos) => Entry::Vacant(VacantEntry { + pos: Err(pos), + key, + map: self, + }), + Ok(pos) => { + if self.contents[pos].2.is_some() { + Entry::Occupied(OccupiedEntry { pos, map: self }) + } else { + Entry::Vacant(VacantEntry { + pos: Ok(pos), + key, + map: self, + }) + } + } + } + } + + /// Merges items from `other` into `self`, for each value it checks the `MergeBehavior` of self. + /// The `MergeBehavior` of other is not considered. + /// + /// * `MergeBehavior::Ours`: If a value is set in `other`, a value set in self takes precedence. + /// * `MergeBehavior::Theirs`: if a value in `other` `is_some()`, then it will overwrite values in self. + /// * `MergeBehavior::MutuallyExclusive` If a value is set in `other`, that value should be `None` in self. + /// * If `MergeBehavior` is unset, defaults to `MergeBehavior::MutuallyExclusive`. + /// + /// 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, other: Self) -> Result<(), MergeError>> { + for (their_key, their_mark, their_val) in other.contents { + let pos = self.contents.binary_search_by(|x| x.0.cmp(&their_key)); + match pos { + Ok(pos) => { + let (_, my_mark, my_val) = &self.contents[pos]; + let theirs_mark = Mark::MergeBehavior(MergeBehavior::Theirs).repr(); + let ours_mark = Mark::MergeBehavior(MergeBehavior::Ours).repr(); + let exclusive_mark = + Mark::MergeBehavior(MergeBehavior::MutuallyExclusive).repr(); + let merge_behavior = (my_mark & exclusive_mark) + | (my_mark & ours_mark) + | (my_mark & theirs_mark); + if merge_behavior == exclusive_mark || merge_behavior == 0 { + // If only clippy could convince me and the borrow checker this is actually unnecessary. + #[allow(clippy::unnecessary_unwrap)] + if my_val.is_some() && their_val.is_some() { + return Err(MergeError::Exclusivity( + their_key, + Box::new(their_val.unwrap()), + )); + } else if my_val.is_none() { + self.contents[pos].2 = their_val; + return Ok(()); + } + } + if merge_behavior == theirs_mark && their_val.is_some() { + self.contents[pos].2 = their_val; + return Ok(()); + } + + if merge_behavior == ours_mark && my_val.is_none() { + self.contents[pos].2 = their_val; + return Ok(()); + } + } + Err(pos) => { + self.contents + .insert(pos, (their_key, their_mark, their_val)); + } + } + } + Ok(()) + } + + /// Returns whether `key` has been marked as used. + #[doc(hidden)] + pub fn is_used(&self, key: &Q) -> bool + where + K: Borrow, + Q: Ord + ?Sized, + { + if let Ok(pos) = self.contents.binary_search_by(|x| { + let k: &Q = x.0.borrow(); + k.borrow().cmp(key) + }) { + self.contents[pos].1 & Mark::Used.repr() != 0 + } else { + false + } + } + + /// 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 { + let used_mark = Mark::Used.repr(); + if v.is_some() && mark & used_mark == 0 { + ret.push(k.to_owned()) + } + } + ret + } + + /// 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 { + let required_mark = Mark::Required.repr(); + if v.is_none() && mark & required_mark != 0 { + ret.push(k) + } + } + ret + } +} + +/// Iterator over the owned keys and values of a `MarkMap`. +#[doc(hidden)] +pub struct MarkMapIter { + map: MarkMap, +} + +#[doc(hidden)] +impl Iterator for MarkMapIter { + type Item = (K, V); + + fn next(&mut self) -> Option { + if !self.map.contents.is_empty() { + let (k, _, v) = self.map.contents.swap_remove(0); + v.map(|v| (k, v)) + } else { + None + } + } +} + +/// Iterator over references to keys and values of a `MarkMap`. +#[doc(hidden)] +pub struct MarkMapIterRef<'a, K, V> { + pos: usize, + map: &'a MarkMap, +} + +#[doc(hidden)] +impl<'a, K, V> Iterator for MarkMapIterRef<'a, K, V> { + type Item = (&'a K, &'a V); + + fn next(&mut self) -> Option { + if let Some((k, _, v)) = self.map.contents.get(self.pos) { + self.pos += 1; + v.as_ref().map(|v| (k, v)) + } else { + None + } + } +} + +#[doc(hidden)] +impl<'a, K, V> IntoIterator for &'a MarkMap { + type Item = (&'a K, &'a V); + type IntoIter = MarkMapIterRef<'a, K, V>; + fn into_iter(self) -> Self::IntoIter { + MarkMapIterRef { pos: 0, map: self } + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_insert_get_remove() { + let mut mm = MarkMap::new(); + mm.insert("a", "test"); + assert_eq!(mm.get(&"a"), Some(&"test")); + assert_eq!(mm.remove(&"a"), Some("test")); + assert_eq!(mm.get(&"a"), None); + } + + #[test] + fn test_entry_occupied() { + let mut mm = MarkMap::new(); + mm.insert("a", "test"); + assert!(!mm.is_required(&"a")); + assert!(!mm.is_used(&"a")); + let ent = mm.entry("a"); + match ent { + Entry::Occupied(mut o) => { + assert!(!o.is_required()); + assert!(!o.is_used()); + o.mark_used(); + assert!(o.is_used()); + assert!(!o.is_required()); + o.mark_required(); + assert!(o.is_required()); + assert!(o.is_used()); + } + Entry::Vacant(_) => { + panic!("Expected occupied entry"); + } + } + assert!(mm.is_required(&"a")); + assert!(mm.is_used(&"a")); + } + + #[test] + fn test_entry_vacant() { + let mut mm = MarkMap::new(); + mm.insert("a", "test"); + let ent = mm.entry("b"); + match ent { + Entry::Occupied(_) => { + panic!("Expected vacant entry"); + } + Entry::Vacant(v) => { + v.insert("test b"); + } + } + } + + #[test] + fn test_mark() { + let mut mm = MarkMap::new(); + assert!(mm.insert("a", "test").is_none()); + mm.mark_used(&"a"); + mm.set_merge_behavior(&"b", MergeBehavior::MutuallyExclusive); + assert_eq!(mm.get_mark(&"a"), Some(Mark::Used.repr())); + assert_eq!( + mm.get_mark(&"b"), + Some(Mark::MergeBehavior(MergeBehavior::MutuallyExclusive).repr()) + ); + assert_eq!(mm.get_mark(&"c"), None); + assert_eq!(mm.get(&"a"), Some(&"test")); + assert_eq!(mm.insert("a", "changed"), Some("test")); + assert_eq!(mm.get(&"b"), None); + assert_eq!(mm.insert("b", "added"), None); + assert_eq!( + mm.get_mark(&"b"), + Some(Mark::MergeBehavior(MergeBehavior::MutuallyExclusive).repr()) + ); + assert_eq!(mm.get(&"a"), Some(&"changed")); + assert_eq!(mm.get(&"c"), None); + } + + #[test] + fn test_mark_stable() { + let mut mm: MarkMap<&str, &str> = MarkMap::new(); + mm.mark_used(&"a"); + assert_eq!(mm.get_mark(&"a"), Some(Mark::Used.repr())); + mm.mark_required(&"a"); + assert_eq!( + mm.get_mark(&"a"), + Some(Mark::Used.repr() | Mark::Required.repr()) + ); + mm.set_merge_behavior(&"a", MergeBehavior::MutuallyExclusive); + assert_eq!( + mm.get_mark(&"a"), + Some( + Mark::Used.repr() + | Mark::Required.repr() + | Mark::MergeBehavior(MergeBehavior::MutuallyExclusive).repr() + ) + ); + mm.set_merge_behavior(&"a", MergeBehavior::Theirs); + assert_eq!( + mm.get_mark(&"a"), + Some( + Mark::Used.repr() + | Mark::Required.repr() + | Mark::MergeBehavior(MergeBehavior::Theirs).repr() + ) + ); + mm.set_merge_behavior(&"a", MergeBehavior::Ours); + assert_eq!( + mm.get_mark(&"a"), + Some( + Mark::Used.repr() + | Mark::Required.repr() + | Mark::MergeBehavior(MergeBehavior::Ours).repr() + ) + ); + } + + #[test] + fn test_unused() { + { + let mut mm = MarkMap::new(); + assert!(mm.insert("a", "test").is_none()); + mm.mark_used(&"a"); + assert_eq!(mm.get_mark(&"a"), Some(Mark::Used.repr())); + let empty: &[&String] = &[]; + assert_eq!(mm.unused().as_slice(), empty); + } + + { + let mut mm = MarkMap::new(); + assert!(mm.insert("a", "test").is_none()); + mm.mark_used(&"a"); + assert!(mm.insert("b", "unused").is_none()); + assert_eq!(mm.get_mark(&"a"), Some(Mark::Used.repr())); + assert_eq!(mm.get_mark(&"b"), Some(0)); + assert_eq!(mm.unused().as_slice(), &["b"]); + } + } + + #[test] + fn test_required() { + { + let mut mm = MarkMap::new(); + let empty: &[&String] = &[]; + mm.mark_required(&"a"); + assert!(mm.insert("a", "test").is_none()); + assert_eq!(mm.get_mark(&"a"), Some(Mark::Required.repr())); + assert_eq!(mm.missing().as_slice(), empty); + } + + { + let mut mm = MarkMap::new(); + mm.mark_required(&"a"); + mm.insert("for", "inference"); + assert_eq!(mm.get_mark(&"a"), Some(Mark::Required.repr())); + assert_eq!(mm.missing().as_slice(), &[&"a"]); + } + } + + #[test] + fn test_merge_empty() { + { + let mut ours: MarkMap<&str, &str> = MarkMap::new(); + let theirs = MarkMap::new(); + assert!(ours.merge_from(theirs).is_ok()); + } + { + let mut ours: MarkMap<&str, &str> = MarkMap::new(); + let theirs: MarkMap<&str, &str> = MarkMap::new(); + ours.mark_required(&"a"); + assert!(ours.merge_from(theirs).is_ok()); + assert_eq!(ours.missing().as_slice(), &[&"a"]); + } + + { + let mut ours: MarkMap<&str, &str> = MarkMap::new(); + let theirs: MarkMap<&str, &str> = MarkMap::new(); + ours.mark_required(&"a"); + ours.set_merge_behavior(&"a", MergeBehavior::Ours); + assert!(ours.merge_from(theirs).is_ok()); + assert_eq!(ours.missing().as_slice(), &[&"a"]); + } + + { + let mut ours: MarkMap<&str, &str> = MarkMap::new(); + let theirs: MarkMap<&str, &str> = MarkMap::new(); + ours.mark_required(&"a"); + ours.set_merge_behavior(&"a", MergeBehavior::Theirs); + assert!(ours.merge_from(theirs).is_ok()); + assert_eq!(ours.missing().as_slice(), &[&"a"]); + } + } + + #[test] + fn test_merge_conflict() { + { + let mut ours = MarkMap::new(); + let mut theirs = MarkMap::new(); + ours.insert("a", "ours"); + ours.set_merge_behavior(&"a", MergeBehavior::MutuallyExclusive); + theirs.insert("a", "theirs"); + assert!(ours.merge_from(theirs).is_err()); + assert_eq!(ours.get(&"a"), Some(&"ours")); + } + { + // Default behavior should match `MutuallyExclusive` + let mut ours = MarkMap::new(); + let mut theirs = MarkMap::new(); + ours.insert("a", "ours"); + theirs.insert("a", "theirs"); + assert!(ours.merge_from(theirs).is_err()); + assert_eq!(ours.get(&"a"), Some(&"ours")); + } + } + + #[test] + fn test_merge_ours() { + { + let mut ours: MarkMap<&str, &str> = MarkMap::new(); + let mut theirs: MarkMap<&str, &str> = MarkMap::new(); + ours.insert("a", "ours"); + theirs.set_merge_behavior(&"a", MergeBehavior::MutuallyExclusive); + assert!(ours.merge_from(theirs).is_ok()); + assert_eq!(ours.get(&"a"), Some(&"ours")); + } + { + // Default behavior should match MutuallyExclusive + let mut ours: MarkMap<&str, &str> = MarkMap::new(); + let theirs: MarkMap<&str, &str> = MarkMap::new(); + ours.insert("a", "ours"); + assert!(ours.merge_from(theirs).is_ok()); + assert_eq!(ours.get(&"a"), Some(&"ours")); + } + + { + let mut ours: MarkMap<&str, &str> = MarkMap::new(); + let theirs: MarkMap<&str, &str> = MarkMap::new(); + ours.insert("a", "ours"); + ours.set_merge_behavior(&"a", MergeBehavior::MutuallyExclusive); + assert!(ours.merge_from(theirs).is_ok()); + assert_eq!(ours.get(&"a"), Some(&"ours")); + } + { + let mut ours = MarkMap::new(); + let mut theirs = MarkMap::new(); + ours.insert("a", "ours"); + ours.set_merge_behavior(&"a", MergeBehavior::Ours); + theirs.insert("a", "theirs"); + assert!(ours.merge_from(theirs).is_ok()); + assert_eq!(ours.get(&"a"), Some(&"ours")); + } + } + + #[test] + fn test_merge_theirs() { + { + let mut ours: MarkMap<&str, &str> = MarkMap::new(); + let mut theirs: MarkMap<&str, &str> = MarkMap::new(); + ours.set_merge_behavior(&"a", MergeBehavior::MutuallyExclusive); + theirs.insert("a", "theirs"); + assert!(ours.merge_from(theirs).is_ok()); + assert_eq!(ours.get(&"a"), Some(&"theirs")); + } + + { + // Should match default behavior. + let mut ours: MarkMap<&str, &str> = MarkMap::new(); + let mut theirs: MarkMap<&str, &str> = MarkMap::new(); + theirs.insert("a", "theirs"); + assert!(ours.merge_from(theirs).is_ok()); + assert_eq!(ours.get(&"a"), Some(&"theirs")); + } + { + let mut ours = MarkMap::new(); + let mut theirs = MarkMap::new(); + ours.insert("a", "ours"); + ours.set_merge_behavior(&"a", MergeBehavior::Theirs); + theirs.insert("a", "theirs"); + assert!(ours.merge_from(theirs).is_ok()); + assert_eq!(ours.get(&"a"), Some(&"theirs")); + } + } + + #[test] + fn test_merge_both() { + { + let mut ours = MarkMap::new(); + let mut theirs = MarkMap::new(); + ours.insert("a", "ours"); + ours.set_merge_behavior(&"a", MergeBehavior::MutuallyExclusive); + theirs.insert("b", "theirs"); + theirs.set_merge_behavior(&"b", MergeBehavior::MutuallyExclusive); + assert!(ours.merge_from(theirs).is_ok()); + assert_eq!(ours.get(&"a"), Some(&"ours")); + assert_eq!(ours.get(&"b"), Some(&"theirs")); + } + } + + #[test] + fn test_vacant() { + let mut mm = MarkMap::new(); + let v = mm.entry("a"); + match v { + Entry::Occupied(_) => { + panic!("Expected vacant entry"); + } + Entry::Vacant(v) => { + let mut o = v.insert_entry("ours"); + o.mark_required(); + o.mark_used(); + assert_eq!(o.get(), &"ours"); + assert!(o.is_required()); + assert!(o.is_used()); + } + } + + assert_eq!(mm.get(&"a"), Some(&"ours")); + assert!(mm.is_required(&"a")); + assert!(mm.is_used(&"a")); + } +} diff --git a/cfgrammar/src/lib/mod.rs b/cfgrammar/src/lib/mod.rs index 6a3ad66ef..ca750d014 100644 --- a/cfgrammar/src/lib/mod.rs +++ b/cfgrammar/src/lib/mod.rs @@ -56,15 +56,17 @@ use bincode::{Decode, Encode}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -#[doc(hidden)] pub mod header; mod idxnewtype; +pub mod markmap; pub mod newlinecache; pub mod span; +#[cfg(test)] +pub mod test_utils; pub mod yacc; pub use newlinecache::NewlineCache; -pub use span::{Span, Spanned}; +pub use span::{Location, Span, Spanned}; /// A type specifically for rule indices. pub use crate::idxnewtype::{PIdx, RIdx, SIdx, TIdx}; diff --git a/cfgrammar/src/lib/span.rs b/cfgrammar/src/lib/span.rs index 6a5ec6178..679d817ad 100644 --- a/cfgrammar/src/lib/span.rs +++ b/cfgrammar/src/lib/span.rs @@ -66,3 +66,12 @@ impl ToTokens for Span { tokens.append_all(quote! {::cfgrammar::Span::new(#start, #end)}); } } + +/// A possibly inexact location which could either be a `Span`, +/// a command-line option, or some other location described textually. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Location { + Span(Span), + CommandLine, + Other(String), +} diff --git a/cfgrammar/src/lib/test_utils.rs b/cfgrammar/src/lib/test_utils.rs new file mode 100644 index 000000000..863127bfc --- /dev/null +++ b/cfgrammar/src/lib/test_utils.rs @@ -0,0 +1,25 @@ +#[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 8abbca51b..04676e5c9 100644 --- a/cfgrammar/src/lib/yacc/ast.rs +++ b/cfgrammar/src/lib/yacc/ast.rs @@ -6,17 +6,17 @@ use std::{ use indexmap::{IndexMap, IndexSet}; use super::{ - parser::YaccParser, Precedence, YaccGrammarError, YaccGrammarErrorKind, YaccGrammarWarning, - YaccGrammarWarningKind, YaccKind, YaccKindResolver, + parser::YaccParser, ParserError, Precedence, YaccGrammarError, YaccGrammarErrorKind, + YaccGrammarWarning, YaccGrammarWarningKind, YaccKind, }; -use crate::Span; +use crate::{header::Header, 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, ast: GrammarAST, - errs: Vec, + errs: Vec, } impl ASTWithValidityInfo { @@ -24,14 +24,16 @@ 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(yacc_kind_resolver: YaccKindResolver, s: &str) -> Self { + pub fn new(header: &mut Header, s: &str) -> Self { let mut errs = Vec::new(); let (yacc_kind, ast) = { - let mut yp = YaccParser::new(yacc_kind_resolver, s.to_string()); + let mut yp = YaccParser::new(header, s.to_string()); 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)).ok(); + ast.complete_and_validate() + .map_err(|e| errs.push(e.into())) + .ok(); } (yacc_kind, ast) }; @@ -62,7 +64,7 @@ impl ASTWithValidityInfo { } /// Returns all errors which were encountered during AST construction. - pub fn errors(&self) -> &[YaccGrammarError] { + pub fn errors(&self) -> &[ParserError] { self.errs.as_slice() } } diff --git a/cfgrammar/src/lib/yacc/firsts.rs b/cfgrammar/src/lib/yacc/firsts.rs index 6bea7312f..44dafca8a 100644 --- a/cfgrammar/src/lib/yacc/firsts.rs +++ b/cfgrammar/src/lib/yacc/firsts.rs @@ -9,7 +9,8 @@ use crate::{RIdx, Symbol, TIdx}; /// `Firsts` stores all the first sets for a given grammar. For example, given this code and /// grammar: /// ```text -/// let grm = YaccGrammar::new(YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " +/// let grm = YaccGrammar::new(&mut Header::new(), " +/// %grmtools{yacckind: YaccKind::Original(YaccOriginalActionKind::GenericParseTree)} /// S: A 'b'; /// A: 'a' /// | ;").unwrap(); @@ -143,9 +144,10 @@ where #[cfg(test)] mod test { use super::{ - super::{YaccGrammar, YaccKind, YaccKindResolver, YaccOriginalActionKind}, + super::{YaccGrammar, YaccKind, YaccOriginalActionKind}, YaccFirsts, }; + use crate::test_utils::*; use num_traits::{AsPrimitive, PrimInt, Unsigned}; fn has( @@ -180,7 +182,7 @@ mod test { #[test] fn test_first() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start C %token c d @@ -202,7 +204,7 @@ mod test { #[test] fn test_first_no_subsequent_rules() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start C %token c d @@ -220,7 +222,7 @@ mod test { #[test] fn test_first_epsilon() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start A %token a b c @@ -241,7 +243,7 @@ mod test { #[test] fn test_last_epsilon() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start A %token b c @@ -261,7 +263,7 @@ mod test { #[test] fn test_first_no_multiples() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start A %token b c @@ -277,7 +279,7 @@ mod test { fn eco_grammar() -> YaccGrammar { YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start S %token a b c d f @@ -308,7 +310,7 @@ mod test { #[test] fn test_first_from_eco_bug() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(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 4af52cc84..962f26ce7 100644 --- a/cfgrammar/src/lib/yacc/follows.rs +++ b/cfgrammar/src/lib/yacc/follows.rs @@ -9,7 +9,8 @@ use crate::{RIdx, Symbol, TIdx}; /// `Follows` stores all the Follow sets for a given grammar. For example, given this code and /// grammar: /// ```text -/// let grm = YaccGrammar::new(YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " +/// let grm = YaccGrammar::new(&mut Header::new(), " +/// %grmtools{yacckind: YaccKind::Original(YaccOriginalActionKind::GenericParseTree)} /// S: A 'b'; /// A: 'a' | ; /// ").unwrap(); @@ -115,9 +116,10 @@ where #[cfg(test)] mod test { use super::{ - super::{YaccGrammar, YaccKind, YaccKindResolver, YaccOriginalActionKind}, + super::{YaccGrammar, YaccKind, YaccOriginalActionKind}, YaccFollows, }; + use crate::test_utils::*; use num_traits::{AsPrimitive, PrimInt, Unsigned}; fn has( @@ -149,7 +151,7 @@ mod test { fn test_follow() { // Adapted from p2 of https://www.cs.uaf.edu/~cs331/notes/FirstFollow.pdf let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start E %% @@ -173,7 +175,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( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start A %% @@ -196,7 +198,7 @@ mod test { #[test] fn test_follow3() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start S %% @@ -213,7 +215,7 @@ mod test { #[test] fn test_follow_corchuelo() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start E %% diff --git a/cfgrammar/src/lib/yacc/grammar.rs b/cfgrammar/src/lib/yacc/grammar.rs index 0c7231705..a1f3f2f9c 100644 --- a/cfgrammar/src/lib/yacc/grammar.rs +++ b/cfgrammar/src/lib/yacc/grammar.rs @@ -8,11 +8,8 @@ use num_traits::{AsPrimitive, PrimInt, Unsigned}; use serde::{Deserialize, Serialize}; use vob::Vob; -use super::{ - ast, firsts::YaccFirsts, follows::YaccFollows, parser::YaccGrammarResult, YaccKind, - YaccKindResolver, -}; -use crate::{PIdx, RIdx, SIdx, Span, Symbol, TIdx}; +use super::{ast, firsts::YaccFirsts, follows::YaccFollows, parser::YaccGrammarResult, YaccKind}; +use crate::{header::Header, PIdx, RIdx, SIdx, Span, Symbol, TIdx}; const START_RULE: &str = "^"; const IMPLICIT_RULE: &str = "~"; @@ -179,8 +176,8 @@ where // create the start rule ourselves (without relying on user input), this is a safe assumption. impl YaccGrammar { - pub fn new(yacc_kind: YaccKindResolver, s: &str) -> YaccGrammarResult { - YaccGrammar::new_with_storaget(yacc_kind, s) + pub fn new(header: &mut Header, s: &str) -> YaccGrammarResult { + YaccGrammar::new_with_storaget(header, s) } } @@ -195,11 +192,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( - yacc_kind_resolver: YaccKindResolver, - s: &str, - ) -> YaccGrammarResult { - let ast_validation = ast::ASTWithValidityInfo::new(yacc_kind_resolver, s); + pub fn new_with_storaget(header: &mut Header, s: &str) -> YaccGrammarResult { + let ast_validation = ast::ASTWithValidityInfo::new(header, s); Self::new_from_ast_with_validity_info(&ast_validation) } @@ -1125,12 +1119,10 @@ where #[cfg(test)] mod test { use super::{ - super::{ - AssocKind, Precedence, YaccGrammar, YaccKind, YaccKindResolver, YaccOriginalActionKind, - }, + super::{AssocKind, Precedence, YaccGrammar, YaccKind, YaccOriginalActionKind}, rule_max_costs, rule_min_costs, IMPLICIT_RULE, IMPLICIT_START_RULE, }; - use crate::{PIdx, RIdx, Span, Symbol, TIdx}; + use crate::{test_utils::*, PIdx, RIdx, Span, Symbol, TIdx}; use std::collections::HashMap; macro_rules! bslice { @@ -1150,7 +1142,7 @@ mod test { #[test] fn test_minimal() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), "%start R %token T %% R: 'T';", ) .unwrap(); @@ -1181,7 +1173,7 @@ mod test { #[test] fn test_rule_ref() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), "%start R %token T %% R : S; S: 'T';", ) .unwrap(); @@ -1210,7 +1202,7 @@ mod test { #[rustfmt::skip] fn test_long_prod() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), "%start R %token T1 T2 %% R : S 'T1' S; S: 'T2';" ).unwrap(); @@ -1241,7 +1233,7 @@ mod test { #[test] fn test_prods_rules() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start A %% @@ -1264,7 +1256,7 @@ mod test { #[rustfmt::skip] fn test_left_right_nonassoc_precs() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start Expr %right '=' @@ -1297,7 +1289,7 @@ mod test { #[rustfmt::skip] fn test_prec_override() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start expr %left '+' '-' @@ -1325,7 +1317,7 @@ mod test { #[rustfmt::skip] fn test_implicit_tokens_rewrite() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Eco), + &mut header_for_yacckind!(YaccKind::Eco), " %implicit_tokens ws1 ws2 %start S @@ -1401,7 +1393,7 @@ mod test { #[rustfmt::skip] fn test_has_path() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start A %% @@ -1428,7 +1420,7 @@ mod test { #[rustfmt::skip] fn test_rule_min_costs() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start A %% @@ -1451,7 +1443,7 @@ mod test { #[test] fn test_min_sentences() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start A %% @@ -1499,7 +1491,7 @@ mod test { #[rustfmt::skip] fn test_rule_max_costs1() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start A %% @@ -1523,7 +1515,7 @@ mod test { #[rustfmt::skip] fn test_rule_max_costs2() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start A %% @@ -1545,7 +1537,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( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start S %% @@ -1577,7 +1569,7 @@ mod test { fn test_token_spans() { let src = "%%\nAB: 'a' | 'foo';"; let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::NoAction)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::NoAction)), src, ) .unwrap(); @@ -1605,7 +1597,7 @@ mod test { %% "; let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::NoAction)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::NoAction)), src, ) .unwrap(); diff --git a/cfgrammar/src/lib/yacc/mod.rs b/cfgrammar/src/lib/yacc/mod.rs index 055ca4124..36832adb1 100644 --- a/cfgrammar/src/lib/yacc/mod.rs +++ b/cfgrammar/src/lib/yacc/mod.rs @@ -8,7 +8,10 @@ pub mod parser; pub use self::{ grammar::{AssocKind, Precedence, SentenceGenerator, YaccGrammar}, - parser::{YaccGrammarError, YaccGrammarErrorKind, YaccGrammarWarning, YaccGrammarWarningKind}, + parser::{ + ParserError, YaccGrammarError, YaccGrammarErrorKind, YaccGrammarWarning, + YaccGrammarWarningKind, + }, }; use proc_macro2::TokenStream; use quote::quote; @@ -16,23 +19,6 @@ use quote::quote; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum YaccKindResolver { - /// The user can specify `%grmtools` in their grammar but unless it is compatible with this `YaccKind`, it's an error - Force(YaccKind), - /// Use `YaccKind` if the user doesn't specify `%grmtools` in their grammar - Default(YaccKind), - /// The user must specify `%grmtools` in their grammars or we throw an error - NoDefault, -} - -impl YaccKindResolver { - fn forced(self) -> bool { - matches!(self, Self::Force(_)) - } -} - /// The particular Yacc variant this grammar makes use of. #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/cfgrammar/src/lib/yacc/parser.rs b/cfgrammar/src/lib/yacc/parser.rs index d5a764c32..b671e1caa 100644 --- a/cfgrammar/src/lib/yacc/parser.rs +++ b/cfgrammar/src/lib/yacc/parser.rs @@ -14,16 +14,44 @@ use std::{ str::FromStr, }; -pub type YaccGrammarResult = Result>; - use crate::{ - header::{GrmtoolsSectionParser, HeaderErrorKind, Namespaced, Setting, Value}, + header::{GrmtoolsSectionParser, Header, HeaderError, HeaderErrorKind}, + markmap::MergeError, 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) + } +} + use super::{ ast::{GrammarAST, Symbol}, - AssocKind, Precedence, YaccKind, YaccKindResolver, YaccOriginalActionKind, + AssocKind, Precedence, YaccKind, }; /// The various different possible Yacc parser errors. @@ -296,8 +324,8 @@ impl Spanned for YaccGrammarError { } } -pub(crate) struct YaccParser { - yacc_kind_resolver: YaccKindResolver, +pub(crate) struct YaccParser<'a> { + header: &'a mut Header, yacc_kind: Option, src: String, num_newlines: usize, @@ -333,10 +361,10 @@ fn add_duplicate_occurrence( } /// The actual parser is intended to be entirely opaque from outside users. -impl YaccParser { - pub(crate) fn new(yacc_kind_resolver: YaccKindResolver, src: String) -> YaccParser { +impl YaccParser<'_> { + pub(crate) fn new(header: &mut Header, src: String) -> YaccParser { YaccParser { - yacc_kind_resolver, + header, yacc_kind: None, src, num_newlines: 0, @@ -346,61 +374,60 @@ impl YaccParser { } pub(crate) fn parse(&mut self) -> YaccGrammarResult { - let mut errs: Vec = Vec::new(); + let mut errs = Vec::new(); // 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 section_required = matches!(self.yacc_kind_resolver, YaccKindResolver::NoDefault); - let header_parser = GrmtoolsSectionParser::new(&self.src, section_required); + let header_parser = GrmtoolsSectionParser::new(&self.src, false); let result = match header_parser.parse() { - Ok((header, i)) => self.update_yacckind(header, i), + 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().map(|e| YaccGrammarError { - kind: match e.kind { - HeaderErrorKind::MissingGrmtoolsSection => { - YaccGrammarErrorKind::MissingGrmtoolsSection - } - HeaderErrorKind::IllegalName => { - YaccGrammarErrorKind::InvalidGrmtoolsSectionEntry - } - HeaderErrorKind::ExpectedToken(c) => YaccGrammarErrorKind::ExpectedInput(c), - HeaderErrorKind::DuplicateEntry => { - YaccGrammarErrorKind::DuplicateGrmtoolsSectionEntry - } - }, - spans: e.spans.clone(), - })); + errs.extend(es.iter().cloned().map(ParserError::from)); return Err(errs); } }; - if result.is_ok() && (self.yacc_kind.is_none() || self.yacc_kind_resolver.forced()) { - match self.yacc_kind_resolver { - YaccKindResolver::Default(kind) | YaccKindResolver::Force(kind) => { - self.yacc_kind = Some(kind); + if result.is_ok() && self.yacc_kind.is_none() { + errs.push( + YaccGrammarError { + kind: YaccGrammarErrorKind::InvalidYaccKind, + spans: vec![Span::new(0, 0)], } - YaccKindResolver::NoDefault => { - errs.push(YaccGrammarError { - kind: YaccGrammarErrorKind::InvalidYaccKind, - spans: vec![Span::new(0, 0)], - }); - return Err(errs); - } - } + .into(), + ); + return Err(errs); } + let mut y_errs = Vec::new(); let mut result = self.parse_declarations( match result { Ok(i) => i, - Err(es) => { - errs.extend(es); + Err(e) => { + errs.push(e.into()); return Err(errs); } }, - &mut errs, + &mut y_errs, ); + errs.extend(y_errs.drain(..).map(ParserError::from)); result = self.parse_rules(match result { Ok(i) => i, Err(e) => { - errs.push(e); + errs.push(e.into()); return Err(errs); } }); @@ -408,140 +435,37 @@ impl YaccParser { match result { Ok(i) => i, Err(e) => { - errs.push(e); + errs.push(e.into()); return Err(errs); } }, - &mut errs, + &mut y_errs, ); - + errs.extend(y_errs.drain(..).map(ParserError::from)); match result { Ok(i) if errs.is_empty() => Ok(i), Err(e) => { - errs.push(e); + errs.push(e.into()); Err(errs) } _ => Err(errs), } } - fn update_yacckind( - &mut self, - header: HashMap, - i: usize, - ) -> Result> { - if let Some((key_span, yk_setting)) = header.get("yacckind") { - match yk_setting { - Value::Flag(_) => { - return Err(vec![YaccGrammarError { - kind: YaccGrammarErrorKind::InvalidYaccKind, - spans: vec![*key_span], - }]) - } - - Value::Setting(Setting::Num(_, span)) => { - return Err(vec![YaccGrammarError { - kind: YaccGrammarErrorKind::InvalidYaccKind, - spans: vec![*span], - }]) - } - Value::Setting(Setting::Unitary(Namespaced { - namespace, - member: (yk_value, yk_value_span), - })) => { - let mut errs = vec![]; - if let Some((ns, ns_span)) = namespace { - if ns != "yacckind" { - errs.push(YaccGrammarError { - kind: YaccGrammarErrorKind::InvalidYaccKindNamespace, - spans: vec![*ns_span], - }); - } - } - let yacckinds = [ - ("grmtools".to_string(), YaccKind::Grmtools), - ("eco".to_string(), YaccKind::Eco), - ]; - let yk_found = yacckinds - .iter() - .find_map(|(yk_str, yk)| (yk_str == yk_value).then_some(yk)); - if yk_found.is_some() { - if errs.is_empty() { - self.yacc_kind = yk_found.cloned(); - return Ok(i); - } else { - return Err(errs); - } - } else { - errs.push(YaccGrammarError { - kind: YaccGrammarErrorKind::InvalidYaccKind, - spans: vec![*yk_value_span], - }); - return Err(errs); - } - } - Value::Setting(Setting::Constructor { - ctor: - Namespaced { - namespace: yk_namespace, - member: (yk_str, yk_span), - }, - arg: - Namespaced { - namespace: ak_namespace, - member: (ak_str, ak_span), - }, - }) => { - let mut errs = vec![]; - - if let Some((yk_ns, yk_ns_span)) = yk_namespace { - if yk_ns != "yacckind" { - errs.push(YaccGrammarError { - kind: YaccGrammarErrorKind::InvalidYaccKindNamespace, - spans: vec![*yk_ns_span], - }); - } - } - - if yk_str != "original" { - errs.push(YaccGrammarError { - kind: YaccGrammarErrorKind::InvalidYaccKind, - spans: vec![*yk_span], - }); - } - - if let Some((ak_ns, ak_ns_span)) = ak_namespace { - if ak_ns != "yaccoriginalactionkind" { - errs.push(YaccGrammarError { - kind: YaccGrammarErrorKind::InvalidActionKindNamespace, - spans: vec![*ak_ns_span], - }); - } - } - - let actionkinds = [ - ("noaction", YaccOriginalActionKind::NoAction), - ("useraction", YaccOriginalActionKind::UserAction), - ("genericparsetree", YaccOriginalActionKind::GenericParseTree), - ]; - let yk_found = actionkinds.iter().find_map(|(actionkind_str, actionkind)| { - (ak_str == actionkind_str).then_some(YaccKind::Original(*actionkind)) - }); - if yk_found.is_some() { - if errs.is_empty() { - self.yacc_kind = yk_found; - return Ok(i); - } else { - return Err(errs); - } - } else { - errs.push(YaccGrammarError { - kind: YaccGrammarErrorKind::InvalidActionKind, - spans: vec![*ak_span], - }); - return 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) @@ -1232,14 +1156,16 @@ mod test { use super::{ super::{ ast::{GrammarAST, Production, Symbol}, - AssocKind, Precedence, YaccKind, YaccKindResolver, YaccOriginalActionKind, + AssocKind, Precedence, YaccKind, YaccOriginalActionKind, }, - Span, Spanned, YaccGrammarError, YaccGrammarErrorKind, YaccParser, + ParserError, Span, Spanned, YaccGrammarErrorKind, YaccParser, }; + use crate::test_utils::*; use std::collections::HashSet; - fn parse(yacc_kind: YaccKind, s: &str) -> Result> { - let mut yp = YaccParser::new(YaccKindResolver::Force(yacc_kind), s.to_string()); + 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()); yp.parse()?; Ok(yp.build().1) } @@ -1295,7 +1221,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 @@ -1304,6 +1230,11 @@ 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); @@ -1333,6 +1264,10 @@ 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() @@ -1354,6 +1289,16 @@ 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() { @@ -2912,7 +2857,7 @@ B"; Start -> () : ;", ]; for src in srcs { - YaccParser::new(YaccKindResolver::NoDefault, src.to_string()) + YaccParser::new(&mut Header::new(), src.to_string()) .parse() .unwrap(); } @@ -2939,7 +2884,7 @@ B"; for src in srcs { let s = format!("{}\n%%\nStart();\n", src); - let parse_result = YaccParser::new(YaccKindResolver::NoDefault, s.to_string()).parse(); + let parse_result = YaccParser::new(&mut Header::new(), s.to_string()).parse(); assert!(parse_result.is_err()); } } @@ -2958,7 +2903,7 @@ B"; %% Start -> () : ; "#; - YaccParser::new(YaccKindResolver::NoDefault, src.to_string()) + YaccParser::new(&mut Header::new(), src.to_string()) .parse() .unwrap(); let src = r#" @@ -2968,7 +2913,7 @@ B"; %% Start -> () : ; "#; - YaccParser::new(YaccKindResolver::NoDefault, src.to_string()) + YaccParser::new(&mut Header::new(), src.to_string()) .parse() .unwrap(); } diff --git a/lrpar/cttests/src/grmtools_section.test b/lrpar/cttests/src/grmtools_section.test index a59c87a52..bceff4b75 100644 --- a/lrpar/cttests/src/grmtools_section.test +++ b/lrpar/cttests/src/grmtools_section.test @@ -3,50 +3,50 @@ grammar: | %token MAGIC IDENT NUM %epp MAGIC "%grmtools" %% - start -> Result, Vec> + start -> Result> : MAGIC '{' contents '}' { $3 } ; - contents -> Result, Vec> - : %empty { Ok(HashMap::new()) } + contents -> Result> + : %empty { Ok(Header::new()) } | val_seq comma_opt { $1 } ; - val_seq -> Result, Vec> + val_seq -> Result> : valbind { - let ((key, key_span), val) = $1; - let mut ret = HashMap::new(); + let ((key, key_loc), val) = $1; + let mut ret = Header::new(); match ret.entry(key) { Entry::Occupied(orig) => { - let (orig_span, _) = orig.get(); + let (orig_loc, _): &(Location, Value) = 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, - spans: vec![*orig_span, key_span] + locations: vec![orig_loc.clone(), key_loc] }]); } Entry::Vacant(entry) => { - entry.insert((key_span, val)); + entry.insert((key_loc, val)); } } Ok(ret) } | val_seq ',' valbind { - let ((key, key_span), val) = $3; + let ((key, key_loc), val) = $3; let mut ret = $1?; match ret.entry(key) { Entry::Occupied(orig) => { - let (orig_span, _) = orig.get(); + let (orig_loc, _) = 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, - spans: vec![*orig_span, key_span] + locations: vec![orig_loc.clone(), key_loc] }]); } Entry::Vacant(entry) => { - entry.insert((key_span, val)); + entry.insert((key_loc, val)); } } Ok(ret) @@ -59,7 +59,7 @@ grammar: | let ident = $lexer.span_str(ident_span).to_string().to_lowercase(); Namespaced{ namespace: None, - member: (ident, ident_span) + member: (ident, Location::Span(ident_span)) } } | IDENT '::' IDENT { @@ -69,27 +69,28 @@ grammar: | let ident_span = $3.as_ref().unwrap().span(); let ident = $lexer.span_str(ident_span).to_string().to_lowercase(); Namespaced { - namespace: Some((namespace, namespace_span)), - member: (ident, ident_span) + namespace: Some((namespace, Location::Span(namespace_span))), + member: (ident, Location::Span(ident_span)) } } ; - valbind -> ((String, Span), Value) + valbind -> ((String, Location), Value) : IDENT ':' val { let key_span = $1.as_ref().unwrap().span(); let key = $lexer.span_str(key_span).to_string().to_lowercase(); - ((key, key_span), Value::Setting($3)) + ((key, Location::Span(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, key_span), Value::Flag(true)) + ((key, Location::Span(key_span)), Value::Flag(true, Location::Span(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, key_span), Value::Flag(false)) + ((key, Location::Span(key_span)), Value::Flag(false, Location::Span(Span::new(bang_span.start(), key_span.end())))) } ; @@ -98,7 +99,7 @@ grammar: | | NUM { let num_span = $1.as_ref().unwrap().span(); let n = str::parse::($lexer.span_str(num_span)); - Setting::Num(n.expect("convertible"), num_span) + Setting::Num(n.expect("convertible"), Location::Span(num_span)) } | namespaced '(' namespaced ')' { Setting::Constructor{ctor: $1, arg: $3} } ; @@ -111,16 +112,18 @@ grammar: | #![allow(dead_code)] #![allow(unused)] - use std::collections::{hash_map::Entry, HashMap}; use cfgrammar::{ Span, + Location, header::{ Value, Setting, HeaderError, HeaderErrorKind, Namespaced, - } + Header, + }, + markmap::Entry, }; lexer: | diff --git a/lrpar/src/lib/ctbuilder.rs b/lrpar/src/lib/ctbuilder.rs index 2d3b938e5..4737cac04 100644 --- a/lrpar/src/lib/ctbuilder.rs +++ b/lrpar/src/lib/ctbuilder.rs @@ -17,11 +17,11 @@ use std::{ use crate::{LexerTypes, RecoveryKind}; use bincode::{decode_from_slice, encode_to_vec, Decode, Encode}; use cfgrammar::{ + header::{Header, Value}, + markmap::{Entry, MergeBehavior}, newlinecache::NewlineCache, - yacc::{ - ast::ASTWithValidityInfo, YaccGrammar, YaccKind, YaccKindResolver, YaccOriginalActionKind, - }, - RIdx, Spanned, Symbol, + yacc::{ast::ASTWithValidityInfo, ParserError, YaccGrammar, YaccKind, YaccOriginalActionKind}, + Location, RIdx, Spanned, Symbol, }; use filetime::FileTime; use lazy_static::lazy_static; @@ -477,12 +477,23 @@ where .output_path .as_ref() .expect("output_path must be specified before processing."); - let yk = match self.yacckind { - None => YaccKindResolver::NoDefault, - Some(YaccKind::Eco) => panic!("Eco compile-time grammar generation not supported."), - Some(x) => YaccKindResolver::Force(x), - }; - + let mut header = Header::new(); + + match header.entry("yacckind".to_string()) { + Entry::Occupied(_) => unreachable!(), + Entry::Vacant(v) => match self.yacckind { + 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)); + o.set_merge_behavior(MergeBehavior::Ours); + } + None => { + v.occupied_entry().mark_required(); + } + }, + } { let mut lk = GENERATED_PATHS.lock().unwrap(); if lk.contains(outp.as_path()) { @@ -493,16 +504,48 @@ where let inc = read_to_string(grmp).map_err(|e| format!("When reading '{}': {e}", grmp.display()))?; - let ast_validation = ASTWithValidityInfo::new(yk, &inc); + let ast_validation = ASTWithValidityInfo::new(&mut header, &inc); + let unused_keys = header.unused(); + if !unused_keys.is_empty() { + return Err(format!("Unused keys in header: {}", unused_keys.join(", ")).into()); + } + let missing_keys = header.missing(); + if !missing_keys.is_empty() { + return Err(format!( + "Required values were missing from the header: {}", + unused_keys.join(", ") + ) + .into()); + } self.yacckind = 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) => { + if let Some((line, column)) = + line_cache.byte_to_line_num_and_col_num(inc, span.start()) + { + format!("{} at line {line} column {column}", err_str) + } else { + err_str + } + } + Location::CommandLine => { + format!("{} from the command-line.", err_str) + } + Location::Other(s) => { + format!("{} from '{}'", err_str, s) + } + }; let spanned_fmt = |x: &dyn Spanned, inc: &str, line_cache: &NewlineCache| { - if let Some((line, column)) = - line_cache.byte_to_line_num_and_col_num(inc, x.spans()[0].start()) - { - format!("{} at line {line} column {column}", x) - } else { - format!("{}", x) + 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) } }; @@ -548,13 +591,13 @@ where format!( "\n\t{}", errs.iter() - .map(|e| spanned_fmt(e, &inc, &line_cache)) + .map(|e| perror_fmt(e, &inc, &line_cache)) .chain(warnings.iter().map(|w| spanned_fmt(w, &inc, &line_cache))) .collect::>() .join("\n\t") ) } else { - spanned_fmt(errs.first().unwrap(), &inc, &line_cache) + perror_fmt(errs.first().unwrap(), &inc, &line_cache) }))?; } }; diff --git a/lrpar/src/lib/mod.rs b/lrpar/src/lib/mod.rs index e3c0f9e90..49e8c5a35 100644 --- a/lrpar/src/lib/mod.rs +++ b/lrpar/src/lib/mod.rs @@ -201,7 +201,7 @@ pub mod lex_api; #[doc(hidden)] pub mod parser; #[cfg(test)] -mod test_utils; +pub mod test_utils; pub use crate::{ ctbuilder::{CTParser, CTParserBuilder, RustEdition, Visibility}, diff --git a/lrpar/src/lib/parser.rs b/lrpar/src/lib/parser.rs index d819841de..c0cad8ddc 100644 --- a/lrpar/src/lib/parser.rs +++ b/lrpar/src/lib/parser.rs @@ -971,7 +971,7 @@ pub(crate) mod test { use std::collections::HashMap; use cfgrammar::{ - yacc::{YaccGrammar, YaccKind, YaccKindResolver, YaccOriginalActionKind}, + yacc::{YaccGrammar, YaccKind, YaccOriginalActionKind}, Span, }; use lrtable::{from_yacc, Minimiser}; @@ -980,7 +980,7 @@ pub(crate) mod test { use super::*; use crate::{ - test_utils::{TestLexError, TestLexeme, TestLexerTypes}, + test_utils::{header_for_yacckind, TestLexError, TestLexeme, TestLexerTypes}, Lexeme, Lexer, }; @@ -1021,7 +1021,7 @@ pub(crate) mod test { >, ) { let grm = YaccGrammar::::new_with_storaget( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), grms, ) .unwrap(); diff --git a/lrpar/src/lib/test_utils.rs b/lrpar/src/lib/test_utils.rs index 2e20b02c9..5dd18fe56 100644 --- a/lrpar/src/lib/test_utils.rs +++ b/lrpar/src/lib/test_utils.rs @@ -1,4 +1,5 @@ #![allow(clippy::len_without_is_empty)] +#![allow(unused)] use std::{error::Error, fmt, hash::Hash}; @@ -86,3 +87,29 @@ 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 bf40c93ee..f7200d710 100644 --- a/lrtable/src/lib/itemset.rs +++ b/lrtable/src/lib/itemset.rs @@ -158,8 +158,9 @@ where #[cfg(test)] mod test { + use crate::test_utils::*; use cfgrammar::{ - yacc::{YaccGrammar, YaccKind, YaccKindResolver, YaccOriginalActionKind}, + yacc::{YaccGrammar, YaccKind, YaccOriginalActionKind}, SIdx, Symbol, }; use vob::Vob; @@ -172,7 +173,7 @@ mod test { fn test_dragon_grammar() { // From http://binarysculpting.com/2012/02/04/computing-lr1-closure/ let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start S %% @@ -200,7 +201,7 @@ mod test { fn eco_grammar() -> YaccGrammar { YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start S %token a b c d f @@ -251,7 +252,7 @@ mod test { // aSb fn grammar3() -> YaccGrammar { YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(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 0cafb464c..1ec5615a6 100644 --- a/lrtable/src/lib/mod.rs +++ b/lrtable/src/lib/mod.rs @@ -16,6 +16,8 @@ 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 a354a11f7..ea674370e 100644 --- a/lrtable/src/lib/pager.rs +++ b/lrtable/src/lib/pager.rs @@ -398,9 +398,10 @@ where mod test { use vob::Vob; + use crate::test_utils::*; use crate::{pager::pager_stategraph, stategraph::state_exists, StIdx}; use cfgrammar::{ - yacc::{YaccGrammar, YaccKind, YaccKindResolver, YaccOriginalActionKind}, + yacc::{YaccGrammar, YaccKind, YaccOriginalActionKind}, SIdx, Symbol, }; @@ -440,7 +441,7 @@ mod test { // aSb fn grammar3() -> YaccGrammar { YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start S %token a b c d @@ -519,7 +520,7 @@ mod test { // Pager grammar fn grammar_pager() -> YaccGrammar { YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start X %% diff --git a/lrtable/src/lib/stategraph.rs b/lrtable/src/lib/stategraph.rs index 8a2cea06e..f04a1e9a2 100644 --- a/lrtable/src/lib/stategraph.rs +++ b/lrtable/src/lib/stategraph.rs @@ -248,9 +248,10 @@ pub(crate) fn state_exists( #[cfg(test)] mod test { + use crate::test_utils::*; use crate::{pager::pager_stategraph, StIdx}; use cfgrammar::{ - yacc::{YaccGrammar, YaccKind, YaccKindResolver, YaccOriginalActionKind}, + yacc::{YaccGrammar, YaccKind, YaccOriginalActionKind}, Symbol, }; @@ -259,7 +260,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( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start A %% diff --git a/lrtable/src/lib/statetable.rs b/lrtable/src/lib/statetable.rs index 441a874e2..53ded4a31 100644 --- a/lrtable/src/lib/statetable.rs +++ b/lrtable/src/lib/statetable.rs @@ -617,11 +617,12 @@ 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::{ ast::{self, ASTWithValidityInfo}, - YaccGrammar, YaccKind, YaccKindResolver, YaccOriginalActionKind, + YaccGrammar, YaccKind, YaccOriginalActionKind, }, PIdx, Span, Symbol, TIdx, }; @@ -632,7 +633,7 @@ mod test { fn test_statetable() { // Taken from p19 of www.cs.umd.edu/~mvz/cmsc430-s07/M10lr.pdf let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start Expr %% @@ -713,7 +714,7 @@ mod test { #[test] #[rustfmt::skip] fn test_default_reduce_reduce() { - let grm = YaccGrammar::new(YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " + let grm = YaccGrammar::new(&mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start A %% A : B 'x' | C 'x' 'x'; @@ -737,7 +738,7 @@ mod test { #[test] #[rustfmt::skip] fn test_default_shift_reduce() { - let grm = YaccGrammar::new(YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " + let grm = YaccGrammar::new(&mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start Expr %% Expr : Expr '+' Expr @@ -767,7 +768,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(YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " + let grm = YaccGrammar::new(&mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start S %% S: A 'c' 'd' @@ -792,7 +793,7 @@ mod test { #[test] #[rustfmt::skip] fn test_left_associativity() { - let grm = YaccGrammar::new(YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " + let grm = YaccGrammar::new(&mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start Expr %left '+' %left '*' @@ -831,7 +832,7 @@ mod test { #[test] #[rustfmt::skip] fn test_left_right_associativity() { - let grm = &YaccGrammar::new(YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " + let grm = &YaccGrammar::new(&mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start Expr %right '=' %left '+' @@ -887,7 +888,7 @@ mod test { #[test] #[rustfmt::skip] fn test_left_right_nonassoc_associativity() { - let grm = YaccGrammar::new(YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " + let grm = YaccGrammar::new(&mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start Expr %right '=' %left '+' @@ -962,7 +963,7 @@ mod test { #[test] fn conflicts() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start A %% @@ -999,7 +1000,7 @@ C : 'a'; #[test] fn reduce_reduce_conflict() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start S %% @@ -1033,7 +1034,7 @@ X: '1' ; Y: '2' ; #[test] fn accept_reduce_conflict() { let grm = YaccGrammar::new( - YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), + &mut header_for_yacckind!(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)), " %start D %% @@ -1056,7 +1057,7 @@ D : D; fn test_accept_reduce_conflict_spans1() { let src = "%% S: S | ;"; - let yk = YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::NoAction)); + let yk = &mut header_for_yacckind!(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); @@ -1108,7 +1109,7 @@ S: S | ;"; let src = "%% S: T | ; T: S | ;"; - let yk = YaccKindResolver::Force(YaccKind::Original(YaccOriginalActionKind::NoAction)); + let yk = &mut header_for_yacckind!(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 new file mode 100644 index 000000000..7fa4f5168 --- /dev/null +++ b/lrtable/src/lib/test_utils.rs @@ -0,0 +1,25 @@ +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 3d7dc8d2e..645f10a86 100644 --- a/nimbleparse/src/main.rs +++ b/nimbleparse/src/main.rs @@ -1,10 +1,10 @@ mod diagnostics; use crate::diagnostics::*; use cfgrammar::{ - yacc::{ - ast::ASTWithValidityInfo, YaccGrammar, YaccKind, YaccKindResolver, YaccOriginalActionKind, - }, - Span, + header::{Header, Value}, + markmap::Entry, + yacc::{ast::ASTWithValidityInfo, ParserError, YaccGrammar, YaccKind, YaccOriginalActionKind}, + Location, Span, }; use getopts::Options; use lrlex::{DefaultLexerTypes, LRNonStreamingLexerDef, LexerDef}; @@ -124,14 +124,26 @@ fn main() { }, }; - let yacckind = match matches.opt_str("y") { - None => YaccKindResolver::NoDefault, - Some(s) => YaccKindResolver::Force(match &*s.to_lowercase() { - "eco" => YaccKind::Eco, - "grmtools" => YaccKind::Grmtools, - "original" => YaccKind::Original(YaccOriginalActionKind::GenericParseTree), - _ => usage(prog, &format!("Unknown Yacc variant '{}'.", s)), - }), + let mut header = Header::new(); + let entry = match header.entry("yacckind".to_string()) { + Entry::Occupied(_) => unreachable!("Header should be empty"), + Entry::Vacant(v) => v.occupied_entry(), + }; + + match matches.opt_str("y") { + None => {} + Some(s) => { + entry.insert(( + Location::CommandLine, + Value::try_from(match &*s.to_lowercase() { + "eco" => YaccKind::Eco, + "grmtools" => YaccKind::Grmtools, + "original" => YaccKind::Original(YaccOriginalActionKind::GenericParseTree), + _ => usage(prog, &format!("Unknown Yacc variant '{}'.", s)), + }) + .expect("All these yacckinds should convert without error"), + )); + } }; if matches.free.len() != 3 { @@ -154,7 +166,7 @@ fn main() { let yacc_y_path = PathBuf::from(&matches.free[1]); let yacc_src = read_file(&yacc_y_path); - let ast_validation = ASTWithValidityInfo::new(yacckind, &yacc_src); + let ast_validation = ASTWithValidityInfo::new(&mut header, &yacc_src); let warnings = ast_validation.ast().warnings(); let res = YaccGrammar::new_from_ast_with_validity_info(&ast_validation); let mut yacc_diagnostic_formatter: Option = None; @@ -174,7 +186,17 @@ fn main() { let formatter = SpannedDiagnosticFormatter::new(&yacc_src, &yacc_y_path).unwrap(); eprintln!("{ERROR}{}", formatter.file_location_msg("", None)); for e in errs { - eprintln!("{}", indent(&formatter.format_error(e).to_string(), " ")); + 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!("{WARNING}{}", formatter.file_location_msg("", None)); for w in warnings {