1use core::fmt::{Debug, Formatter, Result as FmtResult, Write as FmtWrite};
2
3use crate::{AnsiColorScheme, DebugAnsiColored};
4
5#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
13pub struct UnwindContextArg<T> {
14 pub name: Option<&'static str>,
16 pub value: T,
18}
19
20impl<T> UnwindContextArg<T> {
21 #[inline]
29 pub fn new(name: Option<&'static str>, value: T) -> Self {
30 Self { name, value }
31 }
32}
33
34impl<T> Debug for UnwindContextArg<T>
35where
36 T: Debug,
37{
38 #[inline]
39 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
40 if let Some(name) = &self.name {
41 write!(f, "{name}: ")?;
42 }
43 write!(f, "{:?}", self.value)?;
44 Ok(())
45 }
46}
47
48impl<T> DebugAnsiColored for UnwindContextArg<T>
49where
50 T: Debug,
51{
52 #[inline]
53 fn fmt_colored(
54 &self,
55 f: &mut Formatter<'_>,
56 color_scheme: &'static AnsiColorScheme,
57 ) -> FmtResult {
58 if let Some(name) = &self.name {
59 write!(f, "{name}: ")?;
60 }
61 let mut writer = ColoredWriter {
62 writer: f,
63 mode: ColoredWriterMode::Default,
64 color_scheme,
65 };
66 write!(writer, "{:?}", self.value)?;
67 writer.reset()?;
68 Ok(())
69 }
70}
71
72#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
73struct ColoredWriter<W> {
74 writer: W,
75 mode: ColoredWriterMode,
76 color_scheme: &'static AnsiColorScheme,
77}
78
79#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
80enum ColoredWriterMode {
81 Default,
82 Ident,
83 Item,
84 Boolean,
85 Number,
86 DoubleQuoted,
87 DoubleQuotedEscapeChar,
88 DoubleQuotedEscaped,
89 SingleQuoted,
90 SingleQuotedEscapeChar,
91 SingleQuotedEscaped,
92 QuotedEnd,
93 Brace,
94}
95
96#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
97enum ColoredWriterModeStyle {
98 Default,
99 Ident,
100 Item,
101 Boolean,
102 Number,
103 Quoted,
104 Escaped,
105 Brace,
106}
107
108impl ColoredWriterModeStyle {
109 fn ansi_style(self, color_scheme: &AnsiColorScheme) -> &'static str {
110 match self {
111 Self::Default => color_scheme.default,
112 Self::Ident => color_scheme.ident,
113 Self::Item => color_scheme.item,
114 Self::Boolean => color_scheme.boolean,
115 Self::Number => color_scheme.number,
116 Self::Quoted => color_scheme.quoted,
117 Self::Escaped => color_scheme.escaped,
118 Self::Brace => color_scheme.value_braces,
119 }
120 }
121}
122
123impl<W> ColoredWriter<W>
124where
125 W: FmtWrite,
126{
127 fn reset(&mut self) -> FmtResult {
128 if self.mode.style() != ColoredWriterModeStyle::Default {
129 self.writer.write_str(self.color_scheme.default)?;
130 self.mode = ColoredWriterMode::Default;
131 }
132 Ok(())
133 }
134}
135
136impl ColoredWriterMode {
137 fn style(self) -> ColoredWriterModeStyle {
138 match self {
139 Self::Default => ColoredWriterModeStyle::Default,
140 Self::Ident => ColoredWriterModeStyle::Ident,
141 Self::Item => ColoredWriterModeStyle::Item,
142 Self::Boolean => ColoredWriterModeStyle::Boolean,
143 Self::Number => ColoredWriterModeStyle::Number,
144 Self::DoubleQuoted | Self::SingleQuoted | Self::QuotedEnd => {
145 ColoredWriterModeStyle::Quoted
146 }
147 Self::DoubleQuotedEscapeChar
148 | Self::DoubleQuotedEscaped
149 | Self::SingleQuotedEscapeChar
150 | Self::SingleQuotedEscaped => ColoredWriterModeStyle::Escaped,
151 Self::Brace => ColoredWriterModeStyle::Brace,
152 }
153 }
154}
155
156impl<W> FmtWrite for ColoredWriter<W>
157where
158 W: FmtWrite,
159{
160 #[allow(clippy::too_many_lines)]
163 fn write_str(&mut self, s: &str) -> FmtResult {
164 for (offset, ch) in s.char_indices() {
165 let prev_style = self.mode.style();
166 self.mode = match self.mode {
167 ColoredWriterMode::Default
168 | ColoredWriterMode::QuotedEnd
169 | ColoredWriterMode::Brace => match ch {
170 '0'..='9' | '+' | '-' | '.' => ColoredWriterMode::Number,
171 '(' | ')' | '[' | ']' | '{' | '}' => ColoredWriterMode::Brace,
172 '_' => ColoredWriterMode::Ident,
173 '"' => ColoredWriterMode::DoubleQuoted,
174 '\'' => ColoredWriterMode::SingleQuoted,
175 'A'..='Z' => ColoredWriterMode::Item,
176 _ => {
177 if ch.is_alphanumeric() {
178 if match_true_ident(s, offset) || match_false_ident(s, offset) {
180 ColoredWriterMode::Boolean
181 } else {
182 ColoredWriterMode::Ident
183 }
184 } else {
185 ColoredWriterMode::Default
186 }
187 }
188 },
189 ColoredWriterMode::Ident | ColoredWriterMode::Item => match ch {
190 '(' | ')' | '[' | ']' | '{' | '}' => ColoredWriterMode::Brace,
191 '#' | '_' => self.mode,
192 '"' => ColoredWriterMode::DoubleQuoted,
193 '\'' => ColoredWriterMode::SingleQuoted,
194 ch => {
195 if ch.is_alphanumeric() {
196 self.mode
197 } else {
198 ColoredWriterMode::Default
199 }
200 }
201 },
202 ColoredWriterMode::Boolean => match ch {
203 '0'..='9' | '+' | '-' | '.' => ColoredWriterMode::Number,
204 '(' | ')' | '[' | ']' | '{' | '}' => ColoredWriterMode::Brace,
205 '#' | '_' => ColoredWriterMode::Ident,
206 '"' => ColoredWriterMode::DoubleQuoted,
207 '\'' => ColoredWriterMode::SingleQuoted,
208 ch => {
209 if ch.is_alphanumeric() {
210 ColoredWriterMode::Boolean
211 } else {
212 ColoredWriterMode::Default
213 }
214 }
215 },
216 ColoredWriterMode::Number => match ch {
217 '0'..='9' | '+' | '-' | '.' | '_' => ColoredWriterMode::Number,
218 '(' | ')' | '[' | ']' | '{' | '}' => ColoredWriterMode::Brace,
219 '"' => ColoredWriterMode::DoubleQuoted,
220 '\'' => ColoredWriterMode::SingleQuoted,
221 ch => {
222 if ch.is_alphanumeric() {
223 ColoredWriterMode::Ident
224 } else {
225 ColoredWriterMode::Default
226 }
227 }
228 },
229 ColoredWriterMode::DoubleQuoted | ColoredWriterMode::DoubleQuotedEscaped => {
230 match ch {
231 '"' => ColoredWriterMode::QuotedEnd,
232 '\\' => ColoredWriterMode::DoubleQuotedEscapeChar,
233 _ => ColoredWriterMode::DoubleQuoted,
234 }
235 }
236 ColoredWriterMode::DoubleQuotedEscapeChar => ColoredWriterMode::DoubleQuotedEscaped,
237 ColoredWriterMode::SingleQuoted | ColoredWriterMode::SingleQuotedEscaped => {
238 match ch {
239 '\'' => ColoredWriterMode::QuotedEnd,
240 '\\' => ColoredWriterMode::SingleQuotedEscapeChar,
241 _ => ColoredWriterMode::SingleQuoted,
242 }
243 }
244 ColoredWriterMode::SingleQuotedEscapeChar => ColoredWriterMode::SingleQuotedEscaped,
245 };
246 let style = self.mode.style();
247 if prev_style != style {
248 self.writer.write_str(style.ansi_style(self.color_scheme))?;
249 }
250 self.writer.write_char(ch)?;
251 }
252 Ok(())
253 }
254}
255
256fn match_true_ident(s: &str, offset: usize) -> bool {
257 s.as_bytes().get(offset..offset.saturating_add(4)) == Some(b"true")
258 && s.as_bytes()
259 .get(offset.saturating_add(4))
260 .map_or(true, |&ch| !ch.is_ascii_alphanumeric() && ch != b'_')
261}
262
263fn match_false_ident(s: &str, offset: usize) -> bool {
264 s.as_bytes().get(offset..offset.saturating_add(5)) == Some(b"false")
265 && s.as_bytes()
266 .get(offset.saturating_add(5))
267 .map_or(true, |&ch| !ch.is_ascii_alphanumeric() && ch != b'_')
268}
269
270#[cfg(test)]
271mod tests {
272 use core::fmt::{Debug, Error as FmtError};
273 use core::marker::PhantomData;
274
275 use crate::arg::{match_false_ident, match_true_ident};
276 use crate::test_common::{arg, colored_arg, TEST_COLOR_SCHEME};
277 use crate::test_util::{debug_fmt, TransparentDebug};
278 use crate::{AnsiColored, UnwindContextArg};
279
280 #[derive(Clone, Debug)]
281 struct Wrapper<T> {
282 _first: T,
283 _second: T,
284 _phantom: PhantomData<u32>,
285 }
286
287 fn fmt_str_as_arg<'a>(buffer: &'a mut [u8], value: &'static str) -> Result<&'a str, FmtError> {
288 debug_fmt(
289 buffer,
290 &AnsiColored::new(
291 UnwindContextArg::new(None, TransparentDebug(value)),
292 &TEST_COLOR_SCHEME,
293 ),
294 )
295 }
296
297 #[test]
298 fn test_match_true_ident() {
299 assert!(!match_true_ident("", 0));
300 assert!(!match_true_ident("a", 0));
301 assert!(!match_true_ident("false", 0));
302 assert!(!match_true_ident("abcd", 0));
303 assert!(match_true_ident("true", 0));
304 assert!(match_true_ident("true.false", 0));
305 assert!(match_true_ident("true!false", 0));
306 assert!(match_true_ident("true-false", 0));
307 assert!(match_true_ident("true false", 0));
308 assert!(!match_true_ident("truetrue", 0));
309 assert!(!match_true_ident("truest", 0));
310 assert!(!match_true_ident("true1", 0));
311 assert!(!match_true_ident("true_", 0));
312 assert!(match_true_ident("(true)", 1));
313 assert!(match_true_ident("((true))", 2));
314 }
315
316 #[test]
317 fn test_match_false_ident() {
318 assert!(!match_false_ident("", 0));
319 assert!(!match_false_ident("a", 0));
320 assert!(!match_false_ident("true", 0));
321 assert!(!match_false_ident("abcde", 0));
322 assert!(match_false_ident("false", 0));
323 assert!(match_false_ident("false.true", 0));
324 assert!(match_false_ident("false!true", 0));
325 assert!(match_false_ident("false-true", 0));
326 assert!(match_false_ident("false true", 0));
327 assert!(!match_false_ident("falsefalse", 0));
328 assert!(!match_false_ident("falsest", 0));
329 assert!(!match_false_ident("false1", 0));
330 assert!(!match_false_ident("false_", 0));
331 assert!(match_false_ident("(false)", 1));
332 assert!(match_false_ident("((false))", 2));
333 }
334
335 #[test]
336 fn test_arg_fmt() {
337 let mut buffer = [0; 128];
338 assert_eq!(debug_fmt(&mut buffer, &arg(None, "value")), Ok("\"value\""));
339 assert_eq!(
340 debug_fmt(&mut buffer, &arg(Some("foo"), 123)),
341 Ok("foo: 123")
342 );
343 assert_eq!(
344 debug_fmt(&mut buffer, &arg(Some("foo"), "bar\n-\"-'-\"bar")),
345 Ok("foo: \"bar\\n-\\\"-'-\\\"bar\"")
346 );
347 assert_eq!(
348 debug_fmt(&mut buffer, &arg(Some("foo"), 'a')),
349 Ok("foo: 'a'")
350 );
351 assert_eq!(
352 debug_fmt(
353 &mut buffer,
354 &arg(
355 Some("foo"),
356 Wrapper {
357 _first: true,
358 _second: false,
359 _phantom: PhantomData,
360 }
361 )
362 ),
363 Ok("foo: Wrapper { _first: true, _second: false, _phantom: PhantomData<u32> }")
364 );
365 }
366
367 #[test]
368 fn test_arg_colored_fmt() {
369 let mut buffer = [0; 256];
370 assert_eq!(
371 debug_fmt(&mut buffer, &colored_arg(None, "value")),
372 Ok("{QUOT}\"value\"{DEF}")
373 );
374 assert_eq!(
375 debug_fmt(&mut buffer, &colored_arg(Some("foo"), 123)),
376 Ok("foo: {NUM}123{DEF}")
377 );
378 assert_eq!(
379 debug_fmt(&mut buffer, &colored_arg(Some("foo"), "bar\n-\"-'-\"bar")),
380 Ok(concat!(
381 "foo: ",
382 "{QUOT}\"bar",
383 "{ESC}\\n",
384 "{QUOT}-",
385 "{ESC}\\\"",
386 "{QUOT}-'-",
387 "{ESC}\\\"",
388 "{QUOT}bar\"",
389 "{DEF}"
390 ))
391 );
392 assert_eq!(
393 debug_fmt(&mut buffer, &colored_arg(Some("foo"), 'a')),
394 Ok("foo: {QUOT}'a'{DEF}")
395 );
396 assert_eq!(
397 debug_fmt(
398 &mut buffer,
399 &colored_arg(
400 Some("foo"),
401 Wrapper {
402 _first: true,
403 _second: false,
404 _phantom: PhantomData,
405 }
406 )
407 ),
408 Ok(concat!(
409 "foo: ",
410 "{ITEM}Wrapper",
411 "{DEF} {BRACE}{",
412 "{DEF} ",
413 "{IDENT}_first{DEF}: {BOOL}true{DEF}, ",
414 "{IDENT}_second{DEF}: {BOOL}false{DEF}, ",
415 "{IDENT}_phantom{DEF}: ",
416 "{ITEM}PhantomData{DEF}<{IDENT}u32{DEF}> ",
417 "{BRACE}}",
418 "{DEF}"
419 ))
420 );
421 }
422
423 #[test]
424 fn test_complex_colored_fmt() {
425 use fmt_str_as_arg as f;
426
427 let mut buffer = [0; 64];
428 let buf = &mut buffer;
429
430 assert_eq!(f(buf, "123"), Ok("{NUM}123{DEF}"));
431 assert_eq!(f(buf, "()"), Ok("{BRACE}(){DEF}"));
432 assert_eq!(f(buf, "_foo"), Ok("{IDENT}_foo{DEF}"));
433 assert_eq!(f(buf, "\"foo\""), Ok("{QUOT}\"foo\"{DEF}"));
434 assert_eq!(f(buf, "'foo'"), Ok("{QUOT}'foo'{DEF}"));
435 assert_eq!(f(buf, "Bar"), Ok("{ITEM}Bar{DEF}"));
436 assert_eq!(f(buf, "BAR"), Ok("{ITEM}BAR{DEF}"));
437 assert_eq!(f(buf, "true"), Ok("{BOOL}true{DEF}"));
438 assert_eq!(f(buf, "false"), Ok("{BOOL}false{DEF}"));
439 assert_eq!(f(buf, "foo"), Ok("{IDENT}foo{DEF}"));
440 assert_eq!(f(buf, "&"), Ok("&"));
441
442 assert_eq!(f(buf, "foo()"), Ok("{IDENT}foo{BRACE}(){DEF}"));
443 assert_eq!(f(buf, "foo_bar"), Ok("{IDENT}foo_bar{DEF}"));
444 assert_eq!(f(buf, "r#raw"), Ok("{IDENT}r#raw{DEF}"));
445 assert_eq!(f(buf, "b'1'"), Ok("{IDENT}b{QUOT}'1'{DEF}"));
446 assert_eq!(f(buf, "b\"1\""), Ok("{IDENT}b{QUOT}\"1\"{DEF}"));
447 assert_eq!(f(buf, "foo123"), Ok("{IDENT}foo123{DEF}"));
448 assert_eq!(f(buf, "foo&"), Ok("{IDENT}foo{DEF}&"));
449
450 assert_eq!(f(buf, "true+"), Ok("{BOOL}true{NUM}+{DEF}"));
451 assert_eq!(f(buf, "[true]"), Ok("{BRACE}[{BOOL}true{BRACE}]{DEF}"));
452 assert_eq!(f(buf, "true#"), Ok("{BOOL}true{IDENT}#{DEF}"));
453 assert_eq!(f(buf, "false\"\""), Ok("{BOOL}false{QUOT}\"\"{DEF}"));
454 assert_eq!(f(buf, "false\'\'"), Ok("{BOOL}false{QUOT}''{DEF}"));
455 assert_eq!(f(buf, "false&"), Ok("{BOOL}false{DEF}&"));
456
457 assert_eq!(f(buf, "123"), Ok("{NUM}123{DEF}"));
458 assert_eq!(f(buf, "2[]"), Ok("{NUM}2{BRACE}[]{DEF}"));
459 assert_eq!(f(buf, "3\"\""), Ok("{NUM}3{QUOT}\"\"{DEF}"));
460 assert_eq!(f(buf, "4\'\'"), Ok("{NUM}4{QUOT}''{DEF}"));
461 assert_eq!(f(buf, "5a"), Ok("{NUM}5{IDENT}a{DEF}"));
462 assert_eq!(f(buf, "6^7"), Ok("{NUM}6{DEF}^{NUM}7{DEF}"));
463
464 assert_eq!(f(buf, "\"\\\"\""), Ok("{QUOT}\"{ESC}\\\"{QUOT}\"{DEF}"));
465 assert_eq!(f(buf, "'\\''"), Ok("{QUOT}'{ESC}\\'{QUOT}'{DEF}"));
466 assert_eq!(f(buf, ""), Ok(""));
467 }
468
469 #[test]
470 fn test_arg_failed_fmt() {
471 let arg = arg(Some("foo"), TransparentDebug("[1, 2, 3]"));
472
473 let mut buffer = [0; 64];
474 let len = debug_fmt(&mut buffer, &arg).unwrap().len();
475 for len in 0..len {
476 assert_eq!(debug_fmt(&mut buffer[0..len], &arg), Err(FmtError));
477 }
478 }
479
480 #[test]
481 fn test_arg_failed_colored_fmt() {
482 let arg = colored_arg(Some("foo"), TransparentDebug("[1, 2, 3]"));
483
484 let mut buffer = [0; 64];
485 let len = debug_fmt(&mut buffer, &arg).unwrap().len();
486 for len in 0..len {
487 assert_eq!(debug_fmt(&mut buffer[0..len], &arg), Err(FmtError));
488 }
489 }
490}