Skip to content

Commit

Permalink
Implement exhaustiveness.
Browse files Browse the repository at this point in the history
This change implements `Token::is_exhaustive` and fixes minor doc test
failures.
  • Loading branch information
olson-sean-k committed Jan 24, 2024
1 parent 0df1046 commit 043caf3
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 9 deletions.
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -663,8 +663,9 @@ impl<'t> Glob<'t> {
/// if dunce::canonicalize(path)
/// .unwrap()
/// .strip_prefix(&prefix)
/// .map(|path| glob.is_match(path))
/// .unwrap_or(false)
/// .ok()
/// .zip(glob)
/// .map_or(false, |(path, glob)| glob.is_match(path))
/// {
/// // ...
/// }
Expand Down
127 changes: 124 additions & 3 deletions src/token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ impl<'t, A> Token<'t, A> {
}
}

// TODO: Reverse the iterator over child nodes when extending buffers rather than using
// `VecDeque` to maintain ordering in path node types here and in `fold_map`.
// TODO: The use of terms like "fold", "accumulate", "summand", "sum", and "term" are unclear
// and maybe inconsistent here (and elsewhere). Revisit such names.
pub fn fold<F>(&self, f: F) -> Option<F::Term>
Expand Down Expand Up @@ -718,10 +720,129 @@ impl<'t, A> Token<'t, A> {
}
}

// TODO: IMPLEMENT THIS. This function is critical for both performance and correctness.
// TODO: Revisit the names of items in this function. See also `fold` and `fold_map`.
pub fn is_exhaustive(&self) -> bool {
// Safe w.r.t. correctness (false negatives are okay), but terrible for performance.
false
#[derive(Debug)]
enum Node<'i, 't, A> {
Conjunctive { branch: Branch<'i, 't, A> },
Disjunctive { branch: Branch<'i, 't, A> },
}

impl<'i, 't, A> Node<'i, 't, A> {
fn fold(&mut self, depth: Variance<Depth>) {
match self {
Node::Conjunctive { ref mut branch } => {
branch.depth = variance::union(branch.depth, depth);
},
Node::Disjunctive { ref mut branch } => {
branch.depth = variance::conjunction(branch.depth, depth);
},
}
}

fn finalize(self, _parent: Option<&mut Self>) -> Variance<Depth> {
match self {
Node::Conjunctive {
branch: Branch { depth, parent, .. },
} => match parent {
Some(branch) => branch.finalize(depth),
_ => depth,
},
// TODO: A distinction seems to have been intended here in some prior work that
// examined the parent `Node` in the disjunctive case... but I've
// forgotten what that original intention was. Examine this carefully and
// consider collapsing this arm and removing the parent parameter.
Node::Disjunctive {
branch: Branch { depth, parent, .. },
} => match parent {
Some(branch) => branch.finalize(depth),
_ => depth,
},
}
}
}

impl<'i, 't, A> AsMut<Branch<'i, 't, A>> for Node<'i, 't, A> {
fn as_mut(&mut self) -> &mut Branch<'i, 't, A> {
match self {
Node::Conjunctive { ref mut branch } => branch,
Node::Disjunctive { ref mut branch } => branch,
}
}
}

impl<'i, 't, A> From<&'i BranchKind<'t, A>> for Node<'i, 't, A> {
fn from(token: &'i BranchKind<'t, A>) -> Self {
let branch = Branch {
depth: Variance::identity(),
parent: Some(token),
tokens: token.tokens().into_inner().iter().collect(),
};
match token.composition() {
Composition::Conjunctive(_) => Node::Conjunctive { branch },
Composition::Disjunctive(_) => Node::Disjunctive {
branch: Branch {
depth: Variance::unbounded(),
..branch
},
},
}
}
}

#[derive(Debug)]
struct Branch<'i, 't, A> {
depth: Variance<Depth>,
parent: Option<&'i BranchKind<'t, A>>,
tokens: Vec<&'i Token<'t, A>>,
}

impl<'i, 't, A> Branch<'i, 't, A> {
fn reset(&mut self) {
self.depth = Variance::identity();
}
}

let mut nodes = vec![Node::Conjunctive {
branch: Branch {
depth: Variance::identity(),
parent: None,
tokens: vec![self],
},
}];
'node: while let Some(mut node) = nodes.pop() {
'token: while let Some(token) = node.as_mut().tokens.pop() {
match token.topology() {
TokenTopology::Branch(branch) => {
nodes.push(node);
nodes.push(branch.into());
continue 'node;
},
TokenTopology::Leaf(leaf) => {
if let None | Some(Boundary::Component) = token.boundary() {
let breadth: Variance<Breadth> = leaf.term();
let text: Variance<Text> = leaf.term();
if breadth.bound().is_bounded() || text.bound().is_bounded() {
let branch = node.as_mut();
if branch.depth.bound().is_bounded() {
branch.reset();
}
break 'token;
}
}
node.fold(leaf.term());
},
}
}
if let Some(parent) = nodes.last_mut() {
let term = node.finalize(Some(parent));
parent.fold(term);
}
else {
return node.finalize(None).bound().is_unbounded();
}
}
unreachable!()
}
}

Expand Down
21 changes: 17 additions & 4 deletions src/token/variance/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ where
}
}

#[derive(Clone, Debug, Eq)]
#[derive(Clone, Copy, Debug, Eq)]
pub enum Variance<T> {
Invariant(T),
// When variant, the bound describes the constraints of that variance. For example, the
Expand Down Expand Up @@ -403,6 +403,7 @@ mod tests {
assert_eq!(invariant_path_prefix("a?/b"), Path::new(""));
}

// TODO: Rename and expand upon this test. Query other invariants.
#[test]
fn tree_expression_variance() {
use Bound::Unbounded;
Expand All @@ -425,6 +426,9 @@ mod tests {

#[test]
fn exhaustiveness() {
// NOTE: This function does not check token trees against rules. This allows the test to
// examine interesting trees, including some that may not occur in `Glob` but may
// occur elsewhere like `Any`.
fn is_exhaustive(expression: &str) -> bool {
token::parse(expression)
.unwrap()
Expand All @@ -434,14 +438,23 @@ mod tests {

assert!(is_exhaustive("**"));
assert!(is_exhaustive("a/**"));
assert!(is_exhaustive("<a/**>"));
assert!(is_exhaustive("<a/<*/>>"));
assert!(is_exhaustive("a/<*/>"));
assert!(is_exhaustive("a/<*/>*"));
assert!(is_exhaustive("a/<<?>/>*"));
assert!(is_exhaustive("{a/**,b/**}"));
assert!(is_exhaustive("<*{/}>"));

assert!(is_exhaustive("a/**/b"));
assert!(!is_exhaustive(""));
assert!(!is_exhaustive("**/a"));
assert!(!is_exhaustive("a/**/b"));
assert!(!is_exhaustive("a/*"));
assert!(!is_exhaustive("<a/*>"));
assert!(!is_exhaustive("a/<?>"));
assert!(!is_exhaustive("a</**/b>"));
assert!(!is_exhaustive("**/a"));
assert!(!is_exhaustive(""));
assert!(!is_exhaustive("<a/<*/:3,>>"));
assert!(!is_exhaustive("{a/**,b/**/c}"));
assert!(!is_exhaustive("{/**,/**/a}"));
}
}

0 comments on commit 043caf3

Please sign in to comment.