Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
59f354a
Add API for user defined grmtools section entries in GrammarAST
ratmice Sep 11, 2026
eff4480
Clean up new test cases
ratmice Sep 11, 2026
14537c3
Remove unneeded `to_string()` in test
ratmice Sep 11, 2026
a96e799
Move ownership of field to ASTWithValidityInfo
ratmice Sep 11, 2026
86a4457
Use the grmtools section from `YaccParser::build`
ratmice Sep 11, 2026
adb16f4
First attempt at allowing downstream crate keys in CTParserBuilder
ratmice Sep 12, 2026
fd3fa86
Use same naming convention as the codegen module
ratmice Sep 12, 2026
fcba11b
Preemptively call `mark_used` on grmtools crate entries in header
ratmice Sep 12, 2026
751a4d4
stray whitespace
ratmice Sep 12, 2026
e8012c9
Make unused header entries check return spans
ratmice Sep 12, 2026
9bb74bf
Rename lookup function to use similar naming scheme.
ratmice Sep 12, 2026
2c47d29
Make crate name optional in `unused_header_keys_for_crate`
ratmice Sep 12, 2026
1cde528
Document the unused value checks
ratmice Sep 12, 2026
9e0c164
Move the find_span helper to test_utils
ratmice Sep 13, 2026
f3079aa
Make unknown keys without a crate prefix an error
ratmice Sep 13, 2026
ab83ba4
Simplify unused header check arguments
ratmice Sep 14, 2026
75127c6
make header_value_for_crate take one arg
ratmice Sep 14, 2026
a1d8a8f
Update docs
ratmice Sep 14, 2026
0ca6955
First try at avoiding multiple header structures
ratmice Sep 16, 2026
6cca7a6
No longer need to build a header in CTBuilder
ratmice Sep 16, 2026
a4e14d5
remove mark_required call, MissingYaccKind error covers this case
ratmice Sep 16, 2026
45fca2b
Perform unused header checks automatically during the codegen process
ratmice Sep 17, 2026
c587fab
Remove manual unused checks
ratmice Sep 17, 2026
c3b3f6c
Update after review
ratmice Sep 21, 2026
e8e1e7f
Update comment
ratmice Sep 21, 2026
22d5f11
Take set of crate/key prefixes by ref
ratmice Sep 22, 2026
74c2ba0
Update doc strings.
ratmice Sep 22, 2026
70ce6f1
Rename a few functions ofr uniformity
ratmice Sep 22, 2026
d7512ad
Remove reference to hidden function in docs
ratmice Sep 23, 2026
804a34d
initial work towards external checking of header values.
ratmice Oct 2, 2026
534631b
Avoid using helper function for now
ratmice Oct 2, 2026
19289b5
First stab at 'exhaustive' checking
ratmice Oct 2, 2026
a8065ff
add registration error, and method to retreive registered prefixes
ratmice Oct 2, 2026
16d66fd
Add test for prefix registration
ratmice Oct 2, 2026
5c9ac98
Fix docs example
ratmice Oct 2, 2026
8f90a95
rustfmt
ratmice Oct 2, 2026
642f94e
small example improvement
ratmice Oct 2, 2026
d8d00bf
ignore docstring example
ratmice Oct 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 54 additions & 15 deletions cfgrammar/src/lib/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,20 @@ use crate::{
},
};
use regex::{Regex, RegexBuilder};
use std::{collections::HashMap, error::Error, fmt, sync::LazyLock};
use std::{
collections::{HashMap, HashSet},
error::Error,
fmt,
sync::LazyLock,
};

/// 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)]
#[derive(Debug, Clone, PartialEq, Eq)]
#[doc(hidden)]
pub struct HeaderError<T> {
pub kind: HeaderErrorKind,
Expand Down Expand Up @@ -48,7 +53,7 @@ impl Spanned for HeaderError<Span> {

// 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)]
#[derive(Debug, PartialEq, Clone)]
#[doc(hidden)]
pub struct HeaderValue<T>(pub T, pub Value<T>);

Expand Down Expand Up @@ -166,7 +171,7 @@ pub static RE_CRATE_DOT: LazyLock<Regex> = LazyLock::new(|| {
static RE_DIGITS: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^[0-9]+").unwrap());
static RE_STRING: LazyLock<Regex> = LazyLock::new(|| Regex::new(r#"^\"(\\.|[^"\\])*\""#).unwrap());
#[doc(hidden)]
pub static CRATE_KEY_MAP: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
pub static KEY_CRATE_MAP: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
let mut map = HashMap::new();
let cfgrammar = ["yacckind"];
let lrpar = ["recoverer", "test_files", "serialisation_format"];
Expand Down Expand Up @@ -199,6 +204,44 @@ pub static CRATE_KEY_MAP: LazyLock<HashMap<&'static str, &'static str>> = LazyLo
map
});

#[doc(hidden)]
pub static CFGRAMMAR_KEYS: LazyLock<HashSet<&'static str>> =
LazyLock::new(|| HashSet::from_iter(["cfgrammar.yacckind"]));

#[doc(hidden)]
pub static LRPAR_KEYS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
HashSet::from_iter([
"lrpar.recoverer",
"lrpar.test_files",
"lrpar.serialisation_format",
])
});

#[doc(hidden)]
pub static LRLEX_KEYS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
HashSet::from_iter([
"lrlex.lexerkind",
"lrlex.allow_wholeline_comments",
"lrlex.posix_escapes",
])
});

