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 {}
248