Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean defmt-parser #744

Merged
merged 12 commits into from
Mar 23, 2023
Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

- [#744]: `defmt-parser`: Clean and simplify
- [#742]: `defmt-decoder`: Include crate name in symbol name
- [#743]: `defmt-parser`: Simplify tests with `rstest`
- [#741]: `defmt-macros`: Disable default-features for `rstest`
Expand All @@ -17,6 +18,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- [#603]: `defmt`: Raw pointers now print as `0x1234` instead of `1234`
- [#536]: `defmt-parser`: Switch to using an enum for errors, and add some help text pointing you to the defmt docs if you use the wrong type specifier in a format string.

[#744]: https:/knurling-rs/defmt/pull/744
[#742]: https:/knurling-rs/defmt/pull/742
[#743]: https:/knurling-rs/defmt/pull/743
[#741]: https:/knurling-rs/defmt/pull/741
Expand Down
123 changes: 123 additions & 0 deletions parser/src/display_hint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use std::str::FromStr;

/// All display hints
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DisplayHint {
NoHint {
zero_pad: usize,
},
/// `:x` OR `:X`
Hexadecimal {
alternate: bool,
uppercase: bool,
zero_pad: usize,
},
/// `:b`
Binary {
alternate: bool,
zero_pad: usize,
},
/// `:a`
Ascii,
/// `:?`
Debug,
/// `:us`, formats integers as timestamps in microseconds
Microseconds,
/// `:iso8601{ms,s}`, formats integers as timestamp in ISO8601 date time format
ISO8601(TimePrecision),
/// `__internal_bitflags_NAME` instructs the decoder to print the flags that are set, instead of
/// the raw value.
Bitflags {
name: String,
package: String,
disambiguator: String,
crate_name: String,
},
/// Display hints currently not supported / understood
Unknown(String),
}

impl DisplayHint {
/// Parses the display hint (e.g. the `#x` in `{=u8:#x}`)
pub(crate) fn parse(mut s: &str) -> Option<Self> {
const BITFLAGS_HINT_START: &str = "__internal_bitflags_";

// The `#` comes before any padding hints (I think this matches core::fmt).
// It is ignored for types that don't have an alternate representation.
let alternate = if let Some(rest) = s.strip_prefix('#') {
s = rest;
true
} else {
false
};

let zero_pad = if let Some(rest) = s.strip_prefix('0') {
let (rest, columns) = parse_integer::<usize>(rest)?;
s = rest;
columns
} else {
0 // default behavior is the same as no zero-padding.
};

if let Some(stripped) = s.strip_prefix(BITFLAGS_HINT_START) {
let parts = stripped.split('@').collect::<Vec<_>>();
match *parts {
[bitflags_name, package, disambiguator, crate_name] => {
return Some(DisplayHint::Bitflags {
name: bitflags_name.into(),
package: package.into(),
disambiguator: disambiguator.into(),
crate_name: crate_name.into(),
});
}
_ => return Some(DisplayHint::Unknown(s.into())),
}
}

Some(match s {
"" => DisplayHint::NoHint { zero_pad },
"us" => DisplayHint::Microseconds,
"a" => DisplayHint::Ascii,
"b" => DisplayHint::Binary {
alternate,
zero_pad,
},
"x" => DisplayHint::Hexadecimal {
alternate,
uppercase: false,
zero_pad,
},
"X" => DisplayHint::Hexadecimal {
alternate,
uppercase: true,
zero_pad,
},
"iso8601ms" => DisplayHint::ISO8601(TimePrecision::Millis),
"iso8601s" => DisplayHint::ISO8601(TimePrecision::Seconds),
"?" => DisplayHint::Debug,
_ => return None,
})
}
}

/// Precision of ISO8601 datetime
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TimePrecision {
Millis,
Seconds,
}

/// Parses an integer at the beginning of `s`.
///
/// Returns the integer and remaining text, if `s` started with an integer. Any errors parsing the
/// number (which we already know only contains digits) are silently ignored.
fn parse_integer<T: FromStr>(s: &str) -> Option<(&str, usize)> {
let start_digits = s
.as_bytes()
.iter()
.copied()
.take_while(|b| b.is_ascii_digit())
.count();
let num = s[..start_digits].parse().ok()?;
Some((&s[start_digits..], num))
}
Loading