diff --git a/src/bytes.rs b/src/bytes.rs index 796707a..1fdf2c8 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -44,10 +44,11 @@ pub fn bytes(input: TokenStream2) -> TokenStream2 { leading_zero_count .checked_mul(bits_per_digit) .expect("overflow") - } else if leading_zero_count > 0 { + } else if leading_zero_count > 0 && remainder.len() > 1 { // 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. + // us to error. They can proceed by removing the zeros. A single digit + // `0` is permitted as it is unambiguously the value zero. return Error::new( lit.span(), format!( @@ -67,6 +68,7 @@ pub fn bytes(input: TokenStream2) -> TokenStream2 { let int_len = int_bytes.len(); let total_bits = leading_zero_bits.checked_add(int_bits).expect("overflow"); let total_len = (total_bits.checked_add(7).expect("overflow")) / 8; + let total_len = total_len.max(int_len); let mut total_bytes: Vec = vec![0; total_len]; total_bytes[total_len - int_len..].copy_from_slice(&int_bytes); @@ -171,6 +173,127 @@ mod test { } } + #[test] + fn zero() { + let table: &[(_, ExprArray)] = &[ + (quote!(0), parse_quote!([0u8])), + (quote!(0x0), parse_quote!([0u8])), + (quote!(0x00), parse_quote!([0u8])), + (quote!(0b0), parse_quote!([0u8])), + (quote!(0b00000000), parse_quote!([0u8])), + (quote!(0o0), parse_quote!([0u8])), + ]; + for (i, t) in table.iter().cloned().enumerate() { + let tokens = bytes(t.0); + let parsed = syn::parse2::(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])), + (quote!(0o377), parse_quote!([255u8])), + (quote!(255), parse_quote!([255u8])), + // u8 max + 1 + (quote!(0x100), parse_quote!([1u8, 0u8])), + (quote!(0b100000000), parse_quote!([1u8, 0u8])), + (quote!(0o400), parse_quote!([1u8, 0u8])), + (quote!(256), parse_quote!([1u8, 0u8])), + // u16 max + (quote!(0xffff), parse_quote!([255u8, 255u8])), + (quote!(65535), parse_quote!([255u8, 255u8])), + // u16 max + 1 + (quote!(0x10000), parse_quote!([1u8, 0u8, 0u8])), + (quote!(65536), parse_quote!([1u8, 0u8, 0u8])), + // u32 max + ( + quote!(0xffffffff), + parse_quote!([255u8, 255u8, 255u8, 255u8]), + ), + ( + quote!(4294967295), + parse_quote!([255u8, 255u8, 255u8, 255u8]), + ), + // u32 max + 1 + (quote!(0x100000000), parse_quote!([1u8, 0u8, 0u8, 0u8, 0u8])), + (quote!(4294967296), 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::(tokens).unwrap(); + let expect = t.1; + assert_eq!(parsed, expect, "table entry: {}", i); + } + } + + #[test] + fn one() { + let table: &[(_, ExprArray)] = &[ + (quote!(1), parse_quote!([1u8])), + (quote!(0x1), parse_quote!([1u8])), + (quote!(0b1), parse_quote!([1u8])), + (quote!(0o1), parse_quote!([1u8])), + ]; + for (i, t) in table.iter().cloned().enumerate() { + let tokens = bytes(t.0); + let parsed = syn::parse2::(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); + } + #[test] fn leading_zeros_prohibited() { let table: &[(_, Result)] = &[ @@ -178,11 +301,13 @@ mod test { (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!(0o00), 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!(00), 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]))), ]; for (i, t) in table.iter().enumerate() { diff --git a/src/bytesmin.rs b/src/bytesmin.rs index 9c2f9be..25b2a21 100644 --- a/src/bytesmin.rs +++ b/src/bytesmin.rs @@ -76,6 +76,127 @@ mod test { assert_eq!(parsed, expect); } + #[test] + fn zero() { + let table: &[(_, ExprArray)] = &[ + (quote!(0), parse_quote!([0u8])), + (quote!(0x0), parse_quote!([0u8])), + (quote!(0x00), parse_quote!([0u8])), + (quote!(0b0), parse_quote!([0u8])), + (quote!(0b00000000), parse_quote!([0u8])), + (quote!(0o0), parse_quote!([0u8])), + ]; + for (i, t) in table.iter().cloned().enumerate() { + let tokens = bytesmin(t.0); + let parsed = syn::parse2::(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])), + (quote!(0o377), parse_quote!([255u8])), + (quote!(255), parse_quote!([255u8])), + // u8 max + 1 + (quote!(0x100), parse_quote!([1u8, 0u8])), + (quote!(0b100000000), parse_quote!([1u8, 0u8])), + (quote!(0o400), parse_quote!([1u8, 0u8])), + (quote!(256), parse_quote!([1u8, 0u8])), + // u16 max + (quote!(0xffff), parse_quote!([255u8, 255u8])), + (quote!(65535), parse_quote!([255u8, 255u8])), + // u16 max + 1 + (quote!(0x10000), parse_quote!([1u8, 0u8, 0u8])), + (quote!(65536), parse_quote!([1u8, 0u8, 0u8])), + // u32 max + ( + quote!(0xffffffff), + parse_quote!([255u8, 255u8, 255u8, 255u8]), + ), + ( + quote!(4294967295), + parse_quote!([255u8, 255u8, 255u8, 255u8]), + ), + // u32 max + 1 + (quote!(0x100000000), parse_quote!([1u8, 0u8, 0u8, 0u8, 0u8])), + (quote!(4294967296), 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 = bytesmin(t.0); + let parsed = syn::parse2::(tokens).unwrap(); + let expect = t.1; + assert_eq!(parsed, expect, "table entry: {}", i); + } + } + + #[test] + fn one() { + let table: &[(_, ExprArray)] = &[ + (quote!(1), parse_quote!([1u8])), + (quote!(0x1), parse_quote!([1u8])), + (quote!(0b1), parse_quote!([1u8])), + (quote!(0o1), parse_quote!([1u8])), + ]; + for (i, t) in table.iter().cloned().enumerate() { + let tokens = bytesmin(t.0); + let parsed = syn::parse2::(tokens).unwrap(); + let expect = t.1; + assert_eq!(parsed, expect, "table entry: {}", i); + } + } + + #[test] + fn empty_input() { + let tokens = bytesmin(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 = bytesmin(quote! {"hello"}); + let expect = Error::new(Span::call_site(), "expected integer literal") + .to_compile_error() + .to_string(); + assert_eq!(tokens.to_string(), expect); + } + #[test] fn leading_zeros_discarded() { let table: &[(_, ExprArray)] = &[