swc_ecma_regexp_ast/
display.rs

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
303// ---
304
305fn character_to_string(
306    this: &Character,
307    peek: Option<&Character>,
308) -> (
309    /* result */ Cow<'static, str>,
310    /* true of peek should be consumed */ bool,
311) {
312    let cp = this.value;
313
314    if matches!(
315        this.kind,
316        CharacterKind::Symbol | CharacterKind::UnicodeEscape
317    ) {
318        // Trail only
319        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                // Lead+Trail
326                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            // Lead only
332            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        // Not a surrogate, like BMP, or all units in unicode mode
339        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
376// ---
377
378fn 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}