Skip to content
Open
2 changes: 1 addition & 1 deletion cfgrammar/src/lib/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,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
13 changes: 8 additions & 5 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,12 +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> {
pub fn unused(&self) -> Vec<(K, V)>
where
V: Clone,
{
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.push((k.to_owned(), v.as_ref().unwrap().clone()))
}
}
ret
Expand Down Expand Up @@ -711,7 +714,7 @@ 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] = &[];
let empty: &[(&str, &str)] = &[];
assert_eq!(mm.unused().as_slice(), empty);
}

Expand All @@ -722,7 +725,7 @@ 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().as_slice(), &[("b", "unused")]);
}
}

Expand Down
249 changes: 242 additions & 7 deletions cfgrammar/src/lib/yacc/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use super::{

use crate::{
Span,
header::{GrmtoolsSectionParser, HeaderError, HeaderErrorKind, HeaderValue},
header::{GrmtoolsSectionParser, Header, HeaderError, HeaderErrorKind, HeaderValue, Value},
yacc::YaccOriginalActionKind,
};

Expand All @@ -39,6 +39,7 @@ impl fmt::Display for ASTModificationError {
pub struct ASTWithValidityInfo {
yacc_kind: YaccKind,
ast: GrammarAST,
grmtools_section: Header<Span>,
errs: Vec<YaccGrammarError>,
}

Expand All @@ -52,18 +53,19 @@ impl ASTWithValidityInfo {
/// already extracted the `YaccKind` if any.
pub fn new(yacc_kind: YaccKind, s: &str) -> Self {
let mut errs = Vec::new();
let ast = {
let (ast, grmtools_section) = {
let mut yp = YaccParser::new(yacc_kind, s);
yp.parse().map_err(|e| errs.extend(e)).ok();
let mut ast = yp.build();
let (mut ast, grmtools_section) = yp.build();
ast.complete_and_validate(Some(yacc_kind))
.map_err(|e| errs.push(e))
.ok();
ast
(ast, grmtools_section)
};
ASTWithValidityInfo {
ast,
errs,
grmtools_section,
yacc_kind,
}
}
Expand Down Expand Up @@ -108,6 +110,37 @@ impl ASTWithValidityInfo {
})
}
}

/// Performs a lookup in the grmtools section for an entry with the key `crate_name.key_name` and returns it.
/// If the entry is found it marks the key as `used`, for the purposes of `unused_header_keys_for_crate`.
pub fn header_value_for_crate(
&mut self,
crate_name: &str,
key_name: &str,
) -> Option<(Span, &Value<Span>)> {
let key = format!("{crate_name}.{key_name}");
self.grmtools_section.mark_used(&key);
if let Some(HeaderValue(span, value)) = self.grmtools_section.get(&key) {
Some((*span, value))
} else {
None
}
}

pub fn unused_header_keys_for_crate(&self, crate_name: &str) -> Vec<(String, Span)> {
self.grmtools_section
.unused()
.iter()
.filter_map(|(key_name, HeaderValue(key_span, _))| {
let crate_prefix = format!("{crate_name}.");
if key_name.starts_with(&crate_prefix) {
Some((key_name.clone(), *key_span))
} else {
None
}
})
.collect::<Vec<_>>()
}
}

impl FromStr for ASTWithValidityInfo {
Expand All @@ -120,19 +153,20 @@ impl FromStr for ASTWithValidityInfo {
.map_err(|mut errs| errs.drain(..).map(|e| e.into()).collect::<Vec<_>>())?;
if let Some(HeaderValue(_, yk_val)) = header.get("cfgrammar.yacckind") {
let yacc_kind = YaccKind::try_from(yk_val).map_err(|e| vec![e.into()])?;
let ast = {
let (ast, grmtools_section) = {
// We don't want to strip off the header so that span's will be correct.
let mut yp = YaccParser::new(yacc_kind, src);
yp.parse().map_err(|e| errs.extend(e)).ok();
let mut ast = yp.build();
let (mut ast, grmtools_section) = yp.build();
ast.complete_and_validate(Some(yacc_kind))
.map_err(|e| errs.push(e))
.ok();
ast
(ast, grmtools_section)
};
Ok(ASTWithValidityInfo {
ast,
errs,
grmtools_section,
yacc_kind,
})
} else {
Expand Down Expand Up @@ -984,4 +1018,205 @@ start -> () : "a" {$;;;; };
}]
);
}

