bitflags/
parser.rs

1/*!
2Parsing flags from text.
3
4Format and parse a flags value as text using the following grammar:
5
6- _Flags:_ (_Whitespace_ _Flag_ _Whitespace_)`|`*
7- _Flag:_ _Name_ | _Hex Number_
8- _Name:_ The name of any defined flag
9- _Hex Number_: `0x`([0-9a-fA-F])*
10- _Whitespace_: (\s)*
11
12As an example, this is how `Flags::A | Flags::B | 0x0c` can be represented as text:
13
14```text
15A | B | 0x0c
16```
17
18Alternatively, it could be represented without whitespace:
19
20```text
21A|B|0x0C
22```
23
24Note that identifiers are *case-sensitive*, so the following is *not equivalent*:
25
26```text
27a|b|0x0C
28```
29*/
30
31#![allow(clippy::let_unit_value)]
32
33use core::fmt::{self, Write};
34
35use crate::{Bits, Flags};
36
37/**
38Write a flags value as text.
39
40Any bits that aren't part of a contained flag will be formatted as a hex number.
41*/
42pub fn to_writer<B: Flags>(flags: &B, mut writer: impl Write) -> Result<(), fmt::Error>
43where
44    B::Bits: WriteHex,
45{
46    // A formatter for bitflags that produces text output like:
47    //
48    // A | B | 0xf6
49    //
50    // The names of set flags are written in a bar-separated-format,
51    // followed by a hex number of any remaining bits that are set
52    // but don't correspond to any flags.
53
54    // Iterate over known flag values
55    let mut first = true;
56    let mut iter = flags.iter_names();
57    for (name, _) in &mut iter {
58        if !first {
59            writer.write_str(" | ")?;
60        }
61
62        first = false;
63        writer.write_str(name)?;
64    }
65
66    // Append any extra bits that correspond to flags to the end of the format
67    let remaining = iter.remaining().bits();
68    if remaining != B::Bits::EMPTY {
69        if !first {
70            writer.write_str(" | ")?;
71        }
72
73        writer.write_str("0x")?;
74        remaining.write_hex(writer)?;
75    }
76
77    fmt::Result::Ok(())
78}
79
80pub(crate) struct AsDisplay<'a, B>(pub(crate) &'a B);
81
82impl<'a, B: Flags> fmt::Display for AsDisplay<'a, B>
83where
84    B::Bits: WriteHex,
85{
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        to_writer(self.0, f)
88    }
89}
90
91/**
92Parse a flags value from text.
93
94This function will fail on any names that don't correspond to defined flags.
95Unknown bits will be retained.
96*/
97pub fn from_str<B: Flags>(input: &str) -> Result<B, ParseError>
98where
99    B::Bits: ParseHex,
100{
101    let mut parsed_flags = B::empty();
102
103    // If the input is empty then return an empty set of flags
104    if input.trim().is_empty() {
105        return Ok(parsed_flags);
106    }
107
108    for flag in input.split('|') {
109        let flag = flag.trim();
110
111        // If the flag is empty then we've got missing input
112        if flag.is_empty() {
113            return Err(ParseError::empty_flag());
114        }
115
116        // If the flag starts with `0x` then it's a hex number
117        // Parse it directly to the underlying bits type
118        let parsed_flag = if let Some(flag) = flag.strip_prefix("0x") {
119            let bits =
120                <B::Bits>::parse_hex(flag).map_err(|_| ParseError::invalid_hex_flag(flag))?;
121
122            B::from_bits_retain(bits)
123        }
124        // Otherwise the flag is a name
125        // The generated flags type will determine whether
126        // or not it's a valid identifier
127        else {
128            B::from_name(flag).ok_or_else(|| ParseError::invalid_named_flag(flag))?
129        };
130
131        parsed_flags.insert(parsed_flag);
132    }
133
134    Ok(parsed_flags)
135}
136
137/**
138Encode a value as a hex string.
139
140Implementors of this trait should not write the `0x` prefix.
141*/
142pub trait WriteHex {
143    /// Write the value as hex.
144    fn write_hex<W: fmt::Write>(&self, writer: W) -> fmt::Result;
145}
146
147/**
148Parse a value from a hex string.
149*/
150pub trait ParseHex {
151    /// Parse the value from hex.
152    fn parse_hex(input: &str) -> Result<Self, ParseError>
153    where
154        Self: Sized;
155}
156
157/// An error encountered while parsing flags from text.
158#[derive(Debug)]
159pub struct ParseError(ParseErrorKind);
160
161#[derive(Debug)]
162#[allow(clippy::enum_variant_names)]
163enum ParseErrorKind {
164    EmptyFlag,
165    InvalidNamedFlag {
166        #[cfg(not(feature = "std"))]
167        got: (),
168        #[cfg(feature = "std")]
169        got: String,
170    },
171    InvalidHexFlag {
172        #[cfg(not(feature = "std"))]
173        got: (),
174        #[cfg(feature = "std")]
175        got: String,
176    },
177}
178
179impl ParseError {
180    /// An invalid hex flag was encountered.
181    pub fn invalid_hex_flag(flag: impl fmt::Display) -> Self {
182        let _flag = flag;
183
184        let got = {
185            #[cfg(feature = "std")]
186            {
187                _flag.to_string()
188            }
189        };
190
191        ParseError(ParseErrorKind::InvalidHexFlag { got })
192    }
193
194    /// A named flag that doesn't correspond to any on the flags type was encountered.
195    pub fn invalid_named_flag(flag: impl fmt::Display) -> Self {
196        let _flag = flag;
197
198        let got = {
199            #[cfg(feature = "std")]
200            {
201                _flag.to_string()
202            }
203        };
204
205        ParseError(ParseErrorKind::InvalidNamedFlag { got })
206    }
207
208    /// A hex or named flag wasn't found between separators.
209    pub const fn empty_flag() -> Self {
210        ParseError(ParseErrorKind::EmptyFlag)
211    }
212}
213
214impl fmt::Display for ParseError {
215    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216        match &self.0 {
217            ParseErrorKind::InvalidNamedFlag { got } => {
218                let _got = got;
219
220                write!(f, "unrecognized named flag")?;
221
222                #[cfg(feature = "std")]
223                {
224                    write!(f, " `{}`", _got)?;
225                }
226            }
227            ParseErrorKind::InvalidHexFlag { got } => {
228                let _got = got;
229
230                write!(f, "invalid hex flag")?;
231
232                #[cfg(feature = "std")]
233                {
234                    write!(f, " `{}`", _got)?;
235                }
236            }
237            ParseErrorKind::EmptyFlag => {
238                write!(f, "encountered empty flag")?;
239            }
240        }
241
242        Ok(())
243    }
244}
245
246#[cfg(feature = "std")]
247impl std::error::Error for ParseError {}