diff --git a/src/lazy/binary/raw/v1_1/binary_buffer.rs b/src/lazy/binary/raw/v1_1/binary_buffer.rs index 02c32443..7eff3f20 100644 --- a/src/lazy/binary/raw/v1_1/binary_buffer.rs +++ b/src/lazy/binary/raw/v1_1/binary_buffer.rs @@ -260,12 +260,20 @@ impl<'a> BinaryBuffer<'a> { pub fn read_fixed_uint_as_lazy_value(self, size_in_bytes: usize) -> ParseResult<'a, LazyRawBinaryValue_1_1<'a>> { if self.len() < size_in_bytes { - return IonResult::incomplete("a uint8", self.offset()); + return IonResult::incomplete("a uint", self.offset()); } + let encoding = match size_in_bytes { + 1 => BinaryValueEncoding::UInt8, + 2 => BinaryValueEncoding::UInt16, + 4 => BinaryValueEncoding::UInt32, + 8 => BinaryValueEncoding::UInt64, + _ => return IonResult::decoding_error(format!("Invalid byte size for fixed uint: {size_in_bytes}")), + }; + let matched_input = self.slice(0, size_in_bytes); let remaining_input = self.slice_to_end(size_in_bytes); - let value = LazyRawBinaryValue_1_1::for_fixed_uint8(matched_input); + let value = LazyRawBinaryValue_1_1::for_fixed_uint(matched_input, encoding); Ok((value, remaining_input)) } diff --git a/src/lazy/binary/raw/v1_1/e_expression.rs b/src/lazy/binary/raw/v1_1/e_expression.rs index d9a1a025..f5c10c63 100644 --- a/src/lazy/binary/raw/v1_1/e_expression.rs +++ b/src/lazy/binary/raw/v1_1/e_expression.rs @@ -342,6 +342,48 @@ impl<'top> Iterator for BinaryEExpArgsInputIter<'top> { remaining, ) } + ParameterEncoding::UInt16 => { + let (fixed_uint_lazy_value, remaining) = try_or_some_err! { + self.remaining_args_buffer.read_fixed_uint_as_lazy_value(2) + }; + let value_ref = &*self + .remaining_args_buffer + .context() + .allocator() + .alloc_with(|| fixed_uint_lazy_value); + ( + EExpArg::new(parameter, EExpArgExpr::ValueLiteral(value_ref)), + remaining, + ) + } + ParameterEncoding::UInt32 => { + let (fixed_uint_lazy_value, remaining) = try_or_some_err! { + self.remaining_args_buffer.read_fixed_uint_as_lazy_value(4) + }; + let value_ref = &*self + .remaining_args_buffer + .context() + .allocator() + .alloc_with(|| fixed_uint_lazy_value); + ( + EExpArg::new(parameter, EExpArgExpr::ValueLiteral(value_ref)), + remaining, + ) + } + ParameterEncoding::UInt64 => { + let (fixed_uint_lazy_value, remaining) = try_or_some_err! { + self.remaining_args_buffer.read_fixed_uint_as_lazy_value(8) + }; + let value_ref = &*self + .remaining_args_buffer + .context() + .allocator() + .alloc_with(|| fixed_uint_lazy_value); + ( + EExpArg::new(parameter, EExpArgExpr::ValueLiteral(value_ref)), + remaining, + ) + } ParameterEncoding::MacroShaped(_macro_ref) => { todo!("macro-shaped parameter encoding") } // TODO: The other tagless encodings diff --git a/src/lazy/binary/raw/v1_1/value.rs b/src/lazy/binary/raw/v1_1/value.rs index 2b6c7869..18d1d07c 100644 --- a/src/lazy/binary/raw/v1_1/value.rs +++ b/src/lazy/binary/raw/v1_1/value.rs @@ -112,6 +112,9 @@ pub enum BinaryValueEncoding { Tagged, FlexUInt, UInt8, + UInt16, + UInt32, + UInt64, } #[derive(Debug, Copy, Clone)] @@ -361,9 +364,9 @@ impl<'top> LazyRawBinaryValue_1_1<'top> { } } - pub(crate) fn for_fixed_uint8(input: BinaryBuffer<'top>) -> Self { + pub(crate) fn for_fixed_uint(input: BinaryBuffer<'top>, encoding: BinaryValueEncoding) -> Self { let encoded_value = EncodedBinaryValue { - encoding: BinaryValueEncoding::UInt8, + encoding, header: Header { ion_type: IonType::Int, ion_type_code: OpcodeType::Nop, diff --git a/src/lazy/encoder/binary/v1_1/fixed_uint.rs b/src/lazy/encoder/binary/v1_1/fixed_uint.rs index cbee3c8e..9924e853 100644 --- a/src/lazy/encoder/binary/v1_1/fixed_uint.rs +++ b/src/lazy/encoder/binary/v1_1/fixed_uint.rs @@ -1,5 +1,6 @@ use std::io::Write; +use num_traits::{PrimInt, Unsigned}; use ice_code::ice as cold_path; use crate::decimal::coefficient::Coefficient; @@ -66,17 +67,19 @@ impl FixedUInt { self.size_in_bytes } - /// Write the provided UInt-like as a uint8 ensuring that the value fits in 8bits. #[inline] - pub(crate) fn write_as_uint8(output: &mut W, value: impl Into) -> IonResult<()> { - let value = value.into().data; + pub(crate) fn write_as_uint(output: &mut impl Write, value: impl Into) -> IonResult<()> { + let size_in_bytes = std::mem::size_of::(); + let value: u128 = value.into().data; let encoded_bytes = value.to_le_bytes(); + let max_value: u128 = num_traits::cast::cast(I::max_value()) + .ok_or(IonError::encoding_error("Unable to represent bounds for value as 128bit value"))?; - if !(0..256u128).contains(&value) { - return IonResult::encoding_error("provided unsigned integer value does not fit within 1 byte"); + if !(0..=max_value).contains(&value) { + return IonResult::encoding_error(format!("provided unsigned integer value does not fit within {size_in_bytes} byte(s)")); } - output.write_all(&encoded_bytes[..1])?; + output.write_all(&encoded_bytes[..size_in_bytes])?; Ok(()) } } diff --git a/src/lazy/encoder/binary/v1_1/value_writer.rs b/src/lazy/encoder/binary/v1_1/value_writer.rs index a548fdac..8de8c7a4 100644 --- a/src/lazy/encoder/binary/v1_1/value_writer.rs +++ b/src/lazy/encoder/binary/v1_1/value_writer.rs @@ -1076,7 +1076,16 @@ impl<'value, 'top> ValueWriter for BinaryEExpParameterValueWriter_1_1<'value, 't let result = match param.encoding() { PE::UInt8 => value .try_into() - .and_then(|uint: UInt| FixedUInt::write_as_uint8(self.buffer, uint)), + .and_then(|uint: UInt| FixedUInt::write_as_uint::(self.buffer, uint)), + PE::UInt16 => value + .try_into() + .and_then(|uint: UInt| FixedUInt::write_as_uint::(self.buffer, uint)), + PE::UInt32 => value + .try_into() + .and_then(|uint: UInt| FixedUInt::write_as_uint::(self.buffer, uint)), + PE::UInt64 => value + .try_into() + .and_then(|uint: UInt| FixedUInt::write_as_uint::(self.buffer, uint)), PE::FlexUInt => value .try_into() .and_then(|uint: UInt| FlexUInt::write(self.buffer, uint)) @@ -1116,7 +1125,16 @@ impl<'value, 'top> ValueWriter for BinaryEExpParameterValueWriter_1_1<'value, 't let result = match param.encoding() { PE::UInt8 => value .try_into() - .and_then(|uint: UInt| FixedUInt::write_as_uint8(self.buffer, uint)), + .and_then(|uint: UInt| FixedUInt::write_as_uint::(self.buffer, uint)), + PE::UInt16 => value + .try_into() + .and_then(|uint: UInt| FixedUInt::write_as_uint::(self.buffer, uint)), + PE::UInt32 => value + .try_into() + .and_then(|uint: UInt| FixedUInt::write_as_uint::(self.buffer, uint)), + PE::UInt64 => value + .try_into() + .and_then(|uint: UInt| FixedUInt::write_as_uint::(self.buffer, uint)), PE::FlexUInt => value .try_into() .and_then(|uint: UInt| FlexUInt::write(self.buffer, uint)) diff --git a/src/lazy/encoder/writer.rs b/src/lazy/encoder/writer.rs index a1dc87c3..bda7945f 100644 --- a/src/lazy/encoder/writer.rs +++ b/src/lazy/encoder/writer.rs @@ -1375,6 +1375,8 @@ mod tests { mod eexp_parameter_validation { use super::*; + use num_traits::{PrimInt, Unsigned}; + use rstest::*; #[test] fn accept_valid_parameter_encoding() -> IonResult<()> { @@ -1500,5 +1502,96 @@ mod tests { Ok(()) } + + #[rstest] + #[case::uint8("(macro foo (uint8::x) (%x))", 5, "5")] + #[case::uint16("(macro foo (uint16::x) (%x))", 5, "5")] + #[case::uint32("(macro foo (uint32::x) (%x))", 5, "5")] + #[case::uint64("(macro foo (uint64::x) (%x))", 5, "5")] + fn tagless_uint_encoding(#[case] macro_source: &str, #[case] input: i64, #[case] expected: &str) -> IonResult<()> { + use crate::{Int, Element}; + + // write_int + + let mut writer = Writer::new(v1_1::Binary, Vec::new())?; + let foo = writer.compile_macro(macro_source)?; + let mut eexp_writer = writer.eexp_writer(&foo)?; + let int: Int = input.into(); + eexp_writer.write_int(&int)?; + eexp_writer.close()?; + + let output = writer.close()?; + let actual = Element::read_all(&output)?; + let exp_elem = Element::read_all(expected)?; + assert_eq!(actual, exp_elem); + + // write_i64 + + let mut writer = Writer::new(v1_1::Binary, Vec::new())?; + let foo = writer.compile_macro(macro_source)?; + let mut eexp_writer = writer.eexp_writer(&foo)?; + eexp_writer.write_i64(input)?; + eexp_writer.close()?; + + let output = writer.close()?; + let actual = Element::read_all(&output)?; + let exp_elem = Element::read_all(expected)?; + assert_eq!(actual, exp_elem); + + Ok(()) + } + + #[rstest] + #[case::uint8("(macro foo (uint8::x) (%x))", 5u8)] + #[case::uint16("(macro foo (uint16::x) (%x))", 5u16)] + #[case::uint32("(macro foo (uint32::x) (%x))", 5u32)] + #[case::uint64("(macro foo (uint64::x) (%x))", 5u64)] + fn tagless_uint_encoding_write_int_fails(#[case] macro_source: &str, #[case] input: T) -> IonResult<()> { + let max_int = T::max_value(); + let max_int_plus_one = num_traits::cast::cast::<_, i128>(max_int).unwrap() + 1i128; + let neg_input = -num_traits::cast::cast::<_, i128>(input).unwrap(); + + let mut writer = Writer::new(v1_1::Binary, Vec::new())?; + let foo = writer.compile_macro(macro_source)?; + let mut eexp_writer = writer.eexp_writer(&foo)?; + let result = eexp_writer.write_int(&max_int_plus_one.into()); + assert!(result.is_err(), "unexpected success"); + + // Ensure we cannot write a negative value.. + let mut writer = Writer::new(v1_1::Binary, Vec::new())?; + let foo = writer.compile_macro(macro_source)?; + let mut eexp_writer = writer.eexp_writer(&foo)?; + let result = eexp_writer.write_int(&neg_input.into()); + assert!(result.is_err(), "unexpected success"); + + Ok(()) + } + + #[rstest] + #[case::uint8("(macro foo (uint8::x) (%x))", 5u8)] + #[case::uint16("(macro foo (uint16::x) (%x))", 5u16)] + #[case::uint32("(macro foo (uint32::x) (%x))", 5u32)] + fn tagless_uint_encoding_write_i64_fails(#[case] macro_source: &str, #[case] input: T) -> IonResult<()> { + let max_int = T::max_value(); + let max_int_plus_one = num_traits::cast::cast::<_, i128>(max_int).unwrap() + 1i128; + let neg_input = -num_traits::cast::cast::<_, i128>(input).unwrap(); + + let mut writer = Writer::new(v1_1::Binary, Vec::new())?; + let foo = writer.compile_macro(macro_source)?; + let mut eexp_writer = writer.eexp_writer(&foo)?; + // eexp_writer should do the "right thing" given the parameter's encoding. + let result = eexp_writer.write_i64(max_int_plus_one.try_into().unwrap()); + // the "right thing" should be to error, since `x` can only be an 8bit unsigned int. + assert!(result.is_err(), "unexpected success"); + + // Ensure we cannot write a negative value.. + let mut writer = Writer::new(v1_1::Binary, Vec::new())?; + let foo = writer.compile_macro(macro_source)?; + let mut eexp_writer = writer.eexp_writer(&foo)?; + let result = eexp_writer.write_i64(neg_input.try_into().unwrap()); + assert!(result.is_err(), "unexpected success"); + + Ok(()) + } } } diff --git a/src/lazy/expanded/compiler.rs b/src/lazy/expanded/compiler.rs index a58b0b93..cc56c151 100644 --- a/src/lazy/expanded/compiler.rs +++ b/src/lazy/expanded/compiler.rs @@ -379,6 +379,9 @@ impl TemplateCompiler { return match encoding_name { "flex_uint" => Ok(ParameterEncoding::FlexUInt), "uint8" => Ok(ParameterEncoding::UInt8), + "uint16" => Ok(ParameterEncoding::UInt16), + "uint32" => Ok(ParameterEncoding::UInt32), + "uint64" => Ok(ParameterEncoding::UInt64), _ => IonResult::decoding_error(format!( "unrecognized encoding '{encoding_name}' specified for parameter" )), diff --git a/src/lazy/expanded/macro_table.rs b/src/lazy/expanded/macro_table.rs index f32ecced..c29483f2 100644 --- a/src/lazy/expanded/macro_table.rs +++ b/src/lazy/expanded/macro_table.rs @@ -166,6 +166,15 @@ fn write_macro_signature_as_ion( ParameterEncoding::UInt8 => value_writer .with_annotations("uint8")? .write_symbol(param.name())?, + ParameterEncoding::UInt16 => value_writer + .with_annotations("uint16")? + .write_symbol(param.name())?, + ParameterEncoding::UInt32 => value_writer + .with_annotations("uint32")? + .write_symbol(param.name())?, + ParameterEncoding::UInt64 => value_writer + .with_annotations("uint64")? + .write_symbol(param.name())?, ParameterEncoding::MacroShaped(_) => todo!(), }; let cardinality_modifier = match param.cardinality() { diff --git a/src/lazy/expanded/template.rs b/src/lazy/expanded/template.rs index 2d8d72a1..1e7ce5c4 100644 --- a/src/lazy/expanded/template.rs +++ b/src/lazy/expanded/template.rs @@ -208,6 +208,9 @@ pub enum ParameterEncoding { Tagged, FlexUInt, UInt8, + UInt16, + UInt32, + UInt64, // TODO: tagless types, including fixed-width types and macros MacroShaped(Arc), } @@ -219,6 +222,9 @@ impl Display for ParameterEncoding { Tagged => write!(f, "tagged"), FlexUInt => write!(f, "flex_uint"), UInt8 => write!(f, "uint8"), + UInt16 => write!(f, "uint16"), + UInt32 => write!(f, "uint32"), + UInt64 => write!(f, "uint64"), MacroShaped(m) => write!(f, "{}", m.name().unwrap_or("")), } }