Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
@@ -0,0 +1 @@
{"$comment":"This file only protects against accidental modifications. It is not a security mechanism and does not protect against malicious changes.","files":{".cargo_vcs_info.json":"7ab8f01085721143c43db5db2528806f393df2058d03c5b8da20634e14886a05","Cargo.lock":"038b16e51dc7933a17d337845f0485ddd71d5dc420293f8266843df60c1877a7","Cargo.toml":"c4c4df5b09e53450267eacadf1f7f6a0d3d5fc463bc450e199f0d06eba8e6ab3","Cargo.toml.orig":"5d5c0e139cf4076aad5d1f8b992a7b8c9095f5443dab0f9d1da020101adea245","README.md":"a40aae66ea32ccd2b2c30ea976d4ac02fab1025a8dece8bfae6aa83bdcfd9c0e","src/display_hint.rs":"cb3c75bb25ad94142e6d8d97e586e37fd6c4e0bcc5294b684693024f608c8b82","src/lib.rs":"0e0ec0ce038bceb7875717c118916c333a718e67d58c696ccd4f385e4c9feb98","src/tests.rs":"546bc5e8668fec1bec7acfcd5f2f871b0d20ef904827bb501d00b6e6e251814c","src/types.rs":"f518f4ab930d0b4d5f4ac68cc846a74ffc29130e12c237b3153ab8b537f2a990"},"package":"10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"}
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "4a8cdb44891ed57b8ff5a023b6bec7137c48708f"
},
"path_in_vcs": "parser"
}
+170
View File
@@ -0,0 +1,170 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "defmt-parser"
version = "1.0.0"
dependencies = [
"rstest",
"thiserror",
]
[[package]]
name = "glob"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "proc-macro2"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "relative-path"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
[[package]]
name = "rstest"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5316d2a1479eeef1ea21e7f9ddc67c191d497abc8fc3ba2467857abbb68330"
dependencies = [
"rstest_macros",
"rustc_version",
]
[[package]]
name = "rstest_macros"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04a9df72cc1f67020b0d63ad9bfe4a323e459ea7eb68e03bd9824db49f9a4c25"
dependencies = [
"cfg-if",
"glob",
"proc-macro2",
"quote",
"regex",
"relative-path",
"rustc_version",
"syn",
"unicode-ident",
]
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "semver"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
[[package]]
name = "syn"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+47
View File
@@ -0,0 +1,47 @@
# 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 = "1.0.0"
authors = ["The Knurling-rs developers"]
build = false
autolib = 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"]
[features]
unstable = []
[lib]
name = "defmt_parser"
path = "src/lib.rs"
[dependencies.thiserror]
version = "2"
[dev-dependencies.rstest]
version = "0.19"
default-features = false
+23
View File
@@ -0,0 +1,23 @@
[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 = "1.0.0"
[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" ]
+42
View File
@@ -0,0 +1,42 @@
# `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 <https://defmt.ferrous-systems.com>.
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
+151
View File
@@ -0,0 +1,151 @@
use std::str::FromStr;
/// All display hints
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
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<String>,
},
/// `:cbor`: There is CBOR data encoded in those bytes, to be shown in diagnostic notation.
///
/// Technically, the byte string interpreted as a CBOR sequence, and shown in the diagnostic
/// notation of a sequence. That is identical to processing a single CBOR item if there is just
/// one present, but also allows reporting multiple items from consecutive memory; diagnostic
/// notation turns those into comma separated items.
// Should we have a flag to say "do a more display style stringification" (like, if you
// recognize `54([64,h'20010db8'])`, don't show "IP'2001:db8::/64'" but just "2001:db8::64")?
// Should we allow additional params that give a CDDL that further guides processing (like,
// when data is not tagged but the shape is known for processing anyway)?
Cbor,
/// 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<_>>();
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),
"cbor" => DisplayHint::Cbor,
"?" => 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<T: FromStr>(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))
}
+403
View File
@@ -0,0 +1,403 @@
//! 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<DisplayHint>,
}
/// 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<usize>,
ty: Type,
hint: Option<DisplayHint>,
}
/// 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<u8>, 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<usize, Error> {
// 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::<usize>()?;
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<Param, Error> {
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::<usize>()?);
}
// 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<Fragment<'f>>, 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<Item = &'a Parameter> + Clone,
{
let largest_bit_index = params
.clone()
.map(|param| match &param.ty {
Type::BitField(range) => range.end,
_ => unreachable!(),
})
.max();
let smallest_bit_index = params
.map(|param| match &param.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<Vec<Fragment<'_>>, 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)
}
+234
View File
@@ -0,0 +1,234 @@
use 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<usize>,
#[case] ty: Type,
#[case] hint: Option<DisplayHint>,
) {
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<u8>) {
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())])
);
}
+81
View File
@@ -0,0 +1,81 @@
use std::{ops::Range, str::FromStr};
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub enum Type {
BitField(Range<u8>),
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<Self, Self::Err> {
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(()),
})
}
}