Skip to main content

alpm_parsers/error/
context.rs

1//! The [`StringContext`] type
2//!
3//! This is our own `String`-capable version of the [`StrContext`] parser.
4
5use winnow::error::{StrContext, StrContextValue};
6
7/// A single piece of parser context that is attached to a [`ParseStack`] error.
8///
9/// In contrast to winnow's [`StrContext`], which only holds `&'static str`, this types allows
10/// messages as [`String`]s.
11///
12/// This allows for setting error messages during runtime, which is a requirement for translated
13/// messages and generally provides more flexibility.
14///
15/// [`ParseStack`]: crate::error::ParseStack
16#[derive(Clone, Debug)]
17pub(crate) enum StringContext {
18    /// A description of what is currently being parsed.
19    Label(String),
20
21    /// A free-form description of what went wrong.
22    Description(String),
23
24    /// An expected [`char`] literal.
25    ExpectedChar(char),
26
27    /// An expected string literal.
28    ///
29    /// Use this for known, static syntax/keywords.
30    ExpectedString(&'static str),
31
32    /// An free-form string that explains what is expected.
33    ///
34    /// Use this for known, static syntax/keywords.
35    ExpectedText(String),
36}
37
38impl StringContext {
39    /// Returns the label text in case this context is a [`StringContext::Label`].
40    pub(crate) fn label(&self) -> Option<&str> {
41        match self {
42            StringContext::Label(label) => Some(label),
43            _ => None,
44        }
45    }
46
47    /// Returns the expected literal if `Self` is of a literal variant.
48    pub(crate) fn expected(&self) -> Option<String> {
49        match self {
50            StringContext::Label(_) => None,
51            StringContext::Description(_) => None,
52            StringContext::ExpectedChar(c) => Some(Self::render_char(*c)),
53            StringContext::ExpectedString(s) => Some(format!("'{s}'")),
54            StringContext::ExpectedText(s) => Some(s.into()),
55        }
56    }
57
58    /// Renders an expected [`char`].
59    ///
60    /// This is copied over from winnow's [`StrContextValue`] `Display` impl.
61    fn render_char(c: char) -> String {
62        match c {
63            '\n' => "newline".to_string(),
64            '`' => "'`'".to_string(),
65            c if c.is_ascii_control() => format!("`{}`", c.escape_debug()),
66            c => format!("`{c}`"),
67        }
68    }
69}
70
71impl From<StrContext> for StringContext {
72    fn from(context: StrContext) -> Self {
73        match context {
74            StrContext::Label(label) => StringContext::Label(label.to_string()),
75            StrContext::Expected(value) => value.into(),
76            // `StrContext` is non-exhaustive for some reason.
77            // We just create a `Description` in case a new variant is introduced. Since we aim to
78            // only use our own error type, this form of backwards compatibility will become
79            // obsolete after some time anyway.
80            other => StringContext::Description(other.to_string()),
81        }
82    }
83}
84
85impl From<StrContextValue> for StringContext {
86    fn from(value: StrContextValue) -> Self {
87        match value {
88            StrContextValue::CharLiteral(c) => StringContext::ExpectedChar(c),
89            StrContextValue::StringLiteral(s) => StringContext::ExpectedString(s),
90            StrContextValue::Description(description) => {
91                StringContext::ExpectedText(description.to_string())
92            }
93            // See the `impl From<StrContext>` equivalent for more context.
94            other => StringContext::Description(other.to_string()),
95        }
96    }
97}