defmt-parser-0.4.1/.cargo_vcs_info.json0000644000000001440000000000100134260ustar { "git": { "sha1": "042430261980f8d4f9b03a11c6504c0340931141" }, "path_in_vcs": "parser" }defmt-parser-0.4.1/Cargo.toml0000644000000021310000000000100114220ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "defmt-parser" version = "0.4.1" authors = ["The Knurling-rs developers"] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Parsing library for defmt format strings" readme = "README.md" keywords = [ "knurling", "defmt", ] license = "MIT OR Apache-2.0" repository = "https://github.com/knurling-rs/defmt" [package.metadata.docs.rs] rustdoc-args = ["--cfg=docsrs"] [lib] name = "defmt_parser" path = "src/lib.rs" [dependencies.thiserror] version = "2" [dev-dependencies.rstest] version = "0.19" default-features = false [features] unstable = [] defmt-parser-0.4.1/Cargo.toml.orig000064400000000000000000000010341046102023000151040ustar 00000000000000[package] authors = ["The Knurling-rs developers"] description = "Parsing library for defmt format strings" edition = "2021" keywords = ["knurling", "defmt"] license = "MIT OR Apache-2.0" name = "defmt-parser" readme = "README.md" repository = "https://github.com/knurling-rs/defmt" version = "0.4.1" [dependencies] thiserror = "2" [dev-dependencies] rstest = { version = "0.19", default-features = false } [features] # DEPRECATED: noop, will be removed in 1.0 unstable = [] [package.metadata.docs.rs] rustdoc-args = [ "--cfg=docsrs" ] defmt-parser-0.4.1/README.md000064400000000000000000000030561046102023000135020ustar 00000000000000# `defmt-parser` `defmt` ("de format", short for "deferred formatting") is a highly efficient logging framework that targets resource-constrained devices, like microcontrollers. For more details about the framework check the book at . The git version of the defmt book can be viewed at . This library decodes `defmt` frames into Rust structures. It is mainly used by [`defmt-decoder`](https://crates.io/crates/defmt-decoder), and you should prefer using that crate to this one. ## MSRV The minimum supported Rust version is 1.76 (or Ferrocene 24.05). `defmt` is tested against the latest stable Rust version and the MSRV. ## Support `defmt` is part of the [Knurling] project, [Ferrous Systems]' effort at improving tooling used to develop for embedded systems. If you think that our work is useful, consider sponsoring it via [GitHub Sponsors]. ## License Licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions. [Knurling]: https://knurling.ferrous-systems.com/ [Ferrous Systems]: https://ferrous-systems.com/ [GitHub Sponsors]: https://github.com/sponsors/knurling-rs defmt-parser-0.4.1/src/display_hint.rs000064400000000000000000000103561046102023000160500ustar 00000000000000use 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, }, /// `:o` Octal { alternate: bool, zero_pad: usize, }, /// `:b` Binary { alternate: bool, zero_pad: usize, }, /// `:a` Ascii, /// `:?` Debug, /// `:us` `:ms`, formats integers as timestamps in seconds Seconds(TimePrecision), /// `:tus` `:tms` `:ts`, formats integers as human-readable time Time(TimePrecision), /// `: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: Option, }, /// 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 { 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::(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::>(); if parts.len() < 3 || parts.len() > 4 { return Some(DisplayHint::Unknown(s.into())); } return Some(DisplayHint::Bitflags { name: parts[0].into(), package: parts[1].into(), disambiguator: parts[2].into(), // crate_name was added in wire format version 4 crate_name: parts.get(3).map(|&s| s.to_string()), }); } Some(match s { "" => DisplayHint::NoHint { zero_pad }, "us" => DisplayHint::Seconds(TimePrecision::Micros), "ms" => DisplayHint::Seconds(TimePrecision::Millis), "tus" => DisplayHint::Time(TimePrecision::Micros), "tms" => DisplayHint::Time(TimePrecision::Millis), "ts" => DisplayHint::Time(TimePrecision::Seconds), "a" => DisplayHint::Ascii, "b" => DisplayHint::Binary { alternate, zero_pad, }, "o" => DisplayHint::Octal { 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 timestamp #[derive(Clone, Debug, Eq, PartialEq)] pub enum TimePrecision { Micros, 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(s: &str) -> Option<(&str, T)> { 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)) } defmt-parser-0.4.1/src/lib.rs000064400000000000000000000306311046102023000141250ustar 00000000000000//! Parsing library for [`defmt`] format strings. //! //! This is an implementation detail of [`defmt`] and not meant to be consumed by other tools at the //! moment so all the API is unstable. //! //! [`defmt`]: https://github.com/knurling-rs/defmt #![cfg_attr(docsrs, feature(doc_cfg))] #![doc(html_logo_url = "https://knurling.ferrous-systems.com/knurling_logo_light_text.svg")] mod display_hint; #[cfg(test)] mod tests; mod types; use std::{borrow::Cow, ops::Range}; pub use crate::{ display_hint::{DisplayHint, TimePrecision}, types::Type, }; /// The kinds of error this library can return #[derive(thiserror::Error, Debug, PartialEq, Eq, Clone)] pub enum Error { #[error("invalid type specifier `{0:?}`")] InvalidTypeSpecifier(String), #[error("unable to parse given integer")] InvalidInteger(#[from] std::num::ParseIntError), #[error("invalid array specifier (missing length)")] InvalidArraySpecifierMissingLength, #[error("invalid array specifier (missing `]`")] InvalidArraySpecifierMissingBracket, #[error("trailing data after bitfield range")] TrailingDataAfterBitfieldRange, #[error("malformed format string (missing display hint after ':')")] MalformedFormatString, #[error("unknown display hint: {0:?}")] UnknownDisplayHint(String), #[error("unexpected content `{0:?}` in format string")] UnexpectedContentInFormatString(String), #[error("unmatched `{{` in format string")] UnmatchedOpenBracket, #[error("unmatched `}}` in format string")] UnmatchedCloseBracket, #[error("conflicting types for argument {0}: used as {1:?} and {2:?}")] ConflictingTypes(usize, Type, Type), #[error("argument {0} is not used in this format string")] UnusedArgument(usize), } /// A parameter of the form `{{0=Type:hint}}` in a format string. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Parameter { /// The argument index to display at this position. pub index: usize, /// The type of the argument to display, e.g. '=u8', '=bool'. pub ty: Type, /// The display hint, e.g. ':x', ':b', ':a'. pub hint: Option, } /// A part of a format string. #[derive(Clone, Debug, Eq, PartialEq)] pub enum Fragment<'f> { /// A literal string (eg. `"literal "` in `"literal {:?}"`). Literal(Cow<'f, str>), /// A format parameter. Parameter(Parameter), } /// A parsed formatting parameter (contents of `{` `}` block). /// /// # Syntax /// /// ```notrust /// param := '{' [ argument ] [ '=' argtype ] [ ':' format_spec ] '}' /// argument := integer /// /// argtype := bitfield | '?' | format-array | '[?]' | byte-array | '[u8]' | 'istr' | 'str' | /// 'bool' | 'char' | 'u8' | 'u16' | 'u32' | 'u64' | 'u128' | 'usize' | 'i8' | 'i16' | 'i32' | /// 'i64' | 'i128 | 'isize' | 'f32' | 'f64' /// bitfield := integer '..' integer /// format-array := '[?;' spaces integer ']' /// byte-array := '[u8;' spaces integer ']' /// spaces := ' '* /// /// format_spec := [ zero_pad ] type /// zero_pad := '0' integer /// type := 'a' | 'b' | 'o' | 'x' | 'X' | '?' | 'us' /// ``` #[derive(Debug, PartialEq)] struct Param { index: Option, ty: Type, hint: Option, } /// The log level #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd)] pub enum Level { Trace, Debug, Info, Warn, Error, } impl Level { pub fn as_str(self) -> &'static str { match self { Level::Trace => "trace", Level::Debug => "debug", Level::Info => "info", Level::Warn => "warn", Level::Error => "error", } } } fn parse_range(mut s: &str) -> Option<(Range, usize /* consumed */)> { // consume first number let start_digits = s .as_bytes() .iter() .take_while(|b| (**b as char).is_ascii_digit()) .count(); let start = s[..start_digits].parse().ok()?; // next two `char`s should be `..` if &s[start_digits..start_digits + 2] != ".." { return None; } s = &s[start_digits + 2..]; // consume second number let end_digits = s .as_bytes() .iter() .take_while(|b| (**b as char).is_ascii_digit()) .count(); let end = s[..end_digits].parse().ok()?; // check for faulty state if end <= start || start >= 128 || end > 128 { return None; } Some((start..end, start_digits + end_digits + 2)) } /// Parse and consume an array at the beginning of `s`. /// /// Return the length of the array. fn parse_array(mut s: &str) -> Result { // skip spaces let len_pos = s .find(|c: char| c != ' ') .ok_or(Error::InvalidArraySpecifierMissingLength)?; s = &s[len_pos..]; // consume length let after_len = s .find(|c: char| !c.is_ascii_digit()) .ok_or(Error::InvalidArraySpecifierMissingBracket)?; let len = s[..after_len].parse::()?; s = &s[after_len..]; // consume final `]` if s != "]" { return Err(Error::InvalidArraySpecifierMissingBracket); } Ok(len) } /// Parser mode #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ParserMode { /// Rejects unknown display hints Strict, /// Accepts unknown display hints ForwardsCompatible, } /// Parse `Param` from `&str` /// /// * example `input`: `0=Type:hint` (note: no curly braces) fn parse_param(mut input: &str, mode: ParserMode) -> Result { const TYPE_PREFIX: &str = "="; const HINT_PREFIX: &str = ":"; // First, optional argument index. let mut index = None; let index_end = input .find(|c: char| !c.is_ascii_digit()) .unwrap_or(input.len()); if index_end != 0 { index = Some(input[..index_end].parse::()?); } // Then, optional type let mut ty = Type::default(); // when no explicit type; use the default one input = &input[index_end..]; if input.starts_with(TYPE_PREFIX) { // skip the prefix input = &input[TYPE_PREFIX.len()..]; // type is delimited by `HINT_PREFIX` or end-of-string let type_end = input.find(HINT_PREFIX).unwrap_or(input.len()); let type_fragment = &input[..type_end]; const FORMAT_ARRAY_START: &str = "[?;"; const U8_ARRAY_START: &str = "[u8;"; // what comes next is the type ty = if let Ok(ty) = type_fragment.parse() { Ok(ty) } else if let Some(s) = type_fragment.strip_prefix(U8_ARRAY_START) { Ok(Type::U8Array(parse_array(s)?)) } else if let Some(s) = type_fragment.strip_prefix(FORMAT_ARRAY_START) { Ok(Type::FormatArray(parse_array(s)?)) } else if let Some((range, used)) = parse_range(type_fragment) { // Check for bitfield syntax. match used != type_fragment.len() { true => Err(Error::TrailingDataAfterBitfieldRange), false => Ok(Type::BitField(range)), } } else { Err(Error::InvalidTypeSpecifier(input.to_owned())) }?; input = &input[type_end..]; } // Then, optional hint let mut hint = None; if input.starts_with(HINT_PREFIX) { // skip the prefix input = &input[HINT_PREFIX.len()..]; if input.is_empty() { return Err(Error::MalformedFormatString); } hint = match (DisplayHint::parse(input), mode) { (Some(a), _) => Some(a), (None, ParserMode::Strict) => return Err(Error::UnknownDisplayHint(input.to_owned())), (None, ParserMode::ForwardsCompatible) => Some(DisplayHint::Unknown(input.to_owned())), }; } else if !input.is_empty() { return Err(Error::UnexpectedContentInFormatString(input.to_owned())); } Ok(Param { index, ty, hint }) } fn push_literal<'f>(frag: &mut Vec>, unescaped_literal: &'f str) -> Result<(), Error> { // Replace `{{` with `{` and `}}` with `}`. Single braces are errors. // Scan for single braces first. The rest is trivial. let mut last_open = false; let mut last_close = false; for c in unescaped_literal.chars() { match c { '{' => last_open = !last_open, '}' => last_close = !last_close, _ if last_open => return Err(Error::UnmatchedOpenBracket), _ if last_close => return Err(Error::UnmatchedCloseBracket), _ => {} } } // Handle trailing unescaped `{` or `}`. if last_open { return Err(Error::UnmatchedOpenBracket); } else if last_close { return Err(Error::UnmatchedCloseBracket); } // FIXME: This always allocates a `String`, so the `Cow` is useless. let literal = unescaped_literal.replace("{{", "{").replace("}}", "}"); frag.push(Fragment::Literal(literal.into())); Ok(()) } /// Returns `Some(smallest_bit_index, largest_bit_index)` contained in `params` if /// `params` contains any bitfields. Otherwise `None`. pub fn get_max_bitfield_range<'a, I>(params: I) -> Option<(u8, u8)> where I: Iterator + Clone, { let largest_bit_index = params .clone() .map(|param| match ¶m.ty { Type::BitField(range) => range.end, _ => unreachable!(), }) .max(); let smallest_bit_index = params .map(|param| match ¶m.ty { Type::BitField(range) => range.start, _ => unreachable!(), }) .min(); match (smallest_bit_index, largest_bit_index) { (Some(smallest), Some(largest)) => Some((smallest, largest)), (None, None) => None, _ => unreachable!(), } } pub fn parse(format_string: &str, mode: ParserMode) -> Result>, Error> { let mut fragments = Vec::new(); // Index after the `}` of the last format specifier. let mut end_pos = 0; // Next argument index assigned to a parameter without an explicit one. let mut next_arg_index = 0; let mut chars = format_string.char_indices(); while let Some((brace_pos, ch)) = chars.next() { if ch != '{' { // Part of a literal fragment. continue; } // Peek at the next char. if chars.as_str().starts_with('{') { // Escaped `{{`, also part of a literal fragment. chars.next(); // Move after both `{`s. continue; } if brace_pos > end_pos { // There's a literal fragment with at least 1 character before this parameter fragment. let unescaped_literal = &format_string[end_pos..brace_pos]; push_literal(&mut fragments, unescaped_literal)?; } // Else, this is a format specifier. It ends at the next `}`. let len = chars .as_str() .find('}') .ok_or(Error::UnmatchedOpenBracket)?; end_pos = brace_pos + 1 + len + 1; // Parse the contents inside the braces. let param_str = &format_string[brace_pos + 1..][..len]; let param = parse_param(param_str, mode)?; fragments.push(Fragment::Parameter(Parameter { index: param.index.unwrap_or_else(|| { // If there is no explicit index, assign the next one. let idx = next_arg_index; next_arg_index += 1; idx }), ty: param.ty, hint: param.hint, })); } // Trailing literal. if end_pos != format_string.len() { push_literal(&mut fragments, &format_string[end_pos..])?; } // Check for argument type conflicts. let mut args = Vec::new(); for frag in &fragments { if let Fragment::Parameter(Parameter { index, ty, .. }) = frag { if args.len() <= *index { args.resize(*index + 1, None); } match &args[*index] { None => args[*index] = Some(ty.clone()), Some(other_ty) => match (other_ty, ty) { (Type::BitField(_), Type::BitField(_)) => {} // FIXME: Bitfield range shouldn't be part of the type. (a, b) if a != b => { return Err(Error::ConflictingTypes(*index, a.clone(), b.clone())) } _ => {} }, } } } // Check that argument indices are dense (all arguments must be used). for (index, arg) in args.iter().enumerate() { if arg.is_none() { return Err(Error::UnusedArgument(index)); } } Ok(fragments) } defmt-parser-0.4.1/src/tests.rs000064400000000000000000000210621046102023000145170ustar 00000000000000use rstest::rstest; use super::*; #[rstest] #[case::noo_param("", None, Type::Format, None)] #[case::one_param_type("=u8", None, Type::U8, None)] #[case::one_param_hint(":a", None, Type::Format, Some(DisplayHint::Ascii))] #[case::one_param_index("1", Some(1), Type::Format, None)] #[case::two_param_type_hint("=u8:x", None, Type::U8, Some(DisplayHint::Hexadecimal {alternate: false, uppercase: false, zero_pad: 0}))] #[case::two_param_index_type("0=u8", Some(0), Type::U8, None)] #[case::two_param_index_hint("0:a", Some(0), Type::Format, Some(DisplayHint::Ascii))] #[case::two_param_type_hint("=[u8]:#04x", None, Type::U8Slice, Some(DisplayHint::Hexadecimal {alternate: true, uppercase: false, zero_pad: 4}))] #[case::all_param("1=u8:b", Some(1), Type::U8, Some(DisplayHint::Binary { alternate: false, zero_pad: 0}))] fn all_parse_param_cases( #[case] input: &str, #[case] index: Option, #[case] ty: Type, #[case] hint: Option, ) { assert_eq!( parse_param(input, ParserMode::Strict), Ok(Param { index, ty, hint }) ); } #[rstest] #[case(":a", DisplayHint::Ascii)] #[case(":b", DisplayHint::Binary { alternate: false, zero_pad: 0 })] #[case(":#b", DisplayHint::Binary { alternate: true, zero_pad: 0 })] #[case(":o", DisplayHint::Octal { alternate: false, zero_pad: 0 })] #[case(":#o", DisplayHint::Octal { alternate: true, zero_pad: 0 })] #[case(":x", DisplayHint::Hexadecimal { alternate: false, uppercase: false, zero_pad: 0 })] #[case(":02x", DisplayHint::Hexadecimal { alternate: false, uppercase: false, zero_pad: 2 })] #[case(":#x", DisplayHint::Hexadecimal { alternate: true, uppercase: false, zero_pad: 0 })] #[case(":#04x", DisplayHint::Hexadecimal { alternate: true, uppercase: false, zero_pad: 4 })] #[case(":X", DisplayHint::Hexadecimal { alternate: false, uppercase: true, zero_pad: 0 })] #[case(":#X", DisplayHint::Hexadecimal { alternate: true, uppercase: true, zero_pad: 0 })] #[case(":ms", DisplayHint::Seconds(TimePrecision::Millis))] #[case(":us", DisplayHint::Seconds(TimePrecision::Micros))] #[case(":ts", DisplayHint::Time(TimePrecision::Seconds))] #[case(":tms", DisplayHint::Time(TimePrecision::Millis))] #[case(":tus", DisplayHint::Time(TimePrecision::Micros))] #[case(":iso8601ms", DisplayHint::ISO8601(TimePrecision::Millis))] #[case(":iso8601s", DisplayHint::ISO8601(TimePrecision::Seconds))] #[case(":?", DisplayHint::Debug)] #[case(":02", DisplayHint::NoHint { zero_pad: 2 })] fn all_display_hints(#[case] input: &str, #[case] hint: DisplayHint) { assert_eq!( parse_param(input, ParserMode::Strict), Ok(Param { index: None, ty: Type::Format, hint: Some(hint), }) ); } #[test] // separate test, because of `ParserMode::ForwardsCompatible` fn display_hint_unknown() { assert_eq!( parse_param(":unknown", ParserMode::ForwardsCompatible), Ok(Param { index: None, ty: Type::Format, hint: Some(DisplayHint::Unknown("unknown".to_string())), }) ); } #[rstest] #[case("=i8", Type::I8)] #[case("=i16", Type::I16)] #[case("=i32", Type::I32)] #[case("=i64", Type::I64)] #[case("=i128", Type::I128)] #[case("=isize", Type::Isize)] #[case("=u8", Type::U8)] #[case("=u16", Type::U16)] #[case("=u32", Type::U32)] #[case("=u64", Type::U64)] #[case("=u128", Type::U128)] #[case("=usize", Type::Usize)] #[case("=f32", Type::F32)] #[case("=f64", Type::F64)] #[case("=bool", Type::Bool)] #[case("=?", Type::Format)] #[case("=str", Type::Str)] #[case("=[u8]", Type::U8Slice)] fn all_types(#[case] input: &str, #[case] ty: Type) { assert_eq!( parse_param(input, ParserMode::Strict), Ok(Param { index: None, ty, hint: None, }) ); } #[rstest] #[case::implicit("{=u8}{=u16}", [(0, Type::U8), (1, Type::U16)])] #[case::single_parameter_formatted_twice("{=u8}{0=u8}", [(0, Type::U8), (0, Type::U8)])] #[case::explicit_index("{=u8}{1=u16}", [(0, Type::U8), (1, Type::U16)])] #[case::reversed_order("{1=u8}{0=u16}", [(1, Type::U8), (0, Type::U16)])] fn index(#[case] input: &str, #[case] params: [(usize, Type); 2]) { assert_eq!( parse(input, ParserMode::Strict), Ok(vec![ Fragment::Parameter(Parameter { index: params[0].0, ty: params[0].1.clone(), hint: None, }), Fragment::Parameter(Parameter { index: params[1].0, ty: params[1].1.clone(), hint: None, }), ]) ); } #[rstest] #[case("{=0..4}", 0..4)] #[case::just_inside_128bit_range_1("{=0..128}", 0..128)] #[case::just_inside_128bit_range_2("{=127..128}", 127..128)] fn range(#[case] input: &str, #[case] bit_field: Range) { assert_eq!( parse(input, ParserMode::Strict), Ok(vec![Fragment::Parameter(Parameter { index: 0, ty: Type::BitField(bit_field), hint: None, })]) ); } #[test] fn multiple_ranges() { assert_eq!( parse("{0=30..31}{1=0..4}{1=2..6}", ParserMode::Strict), Ok(vec![ Fragment::Parameter(Parameter { index: 0, ty: Type::BitField(30..31), hint: None, }), Fragment::Parameter(Parameter { index: 1, ty: Type::BitField(0..4), hint: None, }), Fragment::Parameter(Parameter { index: 1, ty: Type::BitField(2..6), hint: None, }), ]) ); } #[rstest] #[case("{=[u8; 0]}", 0)] #[case::space_is_optional("{=[u8;42]}", 42)] #[case::multiple_spaces_are_ok("{=[u8; 257]}", 257)] fn arrays(#[case] input: &str, #[case] length: usize) { assert_eq!( parse(input, ParserMode::Strict), Ok(vec![Fragment::Parameter(Parameter { index: 0, ty: Type::U8Array(length), hint: None, })]) ); } #[rstest] #[case::no_tabs("{=[u8; \t 3]}")] #[case::no_linebreaks("{=[u8; \n 3]}")] #[case::too_large("{=[u8; 9999999999999999999999999]}")] fn arrays_err(#[case] input: &str) { assert!(parse(input, ParserMode::Strict).is_err()); } #[rstest] #[case("{=dunno}", Error::InvalidTypeSpecifier("dunno".to_string()))] #[case("{dunno}", Error::UnexpectedContentInFormatString("dunno".to_string()))] #[case("{=u8;x}", Error::InvalidTypeSpecifier("u8;x".to_string()))] #[case("{dunno=u8:x}", Error::UnexpectedContentInFormatString("dunno=u8:x".to_string()))] #[case("{0dunno}", Error::UnexpectedContentInFormatString("dunno".to_string()))] #[case("{:}", Error::MalformedFormatString)] #[case::stray_braces_1("}string", Error::UnmatchedCloseBracket)] #[case::stray_braces_2("{string", Error::UnmatchedOpenBracket)] #[case::stray_braces_3("}", Error::UnmatchedCloseBracket)] #[case::stray_braces_4("{", Error::UnmatchedOpenBracket)] #[case::range_empty("{=0..0}", Error::InvalidTypeSpecifier("0..0".to_string()))] #[case::range_start_gt_end("{=1..0}", Error::InvalidTypeSpecifier("1..0".to_string()))] #[case::range_out_of_128bit_1("{=0..129}", Error::InvalidTypeSpecifier("0..129".to_string()))] #[case::range_out_of_128bit_2("{=128..128}", Error::InvalidTypeSpecifier("128..128".to_string()))] #[case::range_missing_parts_1("{=0..4", Error::UnmatchedOpenBracket)] #[case::range_missing_parts_2("{=0..}", Error::InvalidTypeSpecifier("0..".to_string()))] #[case::range_missing_parts_3("{=..4}", Error::InvalidTypeSpecifier("..4".to_string()))] #[case::range_missing_parts_4("{=0.4}", Error::InvalidTypeSpecifier("0.4".to_string()))] #[case::range_missing_parts_5("{=0...4}", Error::InvalidTypeSpecifier("0...4".to_string()))] #[case::index_with_different_types( "{0=u8}{0=u16}", Error::ConflictingTypes(0, Type::U8, Type::U16) )] #[case::index_with_different_types_bool_is_autoassigned_index_0( "Hello {1=u16} {0=u8} {=bool}", Error::ConflictingTypes(0, Type::U8, Type::Bool) )] #[case::index_0_is_omitted("{1=u8}", Error::UnusedArgument(0))] #[case::index_1_is_missing("{2=u8}{=u16}", Error::UnusedArgument(1))] #[case::index_0_is_missing("{2=u8}{1=u16}", Error::UnusedArgument(0))] fn error_msg(#[case] input: &str, #[case] err: Error) { assert_eq!(parse(input, ParserMode::Strict), Err(err)); } #[rstest] #[case("}}", "}")] #[case("{{", "{")] #[case("literal{{literal", "literal{literal")] #[case("literal}}literal", "literal}literal")] #[case("{{}}", "{}")] #[case("}}{{", "}{")] fn escaped_braces(#[case] input: &str, #[case] literal: &str) { assert_eq!( parse(input, ParserMode::Strict), Ok(vec![Fragment::Literal(literal.into())]) ); } defmt-parser-0.4.1/src/types.rs000064400000000000000000000036711046102023000145270ustar 00000000000000use std::{ops::Range, str::FromStr}; #[derive(Clone, Debug, Default, Eq, PartialEq)] pub enum Type { BitField(Range), Bool, /// A single Unicode character Char, Debug, Display, FormatSequence, F32, F64, /// `{=?}` OR `{}` #[default] // when not specified in the format string, this type is assumed Format, FormatArray(usize), // FIXME: This `usize` is not the target's `usize`; use `u64` instead? /// `{=[?]}` FormatSlice, I8, I16, I32, I64, I128, Isize, /// Interned string index. IStr, /// String slice (i.e. passed directly; not as interned string indices). Str, U8, U16, U32, U64, U128, Usize, /// Byte slice `{=[u8]}`. U8Slice, U8Array(usize), // FIXME: This `usize` is not the target's `usize`; use `u64` instead? } // FIXME: either all or none of the type parsing should be done in here impl FromStr for Type { type Err = (); fn from_str(s: &str) -> Result { Ok(match s { "u8" => Type::U8, "u16" => Type::U16, "u32" => Type::U32, "u64" => Type::U64, "u128" => Type::U128, "usize" => Type::Usize, "i8" => Type::I8, "i16" => Type::I16, "i32" => Type::I32, "i64" => Type::I64, "i128" => Type::I128, "isize" => Type::Isize, "f32" => Type::F32, "f64" => Type::F64, "bool" => Type::Bool, "str" => Type::Str, "istr" => Type::IStr, "__internal_Debug" => Type::Debug, "__internal_Display" => Type::Display, "__internal_FormatSequence" => Type::FormatSequence, "[u8]" => Type::U8Slice, "?" => Type::Format, "[?]" => Type::FormatSlice, "char" => Type::Char, _ => return Err(()), }) } }