diff --git a/src/lazy/binary/raw/v1_1/binary_buffer.rs b/src/lazy/binary/raw/v1_1/binary_buffer.rs index 3e733bd4..4edf7744 100644 --- a/src/lazy/binary/raw/v1_1/binary_buffer.rs +++ b/src/lazy/binary/raw/v1_1/binary_buffer.rs @@ -278,6 +278,24 @@ impl<'a> BinaryBuffer<'a> { Ok((value, remaining_input)) } + pub fn read_float_as_lazy_value(self, encoding: BinaryValueEncoding) -> ParseResult<'a, LazyRawBinaryValue_1_1<'a>> { + use BinaryValueEncoding::{Float32, Float64}; + let size_in_bytes = match encoding { + Float32 => 4, + Float64 => 8, + _ => return IonResult::illegal_operation(format!("invalid binary encoding for taggless float value: {encoding:?}")), + }; + + if self.len() < size_in_bytes { + return IonResult::incomplete("a float", self.offset()); + } + + 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_float_type(matched_input, encoding); + Ok((value, remaining_input)) + } + pub fn slice_to_end(&self, offset: usize) -> BinaryBuffer<'a> { BinaryBuffer { data: &self.data[offset..], diff --git a/src/lazy/binary/raw/v1_1/e_expression.rs b/src/lazy/binary/raw/v1_1/e_expression.rs index 4b718019..37b19269 100644 --- a/src/lazy/binary/raw/v1_1/e_expression.rs +++ b/src/lazy/binary/raw/v1_1/e_expression.rs @@ -351,6 +351,23 @@ impl<'top> Iterator for BinaryEExpArgsInputIter<'top> { remaining, ) } + enc@ ParameterEncoding::Float32 | + enc@ ParameterEncoding::Float64 + => { + let binary_enc = try_or_some_err!(enc.try_into()); + let (float_lazy_value, remaining) = try_or_some_err! { + self.remaining_args_buffer.read_float_as_lazy_value(binary_enc) + }; + let value_ref = &*self + .remaining_args_buffer + .context() + .allocator() + .alloc_with(|| float_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 4c7ad341..70c8abff 100644 --- a/src/lazy/binary/raw/v1_1/value.rs +++ b/src/lazy/binary/raw/v1_1/value.rs @@ -119,6 +119,8 @@ pub enum BinaryValueEncoding { Int16, Int32, Int64, + Float32, + Float64, } #[derive(Debug, Copy, Clone)] @@ -395,6 +397,33 @@ impl<'top> LazyRawBinaryValue_1_1<'top> { } } + pub(crate) fn for_float_type(input: BinaryBuffer<'top>, encoding: BinaryValueEncoding) -> Self { + let encoded_value = EncodedBinaryValue { + encoding, + header: Header { + ion_type: IonType::Float, + ion_type_code: OpcodeType::Nop, + length_type: LengthType::Unknown, + byte: 0, + }, + + annotations_header_length: 0, + annotations_sequence_length: 0, + annotations_encoding: AnnotationsEncoding::SymbolAddress, + + header_offset: input.offset(), + length_length: 0, + value_body_length: input.len(), + total_length: input.len(), + }; + + LazyRawBinaryValue_1_1 { + encoded_value, + input, + delimited_contents: DelimitedContents::None, + } + } + /// Indicates the Ion data type of this value. Calling this method does not require additional /// parsing of the input stream. pub fn ion_type(&'top self) -> IonType { diff --git a/src/lazy/encoder/binary/v1_1/value_writer.rs b/src/lazy/encoder/binary/v1_1/value_writer.rs index ca3f267f..9d4ee641 100644 --- a/src/lazy/encoder/binary/v1_1/value_writer.rs +++ b/src/lazy/encoder/binary/v1_1/value_writer.rs @@ -222,6 +222,16 @@ impl<'value, 'top> BinaryValueWriter_1_1<'value, 'top> { Ok(()) } + pub fn write_tagless_f32(mut self, value: f32) -> IonResult<()> { + self.push_bytes(&value.to_le_bytes()); + Ok(()) + } + + pub fn write_tagless_f64(mut self, value: f64) -> IonResult<()> { + self.push_bytes(&value.to_le_bytes()); + Ok(()) + } + pub fn write_decimal(mut self, value: &Decimal) -> IonResult<()> { // Insert a placeholder opcode; we'll overwrite the length nibble with the appropriate value when the encoding // is complete. @@ -1169,10 +1179,9 @@ impl<'value, 'top> ValueWriter for BinaryEExpParameterValueWriter_1_1<'value, 't use crate::lazy::expanded::template::ParameterEncoding; // TODO: Support tagless types. - let _param = self + let param = self .parameter .ok_or(IonError::encoding_error("unexpected parameter provided")) - .and_then(|p| p.expect_encoding(&ParameterEncoding::Tagged)) .and_then(|p| p.expect_single_expression())?; let value_writer = BinaryValueWriter_1_1::new( @@ -1181,18 +1190,22 @@ impl<'value, 'top> ValueWriter for BinaryEExpParameterValueWriter_1_1<'value, 't self.value_writer_config, self.macros, ); - value_writer.write_f32(value) + if param.is_tagged() { + value_writer.write_f32(value) + } else if matches!(param.encoding(), &ParameterEncoding::Float32 | &ParameterEncoding::Float64) { + value_writer.write_tagless_f32(value) + } else { + IonResult::encoding_error(format!("unable to write float for tagless parameter with encoding: {:?}", param.encoding())) + } } fn write_f64(self, value: f64) -> IonResult<()> { use crate::IonError; use crate::lazy::expanded::template::ParameterEncoding; - // TODO: Support tagless types. - let _param = self + let param = self .parameter .ok_or(IonError::encoding_error("unexpected parameter provided")) - .and_then(|p| p.expect_encoding(&ParameterEncoding::Tagged)) .and_then(|p| p.expect_single_expression())?; let value_writer = BinaryValueWriter_1_1::new( @@ -1201,7 +1214,15 @@ impl<'value, 'top> ValueWriter for BinaryEExpParameterValueWriter_1_1<'value, 't self.value_writer_config, self.macros, ); - value_writer.write_f64(value) + + if param.is_tagged() { + value_writer.write_f64(value) + } + else if matches!(param.encoding(), &ParameterEncoding::Float32 | &ParameterEncoding::Float64) { + value_writer.write_tagless_f64(value) + } else { + IonResult::encoding_error(format!("unable to write float for tagless parameter with encoding: {:?}", param.encoding())) + } } fn list_writer(self) -> IonResult { diff --git a/src/lazy/encoder/writer.rs b/src/lazy/encoder/writer.rs index 906315cb..b10b87d8 100644 --- a/src/lazy/encoder/writer.rs +++ b/src/lazy/encoder/writer.rs @@ -1375,7 +1375,7 @@ mod tests { mod eexp_parameter_validation { use super::*; - use num_traits::{PrimInt, Signed, Unsigned}; + use num_traits::{Float, PrimInt, Signed, Unsigned}; use rstest::*; #[test] @@ -1671,5 +1671,33 @@ mod tests { Ok(()) } + #[rstest] + #[case::float32("(macro foo (float32::x) (%x))", 1.0f32, "1.0e0")] + #[case::float32_neg("(macro foo (float32::x) (%x))", -1.0f32, "-1.0e0")] + #[case::float64("(macro foo (float64::x) (%x))", 5.0f64, "5.0e0")] + #[case::float64_neg("(macro foo (float64::x) (%x))", -5.0f64, "-5.0e0")] + fn tagless_float_encoding(#[case] macro_source: &str, #[case] input: T, #[case] expected: &str) -> IonResult<()> { + use crate::Element; + + + 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)?; + + if std::mem::size_of::() == std::mem::size_of::() { + eexp_writer.write_f64(num_traits::cast::<_, f64>(input).unwrap())?; + } else { + eexp_writer.write_f32(num_traits::cast::<_, f32>(input).unwrap())?; + } + 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(()) + } + } } diff --git a/src/lazy/expanded/compiler.rs b/src/lazy/expanded/compiler.rs index eb741431..898d59c6 100644 --- a/src/lazy/expanded/compiler.rs +++ b/src/lazy/expanded/compiler.rs @@ -386,6 +386,8 @@ impl TemplateCompiler { "int16" => Ok(ParameterEncoding::Int16), "int32" => Ok(ParameterEncoding::Int32), "int64" => Ok(ParameterEncoding::Int64), + "float32" => Ok(ParameterEncoding::Float32), + "float64" => Ok(ParameterEncoding::Float64), _ => 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 5cf20f3b..a9029c73 100644 --- a/src/lazy/expanded/macro_table.rs +++ b/src/lazy/expanded/macro_table.rs @@ -187,6 +187,12 @@ fn write_macro_signature_as_ion( ParameterEncoding::Int64 => value_writer .with_annotations("int64")? .write_symbol(param.name())?, + ParameterEncoding::Float32 => value_writer + .with_annotations("float32")? + .write_symbol(param.name())?, + ParameterEncoding::Float64 => value_writer + .with_annotations("float64")? + .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 74e56f48..ef39a1d6 100644 --- a/src/lazy/expanded/template.rs +++ b/src/lazy/expanded/template.rs @@ -215,6 +215,8 @@ pub enum ParameterEncoding { Int16, Int32, Int64, + Float32, + Float64, // TODO: tagless types, including fixed-width types and macros MacroShaped(Arc), } @@ -233,6 +235,8 @@ impl Display for ParameterEncoding { Int16 => write!(f, "int16"), Int32 => write!(f, "int32"), Int64 => write!(f, "int64"), + Float32 => write!(f, "float32"), + Float64 => write!(f, "float64"), MacroShaped(m) => write!(f, "{}", m.name().unwrap_or("")), } } @@ -255,6 +259,8 @@ impl TryFrom<&ParameterEncoding> for BinaryValueEncoding { ParameterEncoding::Int16 => Ok(BinaryValueEncoding::Int16), ParameterEncoding::Int32 => Ok(BinaryValueEncoding::Int32), ParameterEncoding::Int64 => Ok(BinaryValueEncoding::Int64), + ParameterEncoding::Float32 => Ok(BinaryValueEncoding::Float32), + ParameterEncoding::Float64 => Ok(BinaryValueEncoding::Float64), } } }