#[test]
fn test_grmtools_section_values() {
use super::*;
use crate::header::Value;
let src = r#"
%grmtools {
yacckind: Grmtools,
lrpar.recoverer: CPCTPlus,
test.Flag,
!test.Negative,
test.string: "Foo",
test.vec: ["Aaaa", "Bbbb"],
test.num: 1234,
test.unused: 5678
}
%token a
%%
start -> () : "a" { () };
"#;
let mut ast_validity = ASTWithValidityInfo::from_str(src).unwrap();
for (key, (expected_span, expected_value)) in [
(
"Flag",
(
src.find_span("test.Flag"),
Value::Bool(true, src.find_span("test.Flag")),
),
),
(
"Negative",
(
src.find_span("test.Negative"),
Value::Bool(false, src.find_span("!test.Negative")),
),
),
(
"string",
(
src.find_span("test.string"),
Value::String("Foo".to_string(), src.find_span("Foo")),
),
),
(
"vec",
(
src.find_span("test.vec"),
Value::Array(
vec![
Value::String("Aaaa".to_string(), src.find_span("Aaaa")),
Value::String("Bbbb".to_string(), src.find_span("Bbbb")),
],
src.find_span("[\"Aaaa\", \"Bbbb\"]"),
),
),
),
(
"num",
(
src.find_span("test.num"),
Value::Num(1234, src.find_span("1234")),
),
),
] {
let value = ast_validity.header_value_for_crate("test", key);
assert_eq!(value, Some((expected_span, &expected_value)));
}
assert_eq!(
ast_validity.unused_header_keys_for_crate("test"),
vec![("test.unused".to_string(), src.find_span("test.unused"))]
);
assert_eq!(
ast_validity.header_value_for_crate("cfgrammar", "yacckind"),
Some((
src.find_span("yacckind"),
&Value::Namespaced("Grmtools".to_string(), src.find_span("Grmtools"))
))
);

assert!(
ast_validity
.unused_header_keys_for_crate("cfgrammar")
.is_empty()
);

assert_eq!(
ast_validity.header_value_for_crate("lrpar", "recoverer"),
Some((
src.find_span("lrpar.recoverer"),
&Value::Namespaced("CPCTPlus".to_string(), src.find_span("CPCTPlus"))
))
);

assert!(
ast_validity
.unused_header_keys_for_crate("lrpar")
.is_empty()
);
}

#[test]
fn test_grmtools_section_values2() {
use super::*;
let src = r#"
%grmtools {
yacckind: Original(YaccOriginalActionKind::UserAction),
}
%token a
%actiontype ()
%%
start: "a" { () };
"#;
let mut ast_validity = ASTWithValidityInfo::from_str(src).unwrap();
assert_eq!(
ast_validity.header_value_for_crate("cfgrammar", "yacckind"),
Some((
src.find_span("yacckind"),
&Value::Namespaced(
"Original(YaccOriginalActionKind::UserAction)".to_string(),
src.find_span("Original(YaccOriginalActionKind::UserAction)"),
),
))
);
assert!(
ast_validity
.unused_header_keys_for_crate("cfgrammar")
.is_empty()
);
}

#[test]
fn test_grmtools_section_values3() {
use super::*;
let src = r#"
%grmtools {
yacckind: YaccKind::Original(YaccOriginalActionKind::UserAction),
}
%token a
%actiontype ()
%%
start: "a" { () };
"#;
let mut ast_validity = ASTWithValidityInfo::from_str(src).unwrap();
assert_eq!(
ast_validity.header_value_for_crate("cfgrammar", "yacckind"),
Some((
src.find_span("yacckind"),
&Value::Namespaced(
"YaccKind::Original(YaccOriginalActionKind::UserAction)".to_string(),
src.find_span("YaccKind::Original(YaccOriginalActionKind::UserAction)"),
),
))
);
assert!(
ast_validity
.unused_header_keys_for_crate("cfgrammar")
.is_empty()
);
}

#[test]
fn test_grmtools_section_values4() {
use super::*;
let src = r#"
%grmtools {
yacckind: YaccKind::Original(UserAction),
}
%token a
%actiontype ()
%%
start: "a" { () };
"#;
let mut ast_validity = ASTWithValidityInfo::from_str(src).unwrap();
assert_eq!(
ast_validity.header_value_for_crate("cfgrammar", "yacckind"),
Some((
src.find_span("yacckind"),
&Value::Namespaced(
"YaccKind::Original(UserAction)".to_string(),
src.find_span("YaccKind::Original(UserAction)"),
),
))
);
assert!(
ast_validity
.unused_header_keys_for_crate("cfgrammar")
.is_empty()
);
}

trait FindSpan {
fn find_span(&self, s: &str) -> Span;
}

impl FindSpan for &'_ str {
#[track_caller]
fn find_span(&self, s: &str) -> Span {
let start_pos = self.find(s).unwrap();
Span::new(start_pos, start_pos + s.len())
}
}
}
Loading