#![cfg(feature = "unstable")]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, doc(cfg(unstable)))]
use std::borrow::Cow;
use std::ops::Range;
#[derive(Clone, Debug, PartialEq)]
pub struct Parameter {
pub index: usize,
pub ty: Type,
pub hint: Option<DisplayHint>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum DisplayHint {
Hexadecimal { is_uppercase: bool },
Binary,
Ascii,
Debug,
Microseconds,
Unknown(String),
}
#[derive(Clone, Debug, PartialEq)]
pub enum Fragment<'f> {
Literal(Cow<'f, str>),
Parameter(Parameter),
}
#[derive(Debug, PartialEq)]
struct Param {
index: Option<usize>,
ty: Type,
hint: Option<DisplayHint>,
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub enum Level {
Trace,
Debug,
Info,
Warn,
Error,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Type {
BitField(Range<u8>),
Bool,
Format,
FormatSlice,
FormatArray(usize),
Debug,
Display,
I8,
I16,
I32,
I64,
I128,
Isize,
Str,
IStr,
U8,
U16,
U24,
U32,
U64,
U128,
Usize,
U8Slice,
U8Array(usize),
F32,
F64,
Char,
}
impl Default for Type {
fn default() -> Self {
Type::Format
}
}
fn is_digit(c: Option<char>) -> bool {
matches!(c.unwrap_or('\0'), '0'..='9')
}
fn parse_range(mut s: &str) -> Option<(Range<u8>, usize )> {
let start_digits = s
.as_bytes()
.iter()
.take_while(|b| is_digit(Some(**b as char)))
.count();
let start = s[..start_digits].parse().ok()?;
if &s[start_digits..start_digits + 2] != ".." {
return None;
}
s = &s[start_digits + 2..];
let end_digits = s
.as_bytes()
.iter()
.take_while(|b| is_digit(Some(**b as char)))
.count();
let end = s[..end_digits].parse().ok()?;
if end <= start {
return None;
}
if start >= 128 || end > 128 {
return None;
}
Some((start..end, start_digits + end_digits + 2))
}
fn parse_array(mut s: &str) -> Result<usize, Cow<'static, str>> {
let len_pos = s
.find(|c: char| c != ' ')
.ok_or("invalid array specifier (missing length)")?;
s = &s[len_pos..];
let after_len = s
.find(|c: char| !c.is_digit(10))
.ok_or("invalid array specifier (missing `]`)")?;
let len = s[..after_len].parse::<usize>().map_err(|e| e.to_string())?;
s = &s[after_len..];
if s != "]" {
return Err("invalid array specifier (missing `]`)".into());
}
Ok(len)
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ParserMode {
Strict,
ForwardsCompatible,
}
fn parse_param(mut input: &str, mode: ParserMode) -> Result<Param, Cow<'static, str>> {
const TYPE_PREFIX: &str = "=";
const HINT_PREFIX: &str = ":";
let mut index = None;
let index_end = input
.find(|c: char| !c.is_digit(10))
.unwrap_or_else(|| input.len());
if index_end != 0 {
index = Some(
input[..index_end]
.parse::<usize>()
.map_err(|e| e.to_string())?,
);
}
let mut ty = Type::default();
input = &input[index_end..];
if input.starts_with(TYPE_PREFIX) {
input = &input[TYPE_PREFIX.len()..];
let type_end = input.find(HINT_PREFIX).unwrap_or_else(|| input.len());
let type_fragment = &input[..type_end];
static FORMAT_ARRAY_START: &str = "[?;";
static U8_ARRAY_START: &str = "[u8;";
ty = match type_fragment {
"u8" => Type::U8,
"u16" => Type::U16,
"u24" => Type::U24,
"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,
"[u8]" => Type::U8Slice,
"?" => Type::Format,
"[?]" => Type::FormatSlice,
"char" => Type::Char,
_ if input.starts_with(U8_ARRAY_START) => {
let len = parse_array(&type_fragment[U8_ARRAY_START.len()..])?;
Type::U8Array(len)
}
_ if input.starts_with(FORMAT_ARRAY_START) => {
let len = parse_array(&type_fragment[FORMAT_ARRAY_START.len()..])?;
Type::FormatArray(len)
}
_ => {
match parse_range(type_fragment) {
Some((range, used)) => {
if used != type_fragment.len() {
return Err("trailing data after bitfield range".into());
}
Type::BitField(range)
}
None => {
return Err(format!(
"malformed format string (invalid type specifier `{}`)",
input
)
.into());
}
}
}
};
input = &input[type_end..];
}
let mut hint = None;
if input.starts_with(HINT_PREFIX) {
input = &input[HINT_PREFIX.len()..];
hint = Some(match input {
"µs" => DisplayHint::Microseconds,
"a" => DisplayHint::Ascii,
"b" => DisplayHint::Binary,
"x" => DisplayHint::Hexadecimal {
is_uppercase: false,
},
"X" => DisplayHint::Hexadecimal { is_uppercase: true },
"?" => DisplayHint::Debug,
_ => match mode {
ParserMode::Strict => {
return Err(format!("unknown display hint: {:?}", input).into());
}
ParserMode::ForwardsCompatible => DisplayHint::Unknown(input.to_owned()),
},
});
} else if !input.is_empty() {
return Err(format!("unexpected content {:?} in format string", input).into());
}
Ok(Param { index, ty, hint })
}
fn push_literal<'f>(
frag: &mut Vec<Fragment<'f>>,
unescaped_literal: &'f str,
) -> Result<(), Cow<'static, str>> {
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("unmatched `{` in format string".into());
}
if last_close {
return Err("unmatched `}` in format string".into());
}
}
}
}
if last_open {
return Err("unmatched `{` in format string".into());
}
if last_close {
return Err("unmatched `}` in format string".into());
}
let literal = unescaped_literal.replace("{{", "{").replace("}}", "}");
frag.push(Fragment::Literal(literal.into()));
Ok(())
}
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 ¶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<'f>(
format_string: &'f str,
mode: ParserMode,
) -> Result<Vec<Fragment<'f>>, Cow<'static, str>> {
let mut fragments = Vec::new();
let mut end_pos = 0;
let mut next_arg_index = 0;
let mut chars = format_string.char_indices();
while let Some((brace_pos, ch)) = chars.next() {
if ch != '{' {
continue;
}
if chars.as_str().starts_with('{') {
chars.next();
continue;
}
if brace_pos > end_pos {
let unescaped_literal = &format_string[end_pos..brace_pos];
push_literal(&mut fragments, unescaped_literal)?;
}
let len = chars
.as_str()
.find('}')
.ok_or("missing `}` in format string")?;
end_pos = brace_pos + 1 + len + 1;
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(|| {
let idx = next_arg_index;
next_arg_index += 1;
idx
}),
ty: param.ty,
hint: param.hint,
}));
}
if end_pos != format_string.len() {
push_literal(&mut fragments, &format_string[end_pos..])?;
}
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 &mut args[*index] {
none @ None => {
*none = Some(ty.clone());
}
Some(other_ty) => {
match (&*other_ty, ty) {
(Type::BitField(_), Type::BitField(_)) => {}
(a, b) if a != b => {
return Err(format!(
"conflicting types for argument {}: used as {:?} and {:?}",
index, other_ty, ty
)
.into());
}
_ => {}
}
}
}
}
}
for (index, arg) in args.iter().enumerate() {
if arg.is_none() {
return Err(format!("argument {} is not used in this format string", index).into());
}
}
Ok(fragments)
}
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",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_parse_param_cases() {
assert_eq!(
parse_param("", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::Format,
hint: None,
})
);
assert_eq!(
parse_param("=u8", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::U8,
hint: None,
})
);
assert_eq!(
parse_param(":a", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::Format,
hint: Some(DisplayHint::Ascii),
})
);
assert_eq!(
parse_param("1", ParserMode::Strict),
Ok(Param {
index: Some(1),
ty: Type::Format,
hint: None,
})
);
assert_eq!(
parse_param("=u8:x", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::U8,
hint: Some(DisplayHint::Hexadecimal {
is_uppercase: false
}),
})
);
assert_eq!(
parse_param("0=u8", ParserMode::Strict),
Ok(Param {
index: Some(0),
ty: Type::U8,
hint: None,
})
);
assert_eq!(
parse_param("0:a", ParserMode::Strict),
Ok(Param {
index: Some(0),
ty: Type::Format,
hint: Some(DisplayHint::Ascii),
})
);
assert_eq!(
parse_param("1=u8:b", ParserMode::Strict),
Ok(Param {
index: Some(1),
ty: Type::U8,
hint: Some(DisplayHint::Binary),
})
);
}
#[test]
fn all_display_hints() {
assert_eq!(
parse_param(":a", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::Format,
hint: Some(DisplayHint::Ascii),
})
);
assert_eq!(
parse_param(":b", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::Format,
hint: Some(DisplayHint::Binary),
})
);
assert_eq!(
parse_param(":x", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::Format,
hint: Some(DisplayHint::Hexadecimal {
is_uppercase: false
}),
})
);
assert_eq!(
parse_param(":X", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::Format,
hint: Some(DisplayHint::Hexadecimal { is_uppercase: true }),
})
);
assert_eq!(
parse_param(":?", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::Format,
hint: Some(DisplayHint::Debug),
})
);
assert_eq!(
parse_param(":unknown", ParserMode::ForwardsCompatible),
Ok(Param {
index: None,
ty: Type::Format,
hint: Some(DisplayHint::Unknown("unknown".to_string())),
})
);
}
#[test]
fn all_types() {
assert_eq!(
parse_param("=bool", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::Bool,
hint: None,
})
);
assert_eq!(
parse_param("=?", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::Format,
hint: None,
})
);
assert_eq!(
parse_param("=i16", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::I16,
hint: None,
})
);
assert_eq!(
parse_param("=i32", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::I32,
hint: None,
})
);
assert_eq!(
parse_param("=i64", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::I64,
hint: None,
})
);
assert_eq!(
parse_param("=i128", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::I128,
hint: None,
})
);
assert_eq!(
parse_param("=i8", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::I8,
hint: None,
})
);
assert_eq!(
parse_param("=str", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::Str,
hint: None,
})
);
assert_eq!(
parse_param("=u16", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::U16,
hint: None,
})
);
assert_eq!(
parse_param("=u24", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::U24,
hint: None,
})
);
assert_eq!(
parse_param("=u32", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::U32,
hint: None,
})
);
assert_eq!(
parse_param("=u64", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::U64,
hint: None,
})
);
assert_eq!(
parse_param("=u128", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::U128,
hint: None,
})
);
assert_eq!(
parse_param("=f32", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::F32,
hint: None,
})
);
assert_eq!(
parse_param("=u8", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::U8,
hint: None,
})
);
assert_eq!(
parse_param("=[u8]", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::U8Slice,
hint: None,
})
);
assert_eq!(
parse_param("=usize", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::Usize,
hint: None,
})
);
assert_eq!(
parse_param("=isize", ParserMode::Strict),
Ok(Param {
index: None,
ty: Type::Isize,
hint: None,
})
);
}
#[test]
fn index() {
assert_eq!(
parse("{=u8}{=u16}", ParserMode::Strict),
Ok(vec![
Fragment::Parameter(Parameter {
index: 0,
ty: Type::U8,
hint: None,
}),
Fragment::Parameter(Parameter {
index: 1,
ty: Type::U16,
hint: None,
}),
])
);
assert_eq!(
parse("{=u8}{0=u8}", ParserMode::Strict),
Ok(vec![
Fragment::Parameter(Parameter {
index: 0,
ty: Type::U8,
hint: None,
}),
Fragment::Parameter(Parameter {
index: 0,
ty: Type::U8,
hint: None,
}),
])
);
assert_eq!(
parse("{=u8}{1=u16}", ParserMode::Strict),
Ok(vec![
Fragment::Parameter(Parameter {
index: 0,
ty: Type::U8,
hint: None,
}),
Fragment::Parameter(Parameter {
index: 1,
ty: Type::U16,
hint: None,
}),
])
);
assert_eq!(
parse("{1=u8}{0=u16}", ParserMode::Strict),
Ok(vec![
Fragment::Parameter(Parameter {
index: 1,
ty: Type::U8,
hint: None,
}),
Fragment::Parameter(Parameter {
index: 0,
ty: Type::U16,
hint: None,
}),
])
);
assert!(parse("{0=u8}{0=u16}", ParserMode::Strict).is_err());
assert!(parse("Hello {1=u16} {0=u8} {=bool}", ParserMode::Strict).is_err());
assert!(parse("{1=u8}", ParserMode::Strict).is_err());
assert!(parse("{2=u8}{=u16}", ParserMode::Strict).is_err());
assert!(parse("{2=u8}{1=u16}", ParserMode::Strict).is_err());
}
#[test]
fn range() {
assert_eq!(
parse("{=0..4}", ParserMode::Strict),
Ok(vec![Fragment::Parameter(Parameter {
index: 0,
ty: Type::BitField(0..4),
hint: None,
})])
);
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,
}),
])
);
assert!(parse("{=0..0}", ParserMode::Strict).is_err());
assert!(parse("{=1..0}", ParserMode::Strict).is_err());
assert!(parse("{=0..129}", ParserMode::Strict).is_err());
assert!(parse("{=128..128}", ParserMode::Strict).is_err());
assert!(parse("{=0..128}", ParserMode::Strict).is_ok());
assert!(parse("{=127..128}", ParserMode::Strict).is_ok());
assert!(parse("{=0..4", ParserMode::Strict).is_err());
assert!(parse("{=0..}", ParserMode::Strict).is_err());
assert!(parse("{=..4}", ParserMode::Strict).is_err());
assert!(parse("{=0.4}", ParserMode::Strict).is_err());
assert!(parse("{=0...4}", ParserMode::Strict).is_err());
}
#[test]
fn arrays() {
assert_eq!(
parse("{=[u8; 0]}", ParserMode::Strict),
Ok(vec![Fragment::Parameter(Parameter {
index: 0,
ty: Type::U8Array(0),
hint: None,
})])
);
assert_eq!(
parse("{=[u8;42]}", ParserMode::Strict),
Ok(vec![Fragment::Parameter(Parameter {
index: 0,
ty: Type::U8Array(42),
hint: None,
})])
);
assert_eq!(
parse("{=[u8; 257]}", ParserMode::Strict),
Ok(vec![Fragment::Parameter(Parameter {
index: 0,
ty: Type::U8Array(257),
hint: None,
})])
);
assert!(parse("{=[u8; \t 3]}", ParserMode::Strict).is_err());
assert!(parse("{=[u8; \n 3]}", ParserMode::Strict).is_err());
assert!(parse("{=[u8; 9999999999999999999999999]}", ParserMode::Strict).is_err());
}
#[test]
fn error_msg() {
assert_eq!(
parse("{=dunno}", ParserMode::Strict),
Err("malformed format string (invalid type specifier `dunno`)".into())
);
assert_eq!(
parse("{dunno}", ParserMode::Strict),
Err("unexpected content \"dunno\" in format string".into())
);
assert_eq!(
parse("{=u8;x}", ParserMode::Strict),
Err("malformed format string (invalid type specifier `u8;x`)".into())
);
assert_eq!(
parse("{dunno=u8:x}", ParserMode::Strict),
Err("unexpected content \"dunno=u8:x\" in format string".into())
);
assert_eq!(
parse("{0dunno}", ParserMode::Strict),
Err("unexpected content \"dunno\" in format string".into())
);
}
#[test]
fn brace_escape() {
assert!(parse("}string", ParserMode::Strict).is_err());
assert!(parse("{string", ParserMode::Strict).is_err());
assert!(parse("}", ParserMode::Strict).is_err());
assert!(parse("{", ParserMode::Strict).is_err());
assert_eq!(
parse("}}", ParserMode::Strict),
Ok(vec![Fragment::Literal("}".into())])
);
assert_eq!(
parse("{{", ParserMode::Strict),
Ok(vec![Fragment::Literal("{".into())])
);
assert_eq!(
parse("literal{{literal", ParserMode::Strict),
Ok(vec![Fragment::Literal("literal{literal".into())])
);
assert_eq!(
parse("literal}}literal", ParserMode::Strict),
Ok(vec![Fragment::Literal("literal}literal".into())])
);
assert_eq!(
parse("{{}}", ParserMode::Strict),
Ok(vec![Fragment::Literal("{}".into())])
);
assert_eq!(
parse("}}{{", ParserMode::Strict),
Ok(vec![Fragment::Literal("}{".into())])
);
}
}