build(rust): vendor googletest crate
This commit was merged in pull request #76.
This commit is contained in:
1 parent
a6fe84190e
commit
6887b104d6
576 files changed
+284290
-2
No files matched your search
@@ -0,0 +1,591 @@
|
||||
use core::{char, cmp, fmt::Debug, slice};
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::unicode;
|
||||
|
||||
// This module contains an *internal* implementation of interval sets.
|
||||
//
|
||||
// The primary invariant that interval sets guards is canonical ordering. That
|
||||
// is, every interval set contains an ordered sequence of intervals where
|
||||
// no two intervals are overlapping or adjacent. While this invariant is
|
||||
// occasionally broken within the implementation, it should be impossible for
|
||||
// callers to observe it.
|
||||
//
|
||||
// Since case folding (as implemented below) breaks that invariant, we roll
|
||||
// that into this API even though it is a little out of place in an otherwise
|
||||
// generic interval set. (Hence the reason why the `unicode` module is imported
|
||||
// here.)
|
||||
//
|
||||
// Some of the implementation complexity here is a result of me wanting to
|
||||
// preserve the sequential representation without using additional memory.
|
||||
// In many cases, we do use linear extra memory, but it is at most 2x and it
|
||||
// is amortized. If we relaxed the memory requirements, this implementation
|
||||
// could become much simpler. The extra memory is honestly probably OK, but
|
||||
// character classes (especially of the Unicode variety) can become quite
|
||||
// large, and it would be nice to keep regex compilation snappy even in debug
|
||||
// builds. (In the past, I have been careless with this area of code and it has
|
||||
// caused slow regex compilations in debug mode, so this isn't entirely
|
||||
// unwarranted.)
|
||||
//
|
||||
// Tests on this are relegated to the public API of HIR in src/hir.rs.
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IntervalSet<I> {
|
||||
/// A sorted set of non-overlapping ranges.
|
||||
ranges: Vec<I>,
|
||||
/// While not required at all for correctness, we keep track of whether an
|
||||
/// interval set has been case folded or not. This helps us avoid doing
|
||||
/// redundant work if, for example, a set has already been cased folded.
|
||||
/// And note that whether a set is folded or not is preserved through
|
||||
/// all of the pairwise set operations. That is, if both interval sets
|
||||
/// have been case folded, then any of difference, union, intersection or
|
||||
/// symmetric difference all produce a case folded set.
|
||||
///
|
||||
/// Note that when this is true, it *must* be the case that the set is case
|
||||
/// folded. But when it's false, the set *may* be case folded. In other
|
||||
/// words, we only set this to true when we know it to be case, but we're
|
||||
/// okay with it being false if it would otherwise be costly to determine
|
||||
/// whether it should be true. This means code cannot assume that a false
|
||||
/// value necessarily indicates that the set is not case folded.
|
||||
///
|
||||
/// Bottom line: this is a performance optimization.
|
||||
folded: bool,
|
||||
}
|
||||
|
||||
impl<I: Interval> Eq for IntervalSet<I> {}
|
||||
|
||||
// We implement PartialEq manually so that we don't consider the set's internal
|
||||
// 'folded' property to be part of its identity. The 'folded' property is
|
||||
// strictly an optimization.
|
||||
impl<I: Interval> PartialEq for IntervalSet<I> {
|
||||
fn eq(&self, other: &IntervalSet<I>) -> bool {
|
||||
self.ranges.eq(&other.ranges)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Interval> IntervalSet<I> {
|
||||
/// Create a new set from a sequence of intervals. Each interval is
|
||||
/// specified as a pair of bounds, where both bounds are inclusive.
|
||||
///
|
||||
/// The given ranges do not need to be in any specific order, and ranges
|
||||
/// may overlap.
|
||||
pub fn new<T: IntoIterator<Item = I>>(intervals: T) -> IntervalSet<I> {
|
||||
let ranges: Vec<I> = intervals.into_iter().collect();
|
||||
// An empty set is case folded.
|
||||
let folded = ranges.is_empty();
|
||||
let mut set = IntervalSet { ranges, folded };
|
||||
set.canonicalize();
|
||||
set
|
||||
}
|
||||
|
||||
/// Add a new interval to this set.
|
||||
pub fn push(&mut self, mut interval: I) {
|
||||
let Err(i) = self.ranges.binary_search(&interval) else {
|
||||
// Exact match, `interval` is already in the set.
|
||||
return;
|
||||
};
|
||||
|
||||
// The search finds us the first index where the previous interval
|
||||
// start is less than or equal to the new interval start. Since the
|
||||
// existing intervals are non-overlapping we only need to try to union
|
||||
// this single preceding interval
|
||||
let mut start = i;
|
||||
if let Some(before_i) = i.checked_sub(1) {
|
||||
let before = &self.ranges[before_i];
|
||||
if let Some(union) = before.union(&interval) {
|
||||
interval = union;
|
||||
start = before_i;
|
||||
}
|
||||
}
|
||||
// `interval` may overlap any number of intervals following the
|
||||
// insertion point so will union each of them until we reach the
|
||||
// first non-overlapping interval
|
||||
let mut end = i;
|
||||
for after_i in i..self.ranges.len() {
|
||||
let after = &self.ranges[after_i];
|
||||
let Some(union) = interval.union(after) else {
|
||||
break;
|
||||
};
|
||||
interval = union;
|
||||
end = after_i + 1;
|
||||
}
|
||||
self.ranges.splice(start..end, core::iter::once(interval));
|
||||
|
||||
// We don't know whether the new interval added here is considered
|
||||
// case folded, so we conservatively assume that the entire set is
|
||||
// no longer case folded if it was previously.
|
||||
self.folded = false;
|
||||
}
|
||||
|
||||
/// Return an iterator over all intervals in this set.
|
||||
///
|
||||
/// The iterator yields intervals in ascending order.
|
||||
pub fn iter(&self) -> IntervalSetIter<'_, I> {
|
||||
IntervalSetIter(self.ranges.iter())
|
||||
}
|
||||
|
||||
/// Return an immutable slice of intervals in this set.
|
||||
///
|
||||
/// The sequence returned is in canonical ordering.
|
||||
pub fn intervals(&self) -> &[I] {
|
||||
&self.ranges
|
||||
}
|
||||
|
||||
/// Expand this interval set such that it contains all case folded
|
||||
/// characters. For example, if this class consists of the range `a-z`,
|
||||
/// then applying case folding will result in the class containing both the
|
||||
/// ranges `a-z` and `A-Z`.
|
||||
///
|
||||
/// This returns an error if the necessary case mapping data is not
|
||||
/// available.
|
||||
pub fn case_fold_simple(&mut self) -> Result<(), unicode::CaseFoldError> {
|
||||
if self.folded {
|
||||
return Ok(());
|
||||
}
|
||||
let len = self.ranges.len();
|
||||
for i in 0..len {
|
||||
let range = self.ranges[i];
|
||||
if let Err(err) = range.case_fold_simple(&mut self.ranges) {
|
||||
self.canonicalize();
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
self.canonicalize();
|
||||
self.folded = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Union this set with the given set, in place.
|
||||
pub fn union(&mut self, other: &IntervalSet<I>) {
|
||||
if other.ranges.is_empty() || self.ranges == other.ranges {
|
||||
return;
|
||||
}
|
||||
// This could almost certainly be done more efficiently.
|
||||
self.ranges.extend(&other.ranges);
|
||||
self.canonicalize();
|
||||
self.folded = self.folded && other.folded;
|
||||
}
|
||||
|
||||
/// Intersect this set with the given set, in place.
|
||||
pub fn intersect(&mut self, other: &IntervalSet<I>) {
|
||||
if self.ranges.is_empty() {
|
||||
return;
|
||||
}
|
||||
if other.ranges.is_empty() {
|
||||
self.ranges.clear();
|
||||
// An empty set is case folded.
|
||||
self.folded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// There should be a way to do this in-place with constant memory,
|
||||
// but I couldn't figure out a simple way to do it. So just append
|
||||
// the intersection to the end of this range, and then drain it before
|
||||
// we're done.
|
||||
let drain_end = self.ranges.len();
|
||||
|
||||
let mut ita = 0..drain_end;
|
||||
let mut itb = 0..other.ranges.len();
|
||||
let mut a = ita.next().unwrap();
|
||||
let mut b = itb.next().unwrap();
|
||||
loop {
|
||||
if let Some(ab) = self.ranges[a].intersect(&other.ranges[b]) {
|
||||
self.ranges.push(ab);
|
||||
}
|
||||
let (it, aorb) =
|
||||
if self.ranges[a].upper() < other.ranges[b].upper() {
|
||||
(&mut ita, &mut a)
|
||||
} else {
|
||||
(&mut itb, &mut b)
|
||||
};
|
||||
match it.next() {
|
||||
Some(v) => *aorb = v,
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
self.ranges.drain(..drain_end);
|
||||
self.folded = self.folded && other.folded;
|
||||
}
|
||||
|
||||
/// Subtract the given set from this set, in place.
|
||||
pub fn difference(&mut self, other: &IntervalSet<I>) {
|
||||
if self.ranges.is_empty() || other.ranges.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// This algorithm is (to me) surprisingly complex. A search of the
|
||||
// interwebs indicate that this is a potentially interesting problem.
|
||||
// Folks seem to suggest interval or segment trees, but I'd like to
|
||||
// avoid the overhead (both runtime and conceptual) of that.
|
||||
//
|
||||
// The following is basically my Shitty First Draft. Therefore, in
|
||||
// order to grok it, you probably need to read each line carefully.
|
||||
// Simplifications are most welcome!
|
||||
//
|
||||
// Remember, we can assume the canonical format invariant here, which
|
||||
// says that all ranges are sorted, not overlapping and not adjacent in
|
||||
// each class.
|
||||
let drain_end = self.ranges.len();
|
||||
let (mut a, mut b) = (0, 0);
|
||||
'LOOP: while a < drain_end && b < other.ranges.len() {
|
||||
// Basically, the easy cases are when neither range overlaps with
|
||||
// each other. If the `b` range is less than our current `a`
|
||||
// range, then we can skip it and move on.
|
||||
if other.ranges[b].upper() < self.ranges[a].lower() {
|
||||
b += 1;
|
||||
continue;
|
||||
}
|
||||
// ... similarly for the `a` range. If it's less than the smallest
|
||||
// `b` range, then we can add it as-is.
|
||||
if self.ranges[a].upper() < other.ranges[b].lower() {
|
||||
let range = self.ranges[a];
|
||||
self.ranges.push(range);
|
||||
a += 1;
|
||||
continue;
|
||||
}
|
||||
// Otherwise, we have overlapping ranges.
|
||||
assert!(!self.ranges[a].is_intersection_empty(&other.ranges[b]));
|
||||
|
||||
// This part is tricky and was non-obvious to me without looking
|
||||
// at explicit examples (see the tests). The trickiness stems from
|
||||
// two things: 1) subtracting a range from another range could
|
||||
// yield two ranges and 2) after subtracting a range, it's possible
|
||||
// that future ranges can have an impact. The loop below advances
|
||||
// the `b` ranges until they can't possible impact the current
|
||||
// range.
|
||||
//
|
||||
// For example, if our `a` range is `a-t` and our next three `b`
|
||||
// ranges are `a-c`, `g-i`, `r-t` and `x-z`, then we need to apply
|
||||
// subtraction three times before moving on to the next `a` range.
|
||||
let mut range = self.ranges[a];
|
||||
while b < other.ranges.len()
|
||||
&& !range.is_intersection_empty(&other.ranges[b])
|
||||
{
|
||||
let old_range = range;
|
||||
range = match range.difference(&other.ranges[b]) {
|
||||
(None, None) => {
|
||||
// We lost the entire range, so move on to the next
|
||||
// without adding this one.
|
||||
a += 1;
|
||||
continue 'LOOP;
|
||||
}
|
||||
(Some(range1), None) | (None, Some(range1)) => range1,
|
||||
(Some(range1), Some(range2)) => {
|
||||
self.ranges.push(range1);
|
||||
range2
|
||||
}
|
||||
};
|
||||
// It's possible that the `b` range has more to contribute
|
||||
// here. In particular, if it is greater than the original
|
||||
// range, then it might impact the next `a` range *and* it
|
||||
// has impacted the current `a` range as much as possible,
|
||||
// so we can quit. We don't bump `b` so that the next `a`
|
||||
// range can apply it.
|
||||
if other.ranges[b].upper() > old_range.upper() {
|
||||
break;
|
||||
}
|
||||
// Otherwise, the next `b` range might apply to the current
|
||||
// `a` range.
|
||||
b += 1;
|
||||
}
|
||||
self.ranges.push(range);
|
||||
a += 1;
|
||||
}
|
||||
while a < drain_end {
|
||||
let range = self.ranges[a];
|
||||
self.ranges.push(range);
|
||||
a += 1;
|
||||
}
|
||||
self.ranges.drain(..drain_end);
|
||||
self.folded = self.folded && other.folded;
|
||||
}
|
||||
|
||||
/// Compute the symmetric difference of the two sets, in place.
|
||||
///
|
||||
/// This computes the symmetric difference of two interval sets. This
|
||||
/// removes all elements in this set that are also in the given set,
|
||||
/// but also adds all elements from the given set that aren't in this
|
||||
/// set. That is, the set will contain all elements in either set,
|
||||
/// but will not contain any elements that are in both sets.
|
||||
pub fn symmetric_difference(&mut self, other: &IntervalSet<I>) {
|
||||
// TODO(burntsushi): Fix this so that it amortizes allocation.
|
||||
let mut intersection = self.clone();
|
||||
intersection.intersect(other);
|
||||
self.union(other);
|
||||
self.difference(&intersection);
|
||||
}
|
||||
|
||||
/// Negate this interval set.
|
||||
///
|
||||
/// For all `x` where `x` is any element, if `x` was in this set, then it
|
||||
/// will not be in this set after negation.
|
||||
pub fn negate(&mut self) {
|
||||
if self.ranges.is_empty() {
|
||||
let (min, max) = (I::Bound::min_value(), I::Bound::max_value());
|
||||
self.ranges.push(I::create(min, max));
|
||||
// The set containing everything must case folded.
|
||||
self.folded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// There should be a way to do this in-place with constant memory,
|
||||
// but I couldn't figure out a simple way to do it. So just append
|
||||
// the negation to the end of this range, and then drain it before
|
||||
// we're done.
|
||||
let drain_end = self.ranges.len();
|
||||
|
||||
// We do checked arithmetic below because of the canonical ordering
|
||||
// invariant.
|
||||
if self.ranges[0].lower() > I::Bound::min_value() {
|
||||
let upper = self.ranges[0].lower().decrement();
|
||||
self.ranges.push(I::create(I::Bound::min_value(), upper));
|
||||
}
|
||||
for i in 1..drain_end {
|
||||
let lower = self.ranges[i - 1].upper().increment();
|
||||
let upper = self.ranges[i].lower().decrement();
|
||||
self.ranges.push(I::create(lower, upper));
|
||||
}
|
||||
if self.ranges[drain_end - 1].upper() < I::Bound::max_value() {
|
||||
let lower = self.ranges[drain_end - 1].upper().increment();
|
||||
self.ranges.push(I::create(lower, I::Bound::max_value()));
|
||||
}
|
||||
self.ranges.drain(..drain_end);
|
||||
// We don't need to update whether this set is folded or not, because
|
||||
// it is conservatively preserved through negation. Namely, if a set
|
||||
// is not folded, then it is possible that its negation is folded, for
|
||||
// example, [^☃]. But we're fine with assuming that the set is not
|
||||
// folded in that case. (`folded` permits false negatives but not false
|
||||
// positives.)
|
||||
//
|
||||
// But what about when a set is folded, is its negation also
|
||||
// necessarily folded? Yes. Because if a set is folded, then for every
|
||||
// character in the set, it necessarily included its equivalence class
|
||||
// of case folded characters. Negating it in turn means that all
|
||||
// equivalence classes in the set are negated, and any equivalence
|
||||
// class that was previously not in the set is now entirely in the set.
|
||||
}
|
||||
|
||||
/// Converts this set into a canonical ordering.
|
||||
fn canonicalize(&mut self) {
|
||||
if self.is_canonical() {
|
||||
return;
|
||||
}
|
||||
self.ranges.sort();
|
||||
assert!(!self.ranges.is_empty());
|
||||
|
||||
// Is there a way to do this in-place with constant memory? I couldn't
|
||||
// figure out a way to do it. So just append the canonicalization to
|
||||
// the end of this range, and then drain it before we're done.
|
||||
let drain_end = self.ranges.len();
|
||||
for oldi in 0..drain_end {
|
||||
// If we've added at least one new range, then check if we can
|
||||
// merge this range in the previously added range.
|
||||
if self.ranges.len() > drain_end {
|
||||
let (last, rest) = self.ranges.split_last_mut().unwrap();
|
||||
if let Some(union) = last.union(&rest[oldi]) {
|
||||
*last = union;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let range = self.ranges[oldi];
|
||||
self.ranges.push(range);
|
||||
}
|
||||
self.ranges.drain(..drain_end);
|
||||
}
|
||||
|
||||
/// Returns true if and only if this class is in a canonical ordering.
|
||||
fn is_canonical(&self) -> bool {
|
||||
for pair in self.ranges.windows(2) {
|
||||
if pair[0] >= pair[1] {
|
||||
return false;
|
||||
}
|
||||
if pair[0].is_contiguous(&pair[1]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over intervals.
|
||||
#[derive(Debug)]
|
||||
pub struct IntervalSetIter<'a, I>(slice::Iter<'a, I>);
|
||||
|
||||
impl<'a, I> Iterator for IntervalSetIter<'a, I> {
|
||||
type Item = &'a I;
|
||||
|
||||
fn next(&mut self) -> Option<&'a I> {
|
||||
self.0.next()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Interval:
|
||||
Clone + Copy + Debug + Default + Eq + PartialEq + PartialOrd + Ord
|
||||
{
|
||||
type Bound: Bound;
|
||||
|
||||
fn lower(&self) -> Self::Bound;
|
||||
fn upper(&self) -> Self::Bound;
|
||||
fn set_lower(&mut self, bound: Self::Bound);
|
||||
fn set_upper(&mut self, bound: Self::Bound);
|
||||
fn case_fold_simple(
|
||||
&self,
|
||||
intervals: &mut Vec<Self>,
|
||||
) -> Result<(), unicode::CaseFoldError>;
|
||||
|
||||
/// Create a new interval.
|
||||
fn create(lower: Self::Bound, upper: Self::Bound) -> Self {
|
||||
let mut int = Self::default();
|
||||
if lower <= upper {
|
||||
int.set_lower(lower);
|
||||
int.set_upper(upper);
|
||||
} else {
|
||||
int.set_lower(upper);
|
||||
int.set_upper(lower);
|
||||
}
|
||||
int
|
||||
}
|
||||
|
||||
/// Union the given overlapping range into this range.
|
||||
///
|
||||
/// If the two ranges aren't contiguous, then this returns `None`.
|
||||
fn union(&self, other: &Self) -> Option<Self> {
|
||||
if !self.is_contiguous(other) {
|
||||
return None;
|
||||
}
|
||||
let lower = cmp::min(self.lower(), other.lower());
|
||||
let upper = cmp::max(self.upper(), other.upper());
|
||||
Some(Self::create(lower, upper))
|
||||
}
|
||||
|
||||
/// Intersect this range with the given range and return the result.
|
||||
///
|
||||
/// If the intersection is empty, then this returns `None`.
|
||||
fn intersect(&self, other: &Self) -> Option<Self> {
|
||||
let lower = cmp::max(self.lower(), other.lower());
|
||||
let upper = cmp::min(self.upper(), other.upper());
|
||||
if lower <= upper {
|
||||
Some(Self::create(lower, upper))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Subtract the given range from this range and return the resulting
|
||||
/// ranges.
|
||||
///
|
||||
/// If subtraction would result in an empty range, then no ranges are
|
||||
/// returned.
|
||||
fn difference(&self, other: &Self) -> (Option<Self>, Option<Self>) {
|
||||
if self.is_subset(other) {
|
||||
return (None, None);
|
||||
}
|
||||
if self.is_intersection_empty(other) {
|
||||
return (Some(self.clone()), None);
|
||||
}
|
||||
let add_lower = other.lower() > self.lower();
|
||||
let add_upper = other.upper() < self.upper();
|
||||
// We know this because !self.is_subset(other) and the ranges have
|
||||
// a non-empty intersection.
|
||||
assert!(add_lower || add_upper);
|
||||
let mut ret = (None, None);
|
||||
if add_lower {
|
||||
let upper = other.lower().decrement();
|
||||
ret.0 = Some(Self::create(self.lower(), upper));
|
||||
}
|
||||
if add_upper {
|
||||
let lower = other.upper().increment();
|
||||
let range = Self::create(lower, self.upper());
|
||||
if ret.0.is_none() {
|
||||
ret.0 = Some(range);
|
||||
} else {
|
||||
ret.1 = Some(range);
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
/// Returns true if and only if the two ranges are contiguous. Two ranges
|
||||
/// are contiguous if and only if the ranges are either overlapping or
|
||||
/// adjacent.
|
||||
fn is_contiguous(&self, other: &Self) -> bool {
|
||||
let lower1 = self.lower().as_u32();
|
||||
let upper1 = self.upper().as_u32();
|
||||
let lower2 = other.lower().as_u32();
|
||||
let upper2 = other.upper().as_u32();
|
||||
cmp::max(lower1, lower2) <= cmp::min(upper1, upper2).saturating_add(1)
|
||||
}
|
||||
|
||||
/// Returns true if and only if the intersection of this range and the
|
||||
/// other range is empty.
|
||||
fn is_intersection_empty(&self, other: &Self) -> bool {
|
||||
let (lower1, upper1) = (self.lower(), self.upper());
|
||||
let (lower2, upper2) = (other.lower(), other.upper());
|
||||
cmp::max(lower1, lower2) > cmp::min(upper1, upper2)
|
||||
}
|
||||
|
||||
/// Returns true if and only if this range is a subset of the other range.
|
||||
fn is_subset(&self, other: &Self) -> bool {
|
||||
let (lower1, upper1) = (self.lower(), self.upper());
|
||||
let (lower2, upper2) = (other.lower(), other.upper());
|
||||
(lower2 <= lower1 && lower1 <= upper2)
|
||||
&& (lower2 <= upper1 && upper1 <= upper2)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Bound:
|
||||
Copy + Clone + Debug + Eq + PartialEq + PartialOrd + Ord
|
||||
{
|
||||
fn min_value() -> Self;
|
||||
fn max_value() -> Self;
|
||||
fn as_u32(self) -> u32;
|
||||
fn increment(self) -> Self;
|
||||
fn decrement(self) -> Self;
|
||||
}
|
||||
|
||||
impl Bound for u8 {
|
||||
fn min_value() -> Self {
|
||||
u8::MIN
|
||||
}
|
||||
fn max_value() -> Self {
|
||||
u8::MAX
|
||||
}
|
||||
fn as_u32(self) -> u32 {
|
||||
u32::from(self)
|
||||
}
|
||||
fn increment(self) -> Self {
|
||||
self.checked_add(1).unwrap()
|
||||
}
|
||||
fn decrement(self) -> Self {
|
||||
self.checked_sub(1).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Bound for char {
|
||||
fn min_value() -> Self {
|
||||
'\x00'
|
||||
}
|
||||
fn max_value() -> Self {
|
||||
'\u{10FFFF}'
|
||||
}
|
||||
fn as_u32(self) -> u32 {
|
||||
u32::from(self)
|
||||
}
|
||||
|
||||
fn increment(self) -> Self {
|
||||
match self {
|
||||
'\u{D7FF}' => '\u{E000}',
|
||||
c => char::from_u32(u32::from(c).checked_add(1).unwrap()).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn decrement(self) -> Self {
|
||||
match self {
|
||||
'\u{E000}' => '\u{D7FF}',
|
||||
c => char::from_u32(u32::from(c).checked_sub(1).unwrap()).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests for interval sets are written in src/hir.rs against the public API.
|
||||
File diff suppressed because it is too large.
Load diff
+3873
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,608 @@
|
||||
/*!
|
||||
This module provides a regular expression printer for `Hir`.
|
||||
*/
|
||||
|
||||
use core::fmt;
|
||||
|
||||
use crate::{
|
||||
hir::{
|
||||
self,
|
||||
visitor::{self, Visitor},
|
||||
Hir, HirKind,
|
||||
},
|
||||
is_meta_character,
|
||||
};
|
||||
|
||||
/// A builder for constructing a printer.
|
||||
///
|
||||
/// Note that since a printer doesn't have any configuration knobs, this type
|
||||
/// remains unexported.
|
||||
#[derive(Clone, Debug)]
|
||||
struct PrinterBuilder {
|
||||
_priv: (),
|
||||
}
|
||||
|
||||
impl Default for PrinterBuilder {
|
||||
fn default() -> PrinterBuilder {
|
||||
PrinterBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PrinterBuilder {
|
||||
fn new() -> PrinterBuilder {
|
||||
PrinterBuilder { _priv: () }
|
||||
}
|
||||
|
||||
fn build(&self) -> Printer {
|
||||
Printer { _priv: () }
|
||||
}
|
||||
}
|
||||
|
||||
/// A printer for a regular expression's high-level intermediate
|
||||
/// representation.
|
||||
///
|
||||
/// A printer converts a high-level intermediate representation (HIR) to a
|
||||
/// regular expression pattern string. This particular printer uses constant
|
||||
/// stack space and heap space proportional to the size of the HIR.
|
||||
///
|
||||
/// Since this printer is only using the HIR, the pattern it prints will likely
|
||||
/// not resemble the original pattern at all. For example, a pattern like
|
||||
/// `\pL` will have its entire class written out.
|
||||
///
|
||||
/// The purpose of this printer is to provide a means to mutate an HIR and then
|
||||
/// build a regular expression from the result of that mutation. (A regex
|
||||
/// library could provide a constructor from this HIR explicitly, but that
|
||||
/// creates an unnecessary public coupling between the regex library and this
|
||||
/// specific HIR representation.)
|
||||
#[derive(Debug)]
|
||||
pub struct Printer {
|
||||
_priv: (),
|
||||
}
|
||||
|
||||
impl Printer {
|
||||
/// Create a new printer.
|
||||
pub fn new() -> Printer {
|
||||
PrinterBuilder::new().build()
|
||||
}
|
||||
|
||||
/// Print the given `Ast` to the given writer. The writer must implement
|
||||
/// `fmt::Write`. Typical implementations of `fmt::Write` that can be used
|
||||
/// here are a `fmt::Formatter` (which is available in `fmt::Display`
|
||||
/// implementations) or a `&mut String`.
|
||||
pub fn print<W: fmt::Write>(&mut self, hir: &Hir, wtr: W) -> fmt::Result {
|
||||
visitor::visit(hir, Writer { wtr })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Writer<W> {
|
||||
wtr: W,
|
||||
}
|
||||
|
||||
impl<W: fmt::Write> Visitor for Writer<W> {
|
||||
type Output = ();
|
||||
type Err = fmt::Error;
|
||||
|
||||
fn finish(self) -> fmt::Result {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn visit_pre(&mut self, hir: &Hir) -> fmt::Result {
|
||||
match *hir.kind() {
|
||||
HirKind::Empty => {
|
||||
// Technically an empty sub-expression could be "printed" by
|
||||
// just ignoring it, but in practice, you could have a
|
||||
// repetition operator attached to an empty expression, and you
|
||||
// really need something in the concrete syntax to make that
|
||||
// work as you'd expect.
|
||||
self.wtr.write_str(r"(?:)")?;
|
||||
}
|
||||
// Repetition operators are strictly suffix oriented.
|
||||
HirKind::Repetition(_) => {}
|
||||
HirKind::Literal(hir::Literal(ref bytes)) => {
|
||||
// See the comment on the 'Concat' and 'Alternation' case below
|
||||
// for why we put parens here. Literals are, conceptually,
|
||||
// a special case of concatenation where each element is a
|
||||
// character. The HIR flattens this into a Box<[u8]>, but we
|
||||
// still need to treat it like a concatenation for correct
|
||||
// printing. As a special case, we don't write parens if there
|
||||
// is only one character. One character means there is no
|
||||
// concat so we don't need parens. Adding parens would still be
|
||||
// correct, but we drop them here because it tends to create
|
||||
// rather noisy regexes even in simple cases.
|
||||
let result = core::str::from_utf8(bytes);
|
||||
let len = result.map_or(bytes.len(), |s| s.chars().count());
|
||||
if len > 1 {
|
||||
self.wtr.write_str(r"(?:")?;
|
||||
}
|
||||
match result {
|
||||
Ok(string) => {
|
||||
for c in string.chars() {
|
||||
self.write_literal_char(c)?;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
for &b in bytes.iter() {
|
||||
self.write_literal_byte(b)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
if len > 1 {
|
||||
self.wtr.write_str(r")")?;
|
||||
}
|
||||
}
|
||||
HirKind::Class(hir::Class::Unicode(ref cls)) => {
|
||||
if cls.ranges().is_empty() {
|
||||
return self.wtr.write_str("[a&&b]");
|
||||
}
|
||||
self.wtr.write_str("[")?;
|
||||
for range in cls.iter() {
|
||||
if range.start() == range.end() {
|
||||
self.write_literal_char(range.start())?;
|
||||
} else if u32::from(range.start()) + 1
|
||||
== u32::from(range.end())
|
||||
{
|
||||
self.write_literal_char(range.start())?;
|
||||
self.write_literal_char(range.end())?;
|
||||
} else {
|
||||
self.write_literal_char(range.start())?;
|
||||
self.wtr.write_str("-")?;
|
||||
self.write_literal_char(range.end())?;
|
||||
}
|
||||
}
|
||||
self.wtr.write_str("]")?;
|
||||
}
|
||||
HirKind::Class(hir::Class::Bytes(ref cls)) => {
|
||||
if cls.ranges().is_empty() {
|
||||
return self.wtr.write_str("[a&&b]");
|
||||
}
|
||||
self.wtr.write_str("(?-u:[")?;
|
||||
for range in cls.iter() {
|
||||
if range.start() == range.end() {
|
||||
self.write_literal_class_byte(range.start())?;
|
||||
} else if range.start() + 1 == range.end() {
|
||||
self.write_literal_class_byte(range.start())?;
|
||||
self.write_literal_class_byte(range.end())?;
|
||||
} else {
|
||||
self.write_literal_class_byte(range.start())?;
|
||||
self.wtr.write_str("-")?;
|
||||
self.write_literal_class_byte(range.end())?;
|
||||
}
|
||||
}
|
||||
self.wtr.write_str("])")?;
|
||||
}
|
||||
HirKind::Look(ref look) => match *look {
|
||||
hir::Look::Start => {
|
||||
self.wtr.write_str(r"\A")?;
|
||||
}
|
||||
hir::Look::End => {
|
||||
self.wtr.write_str(r"\z")?;
|
||||
}
|
||||
hir::Look::StartLF => {
|
||||
self.wtr.write_str("(?m:^)")?;
|
||||
}
|
||||
hir::Look::EndLF => {
|
||||
self.wtr.write_str("(?m:$)")?;
|
||||
}
|
||||
hir::Look::StartCRLF => {
|
||||
self.wtr.write_str("(?mR:^)")?;
|
||||
}
|
||||
hir::Look::EndCRLF => {
|
||||
self.wtr.write_str("(?mR:$)")?;
|
||||
}
|
||||
hir::Look::WordAscii => {
|
||||
self.wtr.write_str(r"(?-u:\b)")?;
|
||||
}
|
||||
hir::Look::WordAsciiNegate => {
|
||||
self.wtr.write_str(r"(?-u:\B)")?;
|
||||
}
|
||||
hir::Look::WordUnicode => {
|
||||
self.wtr.write_str(r"\b")?;
|
||||
}
|
||||
hir::Look::WordUnicodeNegate => {
|
||||
self.wtr.write_str(r"\B")?;
|
||||
}
|
||||
hir::Look::WordStartAscii => {
|
||||
self.wtr.write_str(r"(?-u:\b{start})")?;
|
||||
}
|
||||
hir::Look::WordEndAscii => {
|
||||
self.wtr.write_str(r"(?-u:\b{end})")?;
|
||||
}
|
||||
hir::Look::WordStartUnicode => {
|
||||
self.wtr.write_str(r"\b{start}")?;
|
||||
}
|
||||
hir::Look::WordEndUnicode => {
|
||||
self.wtr.write_str(r"\b{end}")?;
|
||||
}
|
||||
hir::Look::WordStartHalfAscii => {
|
||||
self.wtr.write_str(r"(?-u:\b{start-half})")?;
|
||||
}
|
||||
hir::Look::WordEndHalfAscii => {
|
||||
self.wtr.write_str(r"(?-u:\b{end-half})")?;
|
||||
}
|
||||
hir::Look::WordStartHalfUnicode => {
|
||||
self.wtr.write_str(r"\b{start-half}")?;
|
||||
}
|
||||
hir::Look::WordEndHalfUnicode => {
|
||||
self.wtr.write_str(r"\b{end-half}")?;
|
||||
}
|
||||
},
|
||||
HirKind::Capture(hir::Capture { ref name, .. }) => {
|
||||
self.wtr.write_str("(")?;
|
||||
if let Some(ref name) = *name {
|
||||
write!(self.wtr, "?P<{name}>")?;
|
||||
}
|
||||
}
|
||||
// Why do this? Wrapping concats and alts in non-capturing groups
|
||||
// is not *always* necessary, but is sometimes necessary. For
|
||||
// example, 'concat(a, alt(b, c))' should be written as 'a(?:b|c)'
|
||||
// and not 'ab|c'. The former is clearly the intended meaning, but
|
||||
// the latter is actually 'alt(concat(a, b), c)'.
|
||||
//
|
||||
// It would be possible to only group these things in cases where
|
||||
// it's strictly necessary, but it requires knowing the parent
|
||||
// expression. And since this technique is simpler and always
|
||||
// correct, we take this route. More to the point, it is a non-goal
|
||||
// of an HIR printer to show a nice easy-to-read regex. Indeed,
|
||||
// its construction forbids it from doing so. Therefore, inserting
|
||||
// extra groups where they aren't necessary is perfectly okay.
|
||||
HirKind::Concat(_) | HirKind::Alternation(_) => {
|
||||
self.wtr.write_str(r"(?:")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn visit_post(&mut self, hir: &Hir) -> fmt::Result {
|
||||
match *hir.kind() {
|
||||
// Handled during visit_pre
|
||||
HirKind::Empty
|
||||
| HirKind::Literal(_)
|
||||
| HirKind::Class(_)
|
||||
| HirKind::Look(_) => {}
|
||||
HirKind::Repetition(ref x) => {
|
||||
match (x.min, x.max) {
|
||||
(0, Some(1)) => {
|
||||
self.wtr.write_str("?")?;
|
||||
}
|
||||
(0, None) => {
|
||||
self.wtr.write_str("*")?;
|
||||
}
|
||||
(1, None) => {
|
||||
self.wtr.write_str("+")?;
|
||||
}
|
||||
(1, Some(1)) => {
|
||||
// 'a{1}' and 'a{1}?' are exactly equivalent to 'a'.
|
||||
return Ok(());
|
||||
}
|
||||
(m, None) => {
|
||||
write!(self.wtr, "{{{m},}}")?;
|
||||
}
|
||||
(m, Some(n)) if m == n => {
|
||||
write!(self.wtr, "{{{m}}}")?;
|
||||
// a{m} and a{m}? are always exactly equivalent.
|
||||
return Ok(());
|
||||
}
|
||||
(m, Some(n)) => {
|
||||
write!(self.wtr, "{{{m},{n}}}")?;
|
||||
}
|
||||
}
|
||||
if !x.greedy {
|
||||
self.wtr.write_str("?")?;
|
||||
}
|
||||
}
|
||||
HirKind::Capture(_)
|
||||
| HirKind::Concat(_)
|
||||
| HirKind::Alternation(_) => {
|
||||
self.wtr.write_str(r")")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn visit_alternation_in(&mut self) -> fmt::Result {
|
||||
self.wtr.write_str("|")
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: fmt::Write> Writer<W> {
|
||||
fn write_literal_char(&mut self, c: char) -> fmt::Result {
|
||||
if is_meta_character(c) {
|
||||
self.wtr.write_str("\\")?;
|
||||
}
|
||||
self.wtr.write_char(c)
|
||||
}
|
||||
|
||||
fn write_literal_byte(&mut self, b: u8) -> fmt::Result {
|
||||
if b <= 0x7F && !b.is_ascii_control() && !b.is_ascii_whitespace() {
|
||||
self.write_literal_char(char::try_from(b).unwrap())
|
||||
} else {
|
||||
write!(self.wtr, "(?-u:\\x{b:02X})")
|
||||
}
|
||||
}
|
||||
|
||||
fn write_literal_class_byte(&mut self, b: u8) -> fmt::Result {
|
||||
if b <= 0x7F && !b.is_ascii_control() && !b.is_ascii_whitespace() {
|
||||
self.write_literal_char(char::try_from(b).unwrap())
|
||||
} else {
|
||||
write!(self.wtr, "\\x{b:02X}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::{
|
||||
boxed::Box,
|
||||
string::{String, ToString},
|
||||
};
|
||||
|
||||
use crate::ParserBuilder;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn roundtrip(given: &str, expected: &str) {
|
||||
roundtrip_with(|b| b, given, expected);
|
||||
}
|
||||
|
||||
fn roundtrip_bytes(given: &str, expected: &str) {
|
||||
roundtrip_with(|b| b.utf8(false), given, expected);
|
||||
}
|
||||
|
||||
fn roundtrip_with<F>(mut f: F, given: &str, expected: &str)
|
||||
where
|
||||
F: FnMut(&mut ParserBuilder) -> &mut ParserBuilder,
|
||||
{
|
||||
let mut builder = ParserBuilder::new();
|
||||
f(&mut builder);
|
||||
let hir = builder.build().parse(given).unwrap();
|
||||
|
||||
let mut printer = Printer::new();
|
||||
let mut dst = String::new();
|
||||
printer.print(&hir, &mut dst).unwrap();
|
||||
|
||||
// Check that the result is actually valid.
|
||||
builder.build().parse(&dst).unwrap();
|
||||
|
||||
assert_eq!(expected, dst);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_literal() {
|
||||
roundtrip("a", "a");
|
||||
roundtrip(r"\xff", "\u{FF}");
|
||||
roundtrip_bytes(r"\xff", "\u{FF}");
|
||||
roundtrip_bytes(r"(?-u)\xff", r"(?-u:\xFF)");
|
||||
roundtrip("☃", "☃");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_class() {
|
||||
roundtrip(r"[a]", r"a");
|
||||
roundtrip(r"[ab]", r"[ab]");
|
||||
roundtrip(r"[a-z]", r"[a-z]");
|
||||
roundtrip(r"[a-z--b-c--x-y]", r"[ad-wz]");
|
||||
roundtrip(r"[^\x01-\u{10FFFF}]", "\u{0}");
|
||||
roundtrip(r"[-]", r"\-");
|
||||
roundtrip(r"[☃-⛄]", r"[☃-⛄]");
|
||||
|
||||
roundtrip(r"(?-u)[a]", r"a");
|
||||
roundtrip(r"(?-u)[ab]", r"(?-u:[ab])");
|
||||
roundtrip(r"(?-u)[a-z]", r"(?-u:[a-z])");
|
||||
roundtrip_bytes(r"(?-u)[a-\xFF]", r"(?-u:[a-\xFF])");
|
||||
|
||||
// The following test that the printer escapes meta characters
|
||||
// in character classes.
|
||||
roundtrip(r"[\[]", r"\[");
|
||||
roundtrip(r"[Z-_]", r"[Z-_]");
|
||||
roundtrip(r"[Z-_--Z]", r"[\[-_]");
|
||||
|
||||
// The following test that the printer escapes meta characters
|
||||
// in byte oriented character classes.
|
||||
roundtrip_bytes(r"(?-u)[\[]", r"\[");
|
||||
roundtrip_bytes(r"(?-u)[Z-_]", r"(?-u:[Z-_])");
|
||||
roundtrip_bytes(r"(?-u)[Z-_--Z]", r"(?-u:[\[-_])");
|
||||
|
||||
// This tests that an empty character class is correctly roundtripped.
|
||||
#[cfg(feature = "unicode-gencat")]
|
||||
roundtrip(r"\P{any}", r"[a&&b]");
|
||||
roundtrip_bytes(r"(?-u)[^\x00-\xFF]", r"[a&&b]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_anchor() {
|
||||
roundtrip(r"^", r"\A");
|
||||
roundtrip(r"$", r"\z");
|
||||
roundtrip(r"(?m)^", r"(?m:^)");
|
||||
roundtrip(r"(?m)$", r"(?m:$)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_word_boundary() {
|
||||
roundtrip(r"\b", r"\b");
|
||||
roundtrip(r"\B", r"\B");
|
||||
roundtrip(r"(?-u)\b", r"(?-u:\b)");
|
||||
roundtrip_bytes(r"(?-u)\B", r"(?-u:\B)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_repetition() {
|
||||
roundtrip("a?", "a?");
|
||||
roundtrip("a??", "a??");
|
||||
roundtrip("(?U)a?", "a??");
|
||||
|
||||
roundtrip("a*", "a*");
|
||||
roundtrip("a*?", "a*?");
|
||||
roundtrip("(?U)a*", "a*?");
|
||||
|
||||
roundtrip("a+", "a+");
|
||||
roundtrip("a+?", "a+?");
|
||||
roundtrip("(?U)a+", "a+?");
|
||||
|
||||
roundtrip("a{1}", "a");
|
||||
roundtrip("a{2}", "a{2}");
|
||||
roundtrip("a{1,}", "a+");
|
||||
roundtrip("a{1,5}", "a{1,5}");
|
||||
roundtrip("a{1}?", "a");
|
||||
roundtrip("a{2}?", "a{2}");
|
||||
roundtrip("a{1,}?", "a+?");
|
||||
roundtrip("a{1,5}?", "a{1,5}?");
|
||||
roundtrip("(?U)a{1}", "a");
|
||||
roundtrip("(?U)a{2}", "a{2}");
|
||||
roundtrip("(?U)a{1,}", "a+?");
|
||||
roundtrip("(?U)a{1,5}", "a{1,5}?");
|
||||
|
||||
// Test that various zero-length repetitions always translate to an
|
||||
// empty regex. This is more a property of HIR's smart constructors
|
||||
// than the printer though.
|
||||
roundtrip("a{0}", "(?:)");
|
||||
roundtrip("(?:ab){0}", "(?:)");
|
||||
#[cfg(feature = "unicode-gencat")]
|
||||
{
|
||||
roundtrip(r"\p{any}{0}", "(?:)");
|
||||
roundtrip(r"\P{any}{0}", "(?:)");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_group() {
|
||||
roundtrip("()", "((?:))");
|
||||
roundtrip("(?P<foo>)", "(?P<foo>(?:))");
|
||||
roundtrip("(?:)", "(?:)");
|
||||
|
||||
roundtrip("(a)", "(a)");
|
||||
roundtrip("(?P<foo>a)", "(?P<foo>a)");
|
||||
roundtrip("(?:a)", "a");
|
||||
|
||||
roundtrip("((((a))))", "((((a))))");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_alternation() {
|
||||
roundtrip("|", "(?:(?:)|(?:))");
|
||||
roundtrip("||", "(?:(?:)|(?:)|(?:))");
|
||||
|
||||
roundtrip("a|b", "[ab]");
|
||||
roundtrip("ab|cd", "(?:(?:ab)|(?:cd))");
|
||||
roundtrip("a|b|c", "[a-c]");
|
||||
roundtrip("ab|cd|ef", "(?:(?:ab)|(?:cd)|(?:ef))");
|
||||
roundtrip("foo|bar|quux", "(?:(?:foo)|(?:bar)|(?:quux))");
|
||||
}
|
||||
|
||||
// This is a regression test that stresses a peculiarity of how the HIR
|
||||
// is both constructed and printed. Namely, it is legal for a repetition
|
||||
// to directly contain a concatenation. This particular construct isn't
|
||||
// really possible to build from the concrete syntax directly, since you'd
|
||||
// be forced to put the concatenation into (at least) a non-capturing
|
||||
// group. Concurrently, the printer doesn't consider this case and just
|
||||
// kind of naively prints the child expression and tacks on the repetition
|
||||
// operator.
|
||||
//
|
||||
// As a result, if you attached '+' to a 'concat(a, b)', the printer gives
|
||||
// you 'ab+', but clearly it really should be '(?:ab)+'.
|
||||
//
|
||||
// This bug isn't easy to surface because most ways of building an HIR
|
||||
// come directly from the concrete syntax, and as mentioned above, it just
|
||||
// isn't possible to build this kind of HIR from the concrete syntax.
|
||||
// Nevertheless, this is definitely a bug.
|
||||
//
|
||||
// See: https://github.com/rust-lang/regex/issues/731
|
||||
#[test]
|
||||
fn regression_repetition_concat() {
|
||||
let expr = Hir::concat(alloc::vec![
|
||||
Hir::literal("x".as_bytes()),
|
||||
Hir::repetition(hir::Repetition {
|
||||
min: 1,
|
||||
max: None,
|
||||
greedy: true,
|
||||
sub: Box::new(Hir::literal("ab".as_bytes())),
|
||||
}),
|
||||
Hir::literal("y".as_bytes()),
|
||||
]);
|
||||
assert_eq!(r"(?:x(?:ab)+y)", expr.to_string());
|
||||
|
||||
let expr = Hir::concat(alloc::vec![
|
||||
Hir::look(hir::Look::Start),
|
||||
Hir::repetition(hir::Repetition {
|
||||
min: 1,
|
||||
max: None,
|
||||
greedy: true,
|
||||
sub: Box::new(Hir::concat(alloc::vec![
|
||||
Hir::look(hir::Look::Start),
|
||||
Hir::look(hir::Look::End),
|
||||
])),
|
||||
}),
|
||||
Hir::look(hir::Look::End),
|
||||
]);
|
||||
assert_eq!(r"(?:\A\A\z\z)", expr.to_string());
|
||||
}
|
||||
|
||||
// Just like regression_repetition_concat, but with the repetition using
|
||||
// an alternation as a child expression instead.
|
||||
//
|
||||
// See: https://github.com/rust-lang/regex/issues/731
|
||||
#[test]
|
||||
fn regression_repetition_alternation() {
|
||||
let expr = Hir::concat(alloc::vec![
|
||||
Hir::literal("ab".as_bytes()),
|
||||
Hir::repetition(hir::Repetition {
|
||||
min: 1,
|
||||
max: None,
|
||||
greedy: true,
|
||||
sub: Box::new(Hir::alternation(alloc::vec![
|
||||
Hir::literal("cd".as_bytes()),
|
||||
Hir::literal("ef".as_bytes()),
|
||||
])),
|
||||
}),
|
||||
Hir::literal("gh".as_bytes()),
|
||||
]);
|
||||
assert_eq!(r"(?:(?:ab)(?:(?:cd)|(?:ef))+(?:gh))", expr.to_string());
|
||||
|
||||
let expr = Hir::concat(alloc::vec![
|
||||
Hir::look(hir::Look::Start),
|
||||
Hir::repetition(hir::Repetition {
|
||||
min: 1,
|
||||
max: None,
|
||||
greedy: true,
|
||||
sub: Box::new(Hir::alternation(alloc::vec![
|
||||
Hir::look(hir::Look::Start),
|
||||
Hir::look(hir::Look::End),
|
||||
])),
|
||||
}),
|
||||
Hir::look(hir::Look::End),
|
||||
]);
|
||||
assert_eq!(r"(?:\A(?:\A|\z)\z)", expr.to_string());
|
||||
}
|
||||
|
||||
// This regression test is very similar in flavor to
|
||||
// regression_repetition_concat in that the root of the issue lies in a
|
||||
// peculiarity of how the HIR is represented and how the printer writes it
|
||||
// out. Like the other regression, this one is also rooted in the fact that
|
||||
// you can't produce the peculiar HIR from the concrete syntax. Namely, you
|
||||
// just can't have a 'concat(a, alt(b, c))' because the 'alt' will normally
|
||||
// be in (at least) a non-capturing group. Why? Because the '|' has very
|
||||
// low precedence (lower that concatenation), and so something like 'ab|c'
|
||||
// is actually 'alt(ab, c)'.
|
||||
//
|
||||
// See: https://github.com/rust-lang/regex/issues/516
|
||||
#[test]
|
||||
fn regression_alternation_concat() {
|
||||
let expr = Hir::concat(alloc::vec![
|
||||
Hir::literal("ab".as_bytes()),
|
||||
Hir::alternation(alloc::vec![
|
||||
Hir::literal("mn".as_bytes()),
|
||||
Hir::literal("xy".as_bytes()),
|
||||
]),
|
||||
]);
|
||||
assert_eq!(r"(?:(?:ab)(?:(?:mn)|(?:xy)))", expr.to_string());
|
||||
|
||||
let expr = Hir::concat(alloc::vec![
|
||||
Hir::look(hir::Look::Start),
|
||||
Hir::alternation(alloc::vec![
|
||||
Hir::look(hir::Look::Start),
|
||||
Hir::look(hir::Look::End),
|
||||
]),
|
||||
]);
|
||||
assert_eq!(r"(?:\A(?:\A|\z))", expr.to_string());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,215 @@
|
||||
use alloc::{vec, vec::Vec};
|
||||
|
||||
use crate::hir::{self, Hir, HirKind};
|
||||
|
||||
/// A trait for visiting the high-level IR (HIR) in depth first order.
|
||||
///
|
||||
/// The principle aim of this trait is to enable callers to perform case
|
||||
/// analysis on a high-level intermediate representation of a regular
|
||||
/// expression without necessarily using recursion. In particular, this permits
|
||||
/// callers to do case analysis with constant stack usage, which can be
|
||||
/// important since the size of an HIR may be proportional to end user input.
|
||||
///
|
||||
/// Typical usage of this trait involves providing an implementation and then
|
||||
/// running it using the [`visit`] function.
|
||||
pub trait Visitor {
|
||||
/// The result of visiting an HIR.
|
||||
type Output;
|
||||
/// An error that visiting an HIR might return.
|
||||
type Err;
|
||||
|
||||
/// All implementors of `Visitor` must provide a `finish` method, which
|
||||
/// yields the result of visiting the HIR or an error.
|
||||
fn finish(self) -> Result<Self::Output, Self::Err>;
|
||||
|
||||
/// This method is called before beginning traversal of the HIR.
|
||||
fn start(&mut self) {}
|
||||
|
||||
/// This method is called on an `Hir` before descending into child `Hir`
|
||||
/// nodes.
|
||||
fn visit_pre(&mut self, _hir: &Hir) -> Result<(), Self::Err> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This method is called on an `Hir` after descending all of its child
|
||||
/// `Hir` nodes.
|
||||
fn visit_post(&mut self, _hir: &Hir) -> Result<(), Self::Err> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This method is called between child nodes of an alternation.
|
||||
fn visit_alternation_in(&mut self) -> Result<(), Self::Err> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This method is called between child nodes of a concatenation.
|
||||
fn visit_concat_in(&mut self) -> Result<(), Self::Err> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes an implementation of `Visitor` in constant stack space.
|
||||
///
|
||||
/// This function will visit every node in the given `Hir` while calling
|
||||
/// appropriate methods provided by the [`Visitor`] trait.
|
||||
///
|
||||
/// The primary use case for this method is when one wants to perform case
|
||||
/// analysis over an `Hir` without using a stack size proportional to the depth
|
||||
/// of the `Hir`. Namely, this method will instead use constant stack space,
|
||||
/// but will use heap space proportional to the size of the `Hir`. This may be
|
||||
/// desirable in cases where the size of `Hir` is proportional to end user
|
||||
/// input.
|
||||
///
|
||||
/// If the visitor returns an error at any point, then visiting is stopped and
|
||||
/// the error is returned.
|
||||
pub fn visit<V: Visitor>(hir: &Hir, visitor: V) -> Result<V::Output, V::Err> {
|
||||
HeapVisitor::new().visit(hir, visitor)
|
||||
}
|
||||
|
||||
/// HeapVisitor visits every item in an `Hir` recursively using constant stack
|
||||
/// size and a heap size proportional to the size of the `Hir`.
|
||||
struct HeapVisitor<'a> {
|
||||
/// A stack of `Hir` nodes. This is roughly analogous to the call stack
|
||||
/// used in a typical recursive visitor.
|
||||
stack: Vec<(&'a Hir, Frame<'a>)>,
|
||||
}
|
||||
|
||||
/// Represents a single stack frame while performing structural induction over
|
||||
/// an `Hir`.
|
||||
enum Frame<'a> {
|
||||
/// A stack frame allocated just before descending into a repetition
|
||||
/// operator's child node.
|
||||
Repetition(&'a hir::Repetition),
|
||||
/// A stack frame allocated just before descending into a capture's child
|
||||
/// node.
|
||||
Capture(&'a hir::Capture),
|
||||
/// The stack frame used while visiting every child node of a concatenation
|
||||
/// of expressions.
|
||||
Concat {
|
||||
/// The child node we are currently visiting.
|
||||
head: &'a Hir,
|
||||
/// The remaining child nodes to visit (which may be empty).
|
||||
tail: &'a [Hir],
|
||||
},
|
||||
/// The stack frame used while visiting every child node of an alternation
|
||||
/// of expressions.
|
||||
Alternation {
|
||||
/// The child node we are currently visiting.
|
||||
head: &'a Hir,
|
||||
/// The remaining child nodes to visit (which may be empty).
|
||||
tail: &'a [Hir],
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> HeapVisitor<'a> {
|
||||
fn new() -> HeapVisitor<'a> {
|
||||
HeapVisitor { stack: vec![] }
|
||||
}
|
||||
|
||||
fn visit<V: Visitor>(
|
||||
&mut self,
|
||||
mut hir: &'a Hir,
|
||||
mut visitor: V,
|
||||
) -> Result<V::Output, V::Err> {
|
||||
self.stack.clear();
|
||||
|
||||
visitor.start();
|
||||
loop {
|
||||
visitor.visit_pre(hir)?;
|
||||
if let Some(x) = self.induct(hir) {
|
||||
let child = x.child();
|
||||
self.stack.push((hir, x));
|
||||
hir = child;
|
||||
continue;
|
||||
}
|
||||
// No induction means we have a base case, so we can post visit
|
||||
// it now.
|
||||
visitor.visit_post(hir)?;
|
||||
|
||||
// At this point, we now try to pop our call stack until it is
|
||||
// either empty or we hit another inductive case.
|
||||
loop {
|
||||
let (post_hir, frame) = match self.stack.pop() {
|
||||
None => return visitor.finish(),
|
||||
Some((post_hir, frame)) => (post_hir, frame),
|
||||
};
|
||||
// If this is a concat/alternate, then we might have additional
|
||||
// inductive steps to process.
|
||||
if let Some(x) = self.pop(frame) {
|
||||
match x {
|
||||
Frame::Alternation { .. } => {
|
||||
visitor.visit_alternation_in()?;
|
||||
}
|
||||
Frame::Concat { .. } => {
|
||||
visitor.visit_concat_in()?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
hir = x.child();
|
||||
self.stack.push((post_hir, x));
|
||||
break;
|
||||
}
|
||||
// Otherwise, we've finished visiting all the child nodes for
|
||||
// this HIR, so we can post visit it now.
|
||||
visitor.visit_post(post_hir)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a stack frame for the given HIR if one is needed (which occurs if
|
||||
/// and only if there are child nodes in the HIR). Otherwise, return None.
|
||||
fn induct(&mut self, hir: &'a Hir) -> Option<Frame<'a>> {
|
||||
match *hir.kind() {
|
||||
HirKind::Repetition(ref x) => Some(Frame::Repetition(x)),
|
||||
HirKind::Capture(ref x) => Some(Frame::Capture(x)),
|
||||
HirKind::Concat(ref x) if x.is_empty() => None,
|
||||
HirKind::Concat(ref x) => {
|
||||
Some(Frame::Concat { head: &x[0], tail: &x[1..] })
|
||||
}
|
||||
HirKind::Alternation(ref x) if x.is_empty() => None,
|
||||
HirKind::Alternation(ref x) => {
|
||||
Some(Frame::Alternation { head: &x[0], tail: &x[1..] })
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Pops the given frame. If the frame has an additional inductive step,
|
||||
/// then return it, otherwise return `None`.
|
||||
fn pop(&self, induct: Frame<'a>) -> Option<Frame<'a>> {
|
||||
match induct {
|
||||
Frame::Repetition(_) => None,
|
||||
Frame::Capture(_) => None,
|
||||
Frame::Concat { tail, .. } => {
|
||||
if tail.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Frame::Concat { head: &tail[0], tail: &tail[1..] })
|
||||
}
|
||||
}
|
||||
Frame::Alternation { tail, .. } => {
|
||||
if tail.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Frame::Alternation {
|
||||
head: &tail[0],
|
||||
tail: &tail[1..],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Frame<'a> {
|
||||
/// Perform the next inductive step on this frame and return the next
|
||||
/// child HIR node to visit.
|
||||
fn child(&self) -> &'a Hir {
|
||||
match *self {
|
||||
Frame::Repetition(rep) => &rep.sub,
|
||||
Frame::Capture(capture) => &capture.sub,
|
||||
Frame::Concat { head, .. } => head,
|
||||
Frame::Alternation { head, .. } => head,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user