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

add binary formatting (proof-of-concept) #54

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions macros/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

### Added

- added support for `{:b}`-style formatting arguments. must be used with `ufmt` 0.1.2+

## [v0.3.0] - 2022-08-10

## Changed
Expand Down
50 changes: 49 additions & 1 deletion macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,18 @@ fn write(input: TokenStream, newline: bool) -> TokenStream {
pad_length: #pad_length,
ox_prefix: #prefix})?;));
}
Piece::Bin {
upper_case,
pad_char,
pad_length,
prefix,
} => {
exprs.push(quote!(ufmt::uDisplayBin::fmt_bin(#pat, f, ufmt::BinOptions{
upper_case:#upper_case,
pad_char: #pad_char,
pad_length: #pad_length,
ob_prefix: #prefix})?;));
}
Piece::Str(_) => unreachable!(),
}
}
Expand Down Expand Up @@ -327,6 +339,12 @@ enum Piece<'a> {
pad_length: usize,
prefix: bool,
},
Bin {
upper_case: bool,
pad_char: u8,
pad_length: usize,
prefix: bool,
},
}

impl Piece<'_> {
Expand Down Expand Up @@ -517,10 +535,30 @@ fn parse_colon(format: &str, span: Span) -> parse::Result<(Piece, &str)> {
},
tail,
))
} else if let Some(tail) = format.strip_prefix("b}") {
Ok((
Piece::Bin {
upper_case: false,
pad_char,
pad_length,
prefix,
},
tail,
))
} else if let Some(tail) = format.strip_prefix("B}") {
Ok((
Piece::Bin {
upper_case: true,
pad_char,
pad_length,
prefix,
},
tail,
))
} else {
Err(parse::Error::new(
span,
"invalid format string: expected `{{`, `{}`, `{:?}`, `{:#?}` or '{:x}'",
"invalid format string: expected `{{`, `{}`, `{:?}`, `{:#?}`, '{:x}' or {:b}",
))
}
}
Expand Down Expand Up @@ -566,6 +604,16 @@ mod tests {
}]),
);

assert_eq!(
super::parse("{:b}", span).ok(),
Some(vec![Piece::Bin {
upper_case: false,
pad_char: b' ',
pad_length: 0,
prefix: false
}]),
);

assert_eq!(
super::parse("{:9x}", span).ok(),
Some(vec![Piece::Hex {
Expand Down
1 change: 1 addition & 0 deletions src/impls.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod array;
mod bin;
mod core;
mod hex;
mod ixx;
Expand Down
70 changes: 70 additions & 0 deletions src/impls/bin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use crate::{uDisplayBin, uWrite, BinOptions, Formatter};

macro_rules! bin_format {
($buf:expr, $val:expr, $options:expr) => {{
let mut cursor = $buf.len();
let mut val = $val;
if val <= 0 {
cursor -= 1;
$buf[cursor] = b'0';
} else {
while val != 0 && cursor > 0 {
let rem = val & 0b1;
cursor -= 1;
$buf[cursor] = bin_digit(rem as u8, $options.upper_case);
val >>= 1;
}
}
unsafe { core::str::from_utf8_unchecked(&$buf[cursor..]) }
}};
}

macro_rules! bin_pattern {
($itype: ty, $utype:ty) => {
impl uDisplayBin for $itype {
fn fmt_bin<W>(
&self,
fmt: &mut Formatter<'_, W>,
options: BinOptions,
) -> Result<(), W::Error>
where
W: uWrite + ?Sized,
{
let positive = if false && // the standard rust library doesn't format negative numbers with a minus sign
*self < 0 {
fmt.write_char('-')?;
((!*self) as $utype).wrapping_add(1)
} else {
*self as $utype
};
<$utype as uDisplayBin>::fmt_bin(&positive, fmt, options)
}
}

impl uDisplayBin for $utype {
fn fmt_bin<W>(
&self,
fmt: &mut Formatter<'_, W>,
options: BinOptions,
) -> Result<(), W::Error>
where
W: uWrite + ?Sized,
{
let mut buffer = [b'0'; 2 * core::mem::size_of::<$utype>()];
let bin_string = bin_format!(buffer, *self, options);
options.with_stuff(fmt, bin_string)
}
}
};
}

bin_pattern! {i8, u8}
bin_pattern! {i16, u16}
bin_pattern! {i32, u32}
bin_pattern! {i64, u64}
bin_pattern! {i128, u128}
bin_pattern! {isize, usize}

fn bin_digit(val: u8, _upper_case: bool) -> u8 {
b'0' + val
}
72 changes: 72 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,78 @@ pub trait uDisplayHex {
W: uWrite + ?Sized;
}

/// HEADS UP this is currently an implementation detail and not subject to semver guarantees.
/// do NOT use this outside the `ufmt` crate
// options for formatting binary numbers
#[doc(hidden)]
pub struct BinOptions {
/// does not apply to digits, but to the 0b prefix if used. should the 0b be upper case instead?
pub upper_case: bool,
/// when we are padding to a target length, what character should we pad using?
pub pad_char: u8,
/// when we are padding to a target length, how long should our string be?
pub pad_length: usize,
/// should we include a 0b prefix? (also controlled by upper_case)
pub ob_prefix: bool,
}

impl BinOptions {
/// applies the various padding/prefix options while writing the `payload` string
pub fn with_stuff<W: uWrite + ?Sized>(
&self,
fmt: &mut Formatter<'_, W>,
payload: &str,
) -> Result<(), <W as uWrite>::Error> {
let pad_before = self.ob_prefix && self.pad_char == b' ';

let pad = self.pad_length as isize
- (if self.ob_prefix { 2 } else { 0 } + payload.len()) as isize;

let do_pad = |fmt: &mut Formatter<'_, W>, pad: isize| -> Result<(), <W as uWrite>::Error> {
if pad > 0 {
for _ in 0..pad {
// miri considers the `write_char` defined in `ufmt-write` v0.1.0 unsound
// to workaround the issue we use `write_str` instead of `write_char`
fmt.write_str(unsafe { str::from_utf8_unchecked(&[self.pad_char]) })?;
}
}
Ok(())
};

let do_prefix = |fmt: &mut Formatter<'_, W>,
go: bool,
upper_case: bool|
-> Result<(), <W as uWrite>::Error> {
if go {
fmt.write_str(if upper_case { "0B" } else { "0b" })
} else {
Ok(())
}
};
if pad_before {
do_pad(fmt, pad)?;
do_prefix(fmt, self.ob_prefix, self.upper_case)?;
} else {
do_prefix(fmt, self.ob_prefix, self.upper_case)?;
do_pad(fmt, pad)?;
}

fmt.write_str(payload)
}
}

/// HEADS UP this is currently an implementation detail and not subject to semver guarantees.
/// do NOT use this outside the `ufmt` crate
// just like std::fmt::LowerHex
#[doc(hidden)]
#[allow(non_camel_case_types)]
pub trait uDisplayBin {
/// Formats the value using the given formatter
fn fmt_bin<W>(&self, _: &mut Formatter<'_, W>, options: BinOptions) -> Result<(), W::Error>
where
W: uWrite + ?Sized;
}

/// Configuration for formatting
#[allow(non_camel_case_types)]
pub struct Formatter<'w, W>
Expand Down