use std::cmp::Ordering;
use std::error;
use std::fmt;
pub use ast::visitor::{visit, Visitor};
pub mod parse;
pub mod print;
mod visitor;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Error {
kind: ErrorKind,
pattern: String,
span: Span,
}
impl Error {
pub fn kind(&self) -> &ErrorKind {
&self.kind
}
pub fn pattern(&self) -> &str {
&self.pattern
}
pub fn span(&self) -> &Span {
&self.span
}
pub fn auxiliary_span(&self) -> Option<&Span> {
use self::ErrorKind::*;
match self.kind {
FlagDuplicate { ref original } => Some(original),
FlagRepeatedNegation { ref original, .. } => Some(original),
GroupNameDuplicate { ref original, .. } => Some(original),
_ => None,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ErrorKind {
CaptureLimitExceeded,
ClassEscapeInvalid,
ClassRangeInvalid,
ClassRangeLiteral,
ClassUnclosed,
DecimalEmpty,
DecimalInvalid,
EscapeHexEmpty,
EscapeHexInvalid,
EscapeHexInvalidDigit,
EscapeUnexpectedEof,
EscapeUnrecognized,
FlagDanglingNegation,
FlagDuplicate {
original: Span,
},
FlagRepeatedNegation {
original: Span,
},
FlagUnexpectedEof,
FlagUnrecognized,
GroupNameDuplicate {
original: Span,
},
GroupNameEmpty,
GroupNameInvalid,
GroupNameUnexpectedEof,
GroupUnclosed,
GroupUnopened,
NestLimitExceeded(u32),
RepetitionCountInvalid,
RepetitionCountDecimalEmpty,
RepetitionCountUnclosed,
RepetitionMissing,
UnicodeClassInvalid,
UnsupportedBackreference,
UnsupportedLookAround,
#[doc(hidden)]
__Nonexhaustive,
}
impl error::Error for Error {
#[allow(deprecated)]
fn description(&self) -> &str {
use self::ErrorKind::*;
match self.kind {
CaptureLimitExceeded => "capture group limit exceeded",
ClassEscapeInvalid => "invalid escape sequence in character class",
ClassRangeInvalid => "invalid character class range",
ClassRangeLiteral => "invalid range boundary, must be a literal",
ClassUnclosed => "unclosed character class",
DecimalEmpty => "empty decimal literal",
DecimalInvalid => "invalid decimal literal",
EscapeHexEmpty => "empty hexadecimal literal",
EscapeHexInvalid => "invalid hexadecimal literal",
EscapeHexInvalidDigit => "invalid hexadecimal digit",
EscapeUnexpectedEof => "unexpected eof (escape sequence)",
EscapeUnrecognized => "unrecognized escape sequence",
FlagDanglingNegation => "dangling flag negation operator",
FlagDuplicate { .. } => "duplicate flag",
FlagRepeatedNegation { .. } => "repeated negation",
FlagUnexpectedEof => "unexpected eof (flag)",
FlagUnrecognized => "unrecognized flag",
GroupNameDuplicate { .. } => "duplicate capture group name",
GroupNameEmpty => "empty capture group name",
GroupNameInvalid => "invalid capture group name",
GroupNameUnexpectedEof => "unclosed capture group name",
GroupUnclosed => "unclosed group",
GroupUnopened => "unopened group",
NestLimitExceeded(_) => "nest limit exceeded",
RepetitionCountInvalid => "invalid repetition count range",
RepetitionCountUnclosed => "unclosed counted repetition",
RepetitionMissing => "repetition operator missing expression",
UnicodeClassInvalid => "invalid Unicode character class",
UnsupportedBackreference => "backreferences are not supported",
UnsupportedLookAround => "look-around is not supported",
_ => unreachable!(),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
::error::Formatter::from(self).fmt(f)
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::ErrorKind::*;
match *self {
CaptureLimitExceeded => write!(
f,
"exceeded the maximum number of \
capturing groups ({})",
::std::u32::MAX
),
ClassEscapeInvalid => {
write!(f, "invalid escape sequence found in character class")
}
ClassRangeInvalid => write!(
f,
"invalid character class range, \
the start must be <= the end"
),
ClassRangeLiteral => {
write!(f, "invalid range boundary, must be a literal")
}
ClassUnclosed => write!(f, "unclosed character class"),
DecimalEmpty => write!(f, "decimal literal empty"),
DecimalInvalid => write!(f, "decimal literal invalid"),
EscapeHexEmpty => write!(f, "hexadecimal literal empty"),
EscapeHexInvalid => {
write!(f, "hexadecimal literal is not a Unicode scalar value")
}
EscapeHexInvalidDigit => write!(f, "invalid hexadecimal digit"),
EscapeUnexpectedEof => write!(
f,
"incomplete escape sequence, \
reached end of pattern prematurely"
),
EscapeUnrecognized => write!(f, "unrecognized escape sequence"),
FlagDanglingNegation => {
write!(f, "dangling flag negation operator")
}
FlagDuplicate { .. } => write!(f, "duplicate flag"),
FlagRepeatedNegation { .. } => {
write!(f, "flag negation operator repeated")
}
FlagUnexpectedEof => {
write!(f, "expected flag but got end of regex")
}
FlagUnrecognized => write!(f, "unrecognized flag"),
GroupNameDuplicate { .. } => {
write!(f, "duplicate capture group name")
}
GroupNameEmpty => write!(f, "empty capture group name"),
GroupNameInvalid => write!(f, "invalid capture group character"),
GroupNameUnexpectedEof => write!(f, "unclosed capture group name"),
GroupUnclosed => write!(f, "unclosed group"),
GroupUnopened => write!(f, "unopened group"),
NestLimitExceeded(limit) => write!(
f,
"exceed the maximum number of \
nested parentheses/brackets ({})",
limit
),
RepetitionCountInvalid => write!(
f,
"invalid repetition count range, \
the start must be <= the end"
),
RepetitionCountDecimalEmpty => {
write!(f, "repetition quantifier expects a valid decimal")
}
RepetitionCountUnclosed => {
write!(f, "unclosed counted repetition")
}
RepetitionMissing => {
write!(f, "repetition operator missing expression")
}
UnicodeClassInvalid => {
write!(f, "invalid Unicode character class")
}
UnsupportedBackreference => {
write!(f, "backreferences are not supported")
}
UnsupportedLookAround => write!(
f,
"look-around, including look-ahead and look-behind, \
is not supported"
),
_ => unreachable!(),
}
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct Span {
pub start: Position,
pub end: Position,
}
impl fmt::Debug for Span {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Span({:?}, {:?})", self.start, self.end)
}
}
impl Ord for Span {
fn cmp(&self, other: &Span) -> Ordering {
(&self.start, &self.end).cmp(&(&other.start, &other.end))
}
}
impl PartialOrd for Span {
fn partial_cmp(&self, other: &Span) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct Position {
pub offset: usize,
pub line: usize,
pub column: usize,
}
impl fmt::Debug for Position {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Position(o: {:?}, l: {:?}, c: {:?})",
self.offset, self.line, self.column
)
}
}
impl Ord for Position {
fn cmp(&self, other: &Position) -> Ordering {
self.offset.cmp(&other.offset)
}
}
impl PartialOrd for Position {
fn partial_cmp(&self, other: &Position) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Span {
pub fn new(start: Position, end: Position) -> Span {
Span { start: start, end: end }
}
pub fn splat(pos: Position) -> Span {
Span::new(pos, pos)
}
pub fn with_start(self, pos: Position) -> Span {
Span { start: pos, ..self }
}
pub fn with_end(self, pos: Position) -> Span {
Span { end: pos, ..self }
}
pub fn is_one_line(&self) -> bool {
self.start.line == self.end.line
}
pub fn is_empty(&self) -> bool {
self.start.offset == self.end.offset
}
}
impl Position {
pub fn new(offset: usize, line: usize, column: usize) -> Position {
Position { offset: offset, line: line, column: column }
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WithComments {
pub ast: Ast,
pub comments: Vec<Comment>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Comment {
pub span: Span,
pub comment: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Ast {
Empty(Span),
Flags(SetFlags),
Literal(Literal),
Dot(Span),
Assertion(Assertion),
Class(Class),
Repetition(Repetition),
Group(Group),
Alternation(Alternation),
Concat(Concat),
}
impl Ast {
pub fn span(&self) -> &Span {
match *self {
Ast::Empty(ref span) => span,
Ast::Flags(ref x) => &x.span,
Ast::Literal(ref x) => &x.span,
Ast::Dot(ref span) => span,
Ast::Assertion(ref x) => &x.span,
Ast::Class(ref x) => x.span(),
Ast::Repetition(ref x) => &x.span,
Ast::Group(ref x) => &x.span,
Ast::Alternation(ref x) => &x.span,
Ast::Concat(ref x) => &x.span,
}
}
pub fn is_empty(&self) -> bool {
match *self {
Ast::Empty(_) => true,
_ => false,
}
}
fn has_subexprs(&self) -> bool {
match *self {
Ast::Empty(_)
| Ast::Flags(_)
| Ast::Literal(_)
| Ast::Dot(_)
| Ast::Assertion(_) => false,
Ast::Class(_)
| Ast::Repetition(_)
| Ast::Group(_)
| Ast::Alternation(_)
| Ast::Concat(_) => true,
}
}
}
impl fmt::Display for Ast {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use ast::print::Printer;
Printer::new().print(self, f)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Alternation {
pub span: Span,
pub asts: Vec<Ast>,
}
impl Alternation {
pub fn into_ast(mut self) -> Ast {
match self.asts.len() {
0 => Ast::Empty(self.span),
1 => self.asts.pop().unwrap(),
_ => Ast::Alternation(self),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Concat {
pub span: Span,
pub asts: Vec<Ast>,
}
impl Concat {
pub fn into_ast(mut self) -> Ast {
match self.asts.len() {
0 => Ast::Empty(self.span),
1 => self.asts.pop().unwrap(),
_ => Ast::Concat(self),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Literal {
pub span: Span,
pub kind: LiteralKind,
pub c: char,
}
impl Literal {
pub fn byte(&self) -> Option<u8> {
let short_hex = LiteralKind::HexFixed(HexLiteralKind::X);
if self.c as u32 <= 255 && self.kind == short_hex {
Some(self.c as u8)
} else {
None
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum LiteralKind {
Verbatim,
Punctuation,
Octal,
HexFixed(HexLiteralKind),
HexBrace(HexLiteralKind),
Special(SpecialLiteralKind),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SpecialLiteralKind {
Bell,
FormFeed,
Tab,
LineFeed,
CarriageReturn,
VerticalTab,
Space,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum HexLiteralKind {
X,
UnicodeShort,
UnicodeLong,
}
impl HexLiteralKind {
pub fn digits(&self) -> u32 {
match *self {
HexLiteralKind::X => 2,
HexLiteralKind::UnicodeShort => 4,
HexLiteralKind::UnicodeLong => 8,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Class {
Unicode(ClassUnicode),
Perl(ClassPerl),
Bracketed(ClassBracketed),
}
impl Class {
pub fn span(&self) -> &Span {
match *self {
Class::Perl(ref x) => &x.span,
Class::Unicode(ref x) => &x.span,
Class::Bracketed(ref x) => &x.span,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClassPerl {
pub span: Span,
pub kind: ClassPerlKind,
pub negated: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ClassPerlKind {
Digit,
Space,
Word,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClassAscii {
pub span: Span,
pub kind: ClassAsciiKind,
pub negated: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ClassAsciiKind {
Alnum,
Alpha,
Ascii,
Blank,
Cntrl,
Digit,
Graph,
Lower,
Print,
Punct,
Space,
Upper,
Word,
Xdigit,
}
impl ClassAsciiKind {
pub fn from_name(name: &str) -> Option<ClassAsciiKind> {
use self::ClassAsciiKind::*;
match name {
"alnum" => Some(Alnum),
"alpha" => Some(Alpha),
"ascii" => Some(Ascii),
"blank" => Some(Blank),
"cntrl" => Some(Cntrl),
"digit" => Some(Digit),
"graph" => Some(Graph),
"lower" => Some(Lower),
"print" => Some(Print),
"punct" => Some(Punct),
"space" => Some(Space),
"upper" => Some(Upper),
"word" => Some(Word),
"xdigit" => Some(Xdigit),
_ => None,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClassUnicode {
pub span: Span,
pub negated: bool,
pub kind: ClassUnicodeKind,
}
impl ClassUnicode {
pub fn is_negated(&self) -> bool {
match self.kind {
ClassUnicodeKind::NamedValue {
op: ClassUnicodeOpKind::NotEqual,
..
} => !self.negated,
_ => self.negated,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ClassUnicodeKind {
OneLetter(char),
Named(String),
NamedValue {
op: ClassUnicodeOpKind,
name: String,
value: String,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ClassUnicodeOpKind {
Equal,
Colon,
NotEqual,
}
impl ClassUnicodeOpKind {
pub fn is_equal(&self) -> bool {
match *self {
ClassUnicodeOpKind::Equal | ClassUnicodeOpKind::Colon => true,
_ => false,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClassBracketed {
pub span: Span,
pub negated: bool,
pub kind: ClassSet,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ClassSet {
Item(ClassSetItem),
BinaryOp(ClassSetBinaryOp),
}
impl ClassSet {
pub fn union(ast: ClassSetUnion) -> ClassSet {
ClassSet::Item(ClassSetItem::Union(ast))
}
pub fn span(&self) -> &Span {
match *self {
ClassSet::Item(ref x) => x.span(),
ClassSet::BinaryOp(ref x) => &x.span,
}
}
fn is_empty(&self) -> bool {
match *self {
ClassSet::Item(ClassSetItem::Empty(_)) => true,
_ => false,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ClassSetItem {
Empty(Span),
Literal(Literal),
Range(ClassSetRange),
Ascii(ClassAscii),
Unicode(ClassUnicode),
Perl(ClassPerl),
Bracketed(Box<ClassBracketed>),
Union(ClassSetUnion),
}
impl ClassSetItem {
pub fn span(&self) -> &Span {
match *self {
ClassSetItem::Empty(ref span) => span,
ClassSetItem::Literal(ref x) => &x.span,
ClassSetItem::Range(ref x) => &x.span,
ClassSetItem::Ascii(ref x) => &x.span,
ClassSetItem::Perl(ref x) => &x.span,
ClassSetItem::Unicode(ref x) => &x.span,
ClassSetItem::Bracketed(ref x) => &x.span,
ClassSetItem::Union(ref x) => &x.span,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClassSetRange {
pub span: Span,
pub start: Literal,
pub end: Literal,
}
impl ClassSetRange {
pub fn is_valid(&self) -> bool {
self.start.c <= self.end.c
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClassSetUnion {
pub span: Span,
pub items: Vec<ClassSetItem>,
}
impl ClassSetUnion {
pub fn push(&mut self, item: ClassSetItem) {
if self.items.is_empty() {
self.span.start = item.span().start;
}
self.span.end = item.span().end;
self.items.push(item);
}
pub fn into_item(mut self) -> ClassSetItem {
match self.items.len() {
0 => ClassSetItem::Empty(self.span),
1 => self.items.pop().unwrap(),
_ => ClassSetItem::Union(self),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClassSetBinaryOp {
pub span: Span,
pub kind: ClassSetBinaryOpKind,
pub lhs: Box<ClassSet>,
pub rhs: Box<ClassSet>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ClassSetBinaryOpKind {
Intersection,
Difference,
SymmetricDifference,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Assertion {
pub span: Span,
pub kind: AssertionKind,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AssertionKind {
StartLine,
EndLine,
StartText,
EndText,
WordBoundary,
NotWordBoundary,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Repetition {
pub span: Span,
pub op: RepetitionOp,
pub greedy: bool,
pub ast: Box<Ast>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RepetitionOp {
pub span: Span,
pub kind: RepetitionKind,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum RepetitionKind {
ZeroOrOne,
ZeroOrMore,
OneOrMore,
Range(RepetitionRange),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum RepetitionRange {
Exactly(u32),
AtLeast(u32),
Bounded(u32, u32),
}
impl RepetitionRange {
pub fn is_valid(&self) -> bool {
match *self {
RepetitionRange::Bounded(s, e) if s > e => false,
_ => true,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Group {
pub span: Span,
pub kind: GroupKind,
pub ast: Box<Ast>,
}
impl Group {
pub fn flags(&self) -> Option<&Flags> {
match self.kind {
GroupKind::NonCapturing(ref flags) => Some(flags),
_ => None,
}
}
pub fn is_capturing(&self) -> bool {
match self.kind {
GroupKind::CaptureIndex(_) | GroupKind::CaptureName(_) => true,
GroupKind::NonCapturing(_) => false,
}
}
pub fn capture_index(&self) -> Option<u32> {
match self.kind {
GroupKind::CaptureIndex(i) => Some(i),
GroupKind::CaptureName(ref x) => Some(x.index),
GroupKind::NonCapturing(_) => None,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum GroupKind {
CaptureIndex(u32),
CaptureName(CaptureName),
NonCapturing(Flags),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CaptureName {
pub span: Span,
pub name: String,
pub index: u32,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SetFlags {
pub span: Span,
pub flags: Flags,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Flags {
pub span: Span,
pub items: Vec<FlagsItem>,
}
impl Flags {
pub fn add_item(&mut self, item: FlagsItem) -> Option<usize> {
for (i, x) in self.items.iter().enumerate() {
if x.kind == item.kind {
return Some(i);
}
}
self.items.push(item);
None
}
pub fn flag_state(&self, flag: Flag) -> Option<bool> {
let mut negated = false;
for x in &self.items {
match x.kind {
FlagsItemKind::Negation => {
negated = true;
}
FlagsItemKind::Flag(ref xflag) if xflag == &flag => {
return Some(!negated);
}
_ => {}
}
}
None
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FlagsItem {
pub span: Span,
pub kind: FlagsItemKind,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum FlagsItemKind {
Negation,
Flag(Flag),
}
impl FlagsItemKind {
pub fn is_negation(&self) -> bool {
match *self {
FlagsItemKind::Negation => true,
_ => false,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Flag {
CaseInsensitive,
MultiLine,
DotMatchesNewLine,
SwapGreed,
Unicode,
IgnoreWhitespace,
}
impl Drop for Ast {
fn drop(&mut self) {
use std::mem;
match *self {
Ast::Empty(_)
| Ast::Flags(_)
| Ast::Literal(_)
| Ast::Dot(_)
| Ast::Assertion(_)
| Ast::Class(_) => return,
Ast::Repetition(ref x) if !x.ast.has_subexprs() => return,
Ast::Group(ref x) if !x.ast.has_subexprs() => return,
Ast::Alternation(ref x) if x.asts.is_empty() => return,
Ast::Concat(ref x) if x.asts.is_empty() => return,
_ => {}
}
let empty_span = || Span::splat(Position::new(0, 0, 0));
let empty_ast = || Ast::Empty(empty_span());
let mut stack = vec![mem::replace(self, empty_ast())];
while let Some(mut ast) = stack.pop() {
match ast {
Ast::Empty(_)
| Ast::Flags(_)
| Ast::Literal(_)
| Ast::Dot(_)
| Ast::Assertion(_)
| Ast::Class(_) => {}
Ast::Repetition(ref mut x) => {
stack.push(mem::replace(&mut x.ast, empty_ast()));
}
Ast::Group(ref mut x) => {
stack.push(mem::replace(&mut x.ast, empty_ast()));
}
Ast::Alternation(ref mut x) => {
stack.extend(x.asts.drain(..));
}
Ast::Concat(ref mut x) => {
stack.extend(x.asts.drain(..));
}
}
}
}
}
impl Drop for ClassSet {
fn drop(&mut self) {
use std::mem;
match *self {
ClassSet::Item(ref item) => match *item {
ClassSetItem::Empty(_)
| ClassSetItem::Literal(_)
| ClassSetItem::Range(_)
| ClassSetItem::Ascii(_)
| ClassSetItem::Unicode(_)
| ClassSetItem::Perl(_) => return,
ClassSetItem::Bracketed(ref x) => {
if x.kind.is_empty() {
return;
}
}
ClassSetItem::Union(ref x) => {
if x.items.is_empty() {
return;
}
}
},
ClassSet::BinaryOp(ref op) => {
if op.lhs.is_empty() && op.rhs.is_empty() {
return;
}
}
}
let empty_span = || Span::splat(Position::new(0, 0, 0));
let empty_set = || ClassSet::Item(ClassSetItem::Empty(empty_span()));
let mut stack = vec![mem::replace(self, empty_set())];
while let Some(mut set) = stack.pop() {
match set {
ClassSet::Item(ref mut item) => match *item {
ClassSetItem::Empty(_)
| ClassSetItem::Literal(_)
| ClassSetItem::Range(_)
| ClassSetItem::Ascii(_)
| ClassSetItem::Unicode(_)
| ClassSetItem::Perl(_) => {}
ClassSetItem::Bracketed(ref mut x) => {
stack.push(mem::replace(&mut x.kind, empty_set()));
}
ClassSetItem::Union(ref mut x) => {
stack.extend(x.items.drain(..).map(ClassSet::Item));
}
},
ClassSet::BinaryOp(ref mut op) => {
stack.push(mem::replace(&mut op.lhs, empty_set()));
stack.push(mem::replace(&mut op.rhs, empty_set()));
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(any(unix, windows))]
fn no_stack_overflow_on_drop() {
use std::thread;
let run = || {
let span = || Span::splat(Position::new(0, 0, 0));
let mut ast = Ast::Empty(span());
for i in 0..200 {
ast = Ast::Group(Group {
span: span(),
kind: GroupKind::CaptureIndex(i),
ast: Box::new(ast),
});
}
assert!(!ast.is_empty());
};
thread::Builder::new()
.stack_size(1 << 10)
.spawn(run)
.unwrap()
.join()
.unwrap();
}
}