use std::{
borrow::Cow,
fmt::{self, Display},
iter::Peekable,
};
use crate::{
ast::*,
surrogate_pair::{combine_surrogate_pair, is_lead_surrogate, is_trail_surrogate},
};
impl Display for Pattern<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.body.fmt(f)
}
}
impl Display for Disjunction<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_join(f, "|", &self.body)
}
}
impl Display for Alternative<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn as_character<'a>(term: &'a Term) -> Option<&'a Character> {
if let Term::Character(ch) = term { Some(ch) } else { None }
}
write_join_with(f, "", &self.body, |iter| {
let next = iter.next()?;
let Some(next) = as_character(next) else { return Some(Cow::Owned(next.to_string())) };
let peek = iter.peek().and_then(|it| as_character(it));
let (result, eat) = character_to_string(next, peek);
if eat {
iter.next();
}
Some(result)
})
}
}
impl Display for Term<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BoundaryAssertion(it) => it.fmt(f),
Self::LookAroundAssertion(it) => it.fmt(f),
Self::Quantifier(it) => it.fmt(f),
Self::Character(it) => it.fmt(f),
Self::Dot(it) => it.fmt(f),
Self::CharacterClassEscape(it) => it.fmt(f),
Self::UnicodePropertyEscape(it) => it.fmt(f),
Self::CharacterClass(it) => it.fmt(f),
Self::CapturingGroup(it) => it.fmt(f),
Self::IgnoreGroup(it) => it.fmt(f),
Self::IndexedReference(it) => it.fmt(f),
Self::NamedReference(it) => it.fmt(f),
}
}
}
impl Display for BoundaryAssertion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.kind.fmt(f)
}
}
impl Display for BoundaryAssertionKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::Start => "^",
Self::End => "$",
Self::Boundary => r"\b",
Self::NegativeBoundary => r"\B",
};
f.write_str(s)
}
}
impl Display for LookAroundAssertion<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}{})", self.kind, self.body)
}
}
impl Display for LookAroundAssertionKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::Lookahead => "?=",
Self::NegativeLookahead => "?!",
Self::Lookbehind => "?<=",
Self::NegativeLookbehind => "?<!",
};
f.write_str(s)
}
}
impl Display for Quantifier<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.body.fmt(f)?;
match (self.min, self.max) {
(0, None) => f.write_str("*")?,
(1, None) => f.write_str("+")?,
(0, Some(1)) => f.write_str("?")?,
(min, Some(max)) if min == max => write!(f, "{{{min}}}")?,
(min, Some(max)) => {
write!(f, "{{{min},{max}}}")?;
}
(min, None) => {
write!(f, "{{{min},}}")?;
}
}
if !self.greedy {
f.write_str("?")?;
}
Ok(())
}
}
impl Display for Character {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (string, _) = character_to_string(self, None);
string.fmt(f)
}
}
impl Display for Dot {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(".")
}
}
impl Display for CharacterClassEscape {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.kind.fmt(f)
}
}
impl Display for CharacterClassEscapeKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let escape = match self {
Self::D => r"\d",
Self::NegativeD => r"\D",
Self::S => r"\s",
Self::NegativeS => r"\S",
Self::W => r"\w",
Self::NegativeW => r"\W",
};
f.write_str(escape)
}
}
impl Display for UnicodePropertyEscape<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(if self.negative { r"\P{" } else { r"\p{" })?;
match (&self.name, &self.value) {
(name, Some(value)) if name == "General_Category" => value.fmt(f),
(name, Some(value)) => write!(f, "{name}={value}"),
(name, _) => name.fmt(f),
}?;
f.write_str("}")
}
}
impl Display for CharacterClass<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn as_character<'a>(content: &'a CharacterClassContents) -> Option<&'a Character> {
if let CharacterClassContents::Character(ch) = content { Some(ch) } else { None }
}
f.write_str("[")?;
if self.negative {
f.write_str("^")?;
}
if !self.body.is_empty() {
let sep = match self.kind {
CharacterClassContentsKind::Union => "",
CharacterClassContentsKind::Subtraction => "--",
CharacterClassContentsKind::Intersection => "&&",
};
write_join_with(f, sep, &self.body, |iter| {
let next = iter.next()?;
let Some(next) = as_character(next) else {
return Some(Cow::Owned(next.to_string()));
};
let peek = iter.peek().and_then(|it| as_character(it));
let (result, eat) = character_to_string(next, peek);
if eat {
iter.next();
}
Some(result)
})?;
}
f.write_str("]")
}
}
impl Display for CharacterClassContents<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CharacterClassRange(it) => it.fmt(f),
Self::CharacterClassEscape(it) => it.fmt(f),
Self::UnicodePropertyEscape(it) => it.fmt(f),
Self::Character(it) => it.fmt(f),
Self::NestedCharacterClass(it) => it.fmt(f),
Self::ClassStringDisjunction(it) => it.fmt(f),
}
}
}
impl Display for CharacterClassRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}-{}", self.min, self.max)
}
}
impl Display for ClassStringDisjunction<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(r"\q{")?;
write_join(f, "|", &self.body)?;
f.write_str("}")
}
}
impl Display for ClassString<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_join(f, "", &self.body)
}
}
impl Display for CapturingGroup<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("(")?;
if let Some(name) = &self.name {
write!(f, "?<{name}>")?;
}
write!(f, "{})", &self.body)
}
}
impl Display for IgnoreGroup<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn write_flags(f: &mut fmt::Formatter<'_>, flags: Modifier) -> fmt::Result {
if flags.contains(Modifier::I) {
f.write_str("i")?;
}
if flags.contains(Modifier::M) {
f.write_str("m")?;
}
if flags.contains(Modifier::S) {
f.write_str("s")?;
}
Ok(())
}
f.write_str("(?")?;
if let Some(modifiers) = &self.modifiers {
if !modifiers.enabling.is_empty() {
write_flags(f, modifiers.enabling)?;
}
if !modifiers.disabling.is_empty() {
f.write_str("-")?;
write_flags(f, modifiers.disabling)?;
}
}
write!(f, ":{})", self.body)
}
}
impl Display for IndexedReference {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, r"\{}", self.index)
}
}
impl Display for NamedReference<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, r"\k<{}>", self.name)
}
}
fn character_to_string(
this: &Character,
peek: Option<&Character>,
) -> (/* result */ Cow<'static, str>, /* true of peek should be consumed */ bool) {
let cp = this.value;
if matches!(this.kind, CharacterKind::Symbol | CharacterKind::UnicodeEscape) {
if is_trail_surrogate(cp) {
return (Cow::Owned(format!(r"\u{cp:X}")), false);
}
if is_lead_surrogate(cp) {
if let Some(peek) = peek.filter(|peek| is_trail_surrogate(peek.value)) {
let cp = combine_surrogate_pair(cp, peek.value);
let ch = char::from_u32(cp).expect("Invalid surrogate pair `Character`!");
return (Cow::Owned(format!("{ch}")), true);
}
return (Cow::Owned(format!(r"\u{cp:X}")), false);
}
}
let ch = char::from_u32(cp).expect("Invalid `Character`!");
let result = match this.kind {
CharacterKind::Symbol => Cow::Owned(ch.to_string()),
CharacterKind::ControlLetter => match ch {
'\n' => Cow::Borrowed(r"\cJ"),
'\r' => Cow::Borrowed(r"\cM"),
'\t' => Cow::Borrowed(r"\cI"),
'\u{0019}' => Cow::Borrowed(r"\cY"),
_ => Cow::Owned(format!(r"\c{ch}")),
},
CharacterKind::Identifier => Cow::Owned(format!(r"\{ch}")),
CharacterKind::SingleEscape => match ch {
'\n' => Cow::Borrowed(r"\n"),
'\r' => Cow::Borrowed(r"\r"),
'\t' => Cow::Borrowed(r"\t"),
'\u{b}' => Cow::Borrowed(r"\v"),
'\u{c}' => Cow::Borrowed(r"\f"),
'\u{8}' => Cow::Borrowed(r"\b"),
'\u{2D}' => Cow::Borrowed(r"\-"),
_ => Cow::Owned(format!(r"\{ch}")),
},
CharacterKind::Null => Cow::Borrowed(r"\0"),
CharacterKind::UnicodeEscape => {
let hex = &format!("{cp:04X}");
if hex.len() <= 4 {
Cow::Owned(format!(r"\u{hex}"))
} else {
Cow::Owned(format!(r"\u{{{hex}}}"))
}
}
CharacterKind::HexadecimalEscape => Cow::Owned(format!(r"\x{cp:02X}")),
CharacterKind::Octal1 => Cow::Owned(format!(r"\{cp:o}")),
CharacterKind::Octal2 => Cow::Owned(format!(r"\{cp:02o}")),
CharacterKind::Octal3 => Cow::Owned(format!(r"\{cp:03o}")),
};
(result, false)
}
fn write_join<S, I, E>(f: &mut fmt::Formatter<'_>, sep: S, items: I) -> fmt::Result
where
S: AsRef<str>,
E: Display,
I: IntoIterator<Item = E>,
{
write_join_with(f, sep, items, |iter| iter.next().map(|it| it.to_string()))
}
fn write_join_with<S, I, E, F, D>(
f: &mut fmt::Formatter<'_>,
sep: S,
items: I,
next: F,
) -> fmt::Result
where
S: AsRef<str>,
E: Display,
I: IntoIterator<Item = E>,
F: Fn(&mut Peekable<I::IntoIter>) -> Option<D>,
D: Display,
{
let sep = sep.as_ref();
let iter = &mut items.into_iter().peekable();
if let Some(first) = next(iter) {
first.fmt(f)?;
}
while let Some(it) = next(iter) {
write!(f, "{sep}{it}")?;
}
Ok(())
}
#[cfg(test)]
mod test {
use oxc_allocator::Allocator;
use crate::{LiteralParser, Options};
type Case<'a> = (
&'a str,
Option<&'a str>,
);
static CASES: &[Case] = &[
("/ab/", None),
("/ab/u", None),
("/abc/i", None),
("/abc/iu", None),
("/a*?/i", None),
("/a*?/iu", None),
("/emo👈🏻ji/", None),
("/emo👈🏻ji/u", None),
("/ab|c/i", None),
("/ab|c/iu", None),
("/a|b+|c/i", None),
("/a|b+|c/iu", None),
("/(?=a)|(?<=b)|(?!c)|(?<!d)/i", None),
("/(?=a)|(?<=b)|(?!c)|(?<!d)/iu", None),
(r"/(cg)(?<n>cg)(?:g)/", None),
(r"/(cg)(?<n>cg)(?:g)/u", None),
(r"/^(?=ab)\b(?!cd)(?<=ef)\B(?<!gh)$/", None),
(r"/^(?=ab)\b(?!cd)(?<=ef)\B(?<!gh)$/u", None),
(r"/^(?<!ab)$/", None),
(r"/^(?<!ab)$/u", None),
(r"/[abc]/", None),
(r"/[abc]/u", None),
(r"/[a&&b]/v", None),
(r"/[a--b]/v", None),
(r"/[^a--b--c]/v", None),
(r"/[a[b[c[d[e[f[g[h[i[j[k[l]]]]]]]]]]]]/v", None),
(r"/[\q{abc|d|e|}]/v", None),
(r"/\p{Basic_Emoji}/v", None),
(r"/[|\]]/", None),
(r"/[|\]]/u", None),
(r"/c\]/", None),
(r"/c\]/u", None),
("/a{0}|b{1,2}|c{3,}/i", None),
("/a{0}|b{1,2}|c{3,}/iu", None),
(r"/Em🥹j/", None),
(r"/Em🥹j/u", None),
(r"/\n\cM\0\x41\./", None),
(r"/\n\cM\0\x41\./u", None),
(r"/\n\cM\0\x41\u1234\./", None),
(r"/\n\cM\0\x41\u1234\./u", None),
(r"/[\bb]/", None),
(r"/[\bb]/u", None),
(r"/\d+/g", None),
(r"/\d+/gu", None),
(r"/\D/g", None),
(r"/\D/gu", None),
(r"/\w/g", None),
(r"/\w/gu", None),
(r"/\w+/g", None),
(r"/\w+/gu", None),
(r"/\s/g", None),
(r"/\s/gu", None),
(r"/\s+/g", None),
(r"/\s+/gu", None),
(r"/\t\n\v\f\r/", None),
(r"/\t\n\v\f\r/u", None),
(r"/\p{L}/u", None),
(r"/\d/g", None),
("/abcd/igv", Some("/abcd/igv")),
(r"/\d/ug", Some(r"/\d/ug")),
(r"/\cY/", None),
(r"/\n\cM\0\x41\u{1f600}\./u", Some(r"/\n\cM\0\x41\u{1F600}\./u")),
(r"/\u02c1/u", Some(r"/\u02C1/u")),
(r"/c]/", None),
(r"/\1/", None),
(r"/\2/", None),
(r"/\3/", None),
(r"/\4/", None),
(r"/\5/", None),
(r"/\6/", None),
(r"/\7/", None),
(r"/\00/", None),
(r"/\07/", None),
(r"/\30/", None),
(r"/\37/", None),
(r"/\40/", None),
(r"/\47/", None),
(r"/\70/", None),
(r"/\77/", None),
(r"/\000/", None),
(r"/\007/", None),
(r"/\070/", None),
(r"/\300/", None),
(r"/\307/", None),
(r"/\370/", None),
(r"/\377/", None),
(r"/\0111/", None),
(r"/\0022/", None),
(r"/\0003/", None),
(r"/(.)\1/", None),
(r"/\C/", None),
(r"/O\PQ/", None),
(r"/\8/", None),
(r"/7\89/", None),
(r"/\9/", None),
(r"/8\90/", None),
(r"/(.)(.)(.)(.)(.)(.)(.)(.)\8\8/", None),
(r"/\c0/", None),
(r"/[\c0]/", None),
(r"/\c1/", None),
(r"/[\c10]+/", None),
(r"/\c8/", None),
(r"/[\c8]/", None),
(r"/[\c80]+/", None),
(r"/\c_/", None),
(r"/^|\udf06/gu", Some(r"/^|\uDF06/gu")),
(r"/\udf06/", Some(r"/\uDF06/")),
(r"/\udf06/u", Some(r"/\uDF06/u")),
(r"/^|\udf06/g", Some(r"/^|\uDF06/g")),
(r"/[\-]/", None),
(r"/[\-]/u", None),
(r"/[\-]/v", None),
(r"/([\-a-z]{0,31})/iu", None),
(r"/(?i:.)/", None),
(r"/(?-s:.)/", None),
(r"/(?im-s:.)/u", None),
(r"/(?m-is:.)/v", None),
(r"/(?smi:.)/v", Some(r"/(?ims:.)/v")),
];
#[test]
fn test_display() {
let allocator = &Allocator::default();
for (input, output) in CASES {
let (left_slash, right_slash) = (input.find('/').unwrap(), input.rfind('/').unwrap());
let pattern = &input[left_slash + 1..right_slash];
let flags = &input[right_slash + 1..];
let actual = LiteralParser::new(allocator, pattern, Some(flags), Options::default())
.parse()
.unwrap();
let expect = output.unwrap_or(input);
assert_eq!(expect, format!("/{actual}/{flags}")); }
}
}