1#![cfg_attr(docsrs, feature(doc_cfg))]
9#![doc(html_logo_url = "https://knurling.ferrous-systems.com/knurling_logo_light_text.svg")]
10
11mod display_hint;
12#[cfg(test)]
13mod tests;
14mod types;
15
16use std::{borrow::Cow, ops::Range};
17
18pub use crate::{
19 display_hint::{DisplayHint, TimePrecision},
20 types::Type,
21};
22
23#[derive(thiserror::Error, Debug, PartialEq, Eq, Clone)]
25pub enum Error {
26 #[error("invalid type specifier `{0:?}`")]
27 InvalidTypeSpecifier(String),
28 #[error("unable to parse given integer")]
29 InvalidInteger(#[from] std::num::ParseIntError),
30 #[error("invalid array specifier (missing length)")]
31 InvalidArraySpecifierMissingLength,
32 #[error("invalid array specifier (missing `]`")]
33 InvalidArraySpecifierMissingBracket,
34 #[error("trailing data after bitfield range")]
35 TrailingDataAfterBitfieldRange,
36 #[error("malformed format string (missing display hint after ':')")]
37 MalformedFormatString,
38 #[error("unknown display hint: {0:?}")]
39 UnknownDisplayHint(String),
40 #[error("unexpected content `{0:?}` in format string")]
41 UnexpectedContentInFormatString(String),
42 #[error("unmatched `{{` in format string")]
43 UnmatchedOpenBracket,
44 #[error("unmatched `}}` in format string")]
45 UnmatchedCloseBracket,
46 #[error("conflicting types for argument {0}: used as {1:?} and {2:?}")]
47 ConflictingTypes(usize, Type, Type),
48 #[error("argument {0} is not used in this format string")]
49 UnusedArgument(usize),
50}
51
52#[derive(Clone, Debug, Eq, PartialEq)]
54pub struct Parameter {
55 pub index: usize,
57 pub ty: Type,
59 pub hint: Option<DisplayHint>,
61}
62
63#[derive(Clone, Debug, Eq, PartialEq)]
65pub enum Fragment<'f> {
66 Literal(Cow<'f, str>),
68
69 Parameter(Parameter),
71}
72
73#[derive(Debug, PartialEq)]
94struct Param {
95 index: Option<usize>,
96 ty: Type,
97 hint: Option<DisplayHint>,
98}
99
100#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd)]
102pub enum Level {
103 Trace,
104 Debug,
105 Info,
106 Warn,
107 Error,
108}
109
110impl Level {
111 pub fn as_str(self) -> &'static str {
112 match self {
113 Level::Trace => "trace",
114 Level::Debug => "debug",
115 Level::Info => "info",
116 Level::Warn => "warn",
117 Level::Error => "error",
118 }
119 }
120}
121
122fn parse_range(mut s: &str) -> Option<(Range<u8>, usize )> {
123 let start_digits = s
125 .as_bytes()
126 .iter()
127 .take_while(|b| (**b as char).is_ascii_digit())
128 .count();
129 let start = s[..start_digits].parse().ok()?;
130
131 if &s[start_digits..start_digits + 2] != ".." {
133 return None;
134 }
135 s = &s[start_digits + 2..];
136
137 let end_digits = s
139 .as_bytes()
140 .iter()
141 .take_while(|b| (**b as char).is_ascii_digit())
142 .count();
143 let end = s[..end_digits].parse().ok()?;
144
145 if end <= start || start >= 128 || end > 128 {
147 return None;
148 }
149
150 Some((start..end, start_digits + end_digits + 2))
151}
152
153fn parse_array(mut s: &str) -> Result<usize, Error> {
157 let len_pos = s
159 .find(|c: char| c != ' ')
160 .ok_or(Error::InvalidArraySpecifierMissingLength)?;
161 s = &s[len_pos..];
162
163 let after_len = s
165 .find(|c: char| !c.is_ascii_digit())
166 .ok_or(Error::InvalidArraySpecifierMissingBracket)?;
167 let len = s[..after_len].parse::<usize>()?;
168 s = &s[after_len..];
169
170 if s != "]" {
172 return Err(Error::InvalidArraySpecifierMissingBracket);
173 }
174
175 Ok(len)
176}
177
178#[derive(Clone, Copy, Debug, Eq, PartialEq)]
180pub enum ParserMode {
181 Strict,
183 ForwardsCompatible,
185}
186
187fn parse_param(mut input: &str, mode: ParserMode) -> Result<Param, Error> {
191 const TYPE_PREFIX: &str = "=";
192 const HINT_PREFIX: &str = ":";
193
194 let mut index = None;
196 let index_end = input
197 .find(|c: char| !c.is_ascii_digit())
198 .unwrap_or(input.len());
199
200 if index_end != 0 {
201 index = Some(input[..index_end].parse::<usize>()?);
202 }
203
204 let mut ty = Type::default(); input = &input[index_end..];
207
208 if input.starts_with(TYPE_PREFIX) {
209 input = &input[TYPE_PREFIX.len()..];
211
212 let type_end = input.find(HINT_PREFIX).unwrap_or(input.len());
214 let type_fragment = &input[..type_end];
215
216 const FORMAT_ARRAY_START: &str = "[?;";
217 const U8_ARRAY_START: &str = "[u8;";
218
219 ty = if let Ok(ty) = type_fragment.parse() {
221 Ok(ty)
222 } else if let Some(s) = type_fragment.strip_prefix(U8_ARRAY_START) {
223 Ok(Type::U8Array(parse_array(s)?))
224 } else if let Some(s) = type_fragment.strip_prefix(FORMAT_ARRAY_START) {
225 Ok(Type::FormatArray(parse_array(s)?))
226 } else if let Some((range, used)) = parse_range(type_fragment) {
227 match used != type_fragment.len() {
229 true => Err(Error::TrailingDataAfterBitfieldRange),
230 false => Ok(Type::BitField(range)),
231 }
232 } else {
233 Err(Error::InvalidTypeSpecifier(input.to_owned()))
234 }?;
235
236 input = &input[type_end..];
237 }
238
239 let mut hint = None;
241
242 if input.starts_with(HINT_PREFIX) {
243 input = &input[HINT_PREFIX.len()..];
245 if input.is_empty() {
246 return Err(Error::MalformedFormatString);
247 }
248
249 hint = match (DisplayHint::parse(input), mode) {
250 (Some(a), _) => Some(a),
251 (None, ParserMode::Strict) => return Err(Error::UnknownDisplayHint(input.to_owned())),
252 (None, ParserMode::ForwardsCompatible) => Some(DisplayHint::Unknown(input.to_owned())),
253 };
254 } else if !input.is_empty() {
255 return Err(Error::UnexpectedContentInFormatString(input.to_owned()));
256 }
257
258 Ok(Param { index, ty, hint })
259}
260
261fn push_literal<'f>(frag: &mut Vec<Fragment<'f>>, unescaped_literal: &'f str) -> Result<(), Error> {
262 let mut last_open = false;
266 let mut last_close = false;
267 for c in unescaped_literal.chars() {
268 match c {
269 '{' => last_open = !last_open,
270 '}' => last_close = !last_close,
271 _ if last_open => return Err(Error::UnmatchedOpenBracket),
272 _ if last_close => return Err(Error::UnmatchedCloseBracket),
273 _ => {}
274 }
275 }
276
277 if last_open {
279 return Err(Error::UnmatchedOpenBracket);
280 } else if last_close {
281 return Err(Error::UnmatchedCloseBracket);
282 }
283
284 let literal = unescaped_literal.replace("{{", "{").replace("}}", "}");
286 frag.push(Fragment::Literal(literal.into()));
287 Ok(())
288}
289
290pub fn get_max_bitfield_range<'a, I>(params: I) -> Option<(u8, u8)>
293where
294 I: Iterator<Item = &'a Parameter> + Clone,
295{
296 let largest_bit_index = params
297 .clone()
298 .map(|param| match ¶m.ty {
299 Type::BitField(range) => range.end,
300 _ => unreachable!(),
301 })
302 .max();
303
304 let smallest_bit_index = params
305 .map(|param| match ¶m.ty {
306 Type::BitField(range) => range.start,
307 _ => unreachable!(),
308 })
309 .min();
310
311 match (smallest_bit_index, largest_bit_index) {
312 (Some(smallest), Some(largest)) => Some((smallest, largest)),
313 (None, None) => None,
314 _ => unreachable!(),
315 }
316}
317
318pub fn parse(format_string: &str, mode: ParserMode) -> Result<Vec<Fragment<'_>>, Error> {
319 let mut fragments = Vec::new();
320
321 let mut end_pos = 0;
323
324 let mut next_arg_index = 0;
326
327 let mut chars = format_string.char_indices();
328 while let Some((brace_pos, ch)) = chars.next() {
329 if ch != '{' {
330 continue;
332 }
333
334 if chars.as_str().starts_with('{') {
336 chars.next(); continue;
339 }
340
341 if brace_pos > end_pos {
342 let unescaped_literal = &format_string[end_pos..brace_pos];
344 push_literal(&mut fragments, unescaped_literal)?;
345 }
346
347 let len = chars
349 .as_str()
350 .find('}')
351 .ok_or(Error::UnmatchedOpenBracket)?;
352 end_pos = brace_pos + 1 + len + 1;
353
354 let param_str = &format_string[brace_pos + 1..][..len];
356 let param = parse_param(param_str, mode)?;
357 fragments.push(Fragment::Parameter(Parameter {
358 index: param.index.unwrap_or_else(|| {
359 let idx = next_arg_index;
361 next_arg_index += 1;
362 idx
363 }),
364 ty: param.ty,
365 hint: param.hint,
366 }));
367 }
368
369 if end_pos != format_string.len() {
371 push_literal(&mut fragments, &format_string[end_pos..])?;
372 }
373
374 let mut args = Vec::new();
376 for frag in &fragments {
377 if let Fragment::Parameter(Parameter { index, ty, .. }) = frag {
378 if args.len() <= *index {
379 args.resize(*index + 1, None);
380 }
381
382 match &args[*index] {
383 None => args[*index] = Some(ty.clone()),
384 Some(other_ty) => match (other_ty, ty) {
385 (Type::BitField(_), Type::BitField(_)) => {} (a, b) if a != b => {
387 return Err(Error::ConflictingTypes(*index, a.clone(), b.clone()))
388 }
389 _ => {}
390 },
391 }
392 }
393 }
394
395 for (index, arg) in args.iter().enumerate() {
397 if arg.is_none() {
398 return Err(Error::UnusedArgument(index));
399 }
400 }
401
402 Ok(fragments)
403}