defmt_parser/
lib.rs

1//! Parsing library for [`defmt`] format strings.
2//!
3//! This is an implementation detail of [`defmt`] and not meant to be consumed by other tools at the
4//! moment so all the API is unstable.
5//!
6//! [`defmt`]: https://github.com/knurling-rs/defmt
7
8#![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/// The kinds of error this library can return
24#[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/// A parameter of the form `{{0=Type:hint}}` in a format string.
53#[derive(Clone, Debug, Eq, PartialEq)]
54pub struct Parameter {
55    /// The argument index to display at this position.
56    pub index: usize,
57    /// The type of the argument to display, e.g. '=u8', '=bool'.
58    pub ty: Type,
59    /// The display hint, e.g. ':x', ':b', ':a'.
60    pub hint: Option<DisplayHint>,
61}
62
63/// A part of a format string.
64#[derive(Clone, Debug, Eq, PartialEq)]
65pub enum Fragment<'f> {
66    /// A literal string (eg. `"literal "` in `"literal {:?}"`).
67    Literal(Cow<'f, str>),
68
69    /// A format parameter.
70    Parameter(Parameter),
71}
72
73/// A parsed formatting parameter (contents of `{` `}` block).
74///
75/// # Syntax
76///
77/// ```notrust
78/// param := '{' [ argument ] [ '=' argtype ] [ ':' format_spec ] '}'
79/// argument := integer
80///
81/// argtype := bitfield | '?' | format-array | '[?]' | byte-array | '[u8]' | 'istr' | 'str' |
82///     'bool' | 'char' | 'u8' | 'u16' | 'u32' | 'u64' | 'u128' | 'usize' | 'i8' | 'i16' | 'i32' |
83///     'i64' | 'i128 | 'isize' | 'f32' | 'f64'
84/// bitfield := integer '..' integer
85/// format-array := '[?;' spaces integer ']'
86/// byte-array := '[u8;' spaces integer ']'
87/// spaces := ' '*
88///
89/// format_spec := [ zero_pad ] type
90/// zero_pad := '0' integer
91/// type := 'a' | 'b' | 'o' | 'x' | 'X' | '?' | 'us'
92/// ```
93#[derive(Debug, PartialEq)]
94struct Param {
95    index: Option<usize>,
96    ty: Type,
97    hint: Option<DisplayHint>,
98}
99
100/// The log level
101#[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 /* consumed */)> {
123    // consume first number
124    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    // next two `char`s should be `..`
132    if &s[start_digits..start_digits + 2] != ".." {
133        return None;
134    }
135    s = &s[start_digits + 2..];
136
137    // consume second number
138    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    // check for faulty state
146    if end <= start || start >= 128 || end > 128 {
147        return None;
148    }
149
150    Some((start..end, start_digits + end_digits + 2))
151}
152
153/// Parse and consume an array at the beginning of `s`.
154///
155/// Return the length of the array.
156fn parse_array(mut s: &str) -> Result<usize, Error> {
157    // skip spaces
158    let len_pos = s
159        .find(|c: char| c != ' ')
160        .ok_or(Error::InvalidArraySpecifierMissingLength)?;
161    s = &s[len_pos..];
162
163    // consume length
164    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    // consume final `]`
171    if s != "]" {
172        return Err(Error::InvalidArraySpecifierMissingBracket);
173    }
174
175    Ok(len)
176}
177
178/// Parser mode
179#[derive(Clone, Copy, Debug, Eq, PartialEq)]
180pub enum ParserMode {
181    /// Rejects unknown display hints
182    Strict,
183    /// Accepts unknown display hints
184    ForwardsCompatible,
185}
186
187/// Parse `Param` from `&str`
188///
189/// * example `input`: `0=Type:hint` (note: no curly braces)
190fn parse_param(mut input: &str, mode: ParserMode) -> Result<Param, Error> {
191    const TYPE_PREFIX: &str = "=";
192    const HINT_PREFIX: &str = ":";
193
194    // First, optional argument index.
195    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    // Then, optional type
205    let mut ty = Type::default(); // when no explicit type; use the default one
206    input = &input[index_end..];
207
208    if input.starts_with(TYPE_PREFIX) {
209        // skip the prefix
210        input = &input[TYPE_PREFIX.len()..];
211
212        // type is delimited by `HINT_PREFIX` or end-of-string
213        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        // what comes next is the type
220        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            // Check for bitfield syntax.
228            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    // Then, optional hint
240    let mut hint = None;
241
242    if input.starts_with(HINT_PREFIX) {
243        // skip the prefix
244        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    // Replace `{{` with `{` and `}}` with `}`. Single braces are errors.
263
264    // Scan for single braces first. The rest is trivial.
265    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    // Handle trailing unescaped `{` or `}`.
278    if last_open {
279        return Err(Error::UnmatchedOpenBracket);
280    } else if last_close {
281        return Err(Error::UnmatchedCloseBracket);
282    }
283
284    // FIXME: This always allocates a `String`, so the `Cow` is useless.
285    let literal = unescaped_literal.replace("{{", "{").replace("}}", "}");
286    frag.push(Fragment::Literal(literal.into()));
287    Ok(())
288}
289
290/// Returns `Some(smallest_bit_index, largest_bit_index)` contained in `params` if
291/// `params` contains any bitfields. Otherwise `None`.
292pub 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 &param.ty {
299            Type::BitField(range) => range.end,
300            _ => unreachable!(),
301        })
302        .max();
303
304    let smallest_bit_index = params
305        .map(|param| match &param.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    // Index after the `}` of the last format specifier.
322    let mut end_pos = 0;
323
324    // Next argument index assigned to a parameter without an explicit one.
325    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            // Part of a literal fragment.
331            continue;
332        }
333
334        // Peek at the next char.
335        if chars.as_str().starts_with('{') {
336            // Escaped `{{`, also part of a literal fragment.
337            chars.next(); // Move after both `{`s.
338            continue;
339        }
340
341        if brace_pos > end_pos {
342            // There's a literal fragment with at least 1 character before this parameter fragment.
343            let unescaped_literal = &format_string[end_pos..brace_pos];
344            push_literal(&mut fragments, unescaped_literal)?;
345        }
346
347        // Else, this is a format specifier. It ends at the next `}`.
348        let len = chars
349            .as_str()
350            .find('}')
351            .ok_or(Error::UnmatchedOpenBracket)?;
352        end_pos = brace_pos + 1 + len + 1;
353
354        // Parse the contents inside the braces.
355        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                // If there is no explicit index, assign the next one.
360                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    // Trailing literal.
370    if end_pos != format_string.len() {
371        push_literal(&mut fragments, &format_string[end_pos..])?;
372    }
373
374    // Check for argument type conflicts.
375    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(_)) => {} // FIXME: Bitfield range shouldn't be part of the type.
386                    (a, b) if a != b => {
387                        return Err(Error::ConflictingTypes(*index, a.clone(), b.clone()))
388                    }
389                    _ => {}
390                },
391            }
392        }
393    }
394
395    // Check that argument indices are dense (all arguments must be used).
396    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}