unwind_context/
arg.rs

1use core::fmt::{Debug, Formatter, Result as FmtResult, Write as FmtWrite};
2
3use crate::{AnsiColorScheme, DebugAnsiColored};
4
5/// A structure representing an argument name and its value.
6///
7/// This type is not intended to be used directly. Consider using macros like
8/// [`build_unwind_context_data`] or [`unwind_context`] instead.
9///
10/// [`build_unwind_context_data`]: crate::build_unwind_context_data
11/// [`unwind_context`]: crate::unwind_context
12#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
13pub struct UnwindContextArg<T> {
14    /// Optional argument name.
15    pub name: Option<&'static str>,
16    /// Argument value.
17    pub value: T,
18}
19
20impl<T> UnwindContextArg<T> {
21    /// Create a new `UnwindContextArg` with the provided name and value.
22    ///
23    /// # Examples
24    ///
25    /// ```rust
26    /// let arg = unwind_context::UnwindContextArg::new(Some("foo"), 123);
27    /// ```
28    #[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    // Not the perfect, but a simple and quite performant implementation
161    // that provides sufficient coloring.
162    #[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                            // Look ahead and check for `true` and `false` keywords.
179                            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}