Skip to content
Merged
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
240 changes: 159 additions & 81 deletions src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,6 @@ pub fn bytes(input: TokenStream2) -> TokenStream2 {
Err(e) => return e.to_compile_error(),
};

// Convert the integer literal into a base10 string, and into a slice of
// bytes, via a big integer. The conversion should never fail because
// syn::LitInt already validated the integer, unless the value is negative.
// Any leading zeroes are discarded.
let int = match BigUint::from_str(lit.base10_digits()) {
Ok(int) => int,
Err(_) => return Error::new(lit.span(), "negative values unsupported").to_compile_error(),
};

// Get the raw integer literal as it appears in the token stream.
let raw = lit.to_string();

Expand All @@ -28,37 +19,31 @@ pub fn bytes(input: TokenStream2) -> TokenStream2 {

// Remove any leading prefix that indicates the base, and use the base to
// determine how many bits per leading zero needs to be prefilled into the
// bytes generated. If bits_per_digit is None, leading zero digits are
// unsupported.
let (form, bits_per_zero_digit, remainder) = match normalized.as_bytes() {
[b'0', b'x', r @ ..] => ("hex", Some(4), r),
[b'0', b'b', r @ ..] => ("binary", Some(1), r),
[b'0', b'o', r @ ..] => ("octal", None, r),
[r @ ..] => ("decimal", None, r),
// bytes generated.
let (bits_per_zero_digit, remainder) = match normalized.as_bytes() {
[b'0', b'x', r @ ..] => (4, r),
[b'0', b'b', r @ ..] => (1, r),
_ => {
return Error::new(
lit.span(),
"only positive hex (0x) and binary (0b) integer literals are supported",
)
.to_compile_error();
}
};

// Convert the integer literal into a base10 string, and into a slice of
// bytes, via a big integer. The conversion should never fail because
// syn::LitInt already validated the integer, and the form check above
// ensures only non-negative hex/binary literals reach here.
let int = BigUint::from_str(lit.base10_digits()).expect("valid hex or binary literal");

// Count the leading zero bits by counting the number of leading zeros and
// multiplying by the bits per digit.
let leading_zero_count = remainder.iter().take_while(|d| **d == b'0').count();
let leading_zero_bits = if let Some(bits_per_digit) = bits_per_zero_digit {
leading_zero_count
.checked_mul(bits_per_digit)
.expect("overflow")
} else if leading_zero_count > 0 {
// If there are leading zeros without a bits per digit error, since a
// caller may expect the zeros to be preserved, and so it is better for
// us to error. They can proceed by removing the zeros.
return Error::new(
lit.span(),
format!(
"leading zeros are not preserved or supported on integer literals in {} form",
form,
),
)
.to_compile_error();
} else {
0
};
let leading_zero_bits = leading_zero_count
.checked_mul(bits_per_zero_digit)
.expect("overflow");

// Create the final byte slice, which has length of the leading zero bytes,
// followed by the big integer bytes.
Expand All @@ -84,9 +69,12 @@ mod test {
#[test]
fn neg() {
let tokens = bytes(quote! {-0x1});
let expect = Error::new(Span::call_site(), "negative values unsupported")
.to_compile_error()
.to_string();
let expect = Error::new(
Span::call_site(),
"only positive hex (0x) and binary (0b) integer literals are supported",
)
.to_compile_error()
.to_string();
assert_eq!(tokens.to_string(), expect);
}

Expand Down Expand Up @@ -114,21 +102,38 @@ mod test {
}

#[test]
fn base10() {
let tokens = bytes(quote! {340_282_366_920_938_463_463_374_607_431_768_211_455u128});
let parsed = syn::parse2::<ExprArray>(tokens).unwrap();
let expect = syn::parse_quote!([
255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
255u8, 255u8, 255u8, 255u8
]);
assert_eq!(parsed, expect);

let tokens = bytes(quote! {340_282_366_920_938_463_463_374_607_431_768_211_456});
let parsed = syn::parse2::<ExprArray>(tokens).unwrap();
let expect = syn::parse_quote!([
1u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8
]);
assert_eq!(parsed, expect);
fn decimal_and_octal_unsupported() {
let table: &[_] = &[
// Decimal.
quote!(0),
quote!(1),
quote!(9),
quote!(255),
quote!(256),
quote!(0255),
quote!(00255),
quote!(00),
quote!(340_282_366_920_938_463_463_374_607_431_768_211_455u128),
quote!(340_282_366_920_938_463_463_374_607_431_768_211_456),
// Octal.
quote!(0o0),
quote!(0o1),
quote!(0o377),
quote!(0o0377),
quote!(0o00377),
quote!(0o00),
quote!(0o400),
];
let expect = Error::new(
Span::call_site(),
"only positive hex (0x) and binary (0b) integer literals are supported",
)
.to_compile_error()
.to_string();
for (i, input) in table.iter().enumerate() {
let tokens = bytes(input.clone());
assert_eq!(tokens.to_string(), expect, "table entry: {}", i);
}
}

#[test]
Expand Down Expand Up @@ -172,35 +177,108 @@ mod test {
}

#[test]
fn leading_zeros_prohibited() {
let table: &[(_, Result<ExprArray, Error>)] = &[
// Base 8.
(quote!(0o377), Ok(parse_quote!([255u8]))),
(quote!(0o0377), Err(Error::new(Span::call_site(), "leading zeros are not preserved or supported on integer literals in octal form"))),
(quote!(0o00377), Err(Error::new(Span::call_site(), "leading zeros are not preserved or supported on integer literals in octal form"))),
(quote!(0o400), Ok(parse_quote!([1u8, 0u8]))),
// Base 10.
(quote!(255), Ok(parse_quote!([255u8]))),
(quote!(0255), Err(Error::new(Span::call_site(), "leading zeros are not preserved or supported on integer literals in decimal form"))),
(quote!(00255), Err(Error::new(Span::call_site(), "leading zeros are not preserved or supported on integer literals in decimal form"))),
(quote!(256), Ok(parse_quote!([1u8, 0u8]))),
fn zero() {
let table: &[(_, ExprArray)] = &[
(quote!(0x0), parse_quote!([0u8])),
(quote!(0x00), parse_quote!([0u8])),
(quote!(0b0), parse_quote!([0u8])),
(quote!(0b00000000), parse_quote!([0u8])),
];
for (i, t) in table.iter().enumerate() {
let tokens = bytes(t.0.clone());
match t.1.clone() {
Ok(expect) => {
let parsed = syn::parse2::<ExprArray>(tokens);
assert_eq!(parsed.unwrap(), expect, "table entry: {}", i);
}
Err(e) => {
assert_eq!(
tokens.to_string(),
e.to_compile_error().to_string(),
"table entry: {}",
i
);
}
};
for (i, t) in table.iter().cloned().enumerate() {
let tokens = bytes(t.0);
let parsed = syn::parse2::<ExprArray>(tokens).unwrap();
let expect = t.1;
assert_eq!(parsed, expect, "table entry: {}", i);
}
}

#[test]
fn byte_boundaries() {
let table: &[(_, ExprArray)] = &[
// u8 max
(quote!(0xff), parse_quote!([255u8])),
(quote!(0b11111111), parse_quote!([255u8])),
// u8 max + 1
(quote!(0x100), parse_quote!([1u8, 0u8])),
(quote!(0b100000000), parse_quote!([1u8, 0u8])),
// u16 max
(quote!(0xffff), parse_quote!([255u8, 255u8])),
// u16 max + 1
(quote!(0x10000), parse_quote!([1u8, 0u8, 0u8])),
// u32 max
(
quote!(0xffffffff),
parse_quote!([255u8, 255u8, 255u8, 255u8]),
),
// u32 max + 1
(quote!(0x100000000), parse_quote!([1u8, 0u8, 0u8, 0u8, 0u8])),
// u64 max
(
quote!(0xffffffffffffffff),
parse_quote!([255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8]),
),
// u64 max + 1
(
quote!(0x10000000000000000),
parse_quote!([1u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8]),
),
// u128 max
(
quote!(0xffffffffffffffffffffffffffffffff),
parse_quote!([
255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
255u8, 255u8, 255u8, 255u8, 255u8
]),
),
// u128 max + 1
(
quote!(0x100000000000000000000000000000000),
parse_quote!([
1u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
0u8
]),
),
];
for (i, t) in table.iter().cloned().enumerate() {
let tokens = bytes(t.0);
let parsed = syn::parse2::<ExprArray>(tokens).unwrap();
let expect = t.1;
assert_eq!(parsed, expect, "table entry: {}", i);
}
}

#[test]
fn one() {
let table: &[(_, ExprArray)] = &[
(quote!(0x1), parse_quote!([1u8])),
(quote!(0b1), parse_quote!([1u8])),
];
for (i, t) in table.iter().cloned().enumerate() {
let tokens = bytes(t.0);
let parsed = syn::parse2::<ExprArray>(tokens).unwrap();
let expect = t.1;
assert_eq!(parsed, expect, "table entry: {}", i);
}
}

#[test]
fn empty_input() {
let tokens = bytes(quote! {});
let expect = Error::new(
Span::call_site(),
"unexpected end of input, expected integer literal",
)
.to_compile_error()
.to_string();
assert_eq!(tokens.to_string(), expect);
}

#[test]
fn non_integer_input() {
let tokens = bytes(quote! {"hello"});
let expect = Error::new(Span::call_site(), "expected integer literal")
.to_compile_error()
.to_string();
assert_eq!(tokens.to_string(), expect);
}
}
Loading