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
80#[cfg(feature = "serde")]
81pub(crate) struct AsDisplay<'a, B>(pub(crate) &'a B);
82
83#[cfg(feature = "serde")]
84impl<'a, B: Flags> fmt::Display for AsDisplay<'a, B>
85where
86    B::Bits: WriteHex,
87{
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        to_writer(self.0, f)
90    }
91}
92
93/**
94Parse a flags value from text.
95
96This function will fail on any names that don't correspond to defined flags.
97Unknown bits will be retained.
98*/
99pub fn from_str<B: Flags>(input: &str) -> Result<B, ParseError>
100where
101    B::Bits: ParseHex,
102{
103    let mut parsed_flags = B::empty();
104
105    // If the input is empty then return an empty set of flags
106    if input.trim().is_empty() {
107        return Ok(parsed_flags);
108    }
109
110    for flag in input.split('|') {
111        let flag = flag.trim();
112
113        // If the flag is empty then we've got missing input
114        if flag.is_empty() {
115            return Err(ParseError::empty_flag());
116        }
117
118        // If the flag starts with `0x` then it's a hex number
119        // Parse it directly to the underlying bits type
120        let parsed_flag = if let Some(flag) = flag.strip_prefix("0x") {
121            let bits =
122                <B::Bits>::parse_hex(flag).map_err(|_| ParseError::invalid_hex_flag(flag))?;
123
124            B::from_bits_retain(bits)
125        }
126        // Otherwise the flag is a name
127        // The generated flags type will determine whether
128        // or not it's a valid identifier
129        else {
130            B::from_name(flag).ok_or_else(|| ParseError::invalid_named_flag(flag))?
131        };
132
133        parsed_flags.insert(parsed_flag);
134    }
135
136    Ok(parsed_flags)
137}
138
139/**
140Write a flags value as text, ignoring any unknown bits.
141*/
142pub fn to_writer_truncate<B: Flags>(flags: &B, writer: impl Write) -> Result<(), fmt::Error>
143where
144    B::Bits: WriteHex,
145{
146    to_writer(&B::from_bits_truncate(flags.bits()), writer)
147}
148
149/**
150Parse a flags value from text.
151
152This function will fail on any names that don't correspond to defined flags.
153Unknown bits will be ignored.
154*/
155pub fn from_str_truncate<B: Flags>(input: &str) -> Result<B, ParseError>
156where
157    B::Bits: ParseHex,
158{
159    Ok(B::from_bits_truncate(from_str::<B>(input)?.bits()))
160}
161
162/**
163Write only the contained, defined, named flags in a flags value as text.
164*/
165pub fn to_writer_strict<B: Flags>(flags: &B, mut writer: impl Write) -> Result<(), fmt::Error> {
166    // This is a simplified version of `to_writer` that ignores
167    // any bits not corresponding to a named flag
168
169    let mut first = true;
170    let mut iter = flags.iter_names();
171    for (name, _) in &mut iter {
172        if !first {
173            writer.write_str(" | ")?;
174        }
175
176        first = false;
177        writer.write_str(name)?;
178    }
179
180    fmt::Result::Ok(())
181}
182
183/**
184Parse a flags value from text.
185
186This function will fail on any names that don't correspond to defined flags.
187This function will fail to parse hex values.
188*/
189pub fn from_str_strict<B: Flags>(input: &str) -> Result<B, ParseError> {
190    // This is a simplified version of `from_str` that ignores
191    // any bits not corresponding to a named flag
192
193    let mut parsed_flags = B::empty();
194
195    // If the input is empty then return an empty set of flags
196    if input.trim().is_empty() {
197        return Ok(parsed_flags);
198    }
199
200    for flag in input.split('|') {
201        let flag = flag.trim();
202
203        // If the flag is empty then we've got missing input
204        if flag.is_empty() {
205            return Err(ParseError::empty_flag());
206        }
207
208        // If the flag starts with `0x` then it's a hex number
209        // These aren't supported in the strict parser
210        if flag.starts_with("0x") {
211            return Err(ParseError::invalid_hex_flag("unsupported hex flag value"));
212        }
213
214        let parsed_flag = B::from_name(flag).ok_or_else(|| ParseError::invalid_named_flag(flag))?;
215
216        parsed_flags.insert(parsed_flag);
217    }
218
219    Ok(parsed_flags)
220}
221
222/**
223Encode a value as a hex string.
224
225Implementors of this trait should not write the `0x` prefix.
226*/
227pub trait WriteHex {
228    /// Write the value as hex.
229    fn write_hex<W: fmt::Write>(&self, writer: W) -> fmt::Result;
230}
231
232/**
233Parse a value from a hex string.
234*/
235pub trait ParseHex {
236    /// Parse the value from hex.
237    fn parse_hex(input: &str) -> Result<Self, ParseError>
238    where
239        Self: Sized;
240}
241
242/// An error encountered while parsing flags from text.
243#[derive(Debug)]
244pub struct ParseError(ParseErrorKind);
245
246#[derive(Debug)]
247#[allow(clippy::enum_variant_names)]
248enum ParseErrorKind {
249    EmptyFlag,
250    InvalidNamedFlag {
251        #[cfg(not(feature = "std"))]
252        got: (),
253        #[cfg(feature = "std")]
254        got: String,
255    },
256    InvalidHexFlag {
257        #[cfg(not(feature = "std"))]
258        got: (),
259        #[cfg(feature = "std")]
260        got: String,
261    },
262}
263
264impl ParseError {
265    /// An invalid hex flag was encountered.
266    pub fn invalid_hex_flag(flag: impl fmt::Display) -> Self {
267        let _flag = flag;
268
269        let got = {
270            #[cfg(feature = "std")]
271            {
272                _flag.to_string()
273            }
274        };
275
276        ParseError(ParseErrorKind::InvalidHexFlag { got })
277    }
278
279    /// A named flag that doesn't correspond to any on the flags type was encountered.
280    pub fn invalid_named_flag(flag: impl fmt::Display) -> Self {
281        let _flag = flag;
282
283        let got = {
284            #[cfg(feature = "std")]
285            {
286                _flag.to_string()
287            }
288        };
289
290        ParseError(ParseErrorKind::InvalidNamedFlag { got })
291    }
292
293    /// A hex or named flag wasn't found between separators.
294    pub const fn empty_flag() -> Self {
295        ParseError(ParseErrorKind::EmptyFlag)
296    }
297}
298
299impl fmt::Display for ParseError {
300    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301        match &self.0 {
302            ParseErrorKind::InvalidNamedFlag { got } => {
303                let _got = got;
304
305                write!(f, "unrecognized named flag")?;
306
307                #[cfg(feature = "std")]
308                {
309                    write!(f, " `{}`", _got)?;
310                }
311            }
312            ParseErrorKind::InvalidHexFlag { got } => {
313                let _got = got;
314
315                write!(f, "invalid hex flag")?;
316
317                #[cfg(feature = "std")]
318                {
319                    write!(f, " `{}`", _got)?;
320                }
321            }
322            ParseErrorKind::EmptyFlag => {
323                write!(f, "encountered empty flag")?;
324            }
325        }
326
327        Ok(())
328    }
329}
330
331#[cfg(feature = "std")]
332impl std::error::Error for ParseError {}