Skip to content

Commit

Permalink
Fix all library tests except for exhaustiveness.
Browse files Browse the repository at this point in the history
This change brings various fixes to and atop the refactoring of the
`token` module. All library tests are passing with the notable exception
of those that depend on exhaustiveness queries in some way, which are
not yet implemented.
  • Loading branch information
olson-sean-k committed Jan 23, 2024
1 parent 5abd213 commit 0df1046
Show file tree
Hide file tree
Showing 10 changed files with 501 additions and 366 deletions.
40 changes: 23 additions & 17 deletions src/diagnostics/miette.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ use tardar::{
};
use thiserror::Error;

use crate::diagnostics::SpanExt as _;
use crate::diagnostics::{SpanExt, Spanned};
use crate::rule::{self, Checked};
use crate::token::{self, TokenKind, TokenTree, Tokenized};
use crate::token::{self, Boundary, ExpressionMetadata, TokenTree, Tokenized};
use crate::Glob;

/// APIs for diagnosing globs.
Expand Down Expand Up @@ -40,7 +40,7 @@ impl<'t> Glob<'t> {
/// [`Glob::new`]: crate::Glob::new
pub fn diagnosed(expression: &'t str) -> DiagnosticResult<'t, Self> {
parse_and_diagnose(expression).and_then_diagnose(|tree| {
Glob::compile(tree.as_ref().tokens())
Glob::compile::<Tokenized<_>>(tree.as_ref())
.into_error_diagnostic()
.map_output(|program| Glob { tree, program })
})
Expand Down Expand Up @@ -84,10 +84,12 @@ pub struct TerminatingSeparatorWarning<'t> {
span: SourceSpan,
}

fn parse_and_diagnose(expression: &str) -> DiagnosticResult<Checked<Tokenized>> {
fn parse_and_diagnose(
expression: &str,
) -> DiagnosticResult<Checked<Tokenized<ExpressionMetadata>>> {
token::parse(expression)
.into_error_diagnostic()
.and_then_diagnose(|tokenized| rule::check(tokenized).into_error_diagnostic())
.and_then_diagnose(|tree| rule::check(tree).into_error_diagnostic())
.and_then_diagnose(|checked| {
// TODO: This should accept `&Checked`.
diagnose(checked.as_ref())
Expand All @@ -96,37 +98,41 @@ fn parse_and_diagnose(expression: &str) -> DiagnosticResult<Checked<Tokenized>>
})
}

fn diagnose<'i, 't>(
tokenized: &'i Tokenized<'t>,
) -> impl 'i + Iterator<Item = BoxedDiagnostic<'t>> {
fn diagnose<'i, 't, A>(tree: &'i Tokenized<'t, A>) -> impl 'i + Iterator<Item = BoxedDiagnostic<'t>>
where
A: Spanned,
{
let token = tree.as_token();
None.into_iter()
.chain(
token::literals(tokenized.tokens())
token
.literals()
.filter(|(_, literal)| literal.is_semantic_literal())
.map(|(component, literal)| {
Box::new(SemanticLiteralWarning {
expression: tokenized.expression().clone(),
expression: tree.expression().clone(),
literal: literal.text().clone(),
span: component
.tokens()
.iter()
.map(|token| *token.annotation())
.reduce(|left, right| left.union(&right))
.map(|token| token.annotation().span())
.copied()
.reduce(SpanExt::union)
.map(SourceSpan::from)
.expect("no tokens in component"),
}) as BoxedDiagnostic
}),
)
.chain(
tokenized
.tokens()
token
.concatenation()
.last()
.into_iter()
.filter(|token| matches!(token.kind(), TokenKind::Separator(_)))
.filter(|token| matches!(token.boundary(), Some(Boundary::Separator)))
.map(|token| {
Box::new(TerminatingSeparatorWarning {
expression: tokenized.expression().clone(),
span: (*token.annotation()).into(),
expression: tree.expression().clone(),
span: (*token.annotation().span()).into(),
}) as BoxedDiagnostic
}),
)
Expand Down
14 changes: 12 additions & 2 deletions src/diagnostics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ use std::fmt::{self, Display, Formatter};
pub type Span = (usize, usize);

pub trait SpanExt {
fn union(&self, other: &Self) -> Self;
fn union(self, other: Self) -> Self;
}

impl SpanExt for Span {
fn union(&self, other: &Self) -> Self {
fn union(self, other: Self) -> Self {
let start = cmp::min(self.0, other.0);
let end = cmp::max(self.0 + self.1, other.0 + other.1);
(start, end - start)
Expand All @@ -43,6 +43,16 @@ pub trait Spanned {
fn span(&self) -> &Span;

fn span_mut(&mut self) -> &mut Span;

fn map_span<F>(mut self, f: F) -> Self
where
Self: Sized,
F: FnOnce(Span) -> Span,
{
let span = *self.span();
*self.span_mut() = f(span);
self
}
}

impl Spanned for Span {
Expand Down
98 changes: 73 additions & 25 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ use thiserror::Error;
use crate::encode::CompileError;
use crate::rule::{Checked, RuleError};
use crate::token::{
ConcatenationTree, ExpressionMetadata, IteratorExt as _, ParseError, Text, Token, TokenTree,
Tokenized,
ConcatenationTree, ExpressionMetadata, ParseError, Text, Token, TokenTree, Tokenized,
};
#[cfg(feature = "walk")]
use crate::walk::WalkError;
Expand Down Expand Up @@ -611,6 +610,17 @@ impl<'t> Glob<'t> {
Ok(Glob { tree, program })
}

// TODO: Describe what an empty glob is. In particular, define what it does and does not match.
pub fn empty() -> Self {
Glob::new("").expect("failed to build empty glob")
}

// TODO: Describe what a tree glob is.
pub fn tree() -> Self {
Glob::new("**").expect("failed to build tree glob")
}

// TODO: Describe why and when the `Glob` postfix is `None`.
/// Partitions a [`Glob`] into an invariant [`PathBuf`] prefix and variant [`Glob`] postfix.
///
/// The invariant prefix contains no glob patterns nor other variant components and therefore
Expand Down Expand Up @@ -665,12 +675,29 @@ impl<'t> Glob<'t> {
/// [`PathBuf`]: std::path::PathBuf
/// [`RuleError`]: crate::RuleError
/// [`walk`]: crate::Glob::walk
pub fn partition(self) -> (PathBuf, Self) {
pub fn partition(self) -> (PathBuf, Option<Self>) {
let Glob { tree, .. } = self;
let (prefix, tree) = tree.partition();
let program = Glob::compile::<Tokenized<_>>(tree.as_ref())
.expect("failed to compile partitioned glob");
(prefix, Glob { tree, program })
(
prefix,
tree.map(|tree| {
let program = Glob::compile::<Tokenized<_>>(tree.as_ref())
.expect("failed to compile partitioned glob");
Glob { tree, program }
}),
)
}

// TODO: Describe what an empty glob is and how this relates to `Glob::partition`.
pub fn partition_or_empty(self) -> (PathBuf, Self) {
let (prefix, glob) = self.partition();
(prefix, glob.unwrap_or_else(Glob::empty))
}

// TODO: Describe what a tree glob is and how this relates to `Glob::partition`.
pub fn partition_or_tree(self) -> (PathBuf, Self) {
let (prefix, glob) = self.partition();
(prefix, glob.unwrap_or_else(Glob::tree))
}

/// Clones any borrowed data into an owning instance.
Expand Down Expand Up @@ -745,10 +772,13 @@ impl<'t> Glob<'t> {
self.tree
.as_ref()
.as_token()
.components()
.literals()
.any(|(_, literal)| literal.is_semantic_literal())
}

pub fn is_empty(&self) -> bool {
self.tree.as_ref().as_token().is_empty()
}
}

impl Display for Glob<'_> {
Expand Down Expand Up @@ -1023,10 +1053,21 @@ fn parse_and_check(
// technically specific to platforms that support `/` as a separator.
#[cfg(test)]
mod tests {
use std::path::Path;
use std::path::{Path, PathBuf};

use crate::{BuildError, BuildErrorKind, CandidatePath, Glob, Program};

trait PartitionNonEmpty<'t>: Sized {
fn partition_non_empty(self) -> (PathBuf, Glob<'t>);
}

impl<'t> PartitionNonEmpty<'t> for Glob<'t> {
fn partition_non_empty(self) -> (PathBuf, Glob<'t>) {
let (prefix, glob) = self.partition();
(prefix, glob.expect("glob partition is empty"))
}
}

#[test]
fn escape() {
assert_eq!(crate::escape(""), "");
Expand Down Expand Up @@ -1700,7 +1741,7 @@ mod tests {

#[test]
fn partition_glob_with_variant_and_invariant_parts() {
let (prefix, glob) = Glob::new("a/b/x?z/*.ext").unwrap().partition();
let (prefix, glob) = Glob::new("a/b/x?z/*.ext").unwrap().partition_non_empty();

assert_eq!(prefix, Path::new("a/b"));

Expand All @@ -1710,7 +1751,7 @@ mod tests {

#[test]
fn partition_glob_with_only_variant_wildcard_parts() {
let (prefix, glob) = Glob::new("x?z/*.ext").unwrap().partition();
let (prefix, glob) = Glob::new("x?z/*.ext").unwrap().partition_non_empty();

assert_eq!(prefix, Path::new(""));

Expand All @@ -1720,7 +1761,7 @@ mod tests {

#[test]
fn partition_glob_with_only_invariant_literal_parts() {
let (prefix, glob) = Glob::new("a/b").unwrap().partition();
let (prefix, glob) = Glob::new("a/b").unwrap().partition_or_empty();

assert_eq!(prefix, Path::new("a/b"));

Expand All @@ -1730,7 +1771,7 @@ mod tests {

#[test]
fn partition_glob_with_variant_alternative_parts() {
let (prefix, glob) = Glob::new("{x,z}/*.ext").unwrap().partition();
let (prefix, glob) = Glob::new("{x,z}/*.ext").unwrap().partition_non_empty();

assert_eq!(prefix, Path::new(""));

Expand All @@ -1740,7 +1781,7 @@ mod tests {

#[test]
fn partition_glob_with_invariant_alternative_parts() {
let (prefix, glob) = Glob::new("{a/b}/c").unwrap().partition();
let (prefix, glob) = Glob::new("{a/b}/c").unwrap().partition_or_empty();

assert_eq!(prefix, Path::new("a/b/c"));

Expand All @@ -1750,7 +1791,7 @@ mod tests {

#[test]
fn partition_glob_with_invariant_repetition_parts() {
let (prefix, glob) = Glob::new("</a/b:3>/c").unwrap().partition();
let (prefix, glob) = Glob::new("</a/b:3>/c").unwrap().partition_or_empty();

assert_eq!(prefix, Path::new("/a/b/a/b/a/b/c"));

Expand All @@ -1760,7 +1801,7 @@ mod tests {

#[test]
fn partition_glob_with_literal_dots_and_tree_tokens() {
let (prefix, glob) = Glob::new("../**/*.ext").unwrap().partition();
let (prefix, glob) = Glob::new("../**/*.ext").unwrap().partition_non_empty();

assert_eq!(prefix, Path::new(".."));

Expand All @@ -1770,7 +1811,7 @@ mod tests {

#[test]
fn partition_glob_with_rooted_tree_token() {
let (prefix, glob) = Glob::new("/**/*.ext").unwrap().partition();
let (prefix, glob) = Glob::new("/**/*.ext").unwrap().partition_non_empty();

assert_eq!(prefix, Path::new("/"));
assert!(!glob.has_root());
Expand All @@ -1781,7 +1822,7 @@ mod tests {

#[test]
fn partition_glob_with_rooted_zom_token() {
let (prefix, glob) = Glob::new("/*/*.ext").unwrap().partition();
let (prefix, glob) = Glob::new("/*/*.ext").unwrap().partition_non_empty();

assert_eq!(prefix, Path::new("/"));
assert!(!glob.has_root());
Expand All @@ -1792,7 +1833,7 @@ mod tests {

#[test]
fn partition_glob_with_rooted_literal_token() {
let (prefix, glob) = Glob::new("/root/**/*.ext").unwrap().partition();
let (prefix, glob) = Glob::new("/root/**/*.ext").unwrap().partition_non_empty();

assert_eq!(prefix, Path::new("/root"));
assert!(!glob.has_root());
Expand All @@ -1803,37 +1844,41 @@ mod tests {

#[test]
fn partition_glob_with_invariant_expression_text() {
let (prefix, glob) = Glob::new("/root/file.ext").unwrap().partition();
let (prefix, glob) = Glob::new("/root/file.ext").unwrap().partition_or_empty();
assert_eq!(prefix, Path::new("/root/file.ext"));
assert_eq!(format!("{}", glob), "");

let (prefix, glob) = Glob::new("<a:3>/file.ext").unwrap().partition();
let (prefix, glob) = Glob::new("<a:3>/file.ext").unwrap().partition_or_empty();
assert_eq!(prefix, Path::new("aaa/file.ext"));
assert_eq!(format!("{}", glob), "");
}

#[test]
fn partition_glob_with_variant_expression_text() {
let (prefix, glob) = Glob::new("**/file.ext").unwrap().partition();
let (prefix, glob) = Glob::new("**/file.ext").unwrap().partition_non_empty();
assert_eq!(prefix, Path::new(""));
assert_eq!(format!("{}", glob), "**/file.ext");

let (prefix, glob) = Glob::new("/root/**/file.ext").unwrap().partition();
let (prefix, glob) = Glob::new("/root/**/file.ext")
.unwrap()
.partition_non_empty();
assert_eq!(prefix, Path::new("/root"));
assert_eq!(format!("{}", glob), "**/file.ext");

let (prefix, glob) = Glob::new("/root/**").unwrap().partition();
let (prefix, glob) = Glob::new("/root/**").unwrap().partition_non_empty();
assert_eq!(prefix, Path::new("/root"));
assert_eq!(format!("{}", glob), "**");
}

#[test]
fn repartition_glob_with_variant_tokens() {
let (prefix, glob) = Glob::new("/root/**/file.ext").unwrap().partition();
let (prefix, glob) = Glob::new("/root/**/file.ext")
.unwrap()
.partition_non_empty();
assert_eq!(prefix, Path::new("/root"));
assert_eq!(format!("{}", glob), "**/file.ext");

let (prefix, glob) = glob.partition();
let (prefix, glob) = glob.partition_non_empty();
assert_eq!(prefix, Path::new(""));
assert_eq!(format!("{}", glob), "**/file.ext");
}
Expand Down Expand Up @@ -1863,6 +1908,9 @@ mod tests {
assert!(Glob::new("{a,..}").unwrap().has_semantic_literals());
assert!(Glob::new("<a/..>").unwrap().has_semantic_literals());
assert!(Glob::new("<a/{b,..,c}/d>").unwrap().has_semantic_literals());
assert!(Glob::new("{a,<b/{c,..}/>}d")
.unwrap()
.has_semantic_literals());
assert!(Glob::new("./*.txt").unwrap().has_semantic_literals());
}

Expand Down

0 comments on commit 0df1046

Please sign in to comment.