1use std::{
2 borrow::Cow,
3 fmt::{self, Display},
4 iter::Peekable,
5};
6
7use swc_ecma_regexp_common::{combine_surrogate_pair, is_lead_surrogate, is_trail_surrogate};
8
9use super::*;
10
11impl Display for Pattern {
12 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
13 self.body.fmt(f)
14 }
15}
16
17impl Display for Disjunction {
18 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
19 write_join(f, "|", &self.body)
20 }
21}
22
23impl Display for Alternative {
24 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25 fn as_character(term: &Term) -> Option<&Character> {
26 if let Term::Character(ch) = term {
27 Some(ch)
28 } else {
29 None
30 }
31 }
32
33 write_join_with(f, "", &self.body, |iter| {
34 let next = iter.next()?;
35 let Some(next) = as_character(next) else {
36 return Some(Cow::Owned(next.to_string()));
37 };
38
39 let peek = iter.peek().and_then(|it| as_character(it));
40 let (result, eat) = character_to_string(next, peek);
41 if eat {
42 iter.next();
43 }
44
45 Some(result)
46 })
47 }
48}
49
50impl Display for Term {
51 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
52 match self {
53 Self::BoundaryAssertion(it) => it.fmt(f),
54 Self::LookAroundAssertion(it) => it.fmt(f),
55 Self::Quantifier(it) => it.fmt(f),
56 Self::Character(it) => it.fmt(f),
57 Self::Dot(it) => it.fmt(f),
58 Self::CharacterClassEscape(it) => it.fmt(f),
59 Self::UnicodePropertyEscape(it) => it.fmt(f),
60 Self::CharacterClass(it) => it.fmt(f),
61 Self::CapturingGroup(it) => it.fmt(f),
62 Self::IgnoreGroup(it) => it.fmt(f),
63 Self::IndexedReference(it) => it.fmt(f),
64 Self::NamedReference(it) => it.fmt(f),
65 }
66 }
67}
68
69impl Display for BoundaryAssertion {
70 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
71 self.kind.fmt(f)
72 }
73}
74
75impl Display for BoundaryAssertionKind {
76 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
77 let s = match self {
78 Self::Start => "^",
79 Self::End => "$",
80 Self::Boundary => r"\b",
81 Self::NegativeBoundary => r"\B",
82 };
83 f.write_str(s)
84 }
85}
86
87impl Display for LookAroundAssertion {
88 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
89 write!(f, "({}{})", self.kind, self.body)
90 }
91}
92
93impl Display for LookAroundAssertionKind {
94 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
95 let s = match self {
96 Self::Lookahead => "?=",
97 Self::NegativeLookahead => "?!",
98 Self::Lookbehind => "?<=",
99 Self::NegativeLookbehind => "?<!",
100 };
101 f.write_str(s)
102 }
103}
104
105impl Display for Quantifier {
106 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
107 self.body.fmt(f)?;
108
109 match (self.min, self.max) {
110 (0, None) => f.write_str("*")?,
111 (1, None) => f.write_str("+")?,
112 (0, Some(1)) => f.write_str("?")?,
113 (min, Some(max)) if min == max => write!(f, "{{{min}}}",)?,
114 (min, Some(max)) => {
115 write!(f, "{{{min},{max}}}",)?;
116 }
117 (min, None) => {
118 write!(f, "{{{min},}}",)?;
119 }
120 }
121
122 if !self.greedy {
123 f.write_str("?")?;
124 }
125
126 Ok(())
127 }
128}
129
130impl Display for Character {
131 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
132 let (string, _) = character_to_string(self, None);
133 string.fmt(f)
134 }
135}
136
137impl Display for Dot {
138 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
139 f.write_str(".")
140 }
141}
142
143impl Display for CharacterClassEscape {
144 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
145 self.kind.fmt(f)
146 }
147}
148
149impl Display for CharacterClassEscapeKind {
150 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
151 let escape = match self {
152 Self::D => r"\d",
153 Self::NegativeD => r"\D",
154 Self::S => r"\s",
155 Self::NegativeS => r"\S",
156 Self::W => r"\w",
157 Self::NegativeW => r"\W",
158 };
159 f.write_str(escape)
160 }
161}
162
163impl Display for UnicodePropertyEscape {
164 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
165 f.write_str(if self.negative { r"\P{" } else { r"\p{" })?;
166 match (&self.name, &self.value) {
167 (name, Some(value)) if name == "General_Category" => value.fmt(f),
168 (name, Some(value)) => write!(f, "{name}={value}"),
169 (name, _) => name.fmt(f),
170 }?;
171 f.write_str("}")
172 }
173}
174
175impl Display for CharacterClass {
176 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
177 fn as_character(content: &CharacterClassContents) -> Option<&Character> {
178 if let CharacterClassContents::Character(ch) = content {
179 Some(ch)
180 } else {
181 None
182 }
183 }
184
185 f.write_str("[")?;
186 if self.negative {
187 f.write_str("^")?;
188 }
189
190 if !self.body.is_empty() {
191 let sep = match self.kind {
192 CharacterClassContentsKind::Union => "",
193 CharacterClassContentsKind::Subtraction => "--",
194 CharacterClassContentsKind::Intersection => "&&",
195 };
196
197 write_join_with(f, sep, &self.body, |iter| {
198 let next = iter.next()?;
199 let Some(next) = as_character(next) else {
200 return Some(Cow::Owned(next.to_string()));
201 };
202
203 let peek = iter.peek().and_then(|it| as_character(it));
204 let (result, eat) = character_to_string(next, peek);
205 if eat {
206 iter.next();
207 }
208
209 Some(result)
210 })?;
211 }
212
213 f.write_str("]")
214 }
215}
216
217impl Display for CharacterClassContents {
218 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
219 match self {
220 Self::CharacterClassRange(it) => it.fmt(f),
221 Self::CharacterClassEscape(it) => it.fmt(f),
222 Self::UnicodePropertyEscape(it) => it.fmt(f),
223 Self::Character(it) => it.fmt(f),
224 Self::NestedCharacterClass(it) => it.fmt(f),
225 Self::ClassStringDisjunction(it) => it.fmt(f),
226 }
227 }
228}
229
230impl Display for CharacterClassRange {
231 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
232 write!(f, "{}-{}", self.min, self.max)
233 }
234}
235
236impl Display for ClassStringDisjunction {
237 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
238 f.write_str(r"\q{")?;
239 write_join(f, "|", &self.body)?;
240 f.write_str("}")
241 }
242}
243
244impl Display for ClassString {
245 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
246 write_join(f, "", &self.body)
247 }
248}
249
250impl Display for CapturingGroup {
251 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
252 f.write_str("(")?;
253 if let Some(name) = &self.name {
254 write!(f, "?<{name}>")?;
255 }
256 write!(f, "{})", &self.body)
257 }
258}
259
260impl Display for IgnoreGroup {
261 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
262 fn write_flags(f: &mut fmt::Formatter, flags: Modifier) -> fmt::Result {
263 if flags.contains(Modifier::I) {
264 f.write_str("i")?;
265 }
266 if flags.contains(Modifier::M) {
267 f.write_str("m")?;
268 }
269 if flags.contains(Modifier::S) {
270 f.write_str("s")?;
271 }
272 Ok(())
273 }
274
275 f.write_str("(?")?;
276
277 if let Some(modifiers) = &self.modifiers {
278 if !modifiers.enabling.is_empty() {
279 write_flags(f, modifiers.enabling)?;
280 }
281 if !modifiers.disabling.is_empty() {
282 f.write_str("-")?;
283 write_flags(f, modifiers.disabling)?;
284 }
285 }
286
287 write!(f, ":{})", self.body)
288 }
289}
290
291impl Display for IndexedReference {
292 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
293 write!(f, r"\{}", self.index)
294 }
295}
296
297impl Display for NamedReference {
298 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
299 write!(f, r"\k<{}>", self.name)
300 }
301}
302
303fn character_to_string(
306 this: &Character,
307 peek: Option<&Character>,
308) -> (
309 Cow<'static, str>,
310 bool,
311) {
312 let cp = this.value;
313
314 if matches!(
315 this.kind,
316 CharacterKind::Symbol | CharacterKind::UnicodeEscape
317 ) {
318 if is_trail_surrogate(cp) {
320 return (Cow::Owned(format!(r"\u{cp:X}")), false);
321 }
322
323 if is_lead_surrogate(cp) {
324 if let Some(peek) = peek.filter(|peek| is_trail_surrogate(peek.value)) {
325 let cp = combine_surrogate_pair(cp, peek.value);
327 let ch = char::from_u32(cp).expect("Invalid surrogate pair `Character`!");
328 return (Cow::Owned(format!("{ch}")), true);
329 }
330
331 return (Cow::Owned(format!(r"\u{cp:X}")), false);
333 }
334 }
335
336 let ch = char::from_u32(cp).expect("Invalid `Character`!");
337 let result = match this.kind {
338 CharacterKind::Symbol => Cow::Owned(ch.to_string()),
340 CharacterKind::ControlLetter => match ch {
341 '\n' => Cow::Borrowed(r"\cJ"),
342 '\r' => Cow::Borrowed(r"\cM"),
343 '\t' => Cow::Borrowed(r"\cI"),
344 '\u{0019}' => Cow::Borrowed(r"\cY"),
345 _ => Cow::Owned(format!(r"\c{ch}")),
346 },
347 CharacterKind::Identifier => Cow::Owned(format!(r"\{ch}")),
348 CharacterKind::SingleEscape => match ch {
349 '\n' => Cow::Borrowed(r"\n"),
350 '\r' => Cow::Borrowed(r"\r"),
351 '\t' => Cow::Borrowed(r"\t"),
352 '\u{b}' => Cow::Borrowed(r"\v"),
353 '\u{c}' => Cow::Borrowed(r"\f"),
354 '\u{8}' => Cow::Borrowed(r"\b"),
355 '\u{2D}' => Cow::Borrowed(r"\-"),
356 _ => Cow::Owned(format!(r"\{ch}")),
357 },
358 CharacterKind::Null => Cow::Borrowed(r"\0"),
359 CharacterKind::UnicodeEscape => {
360 let hex = &format!("{cp:04X}");
361 if hex.len() <= 4 {
362 Cow::Owned(format!(r"\u{hex}"))
363 } else {
364 Cow::Owned(format!(r"\u{{{hex}}}"))
365 }
366 }
367 CharacterKind::HexadecimalEscape => Cow::Owned(format!(r"\x{cp:02X}")),
368 CharacterKind::Octal1 => Cow::Owned(format!(r"\{cp:o}")),
369 CharacterKind::Octal2 => Cow::Owned(format!(r"\{cp:02o}")),
370 CharacterKind::Octal3 => Cow::Owned(format!(r"\{cp:03o}")),
371 };
372
373 (result, false)
374}
375
376fn write_join<S, I, E>(f: &mut fmt::Formatter, sep: S, items: I) -> fmt::Result
379where
380 S: AsRef<str>,
381 E: Display,
382 I: IntoIterator<Item = E>,
383{
384 write_join_with(f, sep, items, |iter| iter.next().map(|it| it.to_string()))
385}
386
387fn write_join_with<S, I, E, F, D>(f: &mut fmt::Formatter, sep: S, items: I, next: F) -> fmt::Result
388where
389 S: AsRef<str>,
390 E: Display,
391 I: IntoIterator<Item = E>,
392 F: Fn(&mut Peekable<I::IntoIter>) -> Option<D>,
393 D: Display,
394{
395 let sep = sep.as_ref();
396 let iter = &mut items.into_iter().peekable();
397
398 if let Some(first) = next(iter) {
399 first.fmt(f)?;
400 }
401
402 while let Some(it) = next(iter) {
403 write!(f, "{sep}{it}")?;
404 }
405
406 Ok(())
407}