#[doc(hidden)]
pub static REGEX_KEYS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
HashSet::from_iter([
"regex.case_insensitive",
"regex.dot_matches_new_line",
"regex.multi_line",
"regex.octal",
"regex.swap_greed",
"regex.ignore_whitespace",
"regex.unicode",
"regex.size_limit",
"regex.dfa_size_limit",
"regex.nest_limit",
])
});

const MAGIC: &str = "%grmtools";

fn add_duplicate_occurrence<T: Eq + PartialEq + Clone>(
Expand Down Expand Up @@ -355,10 +398,14 @@ impl<'input> GrmtoolsSectionParser<'input> {
let (key, key_loc, val, j) = match self.parse_key_value(i) {
Ok((key, key_loc, val, pos)) => {
let key = if !RE_CRATE_DOT.is_match(&key) {
if let Some(crate_name) = CRATE_KEY_MAP.get(key.as_str()) {
if let Some(crate_name) = KEY_CRATE_MAP.get(key.as_str()) {
format!("{crate_name}.{key}")
} else {
key
errs.push(HeaderError {
kind: HeaderErrorKind::IllegalName,
locations: vec![key_loc],
});
return Err(errs);
}
} else {
key
Expand Down Expand Up @@ -477,14 +524,6 @@ impl<'input> GrmtoolsSectionParser<'input> {
#[doc(hidden)]
pub type Header<T> = MarkMap<String, HeaderValue<T>>;

impl TryFrom<YaccKind> for Value<Location> {
type Error = HeaderError<Location>;
fn try_from(kind: YaccKind) -> Result<Value<Location>, HeaderError<Location>> {
let from_loc = Location::Other("From<YaccKind>".to_string());
Ok(Value::Namespaced(format!("YaccKind::{kind:?}"), from_loc))
}
}

impl<T: Clone> TryFrom<&Value<T>> for YaccKind {
type Error = HeaderError<T>;
fn try_from(value: &Value<T>) -> Result<YaccKind, HeaderError<T>> {
Expand Down Expand Up @@ -588,7 +627,7 @@ mod test {

#[test]
fn test_header_duplicates() {
let src = "%grmtools {dupe, !dupe, dupe: test}";
let src = "%grmtools {test.dupe, !test.dupe, test.dupe: test}";
for flag in [true, false] {
let parser = GrmtoolsSectionParser::new(src, flag);
let res = parser.parse();
Expand Down
38 changes: 24 additions & 14 deletions cfgrammar/src/lib/markmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use std::fmt;
///
/// Merge behaviors configure how the merge operator handles cases where both `MarkMaps` being merged
/// contain a particular key.
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Clone)]
#[doc(hidden)]
pub struct MarkMap<K, V> {
default_merge_behavior: MergeBehavior,
Expand Down Expand Up @@ -484,15 +484,15 @@ impl<K: Ord + Clone, V> MarkMap<K, V> {
}

/// Returns a `Vec` containing all the keys that are not marked as used.
pub fn unused(&self) -> Vec<K> {
let mut ret = Vec::new();
for (k, mark, v) in &self.contents {
pub fn unused(&self) -> impl Iterator<Item = (&K, &V)> {
self.contents.iter().filter_map(|(k, mark, v)| {
let used_mark = Mark::Used.repr();
if v.is_some() && mark & used_mark == 0 {
ret.push(k.to_owned())
Some((k, v.as_ref().unwrap()))
} else {
None
}
}
ret
})
}

/// Returns a `Vec` containing all the keys that are marked as required,
Expand Down Expand Up @@ -546,11 +546,18 @@ impl<'a, K, V> Iterator for MarkMapIterRef<'a, K, V> {
type Item = (&'a K, &'a V);

fn next(&mut self) -> Option<Self::Item> {
if let Some((k, _, v)) = self.map.contents.get(self.pos) {
loop {
if self.pos >= self.map.contents.len() {
return None;
}
let pos = self.pos;
self.pos += 1;
v.as_ref().map(|v| (k, v))
} else {
None
if self.map.contents[pos].2.is_some() {
return Some((
&self.map.contents[pos].0,
self.map.contents[pos].2.as_ref().unwrap(),
));
}
}
}
}
Expand Down Expand Up @@ -711,8 +718,8 @@ mod test {
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 empty: &[(&&str, &&str)] = &[];
assert_eq!(mm.unused().collect::<Vec<(&&str, &&str)>>(), empty);
}

{
Expand All @@ -722,7 +729,10 @@ mod test {
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"]);
assert_eq!(
mm.unused().collect::<Vec<(&&str, &&str)>>(),
vec![(&"b", &"unused")]
);
}
}

Expand Down
3 changes: 3 additions & 0 deletions cfgrammar/src/lib/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ pub mod yacc;
pub use newlinecache::NewlineCache;
pub use span::{Location, Span, Spanned};

#[cfg(test)]
pub mod test_utils;

/// A type specifically for rule indices.
pub use crate::idxnewtype::{PIdx, RIdx, SIdx, TIdx};

Expand Down
Loading
Loading