1use core::{
2 fmt::{self, Result, Write},
3 mem,
4};
5
6use super::{
7 Context, Expr, Node,
8 chars::{CharRange, Chars, next_char},
9};
10
11struct ReprState<'a, 'b>(bool, &'a Context<'b>);
12
13impl Expr<'_> {
14 pub fn write_repr<W>(&self, w: &mut W) -> Result
17 where
18 W: Write,
19 {
20 ReprState(false, self.get_context()).write(w, &self.root)
21 }
22}
23
24impl Chars {
25 pub fn write_repr<W>(&self, w: &mut W) -> Result
26 where
27 W: Write,
28 {
29 write!(w, "[")?;
30 if let Some(hyphen) = self.0.iter().find(|cr| cr.start == '-') {
31 fmt_charclass(w, hyphen)?;
32 }
33 self.0
34 .iter()
35 .filter(|&cr| cr.start != '-' && cr.end != '-')
36 .try_fold((), |(), cr| fmt_charclass(w, cr))?;
37 if let Some(hyphen) = self.0.iter().find(|cr| cr.end == '-' && cr.start != '-') {
38 fmt_charclass(w, hyphen)?;
39 }
40 write!(w, "]")?;
41 Ok(())
42 }
43}
44
45impl ReprState<'_, '_> {
46 pub fn write<W>(&mut self, w: &mut W, node: &Node) -> Result
47 where
48 W: Write,
49 {
50 match *node {
51 Node::Literal(ref s) => write_literal(w, s),
52 Node::Chars(ref chars) => write!(w, "{chars}"),
53 Node::List(ref list) => {
54 let nested = mem::replace(&mut self.0, true);
55 if nested {
56 write!(w, "(")?;
57 }
58 list.iter().try_for_each(|node| self.write(w, node))?;
59 if nested {
60 write!(w, ")")?;
61 }
62 Ok(())
63 }
64
65 Node::Count(ref node, min, max) => {
66 self.0 = true;
67 self.write(w, node)?;
68 w.write_char('{')?;
69 if min != 0 || max == 0 {
71 write!(w, "{min}")?;
72 }
73 if max != min {
74 write!(w, ",{max}")?;
75 }
76 w.write_char('}')
77 }
78
79 Node::Generator(ref generator) => {
80 w.write_char('{')?;
81 self.1.get_generator(generator.name()).unwrap().write_repr(
82 self.1,
83 w,
84 &generator.args(),
85 )?;
86 w.write_char('}')?;
87 Ok(())
88 }
89 }
90 }
91}
92
93impl fmt::Display for Expr<'_> {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result {
95 self.write_repr(f)
96 }
97}
98
99impl fmt::Display for Chars {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result {
101 self.write_repr(f)
102 }
103}
104
105pub enum Escape {
106 Hex,
107 Str(&'static str),
108}
109
110pub fn write_literal<W>(w: &mut W, s: &str) -> Result
111where
112 W: fmt::Write + ?Sized,
113{
114 use Escape::*;
115 let mut pos = 0;
116 for (i, b) in s.bytes().enumerate() {
117 let escaped = match b {
118 b'\\' => Str("\\\\"),
119 b'(' => Str("\\("),
120 b')' => Str("\\)"),
121 b'[' => Str("\\["),
122 b']' => Str("\\]"),
123 b'{' => Str("\\{"),
124 b'}' => Str("\\}"),
125 b'|' => Str("\\|"),
126 b'\x00'..b'\x20' | b'\x7f' => Hex,
127 _ => continue,
128 };
129 if pos != i {
130 w.write_str(&s[pos..i])?;
131 }
132 match escaped {
133 Str(s) => w.write_str(s),
134 Hex => write!(w, "\\x{b:02x}"),
135 }?;
136 pos = i + 1;
137 }
138 if pos != s.len() {
139 w.write_str(&s[pos..])?;
140 }
141 Ok(())
142}
143
144pub fn write_escape<W>(w: &mut W, c: char) -> Result
145where
146 W: Write,
147{
148 match c {
149 '\x00'..'\x20' | '\x7f' => write!(w, "\\x{:02x}", c as u8),
150 ']' => w.write_str("\\]"),
151 '\\' => w.write_str("\\\\"),
152 c if c.is_ascii() => w.write_char(c),
153 _ => write!(w, "{}", c.escape_debug()),
154 }
155}
156
157pub fn fmt_charclass<W>(w: &mut W, cr: &CharRange) -> Result
158where
159 W: Write,
160{
161 write_escape(w, cr.start)?;
162 if cr.end != cr.start {
163 if let Some(next) = next_char(cr.start)
164 && next != cr.end
165 {
166 w.write_char('-')?;
167 }
168 write_escape(w, cr.end)?;
169 }
170 Ok(())
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn test_chars_hyphens() {
179 let tests: [(&str, &[(char, char)]); _] = [
180 ("[-a]", &[('-', '-'), ('a', 'a')]),
181 ("[Z!--]", &[('Z', 'Z'), ('!', '-')]),
182 ("[\\\\\\]]", &[('\\', ']')]),
183 ("[!-#]", &[('!', '#')]),
184 ("[!\"]", &[('!', '"')]),
185 ];
186 for (want, cs) in tests {
187 let cs = Chars::from_ranges(cs.iter().copied());
188 eprintln!("want=\"{want}\" cs={cs:?}");
189 assert_eq!(want, &format!("{cs}"));
190 let expr = Expr::new(want.parse().unwrap());
191 assert_eq!(want, &format!("{expr}"), "{want:?} cs={cs:?}");
192 }
193 }
194
195 #[test]
196 fn test_non_printable() {
197 for (want, root) in [
198 (
199 "[\\x00-\\x7f]",
200 Node::Chars(Chars::from_ranges([('\0', '\x7f')])),
201 ),
202 (
203 "[\u{2014}-\u{2026}]",
204 Node::Chars(Chars::from_ranges([('—', '…')])),
205 ),
206 (r#"\x00—\x7f"#, Node::Literal("\0—\x7f".into())),
207 ] {
208 assert_eq!(want, &format!("{}", Expr::new(root.clone())));
209 assert_eq!(root, want.parse().unwrap());
210 }
211 }
212
213 #[test]
214 fn test_literal() {
215 assert_eq!(
216 r#"\{\}"#,
217 &format!("{}", Expr::new(Node::Literal("{}".into())))
218 );
219 }
220
221 #[test]
222 fn test_nested() {
223 assert_eq!(
224 "([a-z][0-9]){3,6}",
225 &format!("{}", Expr::parse("([a-z][0-9]){3,6}").unwrap())
226 );
227 assert_eq!(
228 "[a-z]{,3}",
229 &format!("{}", Expr::parse("[a-z]{0,3}").unwrap())
230 );
231 }
232}