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}