From 3d893d0e0672aea0c3d42001b2c8421824946e28 Mon Sep 17 00:00:00 2001 From: Zack Slayton Date: Tue, 21 Nov 2023 10:43:47 -0500 Subject: [PATCH 1/6] Adds binary Ion 1.0 impl of the new writer API --- Cargo.toml | 2 +- src/lazy/encoder/annotate.rs | 32 +- src/lazy/encoder/binary/mod.rs | 409 +++++++++++ src/lazy/encoder/binary/value_writer.rs | 886 ++++++++++++++++++++++++ src/lazy/encoder/mod.rs | 613 ++++++---------- src/lazy/encoder/value_writer.rs | 362 ++++++++++ src/lazy/encoder/write_as_ion.rs | 231 +++--- 7 files changed, 2004 insertions(+), 531 deletions(-) create mode 100644 src/lazy/encoder/binary/mod.rs create mode 100644 src/lazy/encoder/binary/value_writer.rs create mode 100644 src/lazy/encoder/value_writer.rs diff --git a/Cargo.toml b/Cargo.toml index f7b33d9e..b8facab9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,7 @@ num-integer = "0.1.44" num-traits = "0.2" arrayvec = "0.7" smallvec = {version ="1.9.0", features = ["const_generics"]} -bumpalo = {version = "3.13.0", features = ["collections"]} +bumpalo = {version = "3.14.0", features = ["collections", "std"]} digest = { version = "0.9", optional = true } sha2 = { version = "0.9", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } diff --git a/src/lazy/encoder/annotate.rs b/src/lazy/encoder/annotate.rs index 6379de78..455eeb26 100644 --- a/src/lazy/encoder/annotate.rs +++ b/src/lazy/encoder/annotate.rs @@ -1,8 +1,7 @@ +use crate::lazy::encoder::value_writer::AnnotatableValueWriter; use crate::lazy::encoder::write_as_ion::{WriteAsIon, WriteAsIonValue}; -use crate::lazy::encoder::{AnnotatedValueWriter, LazyEncoder}; use crate::raw_symbol_token_ref::AsRawSymbolTokenRef; use crate::IonResult; -use std::io::Write; /// Associates a value to serialize with a sequence of annotations. pub struct Annotated<'a, T: ?Sized, A> { @@ -10,7 +9,7 @@ pub struct Annotated<'a, T: ?Sized, A> { annotations: &'a [A], } -/// Provides implementors with an extension method ([`annotate`](Annotate::annotate)) that allows +/// Provides implementors with an extension method ([`annotate`](Annotate::annotated_with)) that allows /// them to be serialized with an associated sequence of annotations. pub trait Annotate { /// Pairs a reference to the provided value with a slice containing annotations. @@ -25,7 +24,7 @@ pub trait Annotate { /// let mut buffer = vec![]; /// let mut writer = LazyRawTextWriter_1_0::new(&mut buffer); /// - /// writer.write(42_usize.annotate(&["foo", "bar", "baz"]))?.flush()?; + /// writer.write(42_usize.annotated_with(&["foo", "bar", "baz"]))?.flush()?; /// /// let expected = Element::read_one("foo::bar::baz::42")?; /// let actual = Element::read_one(&buffer)?; @@ -34,7 +33,7 @@ pub trait Annotate { ///# Ok(()) ///# } /// ``` - fn annotate<'a, A: AsRawSymbolTokenRef>( + fn annotated_with<'a, A: AsRawSymbolTokenRef>( &'a self, annotations: &'a [A], ) -> Annotated<'a, Self, A>; @@ -43,9 +42,9 @@ pub trait Annotate { // Any Rust value that can be serialized as an Ion value can call `annotate`. impl Annotate for T where - T: WriteAsIonValue, + T: ?Sized + WriteAsIonValue, { - fn annotate<'a, A: AsRawSymbolTokenRef>( + fn annotated_with<'a, A: AsRawSymbolTokenRef>( &'a self, annotations: &'a [A], ) -> Annotated<'a, Self, A> { @@ -58,16 +57,23 @@ where // The `Annotated` struct implements `WriteAsIon` by serializing its sequence of annotations // and then invoking the inner value's implementation of `WriteAsIonValue`. -impl<'b, T, A> WriteAsIon for Annotated<'b, T, A> +impl<'annotations, T, A> WriteAsIon for Annotated<'annotations, T, A> where T: WriteAsIonValue, A: AsRawSymbolTokenRef, { - fn write_as_ion<'a, W: Write + 'a, E: LazyEncoder, V: AnnotatedValueWriter<'a, W, E>>( - &self, - annotations_writer: V, - ) -> IonResult<()> { - let value_writer = annotations_writer.write_annotations(self.annotations.iter())?; + fn write_as_ion(&self, writer: V) -> IonResult<()> { + let value_writer = writer.with_annotations(self.annotations); self.value.write_as_ion_value(value_writer) } } + +impl<'annotations, T, A> WriteAsIon for &Annotated<'annotations, T, A> +where + T: WriteAsIonValue, + A: AsRawSymbolTokenRef, +{ + fn write_as_ion(&self, writer: V) -> IonResult<()> { + (*self).write_as_ion(writer) + } +} diff --git a/src/lazy/encoder/binary/mod.rs b/src/lazy/encoder/binary/mod.rs new file mode 100644 index 00000000..5d0bd211 --- /dev/null +++ b/src/lazy/encoder/binary/mod.rs @@ -0,0 +1,409 @@ +use std::io::Write; + +use bumpalo::collections::Vec as BumpVec; +use bumpalo::Bump as BumpAllocator; +use delegate::delegate; + +use crate::binary::var_uint::VarUInt; +use crate::lazy::encoder::binary::value_writer::{ + BinaryAnnotatableValueWriter_1_0, MAX_INLINE_LENGTH, +}; +use crate::lazy::encoder::private::Sealed; +use crate::lazy::encoder::value_writer::{MakeValueWriter, SequenceWriter, StructWriter}; +use crate::lazy::encoder::write_as_ion::WriteAsIon; +use crate::lazy::encoder::{LazyEncoder, LazyRawWriter}; +use crate::lazy::encoding::BinaryEncoding_1_0; +use crate::raw_symbol_token_ref::AsRawSymbolTokenRef; +use crate::result::EncodingError; +use crate::{IonError, IonResult, RawSymbolTokenRef}; + +mod value_writer; + +impl LazyEncoder for BinaryEncoding_1_0 { + type Writer = LazyRawBinaryWriter_1_0; +} + +/// A "raw"-level streaming binary Ion writer. This writer does not provide symbol table +/// management; symbol-related operations (e.g. setting field IDs and annotations or writing symbol +/// values) require a valid symbol ID to be provided by the caller. +pub struct LazyRawBinaryWriter_1_0 { + // The sink to which all of the writer's encoded data will be written. + output: W, + // A bump allocator that can be used to cheaply create scratch buffers for nested container + // encoding. + allocator: BumpAllocator, + // A pointer to the bump-allocated top-level encoding buffer, if set. + // + // This buffer is constructed in `allocator` above, a region of memory over which we have + // complete control. When the allocator creates a buffer, the buffer has a lifetime equivalent to + // the lifetime of the function in which it was created. However, we know that the data it contains + // will continue to be valid even after that method is complete and any return values are dropped. + // Thus, we store a raw pointer to the buffer and use an `Option` to track whether the pointer + // is set to a meaningful address. This allows us to refer to the contents of the buffer across + // multiple mutable calls of `write` and `value_writer()`. + encoding_buffer_ptr: Option<*mut ()>, +} + +impl LazyRawBinaryWriter_1_0 { + /// Constructs a new binary writer and writes an Ion 1.0 Version Marker to output. + pub fn new(mut output: W) -> IonResult { + // Write the Ion 1.0 IVM + output.write_all(&[0xE0, 0x01, 0x00, 0xEA])?; + // Construct the writer + Ok(Self { + output, + allocator: BumpAllocator::new(), + encoding_buffer_ptr: None, + }) + } + + /// Helper function that turns a raw pointer into a mutable reference of the specified type. + fn ptr_to_mut_ref<'a, T>(ptr: *mut ()) -> &'a mut T { + let typed_ptr: *mut T = ptr.cast(); + unsafe { &mut *typed_ptr } + } + + /// Helper function that turns a mutable reference into a raw pointer. + fn mut_ref_to_ptr(reference: &mut T) -> *mut () { + let ptr: *mut T = reference; + let untyped_ptr: *mut () = ptr.cast(); + untyped_ptr + } + + /// Writes the given Rust value to the output stream as a top-level value. + fn write(&mut self, value: V) -> IonResult<&mut Self> { + value.write_as_ion(self.value_writer())?; + Ok(self) + } + + /// Flushes any encoded bytes that have not already been written to the output sink. + /// + /// Calling `flush` also releases memory used for bookkeeping and storage, but calling it + /// frequently can reduce overall throughput. + fn flush(&mut self) -> IonResult<()> { + // Temporarily break apart `self` to get simultaneous references to its innards. + let Self { + output, + allocator, + encoding_buffer_ptr, + } = self; + + let encoding_buffer = match encoding_buffer_ptr { + // If `encoding_buffer_ptr` is set, get the slice of bytes to which it refers. + Some(ptr) => Self::ptr_to_mut_ref::<'_, BumpVec<'_, u8>>(*ptr).as_slice(), + // Otherwise, there's nothing in the buffer. Use an empty slice. + None => &[], + }; + // Write our top level encoding buffer's contents to the output sink. + output.write_all(encoding_buffer)?; + // Flush the output sink, which may have its own buffers. + output.flush()?; + // Clear the allocator. A new encoding buffer will be allocated on the next write. + allocator.reset(); + Ok(()) + } + + fn value_writer(&mut self) -> BinaryAnnotatableValueWriter_1_0<'_, '_> { + let top_level = match self.encoding_buffer_ptr { + // If the `encoding_buffer_ptr` is set, we already allocated an encoding buffer on + // a previous call to `value_writer()`. Dereference the pointer and continue encoding + // to that buffer. + Some(ptr) => Self::ptr_to_mut_ref::<'_, BumpVec<'_, u8>>(ptr), + // Otherwise, allocate a new encoding buffer and set the pointer to refer to it. + None => { + let buffer = self + .allocator + .alloc_with(|| BumpVec::new_in(&self.allocator)); + self.encoding_buffer_ptr = Some(Self::mut_ref_to_ptr(buffer)); + buffer + } + }; + let annotated_value_writer = + BinaryAnnotatableValueWriter_1_0::new(&self.allocator, top_level); + annotated_value_writer + } +} + +impl Sealed for LazyRawBinaryWriter_1_0 {} + +impl LazyRawWriter for LazyRawBinaryWriter_1_0 { + fn new(output: W) -> IonResult { + Self::new(output) + } + + delegate! { + to self { + fn flush(&mut self) -> IonResult<()>; + } + } +} + +impl MakeValueWriter for LazyRawBinaryWriter_1_0 { + type ValueWriter<'a> = BinaryAnnotatableValueWriter_1_0<'a, 'a> where Self: 'a; + + delegate! { + to self { + fn value_writer(&mut self) -> Self::ValueWriter<'_>; + } + } +} + +impl SequenceWriter for LazyRawBinaryWriter_1_0 { + // Uses the default method implementations from SequenceWriter +} + +/// A helper type that holds fields and logic that is common to [`BinaryListWriter_1_0`], +/// [`BinarySExpWriter_1_0`], and [`BinaryStructWriter_1_0`]. +pub(crate) struct BinaryContainerWriter_1_0<'value, 'top> { + // A byte containing the high nibble of the encoded container's type descriptor. + type_code: u8, + // An allocator reference that can be shared with nested container writers + allocator: &'top BumpAllocator, + // The buffer containing the parent's encoded body. When this list writer is finished encoding + // its own data, a header will be written to the parent and then the list body will be copied + // over. + parent_buffer: &'value mut BumpVec<'top, u8>, +} + +impl<'value, 'top> BinaryContainerWriter_1_0<'value, 'top> { + pub(crate) fn new( + type_code: u8, + allocator: &'top BumpAllocator, + parent_buffer: &'value mut BumpVec<'top, u8>, + ) -> Self { + Self { + type_code, + allocator, + parent_buffer, + } + } + + pub fn write_values<'a, F>(mut self, write_fn: F) -> IonResult<()> + where + 'top: 'a, + F: FnOnce(BinaryContainerValuesWriter_1_0<'a>) -> IonResult>, + { + let container_values_writer = BinaryContainerValuesWriter_1_0::new(self.allocator); + let encoded_values = write_fn(container_values_writer)?; + self.write_header_and_encoded_body(encoded_values.as_slice()) + } + + fn write_header_and_encoded_body(&mut self, body: &[u8]) -> IonResult<()> { + let body_length = body.len(); + if body_length <= MAX_INLINE_LENGTH { + let type_descriptor = self.type_code | (body_length as u8); + self.parent_buffer.push(type_descriptor); + } else { + self.parent_buffer.push(self.type_code | 0xE); + VarUInt::write_u64(&mut self.parent_buffer, body_length as u64)?; + } + self.parent_buffer.extend_from_slice(body); + Ok(()) + } +} + +pub(crate) struct BinaryContainerValuesWriter_1_0<'value> { + allocator: &'value BumpAllocator, + buffer: BumpVec<'value, u8>, +} + +impl<'value> BinaryContainerValuesWriter_1_0<'value> { + pub(crate) fn new(allocator: &'value BumpAllocator) -> Self { + let buffer = BumpVec::new_in(allocator); + Self { allocator, buffer } + } + + pub fn write(&mut self, value: V) -> IonResult<&mut Self> { + let annotated_value_writer = + BinaryAnnotatableValueWriter_1_0::new(&self.allocator, &mut self.buffer); + value.write_as_ion(annotated_value_writer)?; + Ok(self) + } +} + +pub struct BinaryListValuesWriter_1_0<'value> { + values_writer: BinaryContainerValuesWriter_1_0<'value>, +} + +impl<'value> BinaryListValuesWriter_1_0<'value> { + pub(crate) fn new(values_writer: BinaryContainerValuesWriter_1_0<'value>) -> Self { + Self { values_writer } + } + pub fn write(&mut self, value: V) -> IonResult<&mut Self> { + self.values_writer.write(value)?; + Ok(self) + } +} + +impl<'value> MakeValueWriter for BinaryListValuesWriter_1_0<'value> { + type ValueWriter<'a> = BinaryAnnotatableValueWriter_1_0<'a, 'value> where Self: 'a; + + fn value_writer(&mut self) -> Self::ValueWriter<'_> { + BinaryAnnotatableValueWriter_1_0::new( + &*self.values_writer.allocator, + &mut self.values_writer.buffer, + ) + } +} + +impl<'value> SequenceWriter for BinaryListValuesWriter_1_0<'value> { + delegate! { + to self { + fn write(&mut self, value: V) -> IonResult<&mut Self>; + } + } +} + +pub struct BinaryListWriter_1_0<'value, 'top> { + container_writer: BinaryContainerWriter_1_0<'value, 'top>, +} + +impl<'value, 'top> BinaryListWriter_1_0<'value, 'top> { + pub(crate) fn new(container_writer: BinaryContainerWriter_1_0<'value, 'top>) -> Self { + Self { container_writer } + } + + pub fn write_values<'a, F>(self, write_fn: F) -> IonResult<()> + where + 'top: 'a, + F: FnOnce(&mut BinaryListValuesWriter_1_0<'a>) -> IonResult<()>, + { + self.container_writer + .write_values(|container_values_writer| { + let mut list_values_writer = + BinaryListValuesWriter_1_0::new(container_values_writer); + write_fn(&mut list_values_writer)?; + Ok(list_values_writer.values_writer.buffer) + }) + } +} + +pub struct BinarySExpValuesWriter_1_0<'value> { + values_writer: BinaryContainerValuesWriter_1_0<'value>, +} + +impl<'value> BinarySExpValuesWriter_1_0<'value> { + pub(crate) fn new(values_writer: BinaryContainerValuesWriter_1_0<'value>) -> Self { + Self { values_writer } + } + pub fn write(&mut self, value: V) -> IonResult<&mut Self> { + self.values_writer.write(value)?; + Ok(self) + } +} + +impl<'value> MakeValueWriter for BinarySExpValuesWriter_1_0<'value> { + type ValueWriter<'a> = BinaryAnnotatableValueWriter_1_0<'a, 'value> where Self: 'a; + + fn value_writer(&mut self) -> Self::ValueWriter<'_> { + BinaryAnnotatableValueWriter_1_0::new( + &*self.values_writer.allocator, + &mut self.values_writer.buffer, + ) + } +} + +impl<'value> SequenceWriter for BinarySExpValuesWriter_1_0<'value> { + delegate! { + to self { + fn write(&mut self, value: V) -> IonResult<&mut Self>; + } + } +} + +pub struct BinarySExpWriter_1_0<'value, 'top> { + container_writer: BinaryContainerWriter_1_0<'value, 'top>, +} + +impl<'value, 'top> BinarySExpWriter_1_0<'value, 'top> { + pub(crate) fn new(sequence_writer: BinaryContainerWriter_1_0<'value, 'top>) -> Self { + Self { + container_writer: sequence_writer, + } + } + + pub fn write_values<'a, F>(self, write_fn: F) -> IonResult<()> + where + 'top: 'a, + F: FnOnce(&mut BinarySExpValuesWriter_1_0<'a>) -> IonResult<()>, + { + self.container_writer + .write_values(|container_values_writer| { + let mut sexp_values_writer = + BinarySExpValuesWriter_1_0::new(container_values_writer); + write_fn(&mut sexp_values_writer)?; + Ok(sexp_values_writer.values_writer.buffer) + }) + } +} + +pub struct BinaryStructFieldsWriter_1_0<'value> { + container_values_writer: BinaryContainerValuesWriter_1_0<'value>, +} + +impl<'value> BinaryStructFieldsWriter_1_0<'value> { + pub(crate) fn new(container_values_writer: BinaryContainerValuesWriter_1_0<'value>) -> Self { + Self { + container_values_writer, + } + } + + pub fn write( + &mut self, + name: A, + value: V, + ) -> IonResult<&mut Self> { + // Write the field name + let sid = match name.as_raw_symbol_token_ref() { + RawSymbolTokenRef::SymbolId(sid) => sid, + RawSymbolTokenRef::Text(text) => { + return Err(IonError::Encoding(EncodingError::new(format!( + "tried to write a text literal using the v1.0 raw binary writer: '{text}'" + )))); + } + }; + VarUInt::write_u64(&mut self.container_values_writer.buffer, sid as u64)?; + + // Write the field value + self.container_values_writer.write(value)?; + Ok(self) + } +} + +impl<'value> StructWriter for BinaryStructFieldsWriter_1_0<'value> { + delegate! { + to self { + fn write( + &mut self, + name: A, + value: V, + ) -> IonResult<&mut Self>; + } + } +} + +pub struct BinaryStructWriter_1_0<'value, 'top> { + container_writer: BinaryContainerWriter_1_0<'value, 'top>, +} + +impl<'value, 'top> BinaryStructWriter_1_0<'value, 'top> { + pub(crate) fn new(container: BinaryContainerWriter_1_0<'value, 'top>) -> Self { + Self { + container_writer: container, + } + } + + pub fn write_fields<'a, F>(self, write_fn: F) -> IonResult<()> + where + 'top: 'a, + F: FnOnce(&mut BinaryStructFieldsWriter_1_0<'a>) -> IonResult<()>, + { + self.container_writer + .write_values(|container_values_writer| { + let mut struct_fields_writer = + BinaryStructFieldsWriter_1_0::new(container_values_writer); + write_fn(&mut struct_fields_writer)?; + Ok(struct_fields_writer.container_values_writer.buffer) + }) + } +} diff --git a/src/lazy/encoder/binary/value_writer.rs b/src/lazy/encoder/binary/value_writer.rs new file mode 100644 index 00000000..91e643ad --- /dev/null +++ b/src/lazy/encoder/binary/value_writer.rs @@ -0,0 +1,886 @@ +use std::mem; + +use bumpalo::collections::Vec as BumpVec; +use bumpalo::Bump as BumpAllocator; +use bytes::BufMut; +use delegate::delegate; +use num_bigint::Sign; +use num_traits::Zero; + +use crate::binary::decimal::DecimalBinaryEncoder; +use crate::binary::timestamp::TimestampBinaryEncoder; +use crate::binary::uint; +use crate::binary::uint::DecodedUInt; +use crate::binary::var_uint::VarUInt; +use crate::lazy::encoder::binary::{ + BinaryContainerWriter_1_0, BinaryListValuesWriter_1_0, BinaryListWriter_1_0, + BinarySExpValuesWriter_1_0, BinarySExpWriter_1_0, BinaryStructFieldsWriter_1_0, + BinaryStructWriter_1_0, +}; +use crate::lazy::encoder::private::Sealed; +use crate::lazy::encoder::value_writer::{AnnotatableValueWriter, ValueWriter}; +use crate::raw_symbol_token_ref::AsRawSymbolTokenRef; +use crate::result::{EncodingError, IonFailure}; +use crate::types::integer::IntData; +use crate::{Decimal, Int, IonError, IonResult, IonType, RawSymbolTokenRef, SymbolId, Timestamp}; + +// The largest possible 'L' (length) value that can be written directly in a type descriptor byte. +// Larger length values will need to be written as a VarUInt following the type descriptor. +pub(crate) const MAX_INLINE_LENGTH: usize = 13; + +pub struct BinaryValueWriter_1_0<'value, 'top> { + allocator: &'top BumpAllocator, + encoding_buffer: &'value mut BumpVec<'top, u8>, +} + +impl<'value, 'top> BinaryValueWriter_1_0<'value, 'top> { + pub fn new( + allocator: &'top BumpAllocator, + encoding_buffer: &'value mut BumpVec<'top, u8>, + ) -> BinaryValueWriter_1_0<'value, 'top> { + BinaryValueWriter_1_0 { + allocator, + encoding_buffer, + } + } + + #[inline] + fn push_byte(&mut self, byte: u8) { + self.encoding_buffer.push(byte); + } + + #[inline] + fn push_bytes(&mut self, bytes: &[u8]) { + self.encoding_buffer.extend_from_slice(bytes) + } + + pub(crate) fn buffer(&self) -> &[u8] { + self.encoding_buffer.as_slice() + } + + pub fn write_symbol_id(mut self, symbol_id: SymbolId) -> IonResult<()> { + const SYMBOL_BUFFER_SIZE: usize = mem::size_of::(); + let mut buffer = [0u8; SYMBOL_BUFFER_SIZE]; + let mut writer = std::io::Cursor::new(&mut buffer).writer(); + let encoded_length = DecodedUInt::write_u64(&mut writer, symbol_id as u64)?; + + let type_descriptor: u8; + if encoded_length <= MAX_INLINE_LENGTH { + type_descriptor = 0x70 | encoded_length as u8; + self.push_byte(type_descriptor); + } else { + type_descriptor = 0x7E; + self.push_byte(type_descriptor); + VarUInt::write_u64(self.encoding_buffer, encoded_length as u64)?; + } + let raw_buffer = writer.into_inner().into_inner(); + self.push_bytes(&raw_buffer[..encoded_length]); + Ok(()) + } + + pub fn write_lob(mut self, value: &[u8], type_code: u8) -> IonResult<()> { + let encoded_length = value.len(); + let type_descriptor: u8; + if encoded_length <= MAX_INLINE_LENGTH { + type_descriptor = type_code | encoded_length as u8; + self.push_byte(type_descriptor); + } else { + type_descriptor = type_code | 0x0E; + self.push_byte(type_descriptor); + VarUInt::write_u64(self.encoding_buffer, encoded_length as u64)?; + } + self.push_bytes(value); + Ok(()) + } + + pub fn write_null(mut self, ion_type: IonType) -> IonResult<()> { + let byte: u8 = match ion_type { + IonType::Null => 0x0F, + IonType::Bool => 0x1F, + IonType::Int => 0x2F, + IonType::Float => 0x4F, + IonType::Decimal => 0x5F, + IonType::Timestamp => 0x6F, + IonType::Symbol => 0x7F, + IonType::String => 0x8F, + IonType::Clob => 0x9F, + IonType::Blob => 0xAF, + IonType::List => 0xBF, + IonType::SExp => 0xCF, + IonType::Struct => 0xDF, + }; + self.push_byte(byte); + Ok(()) + } + + pub fn write_bool(mut self, value: bool) -> IonResult<()> { + let byte: u8 = if value { 0x11 } else { 0x10 }; + self.push_byte(byte); + Ok(()) + } + + pub fn write_i64(mut self, value: i64) -> IonResult<()> { + // Get the absolute value of the i64 and store it in a u64. + let magnitude: u64 = value.unsigned_abs(); + let encoded = uint::encode_u64(magnitude); + let bytes_to_write = encoded.as_bytes(); + + // The encoded length will never be larger than 8 bytes, so it will + // always fit in the Int's type descriptor byte. + let encoded_length = bytes_to_write.len(); + let type_descriptor: u8 = if value >= 0 { + 0x20 | (encoded_length as u8) + } else { + 0x30 | (encoded_length as u8) + }; + self.push_byte(type_descriptor); + self.push_bytes(bytes_to_write); + + Ok(()) + } + + pub fn write_int(mut self, value: &Int) -> IonResult<()> { + // If the `value` is an `i64`, use `write_i64` and return. + let value = match &value.data { + IntData::I64(i) => return self.write_i64(*i), + IntData::BigInt(i) => i, + }; + + // From here on, `value` is a `BigInt`. + if value.is_zero() { + self.push_byte(0x20); + return Ok(()); + } + + let (sign, magnitude_be_bytes) = value.to_bytes_be(); + + let mut type_descriptor: u8 = match sign { + Sign::Plus | Sign::NoSign => 0x20, + Sign::Minus => 0x30, + }; + + let encoded_length = magnitude_be_bytes.len(); + if encoded_length <= 13 { + type_descriptor |= encoded_length as u8; + self.push_byte(type_descriptor); + } else { + type_descriptor |= 0xEu8; + self.push_byte(type_descriptor); + VarUInt::write_u64(self.encoding_buffer, encoded_length as u64)?; + } + + self.push_bytes(magnitude_be_bytes.as_slice()); + + Ok(()) + } + + pub fn write_f32(mut self, value: f32) -> IonResult<()> { + if value == 0f32 && !value.is_sign_negative() { + self.push_byte(0x40); + return Ok(()); + } + + self.push_byte(0x44); + self.push_bytes(&value.to_be_bytes()); + Ok(()) + } + + pub fn write_f64(mut self, value: f64) -> IonResult<()> { + if value == 0f64 && !value.is_sign_negative() { + self.push_byte(0x40); + return Ok(()); + } + + self.push_byte(0x48); + self.push_bytes(&value.to_be_bytes()); + Ok(()) + } + + pub fn write_decimal(self, value: &Decimal) -> IonResult<()> { + let _encoded_size = self.encoding_buffer.encode_decimal_value(value)?; + Ok(()) + } + + pub fn write_timestamp(self, value: &Timestamp) -> IonResult<()> { + let _ = self.encoding_buffer.encode_timestamp_value(value)?; + Ok(()) + } + + pub fn write_string>(mut self, value: A) -> IonResult<()> { + let text: &str = value.as_ref(); + let encoded_length = text.len(); // The number of utf8 bytes + + let type_descriptor: u8; + if encoded_length <= MAX_INLINE_LENGTH { + type_descriptor = 0x80 | encoded_length as u8; + self.push_byte(type_descriptor); + } else { + type_descriptor = 0x8E; + self.push_byte(type_descriptor); + VarUInt::write_u64(self.encoding_buffer, encoded_length as u64)?; + } + self.push_bytes(text.as_bytes()); + Ok(()) + } + + pub fn write_symbol(self, value: A) -> IonResult<()> { + match value.as_raw_symbol_token_ref() { + RawSymbolTokenRef::SymbolId(sid) => self.write_symbol_id(sid), + RawSymbolTokenRef::Text(text) => IonResult::illegal_operation(format!( + "the Ion 1.0 raw binary writer cannot write text symbols (here: '{text}')" + )), + } + } + + pub fn write_clob>(self, value: A) -> IonResult<()> { + let bytes: &[u8] = value.as_ref(); + // The clob type descriptor's high nibble is type code 9 + self.write_lob(bytes, 0x90) + } + + pub fn write_blob>(self, value: A) -> IonResult<()> { + let bytes: &[u8] = value.as_ref(); + // The blob type descriptor's high nibble is type code 10 (0xA) + self.write_lob(bytes, 0xA0) + } + + fn list_writer(&mut self) -> BinaryListWriter_1_0<'_, 'top> { + const LIST_TYPE_CODE: u8 = 0xB0; + BinaryListWriter_1_0::new(BinaryContainerWriter_1_0::new( + LIST_TYPE_CODE, + self.allocator, + self.encoding_buffer, + )) + } + + fn sexp_writer(&mut self) -> IonResult> { + const SEXP_TYPE_CODE: u8 = 0xC0; + Ok(BinarySExpWriter_1_0::new(BinaryContainerWriter_1_0::new( + SEXP_TYPE_CODE, + self.allocator, + self.encoding_buffer, + ))) + } + + fn struct_writer(&mut self) -> IonResult> { + const STRUCT_TYPE_CODE: u8 = 0xD0; + Ok(BinaryStructWriter_1_0::new(BinaryContainerWriter_1_0::new( + STRUCT_TYPE_CODE, + &self.allocator, + self.encoding_buffer, + ))) + } + + fn write_list< + F: for<'a> FnOnce(&mut ::ListWriter<'a>) -> IonResult<()>, + >( + mut self, + list_fn: F, + ) -> IonResult<()> { + self.list_writer().write_values(list_fn) + } + fn write_sexp< + F: for<'a> FnOnce(&mut ::SExpWriter<'a>) -> IonResult<()>, + >( + mut self, + sexp_fn: F, + ) -> IonResult<()> { + self.sexp_writer()?.write_values(sexp_fn) + } + fn write_struct< + F: for<'a> FnOnce(&mut ::StructWriter<'a>) -> IonResult<()>, + >( + mut self, + struct_fn: F, + ) -> IonResult<()> { + self.struct_writer()?.write_fields(struct_fn) + } +} + +impl<'value, 'top> Sealed for BinaryValueWriter_1_0<'value, 'top> {} + +impl<'value, 'top> ValueWriter for BinaryValueWriter_1_0<'value, 'top> { + type ListWriter<'a> = BinaryListValuesWriter_1_0<'a>; + type SExpWriter<'a> = BinarySExpValuesWriter_1_0<'a>; + type StructWriter<'a> = BinaryStructFieldsWriter_1_0<'a>; + + delegate! { + to self { + fn write_null(self, ion_type: IonType) -> IonResult<()>; + fn write_bool(self, value: bool) -> IonResult<()>; + fn write_i64(self, value: i64) -> IonResult<()>; + fn write_int(self, value: &Int) -> IonResult<()>; + fn write_f32(self, value: f32) -> IonResult<()>; + fn write_f64(self, value: f64) -> IonResult<()>; + fn write_decimal(self, value: &Decimal) -> IonResult<()>; + fn write_timestamp(self, value: &Timestamp) -> IonResult<()>; + fn write_string>(self, value: A) -> IonResult<()>; + fn write_symbol(self, value: A) -> IonResult<()>; + fn write_clob>(self, value: A) -> IonResult<()>; + fn write_blob>(self, value: A) -> IonResult<()>; + fn write_list FnOnce(&mut Self::ListWriter<'a>) -> IonResult<()>>( + self, + list_fn: F, + ) -> IonResult<()>; + fn write_sexp FnOnce(&mut Self::SExpWriter<'a>) -> IonResult<()>>( + self, + sexp_fn: F, + ) -> IonResult<()>; + fn write_struct< + F: for<'a> FnOnce(&mut Self::StructWriter<'a>) -> IonResult<()>, + >( + self, + struct_fn: F, + ) -> IonResult<()>; + } + } +} + +pub struct BinaryAnnotatableValueWriter_1_0<'value, 'top> { + allocator: &'top BumpAllocator, + encoding_buffer: &'value mut BumpVec<'top, u8>, +} + +impl<'value, 'top> BinaryAnnotatableValueWriter_1_0<'value, 'top> { + pub fn new( + allocator: &'top BumpAllocator, + encoding_buffer: &'value mut BumpVec<'top, u8>, + ) -> BinaryAnnotatableValueWriter_1_0<'value, 'top> { + BinaryAnnotatableValueWriter_1_0 { + allocator, + encoding_buffer, + } + } +} + +impl<'value, 'top: 'value> AnnotatableValueWriter + for BinaryAnnotatableValueWriter_1_0<'value, 'top> +{ + type ValueWriter = BinaryValueWriter_1_0<'value, 'top>; + type AnnotatedValueWriter<'a, SymbolType: AsRawSymbolTokenRef + 'a> = + BinaryAnnotationsWrapperWriter<'a, 'top, SymbolType> where Self: 'a; + fn with_annotations<'a, SymbolType: AsRawSymbolTokenRef>( + self, + annotations: &'a [SymbolType], + ) -> Self::AnnotatedValueWriter<'a, SymbolType> + where + Self: 'a, + { + BinaryAnnotationsWrapperWriter::new(self.allocator, annotations, self.encoding_buffer) + } + + #[inline(always)] + fn without_annotations(self) -> BinaryValueWriter_1_0<'value, 'top> { + BinaryValueWriter_1_0::new(self.allocator, self.encoding_buffer) + } +} + +pub struct BinaryAnnotationsWrapperWriter<'value, 'top, SymbolType: AsRawSymbolTokenRef> { + annotations: &'value [SymbolType], + allocator: &'top BumpAllocator, + output_buffer: &'value mut BumpVec<'top, u8>, +} + +impl<'value, 'top, SymbolType: AsRawSymbolTokenRef> + BinaryAnnotationsWrapperWriter<'value, 'top, SymbolType> +{ + pub fn new( + allocator: &'top BumpAllocator, + annotations: &'value [SymbolType], + encoding_buffer: &'value mut BumpVec<'top, u8>, + ) -> BinaryAnnotationsWrapperWriter<'value, 'top, SymbolType> { + BinaryAnnotationsWrapperWriter { + annotations, + allocator, + output_buffer: encoding_buffer, + } + } +} + +impl<'value, 'top, SymbolType: AsRawSymbolTokenRef> + BinaryAnnotationsWrapperWriter<'value, 'top, SymbolType> +{ + fn encode_annotated(self, encode_value_fn: F) -> IonResult<()> + where + F: for<'a> FnOnce(BinaryAnnotatedValueWriter_1_0<'a, 'top>) -> IonResult<()>, + { + let allocator = &*self.allocator; + let buffer = allocator.alloc_with(|| BumpVec::new_in(allocator)); + { + let annotated_value_writer = + BinaryAnnotatedValueWriter_1_0::new(self.allocator, buffer); + encode_value_fn(annotated_value_writer)?; + } + self.annotate_encoded_value(buffer.as_slice()) + } + + fn annotate_encoded_value(self, encoded_value: &[u8]) -> IonResult<()> { + let mut encoded_annotations_sequence = BumpVec::new_in(&self.allocator); + self.encode_annotations_sequence(&mut encoded_annotations_sequence)?; + + let mut encoded_annotations_sequence_length = BumpVec::new_in(&self.allocator); + VarUInt::write_u64( + &mut encoded_annotations_sequence_length, + encoded_annotations_sequence.len() as u64, + )?; + + let total_length = encoded_annotations_sequence.len() + + encoded_annotations_sequence_length.len() + + encoded_value.len(); + + if total_length <= MAX_INLINE_LENGTH { + self.output_buffer.push(0xE0u8 | total_length as u8); + } else { + self.output_buffer.push(0xEEu8); + VarUInt::write_u64(self.output_buffer, total_length as u64)?; + } + + self.output_buffer + .extend_from_slice(encoded_annotations_sequence_length.as_slice()); + self.output_buffer + .extend_from_slice(encoded_annotations_sequence.as_slice()); + self.output_buffer.extend_from_slice(encoded_value); + + Ok(()) + } + + fn encode_annotations_sequence(&self, buffer: &'_ mut BumpVec<'_, u8>) -> IonResult<()> { + for annotation in self.annotations { + let RawSymbolTokenRef::SymbolId(sid) = annotation.as_raw_symbol_token_ref() else { + return Err(IonError::Encoding(EncodingError::new( + "binary Ion 1.0 cannot encode text literal annotations", + ))); + }; + VarUInt::write_u64(buffer, sid as u64)?; + } + Ok(()) + } +} + +impl<'value, 'top, SymbolType: AsRawSymbolTokenRef> Sealed + for BinaryAnnotationsWrapperWriter<'value, 'top, SymbolType> +{ + // No methods, precludes implementations outside the crate. +} + +impl<'value, 'top, SymbolType: AsRawSymbolTokenRef> ValueWriter + for BinaryAnnotationsWrapperWriter<'value, 'top, SymbolType> +{ + type ListWriter<'a> = BinaryListValuesWriter_1_0<'a>; + type SExpWriter<'a> = BinarySExpValuesWriter_1_0<'a>; + + type StructWriter<'a> = BinaryStructFieldsWriter_1_0<'a>; + + fn write_null(self, ion_type: IonType) -> IonResult<()> { + self.encode_annotated(|value_writer| value_writer.write_null(ion_type)) + } + + fn write_bool(self, value: bool) -> IonResult<()> { + self.encode_annotated(|value_writer| value_writer.write_bool(value)) + } + + fn write_i64(self, value: i64) -> IonResult<()> { + self.encode_annotated(|value_writer| value_writer.write_i64(value)) + } + + fn write_int(self, value: &Int) -> IonResult<()> { + self.encode_annotated(|value_writer| value_writer.write_int(value)) + } + + fn write_f32(self, value: f32) -> IonResult<()> { + self.encode_annotated(|value_writer| value_writer.write_f32(value)) + } + + fn write_f64(self, value: f64) -> IonResult<()> { + self.encode_annotated(|value_writer| value_writer.write_f64(value)) + } + + fn write_decimal(self, value: &Decimal) -> IonResult<()> { + self.encode_annotated(|value_writer| value_writer.write_decimal(value)) + } + + fn write_timestamp(self, value: &Timestamp) -> IonResult<()> { + self.encode_annotated(|value_writer| value_writer.write_timestamp(value)) + } + + fn write_string>(self, value: A) -> IonResult<()> { + self.encode_annotated(|value_writer| value_writer.write_string(&value)) + } + + fn write_symbol(self, value: A) -> IonResult<()> { + self.encode_annotated(|value_writer| value_writer.write_symbol(&value)) + } + + fn write_clob>(self, value: A) -> IonResult<()> { + self.encode_annotated(|value_writer| value_writer.write_clob(&value)) + } + + fn write_blob>(self, value: A) -> IonResult<()> { + self.encode_annotated(|value_writer| value_writer.write_blob(&value)) + } + + fn write_list FnOnce(&mut Self::ListWriter<'a>) -> IonResult<()>>( + self, + list_fn: F, + ) -> IonResult<()> { + self.encode_annotated(move |value_writer| value_writer.write_list(list_fn)) + } + fn write_sexp FnOnce(&mut Self::SExpWriter<'a>) -> IonResult<()>>( + self, + sexp_fn: F, + ) -> IonResult<()> { + self.encode_annotated(|value_writer| value_writer.write_sexp(sexp_fn)) + } + fn write_struct FnOnce(&mut Self::StructWriter<'a>) -> IonResult<()>>( + self, + struct_fn: F, + ) -> IonResult<()> { + self.encode_annotated(|value_writer| value_writer.write_struct(struct_fn)) + } +} + +// TODO: Doc comment. Holds its own buffer to limit the type to a single lifetime. +pub struct BinaryAnnotatedValueWriter_1_0<'value, 'top> { + allocator: &'top BumpAllocator, + // Note that unlike the BinaryValueWriter_1_0, the borrow and the BumpVec here have the same + // lifetime. This allows this type to be passed as a closure argument. + buffer: &'value mut BumpVec<'top, u8>, +} + +impl<'value, 'top> BinaryAnnotatedValueWriter_1_0<'value, 'top> { + pub fn new(allocator: &'top BumpAllocator, buffer: &'value mut BumpVec<'top, u8>) -> Self { + Self { allocator, buffer } + } + pub(crate) fn value_writer(&mut self) -> BinaryValueWriter_1_0<'_, 'top> { + BinaryValueWriter_1_0::new(self.allocator, &mut self.buffer) + } + + pub(crate) fn buffer(&self) -> &[u8] { + self.buffer.as_slice() + } +} + +impl<'value, 'top> Sealed for BinaryAnnotatedValueWriter_1_0<'value, 'top> {} + +impl<'value, 'top: 'value> ValueWriter for BinaryAnnotatedValueWriter_1_0<'value, 'top> { + type ListWriter<'a> = BinaryListValuesWriter_1_0<'a>; + type SExpWriter<'a> = BinarySExpValuesWriter_1_0<'a>; + type StructWriter<'a> = BinaryStructFieldsWriter_1_0<'a>; + delegate! { + to self.value_writer() { + fn write_null(mut self, ion_type: IonType) -> IonResult<()>; + fn write_bool(mut self, value: bool) -> IonResult<()>; + fn write_i64(mut self, value: i64) -> IonResult<()>; + fn write_int(mut self, value: &Int) -> IonResult<()>; + fn write_f32(mut self, value: f32) -> IonResult<()>; + fn write_f64(mut self, value: f64) -> IonResult<()>; + fn write_decimal(mut self, value: &Decimal) -> IonResult<()>; + fn write_timestamp(mut self, value: &Timestamp) -> IonResult<()>; + fn write_string>(mut self, value: A) -> IonResult<()>; + fn write_symbol(mut self, value: A) -> IonResult<()>; + fn write_clob>(mut self, value: A) -> IonResult<()>; + fn write_blob>(mut self, value: A) -> IonResult<()>; + fn write_list FnOnce(&mut Self::ListWriter<'a>) -> IonResult<()>>( + mut self, + list_fn: F, + ) -> IonResult<()>; + fn write_sexp FnOnce(&mut Self::SExpWriter<'a>) -> IonResult<()>>( + mut self, + sexp_fn: F, + ) -> IonResult<()>; + fn write_struct< + F: for<'a> FnOnce(&mut Self::StructWriter<'a>) -> IonResult<()>, + >( + mut self, + struct_fn: F, + ) -> IonResult<()>; + } + } +} +#[cfg(test)] +mod tests { + use crate::lazy::encoder::annotate::Annotate; + use crate::lazy::encoder::binary::LazyRawBinaryWriter_1_0; + use crate::lazy::encoder::value_writer::AnnotatableValueWriter; + use crate::lazy::encoder::write_as_ion::WriteAsSExp; + use crate::raw_symbol_token_ref::AsRawSymbolTokenRef; + use crate::{Element, IonData, IonResult, RawSymbolTokenRef, Timestamp}; + + fn writer_test( + expected: &str, + mut test: impl FnMut(&mut LazyRawBinaryWriter_1_0<&mut Vec>) -> IonResult<()>, + ) -> IonResult<()> { + let expected = Element::read_all(expected)?; + let mut buffer = Vec::new(); + let mut writer = LazyRawBinaryWriter_1_0::new(&mut buffer)?; + test(&mut writer)?; + writer.flush()?; + std::fs::write("/tmp/output.ion", &buffer)?; + let actual = Element::read_all(buffer)?; + assert!( + IonData::eq(&expected, &actual), + "Actual \n {actual:?}\nwas not equal to\n {expected:?}\n" + ); + Ok(()) + } + + #[test] + fn write_scalars() -> IonResult<()> { + let expected = r#" + 1 + false + 3e0 + "foo" + name + 2023-11-09T + {{4AEA6g==}} + "#; + let test = |writer: &mut LazyRawBinaryWriter_1_0<&mut Vec>| { + writer + .write(1)? + .write(false)? + .write(3f32)? + .write("foo")? + .write(RawSymbolTokenRef::SymbolId(4))? + .write(Timestamp::with_ymd(2023, 11, 9).build()?)? + .write([0xE0u8, 0x01, 0x00, 0xEA])?; + // .write([1, 2, 3])?; + Ok(()) + }; + writer_test(expected, test) + } + + #[test] + fn write_empty_list() -> IonResult<()> { + let expected = "[]"; + let test = |writer: &mut LazyRawBinaryWriter_1_0<&mut Vec>| { + let value_writer = writer.value_writer(); + value_writer + .without_annotations() + .write_list(|_list| Ok(())) + }; + writer_test(expected, test) + } + + #[test] + fn write_list() -> IonResult<()> { + let expected = r#" + [ + 1, + false, + 3e0, + "foo", + name, + 2023-11-09T, + {{4AEA6g==}}, + // Nested list + [1, 2, 3], + ] + "#; + let test = |writer: &mut LazyRawBinaryWriter_1_0<&mut Vec>| { + writer + .value_writer() + .without_annotations() + .write_list(|list| { + list.write(1)? + .write(false)? + .write(3f32)? + .write("foo")? + .write(RawSymbolTokenRef::SymbolId(4))? + .write(Timestamp::with_ymd(2023, 11, 9).build()?)? + .write([0xE0u8, 0x01, 0x00, 0xEA])? + .write([1, 2, 3])?; + Ok(()) + }) + }; + writer_test(expected, test) + } + + #[test] + fn write_empty_sexp() -> IonResult<()> { + let expected = "()"; + let test = |writer: &mut LazyRawBinaryWriter_1_0<&mut Vec>| { + writer + .value_writer() + .without_annotations() + .write_sexp(|_sexp| Ok(())) + }; + writer_test(expected, test) + } + + #[test] + fn write_sexp() -> IonResult<()> { + let expected = r#" + ( + 1 + false + 3e0 + "foo" + name + 2023-11-09T + {{4AEA6g==}} + // Nested list + [1, 2, 3] + ) + "#; + let test = |writer: &mut LazyRawBinaryWriter_1_0<&mut Vec>| { + writer + .value_writer() + .without_annotations() + .write_sexp(|sexp| { + sexp.write(1)? + .write(false)? + .write(3f32)? + .write("foo")? + .write(RawSymbolTokenRef::SymbolId(4))? + .write(Timestamp::with_ymd(2023, 11, 9).build()?)? + .write([0xE0u8, 0x01, 0x00, 0xEA])? + .write([1, 2, 3])?; + Ok(()) + }) + }; + writer_test(expected, test) + } + + #[test] + fn write_empty_struct() -> IonResult<()> { + let expected = "{}"; + let test = |writer: &mut LazyRawBinaryWriter_1_0<&mut Vec>| { + writer + .value_writer() + .without_annotations() + .write_struct(|_struct| Ok(())) + }; + writer_test(expected, test) + } + + #[test] + fn write_struct() -> IonResult<()> { + let expected = r#" + // This test uses symbol ID field names because the raw writer has no symbol table. + { + $0: 1, + $1: false, + $2: 3e0, + $3: "foo", + $4: name, + $5: 2023-11-09T, + $6: {{4AEA6g==}}, + // Nested list + $7: [1, 2, 3], + } + "#; + let test = |writer: &mut LazyRawBinaryWriter_1_0<&mut Vec>| { + writer + .value_writer() + .without_annotations() + .write_struct(|struct_| { + struct_ + .write(0, 1)? + .write(1, false)? + .write(2, 3f32)? + .write(3, "foo")? + .write(4, RawSymbolTokenRef::SymbolId(4))? + .write(5, Timestamp::with_ymd(2023, 11, 9).build()?)? + .write(6, [0xE0u8, 0x01, 0x00, 0xEA])? + .write(7, [1, 2, 3])?; + Ok(()) + }) + }; + writer_test(expected, test) + } + + #[test] + fn write_annotated_scalars() -> IonResult<()> { + let expected = r#" + // The raw writer doesn't have a symbol table, so this only uses symbols + // that are already in the system symbol table. + name::1 + version::false + imports::symbols::3e0 + max_id::version::"foo" + $ion::$4 + $ion_symbol_table::2023-11-09T + $ion_1_0::{{4AEA6g==}} + "#; + let test = |writer: &mut LazyRawBinaryWriter_1_0<&mut Vec>| { + writer + .write(1.annotated_with(&[4]))? + .write(false.annotated_with(&[5]))? + .write(3f32.annotated_with(&[6, 7]))? + .write("foo".annotated_with(&[8, 5]))? + .write(4usize.as_raw_symbol_token_ref().annotated_with(&[1]))? + .write( + Timestamp::with_ymd(2023, 11, 9) + .build()? + .annotated_with(&[3]), + )? + .write((&[0xE0u8, 0x01, 0x00, 0xEA][..]).annotated_with(&[2]))?; + Ok(()) + }; + writer_test(expected, test) + } + + #[test] + fn write_annotated_containers() -> IonResult<()> { + let expected = r#" + [] + $4::[] + $4::[1, 2, 3] + $4::$7::[1, 2, 3] + $4::$7::[ + $4::$7::[1, 2, 3] + ] + () + $4::() + $4::(1 2 3) + $4::$7::() + $4::$7::( + $4::$7::(1 2 3) + ) + "#; + let test = |writer: &mut LazyRawBinaryWriter_1_0<&mut Vec>| { + let empty_sequence: &[i32] = &[]; + // [] + writer.write(empty_sequence)?; + + // $4::[] + writer.write(empty_sequence.annotated_with(&[4]))?; + + // $4::[1, 2, 3] + writer.write([1, 2, 3].annotated_with(&[4]))?; + + // $4::$7::[1, 2, 3] + writer.write([1, 2, 3].annotated_with(&[4, 7]))?; + + // $4::$7::[ + // $4::$7::[1, 2, 3] + // ] + writer.write([[1, 2, 3].annotated_with(&[4, 7])].annotated_with(&[4, 7]))?; + + // () + writer.write(empty_sequence.as_sexp())?; + + // $4::() + writer.write(empty_sequence.as_sexp().annotated_with(&[4]))?; + + // $4::(1 2 3) + writer.write([1, 2, 3].as_sexp().annotated_with(&[4]))?; + + // $4::$7::() + writer.write(empty_sequence.as_sexp().annotated_with(&[4, 7]))?; + + // $4::$7::( + // $4::$7::(1 2 3) + // ) + writer.write( + [[1, 2, 3].as_sexp().annotated_with(&[4, 7])] + .as_sexp() + .annotated_with(&[4, 7]), + )?; + + Ok(()) + }; + writer_test(expected, test) + } +} diff --git a/src/lazy/encoder/mod.rs b/src/lazy/encoder/mod.rs index d9e4c25d..22bdbc39 100644 --- a/src/lazy/encoder/mod.rs +++ b/src/lazy/encoder/mod.rs @@ -1,17 +1,23 @@ #![allow(non_camel_case_types)] -pub mod annotate; -pub mod write_as_ion; - -use delegate::delegate; use std::fmt::Debug; use std::io::Write; + +use delegate::delegate; +use value_writer::{AnnotatableValueWriter, MakeValueWriter, SequenceWriter, StructWriter}; + use write_as_ion::WriteAsIon; +use crate::lazy::encoder::private::Sealed; use crate::lazy::encoding::TextEncoding_1_0; use crate::raw_symbol_token_ref::AsRawSymbolTokenRef; use crate::text::raw_text_writer::{WhitespaceConfig, PRETTY_WHITESPACE_CONFIG}; -use crate::{Decimal, Int, IonResult, IonType, RawSymbolTokenRef, RawTextWriter, Timestamp}; +use crate::{IonResult, IonType, RawSymbolTokenRef, RawTextWriter}; + +pub mod annotate; +pub mod binary; +mod value_writer; +pub mod write_as_ion; /// A family of types that collectively comprise the writer API for an Ion serialization /// format. These types operate at the 'raw' level; they do not attempt to resolve symbols @@ -20,127 +26,32 @@ use crate::{Decimal, Int, IonResult, IonType, RawSymbolTokenRef, RawTextWriter, // However, many types are generic over some `E: LazyEncoder`, and having this trait // extend 'static, Sized, Debug, Clone and Copy means that those types can #[derive(...)] // those traits themselves without boilerplate `where` clauses. -pub trait LazyEncoder: 'static + Sized + Debug + Clone + Copy { +pub trait LazyEncoder: 'static + Sized + Debug + Clone + Copy { // XXX: ^-- This is named 'Lazy' for symmetry with the `LazyDecoder`. In reality, there's nothing // lazy about it. We should revisit the Lazy* naming scheme, as eventually it will be the // only implementation of a reader and won't need the word 'Lazy' to distinguish itself. /// A writer that serializes Rust values as Ion, emitting the resulting data to an implementation /// of [`Write`]. - type Writer: LazyRawWriter; - - /// A single-use type that can emit an Ion value. - type ValueWriter<'a>: ValueWriter<'a, W, Self> - where - W: 'a; - - /// A single-use type that can emit a sequence of annotations and then return a [`ValueWriter`]. - type AnnotatedValueWriter<'a>: AnnotatedValueWriter<'a, W, Self> - where - W: 'a; - - /// Allows the application to write a (potentially heterogeneously typed) list without necessarily - /// having all of its child values in memory. - type ListWriter<'a>: SequenceWriter<'a, W, Self> - where - W: 'a; - - // TODO: Apply trait constraints to the following associated types - - type SExpWriter<'a>: SequenceWriter<'a, W, Self> - where - W: 'a; - type StructWriter<'a>: StructWriter<'a, W, Self> - where - W: 'a; - type EExpressionWriter<'a>; -} - -impl LazyEncoder for TextEncoding_1_0 { - type Writer = LazyRawTextWriter_1_0; - type ValueWriter<'a> = TextValueWriter_1_0<'a, W> where W: 'a; - type AnnotatedValueWriter<'a> = TextAnnotatedValueWriter_1_0<'a, W> where W: 'a; - type ListWriter<'a> = TextListWriter_1_0<'a, W> where W: 'a; - type SExpWriter<'a> = TextSExpWriter_1_0<'a, W> where W: 'a; - type StructWriter<'a> = TextStructWriter_1_0<'a, W> where W: 'a; - - // TODO: Implement these associated types. - type EExpressionWriter<'a> = (); + type Writer: LazyRawWriter; } -/// One-shot methods that take a (possibly empty) sequence of annotations to encode and return a ValueWriter. -pub trait AnnotatedValueWriter<'a, W: Write, E: LazyEncoder> { - /// Writes the provided annotations to the output stream and returns a [`ValueWriter`] that can - /// be used to serialize the value itself. - /// - /// If there are no annotations, use [`Self::no_annotations`] instead. This method will loop - /// over the empty sequence, incurring minor performance overhead while `no_annotations` - /// is a true no-op. - fn write_annotations< - SymbolType: AsRawSymbolTokenRef, - IterType: Iterator + Clone, - >( - self, - annotations: IterType, - ) -> IonResult>; - - /// Performs no operations and returns a [`ValueWriter`]. - fn no_annotations(self) -> E::ValueWriter<'a>; +impl LazyEncoder for TextEncoding_1_0 { + type Writer = LazyRawTextWriter_1_0; } -pub trait ValueWriter<'a, W: Write, E: LazyEncoder> { - fn write_null(self, ion_type: IonType) -> IonResult<()>; - fn write_bool(self, value: bool) -> IonResult<()>; - fn write_i64(self, value: i64) -> IonResult<()>; - fn write_int(self, value: &Int) -> IonResult<()>; - fn write_f32(self, value: f32) -> IonResult<()>; - fn write_f64(self, value: f64) -> IonResult<()>; - fn write_decimal(self, value: &Decimal) -> IonResult<()>; - fn write_timestamp(self, value: &Timestamp) -> IonResult<()>; - fn write_string>(self, value: A) -> IonResult<()>; - fn write_symbol(self, value: A) -> IonResult<()>; - fn write_clob>(self, value: A) -> IonResult<()>; - fn write_blob>(self, value: A) -> IonResult<()>; - fn list_writer(self) -> IonResult>; - fn sexp_writer(self) -> IonResult>; - fn struct_writer(self) -> IonResult>; +pub(crate) mod private { + /// Prevents types outside the crate from implementing traits that extend it. + pub trait Sealed {} } -pub trait LazyRawWriter> { - fn new(output: W) -> Self; - fn write(&mut self, value: V) -> IonResult<&mut Self>; - - fn value_writer(&mut self) -> E::ValueWriter<'_>; - - fn list_writer(&mut self) -> IonResult> { - self.value_writer().list_writer() - } - fn sexp_writer(&mut self) -> IonResult> { - self.value_writer().sexp_writer() - } +pub trait LazyRawWriter: SequenceWriter { + fn new(output: W) -> IonResult + where + Self: Sized; fn flush(&mut self) -> IonResult<()>; } -pub trait StructWriter<'a, W: Write, E: LazyEncoder> { - /// Writes a struct field using the provided name/value pair. - fn write( - &mut self, - name: A, - value: V, - ) -> IonResult<&mut Self>; - - /// Finalizes the struct, preventing further fields from being written. - fn end(self) -> IonResult<()>; -} - -pub trait SequenceWriter<'a, W: Write, E: LazyEncoder>: Sized { - /// Writes a nested value within the list. - fn write(&mut self, value: V) -> IonResult<&mut Self>; - - /// Finalizes the list, preventing further nested values from being written. - fn end(self) -> IonResult<()>; -} - /// A raw text Ion 1.0 writer. pub struct LazyRawTextWriter_1_0 { output: W, @@ -158,7 +69,7 @@ impl LazyRawTextWriter_1_0 { /// Writes the provided data as a top-level value. pub fn write(&mut self, value: V) -> IonResult<&mut Self> { - value.write_as_ion(self.annotated_value_writer())?; + value.write_as_ion(self.annotatable_value_writer())?; write!( self.output, "{}", @@ -184,49 +95,51 @@ impl LazyRawTextWriter_1_0 { /// Helper method to construct this format's `AnnotatedValueWriter` implementation. #[inline] - fn annotated_value_writer(&mut self) -> TextAnnotatedValueWriter_1_0<'_, W> { - TextAnnotatedValueWriter_1_0 { + fn annotatable_value_writer(&mut self) -> TextAnnotatableValueWriter_1_0<'_, W> { + TextAnnotatableValueWriter_1_0 { value_writer: self.value_writer(), } } +} - #[inline] - pub fn list_writer(&mut self) -> IonResult> { - self.value_writer().list_writer() - } +impl SequenceWriter for LazyRawTextWriter_1_0 { + // All default method impls +} - #[inline] - pub fn sexp_writer(&mut self) -> IonResult> { - self.value_writer().sexp_writer() - } +impl MakeValueWriter for LazyRawTextWriter_1_0 { + type ValueWriter<'a> = TextAnnotatableValueWriter_1_0<'a, W> + where + Self: 'a; - #[inline] - pub fn struct_writer(&mut self) -> IonResult> { - self.value_writer().struct_writer() + fn value_writer(&mut self) -> Self::ValueWriter<'_> { + let value_writer = TextValueWriter_1_0 { + writer: self, + depth: 0, + }; + TextAnnotatableValueWriter_1_0 { value_writer } } } -impl LazyRawWriter for LazyRawTextWriter_1_0 { - fn new(output: W) -> Self { - LazyRawTextWriter_1_0::new(output) +impl LazyRawWriter for LazyRawTextWriter_1_0 { + fn new(output: W) -> IonResult { + Ok(LazyRawTextWriter_1_0::new(output)) } + // Delegate the trait methods to the inherent methods; this allows a version of these // methods to be called on the concrete type even when the trait is not in scope. delegate! { to self { - fn write(&mut self, value: V) -> IonResult<&mut Self>; - fn value_writer(&mut self) -> TextValueWriter_1_0<'_, W>; fn flush(&mut self) -> IonResult<()>; } } } -pub struct TextValueWriter_1_0<'a, W: Write> { - writer: &'a mut LazyRawTextWriter_1_0, +pub struct TextValueWriter_1_0<'value, W: Write + 'value> { + writer: &'value mut LazyRawTextWriter_1_0, depth: usize, } -impl<'a, W: Write> TextValueWriter_1_0<'a, W> { +impl<'value, W: Write> TextValueWriter_1_0<'value, W> { fn output(&mut self) -> &mut W { &mut self.writer.output } @@ -236,157 +149,61 @@ impl<'a, W: Write> TextValueWriter_1_0<'a, W> { } } -pub struct TextAnnotatedValueWriter_1_0<'a, W: Write> { - value_writer: TextValueWriter_1_0<'a, W>, +pub struct TextAnnotatableValueWriter_1_0<'value, W: Write> { + value_writer: TextValueWriter_1_0<'value, W>, } -impl<'a, W: Write> AnnotatedValueWriter<'a, W, TextEncoding_1_0> - for TextAnnotatedValueWriter_1_0<'a, W> -{ - fn write_annotations< - SymbolType: AsRawSymbolTokenRef, - IterType: Iterator + Clone, - >( +impl<'value, W: Write> AnnotatableValueWriter for TextAnnotatableValueWriter_1_0<'value, W> { + type ValueWriter = TextValueWriter_1_0<'value, W>; + type AnnotatedValueWriter<'a, SymbolType: AsRawSymbolTokenRef + 'a> = + TextAnnotatedValueWriter_1_0<'a, W, SymbolType> where Self: 'a; + fn with_annotations<'a, SymbolType: AsRawSymbolTokenRef>( self, - annotations: IterType, - ) -> IonResult> { - let output = &mut self.value_writer.writer.output; - let annotations = annotations.into_iter(); - for annotation in annotations { - match annotation.as_raw_symbol_token_ref() { - RawSymbolTokenRef::Text(token) => write!(output, "{}::", token.as_ref()), - RawSymbolTokenRef::SymbolId(sid) => write!(output, "${sid}::"), - }?; + annotations: &'a [SymbolType], + ) -> Self::AnnotatedValueWriter<'a, SymbolType> + where + Self: 'a, + { + TextAnnotatedValueWriter_1_0 { + annotations, + value_writer: self.value_writer, } - Ok(self.value_writer) } - fn no_annotations(self) -> TextValueWriter_1_0<'a, W> { + fn without_annotations(self) -> TextValueWriter_1_0<'value, W> { self.value_writer } } -impl<'a, W: Write> ValueWriter<'a, W, TextEncoding_1_0> for TextValueWriter_1_0<'a, W> { - fn write_null(mut self, ion_type: IonType) -> IonResult<()> { - use IonType::*; - let null_text = match ion_type { - Null => "null", - Bool => "null.bool", - Int => "null.int", - Float => "null.float", - Decimal => "null.decimal", - Timestamp => "null.timestamp", - Symbol => "null.symbol", - String => "null.string", - Blob => "null.blob", - Clob => "null.clob", - List => "null.list", - SExp => "null.sexp", - Struct => "null.struct", - }; - write!(self.output(), "{null_text}")?; - Ok(()) - } - - fn write_bool(mut self, value: bool) -> IonResult<()> { - let bool_text = match value { - true => "true", - false => "false", - }; - write!(self.output(), "{bool_text}")?; - Ok(()) - } - - fn write_i64(mut self, value: i64) -> IonResult<()> { - write!(self.output(), "{value}")?; - Ok(()) - } - - fn write_int(mut self, value: &Int) -> IonResult<()> { - write!(self.output(), "{value}")?; - Ok(()) - } - - fn write_f32(self, value: f32) -> IonResult<()> { - self.write_f64(value as f64) - } - - fn write_f64(mut self, value: f64) -> IonResult<()> { - if value.is_nan() { - write!(self.output(), "nan")?; - return Ok(()); - } - - if value.is_infinite() { - if value.is_sign_positive() { - write!(self.output(), "+inf")?; - } else { - write!(self.output(), "-inf")?; - } - return Ok(()); - } +pub struct TextAnnotatedValueWriter_1_0<'value, W: Write, SymbolType: AsRawSymbolTokenRef + 'value> +{ + annotations: &'value [SymbolType], + value_writer: TextValueWriter_1_0<'value, W>, +} - // The {:e} formatter provided by the Display trait writes floats using scientific - // notation. It works for all floating point values except -0.0 (it drops the sign). - // See: https://github.com/rust-lang/rust/issues/20596 - if value == 0.0f64 && value.is_sign_negative() { - write!(self.output(), "-0e0")?; - return Ok(()); +impl<'value, W: Write, SymbolType: AsRawSymbolTokenRef> + TextAnnotatedValueWriter_1_0<'value, W, SymbolType> +{ + fn encode_annotations(self) -> IonResult> { + let output = &mut self.value_writer.writer.output; + for annotation in self.annotations { + match annotation.as_raw_symbol_token_ref() { + RawSymbolTokenRef::Text(token) => write!(output, "{}::", token.as_ref()), + RawSymbolTokenRef::SymbolId(sid) => write!(output, "${sid}::"), + }?; } - write!(self.output(), "{value:e}")?; - Ok(()) - } - - fn write_decimal(mut self, value: &Decimal) -> IonResult<()> { - write!(self.output(), "{value}")?; - Ok(()) - } - - fn write_timestamp(mut self, value: &Timestamp) -> IonResult<()> { - write!(self.output(), "{value}")?; - Ok(()) - } - - fn write_string>(mut self, value: A) -> IonResult<()> { - write!(self.output(), "\"")?; - RawTextWriter::::write_escaped_text_body(self.output(), value)?; - write!(self.output(), "\"")?; - Ok(()) - } - - fn write_symbol(mut self, value: A) -> IonResult<()> { - RawTextWriter::::write_symbol_token(self.output(), value)?; - Ok(()) - } - - fn write_clob>(self, _value: A) -> IonResult<()> { - todo!() - } - - fn write_blob>(mut self, value: A) -> IonResult<()> { - // Rust format strings escape curly braces by doubling them. The following string is: - // * The opening {{ from a text Ion blob, with each brace doubled to escape it. - // * A {} pair used by the format string to indicate where the base64-encoded bytes - // should be inserted. - // * The closing }} from a text Ion blob, with each brace doubled to escape it. - write!(self.output(), "{{{{{}}}}}", base64::encode(value))?; - Ok(()) - } - - fn list_writer(self) -> IonResult<>::ListWriter<'a>> { - TextListWriter_1_0::new(self.writer, self.depth + 1) - } - - fn sexp_writer(self) -> IonResult<>::SExpWriter<'a>> { - TextSExpWriter_1_0::new(self.writer, self.depth + 1) + Ok(self.value_writer) } +} - fn struct_writer(self) -> IonResult<>::StructWriter<'a>> { - TextStructWriter_1_0::new(self.writer, self.depth + 1) - } +impl<'value, W: Write + 'value, SymbolType: AsRawSymbolTokenRef> Sealed + for TextAnnotatedValueWriter_1_0<'value, W, SymbolType> +{ } +impl<'value, W: Write> Sealed for TextValueWriter_1_0<'value, W> {} + /// Helper type that is home to information and behavior common to the list writer, s-expression writer, /// and struct writer. struct TextContainerWriter_1_0<'a, W: Write> { @@ -455,7 +272,7 @@ impl<'a, W: Write> TextContainerWriter_1_0<'a, W> { delimiter_between_values: &str, ) -> IonResult<&mut Self> { self.write_indentation()?; - value.write_as_ion(self.annotated_value_writer())?; + value.write_as_ion(self.annotatable_value_writer())?; let space_between_nested_values = self.whitespace_config().space_between_nested_values; write!( self.output(), @@ -493,20 +310,21 @@ impl<'a, W: Write> TextContainerWriter_1_0<'a, W> { } #[inline] - fn annotated_value_writer(&mut self) -> TextAnnotatedValueWriter_1_0<'_, W> { - TextAnnotatedValueWriter_1_0 { + fn annotatable_value_writer(&mut self) -> TextAnnotatableValueWriter_1_0<'_, W> { + TextAnnotatableValueWriter_1_0 { value_writer: self.value_writer(), } } } -/// Incrementally encodes a potentially heterogeneously typed Ion list. -pub struct TextListWriter_1_0<'a, W: Write> { - container_writer: TextContainerWriter_1_0<'a, W>, +/// A superset of the functionality offered by the user-facing `TextListWriter_1_0`. In particular, +/// this type exposes an `end` function that MUST be called to guarantee correct output. +pub struct TextListWriter_1_0<'top, W: Write> { + container_writer: TextContainerWriter_1_0<'top, W>, } -impl<'a, W: Write> TextListWriter_1_0<'a, W> { - pub fn new(writer: &'a mut LazyRawTextWriter_1_0, depth: usize) -> IonResult { +impl<'top, W: Write> TextListWriter_1_0<'top, W> { + pub(crate) fn new(writer: &'top mut LazyRawTextWriter_1_0, depth: usize) -> IonResult { let container_writer = TextContainerWriter_1_0::new(writer, depth, IonType::List, "[")?; Ok(Self { container_writer }) } @@ -518,18 +336,23 @@ impl<'a, W: Write> TextListWriter_1_0<'a, W> { } /// Finalizes the list, preventing further values from being written. - fn end(self) -> IonResult<()> { + pub(crate) fn end(self) -> IonResult<()> { self.container_writer.end("]")?; Ok(()) } } -impl<'a, W: Write, E: LazyEncoder> SequenceWriter<'a, W, E> for TextListWriter_1_0<'a, W> { - delegate! { - to self { - fn write(&mut self, value: V) -> IonResult<&mut Self>; - fn end(self) -> IonResult<()>; - } +impl<'top, W: Write> MakeValueWriter for TextListWriter_1_0<'top, W> { + type ValueWriter<'a> = TextAnnotatableValueWriter_1_0<'a, W> where Self: 'a; + + fn value_writer(&mut self) -> Self::ValueWriter<'_> { + self.container_writer.annotatable_value_writer() + } +} + +impl<'top, W: Write> SequenceWriter for TextListWriter_1_0<'top, W> { + fn write(&mut self, value: V) -> IonResult<&mut Self> { + self.write(value) } } @@ -539,29 +362,36 @@ pub struct TextSExpWriter_1_0<'a, W: Write> { } impl<'a, W: Write> TextSExpWriter_1_0<'a, W> { - pub fn new(writer: &'a mut LazyRawTextWriter_1_0, depth: usize) -> IonResult { + pub(crate) fn new(writer: &'a mut LazyRawTextWriter_1_0, depth: usize) -> IonResult { let container_writer = TextContainerWriter_1_0::new(writer, depth, IonType::SExp, "(")?; Ok(Self { container_writer }) } /// Writes the provided data as a nested value. - fn write(&mut self, value: V) -> IonResult<&mut Self> { + pub(crate) fn write(&mut self, value: V) -> IonResult<&mut Self> { self.container_writer.write_value(value, " ")?; Ok(self) } /// Finalizes the sexp, preventing further values from being written. - fn end(self) -> IonResult<()> { + pub(crate) fn end(self) -> IonResult<()> { self.container_writer.end(")")?; Ok(()) } } -impl<'a, W: Write, E: LazyEncoder> SequenceWriter<'a, W, E> for TextSExpWriter_1_0<'a, W> { +impl<'value, W: Write> MakeValueWriter for TextSExpWriter_1_0<'value, W> { + type ValueWriter<'a> = TextAnnotatableValueWriter_1_0<'a, W> where Self: 'a; + + fn value_writer(&mut self) -> Self::ValueWriter<'_> { + self.container_writer.annotatable_value_writer() + } +} + +impl<'a, W: Write> SequenceWriter for TextSExpWriter_1_0<'a, W> { delegate! { to self { fn write(&mut self, value: V) -> IonResult<&mut Self>; - fn end(self) -> IonResult<()>; } } } @@ -572,13 +402,18 @@ pub struct TextStructWriter_1_0<'a, W: Write> { } impl<'a, W: Write> TextStructWriter_1_0<'a, W> { - pub fn new(writer: &'a mut LazyRawTextWriter_1_0, depth: usize) -> IonResult { + pub(crate) fn new(writer: &'a mut LazyRawTextWriter_1_0, depth: usize) -> IonResult { let container_writer = TextContainerWriter_1_0::new(writer, depth, IonType::Struct, "{")?; Ok(Self { container_writer }) } + + pub(crate) fn end(self) -> IonResult<()> { + self.container_writer.end("}")?; + Ok(()) + } } -impl<'a, W: Write> StructWriter<'a, W, TextEncoding_1_0> for TextStructWriter_1_0<'a, W> { +impl<'a, W: Write> StructWriter for TextStructWriter_1_0<'a, W> { fn write( &mut self, name: A, @@ -597,17 +432,12 @@ impl<'a, W: Write> StructWriter<'a, W, TextEncoding_1_0> for TextStructWriter_1_ self.container_writer.write_value(value, ",")?; Ok(self) } - - fn end(self) -> IonResult<()> { - self.container_writer.end("}")?; - Ok(()) - } } #[cfg(test)] mod tests { use crate::lazy::encoder::annotate::Annotate; - use crate::lazy::encoder::{LazyRawTextWriter_1_0, StructWriter}; + use crate::lazy::encoder::LazyRawTextWriter_1_0; use crate::symbol_ref::AsSymbolRef; use crate::{Element, IonData, IonResult, Timestamp}; @@ -621,7 +451,10 @@ mod tests { test(&mut writer)?; writer.flush()?; let actual = Element::read_all(buffer)?; - assert!(IonData::eq(&expected, &actual)); + assert!( + IonData::eq(&expected, &actual), + "expected:\n{expected:?}\nwas not Ion equal to actual:\n{actual:?}\n" + ); Ok(()) } @@ -665,107 +498,107 @@ mod tests { "#; let test = |writer: &mut LazyRawTextWriter_1_0<&mut Vec>| { writer - .write(1.annotate(&["foo", "bar"]))? - .write(false.annotate(&["quux", "quuz", "gary"]))? - .write(3f32.annotate(&["Mercury", "Venus"]))? - .write("foo".annotate(&["Earth"]))? - .write("bar".as_symbol_ref().annotate(&["Mars", "Jupiter"]))? + .write(1.annotated_with(&["foo", "bar"]))? + .write(false.annotated_with(&["quux", "quuz", "gary"]))? + .write(3f32.annotated_with(&["Mercury", "Venus"]))? + .write("foo".annotated_with(&["Earth"]))? + .write("bar".as_symbol_ref().annotated_with(&["Mars", "Jupiter"]))? .write( Timestamp::with_ymd(2023, 11, 9) .build()? - .annotate(&["Saturn"]), + .annotated_with(&["Saturn"]), )? - .write((&[0xE0u8, 0x01, 0x00, 0xEA][..]).annotate(&["Uranus"]))?; + .write((&[0xE0u8, 0x01, 0x00, 0xEA][..]).annotated_with(&["Uranus"]))?; Ok(()) }; writer_test(expected, test) } - #[test] - fn write_list() -> IonResult<()> { - let expected = r#" - [ - 1, - false, - 3e0, - "foo", - bar, - 2023-11-09T, - {{4AEA6g==}}, - ] - "#; - let test = |writer: &mut LazyRawTextWriter_1_0<&mut Vec>| { - let mut list = writer.list_writer()?; - list.write(1)? - .write(false)? - .write(3f32)? - .write("foo")? - .write("bar".as_symbol_ref())? - .write(Timestamp::with_ymd(2023, 11, 9).build()?)? - .write(&[0xE0u8, 0x01, 0x00, 0xEA][..])?; - list.end()?; - Ok(()) - }; - writer_test(expected, test) - } - - #[test] - fn write_sexp() -> IonResult<()> { - let expected = r#" - ( - 1 - false - 3e0 - "foo" - bar - 2023-11-09T - {{4AEA6g==}} - // Nested list - [1, 2, 3] - ) - "#; - let test = |writer: &mut LazyRawTextWriter_1_0<&mut Vec>| { - let mut sexp = writer.sexp_writer()?; - sexp.write(1)? - .write(false)? - .write(3f32)? - .write("foo")? - .write("bar".as_symbol_ref())? - .write(Timestamp::with_ymd(2023, 11, 9).build()?)? - .write([0xE0u8, 0x01, 0x00, 0xEA])? - .write([1, 2, 3])?; - sexp.end()?; - Ok(()) - }; - writer_test(expected, test) - } - - #[test] - fn write_struct() -> IonResult<()> { - let expected = r#" - { - a: 1, - b: false, - c: 3e0, - d: "foo", - e: bar, - f: 2023-11-09T, - g: {{4AEA6g==}}, - } - "#; - let test = |writer: &mut LazyRawTextWriter_1_0<&mut Vec>| { - let mut struct_ = writer.struct_writer()?; - struct_ - .write("a", 1)? - .write("b", false)? - .write("c", 3f32)? - .write("d", "foo")? - .write("e", "bar".as_symbol_ref())? - .write("f", Timestamp::with_ymd(2023, 11, 9).build()?)? - .write("g", [0xE0u8, 0x01, 0x00, 0xEA])?; - struct_.end()?; - Ok(()) - }; - writer_test(expected, test) - } + // #[test] + // fn write_list() -> IonResult<()> { + // let expected = r#" + // [ + // 1, + // false, + // 3e0, + // "foo", + // bar, + // 2023-11-09T, + // {{4AEA6g==}}, + // ] + // "#; + // let test = |writer: &mut LazyRawTextWriter_1_0<&mut Vec>| { + // writer.value_writer().write_list(|list| { + // list.write(1)? + // .write(false)? + // .write(3f32)? + // .write("foo")? + // .write("bar".as_symbol_ref())? + // .write(Timestamp::with_ymd(2023, 11, 9).build()?)? + // .write(&[0xE0u8, 0x01, 0x00, 0xEA][..])?; + // Ok(()) + // }) + // }; + // writer_test(expected, test) + // } + // + // #[test] + // fn write_sexp() -> IonResult<()> { + // let expected = r#" + // ( + // 1 + // false + // 3e0 + // "foo" + // bar + // 2023-11-09T + // {{4AEA6g==}} + // // Nested list + // [1, 2, 3] + // ) + // "#; + // let test = |writer: &mut LazyRawTextWriter_1_0<&mut Vec>| { + // writer.value_writer().write_sexp(|sexp| { + // sexp.write(1)? + // .write(false)? + // .write(3f32)? + // .write("foo")? + // .write("bar".as_symbol_ref())? + // .write(Timestamp::with_ymd(2023, 11, 9).build()?)? + // .write([0xE0u8, 0x01, 0x00, 0xEA])? + // .write([1, 2, 3])?; + // Ok(()) + // }) + // }; + // writer_test(expected, test) + // } + // + // #[test] + // fn write_struct() -> IonResult<()> { + // let expected = r#" + // { + // a: 1, + // b: false, + // c: 3e0, + // d: "foo", + // e: bar, + // f: 2023-11-09T, + // g: {{4AEA6g==}}, + // } + // "#; + // let test = |writer: &mut LazyRawTextWriter_1_0<&mut Vec>| { + // writer.value_writer().write_struct(|struct_| { + // struct_ + // .write("a", 1)? + // .write("b", false)? + // .write("c", 3f32)? + // .write("d", "foo")? + // .write("e", "bar".as_symbol_ref())? + // .write("f", Timestamp::with_ymd(2023, 11, 9).build()?)? + // .write("g", [0xE0u8, 0x01, 0x00, 0xEA])?; + // Ok(()) + // }) + // }; + // writer_test(expected, test) + // } } diff --git a/src/lazy/encoder/value_writer.rs b/src/lazy/encoder/value_writer.rs new file mode 100644 index 00000000..43f39c39 --- /dev/null +++ b/src/lazy/encoder/value_writer.rs @@ -0,0 +1,362 @@ +use crate::lazy::encoder::write_as_ion::WriteAsIon; +use crate::lazy::encoder::{ + TextAnnotatedValueWriter_1_0, TextListWriter_1_0, TextSExpWriter_1_0, TextStructWriter_1_0, + TextValueWriter_1_0, +}; +use crate::raw_symbol_token_ref::AsRawSymbolTokenRef; +use crate::text::text_formatter::IonValueFormatter; +use crate::{Decimal, Int, IonResult, IonType, RawTextWriter, Timestamp}; +use delegate::delegate; +use std::fmt::Formatter; +use std::io::Write; + +// TODO: Make this private +pub trait MakeValueWriter { + type ValueWriter<'a>: AnnotatableValueWriter + where + Self: 'a; + + fn value_writer(&mut self) -> Self::ValueWriter<'_>; +} + +/// One-shot methods that take a (possibly empty) sequence of annotations to encode and return a ValueWriter. +pub trait AnnotatableValueWriter: Sized { + type ValueWriter: ValueWriter; + type AnnotatedValueWriter<'a, SymbolType: AsRawSymbolTokenRef + 'a>: ValueWriter + where + Self: 'a; + /// Writes the provided annotations to the output stream and returns a [`ValueWriter`] that can + /// be used to serialize the value itself. + /// + /// If there are no annotations, use [`Self::without_annotations`] instead. This method will loop + /// over the empty sequence, incurring minor performance overhead while `without_annotations` + /// is a true no-op. + fn with_annotations<'a, SymbolType: AsRawSymbolTokenRef>( + self, + annotations: &'a [SymbolType], + ) -> Self::AnnotatedValueWriter<'a, SymbolType> + where + Self: 'a; + + /// Performs no operations and returns a [`ValueWriter`]. + fn without_annotations(self) -> Self::ValueWriter; + + // Users can call `ValueWriter` methods on the `AnnotatedValueWriter` directly. Doing so + // will implicitly call `without_annotations`. + delegate! { + to self.without_annotations() { + fn write_null(self, ion_type: IonType) -> IonResult<()>; + fn write_bool(self, value: bool) -> IonResult<()>; + fn write_i64(self, value: i64) -> IonResult<()>; + fn write_int(self, value: &Int) -> IonResult<()>; + fn write_f32(self, value: f32) -> IonResult<()>; + fn write_f64(self, value: f64) -> IonResult<()>; + fn write_decimal(self, value: &Decimal) -> IonResult<()>; + fn write_timestamp(self, value: &Timestamp) -> IonResult<()>; + fn write_string>(self, value: A) -> IonResult<()>; + fn write_symbol(self, value: A) -> IonResult<()>; + fn write_clob>(self, value: A) -> IonResult<()>; + fn write_blob>(self, value: A) -> IonResult<()>; + fn write_list FnOnce(&mut ::ListWriter<'a>) -> IonResult<()>>( + self, + list_fn: F, + ) -> IonResult<()>; + fn write_sexp FnOnce(&mut ::SExpWriter<'a>) -> IonResult<()>>( + self, + sexp_fn: F, + ) -> IonResult<()>; + fn write_struct< + F: for<'a> FnOnce(&mut ::StructWriter<'a>) -> IonResult<()>, + >( + self, + struct_fn: F, + ) -> IonResult<()>; + } + } +} + +pub trait ValueWriter { + type ListWriter<'a>: SequenceWriter; + type SExpWriter<'a>: SequenceWriter; + type StructWriter<'a>: StructWriter; + + fn write_null(self, ion_type: IonType) -> IonResult<()>; + fn write_bool(self, value: bool) -> IonResult<()>; + fn write_i64(self, value: i64) -> IonResult<()>; + fn write_int(self, value: &Int) -> IonResult<()>; + fn write_f32(self, value: f32) -> IonResult<()>; + fn write_f64(self, value: f64) -> IonResult<()>; + fn write_decimal(self, value: &Decimal) -> IonResult<()>; + fn write_timestamp(self, value: &Timestamp) -> IonResult<()>; + fn write_string>(self, value: A) -> IonResult<()>; + fn write_symbol(self, value: A) -> IonResult<()>; + fn write_clob>(self, value: A) -> IonResult<()>; + fn write_blob>(self, value: A) -> IonResult<()>; + + fn write_list FnOnce(&mut Self::ListWriter<'a>) -> IonResult<()>>( + self, + list_fn: F, + ) -> IonResult<()>; + + fn write_sexp FnOnce(&mut Self::SExpWriter<'a>) -> IonResult<()>>( + self, + sexp_fn: F, + ) -> IonResult<()>; + fn write_struct FnOnce(&mut Self::StructWriter<'a>) -> IonResult<()>>( + self, + struct_fn: F, + ) -> IonResult<()>; +} + +pub trait StructWriter { + /// Writes a struct field using the provided name/value pair. + fn write( + &mut self, + name: A, + value: V, + ) -> IonResult<&mut Self>; +} + +/// Takes a series of `TYPE => METHOD` pairs, generating a function for each that calls the +/// corresponding value writer method and then returns `Ok(self)` upon success. +macro_rules! delegate_and_return_self { + // End of iteration + () => {}; + // Recurses one argument pair at a time + ($value_type:ty => $method:ident, $($rest:tt)*) => { + fn $method(&mut self, value: $value_type) -> IonResult<&mut Self> { + self.value_writer().without_annotations().$method(value)?; + Ok(self) + } + delegate_and_return_self!($($rest)*); + }; +} + +pub trait SequenceWriter: MakeValueWriter { + fn annotate<'a, A: AsRawSymbolTokenRef>( + &'a mut self, + annotations: &'a [A], + ) -> <::ValueWriter<'_> as AnnotatableValueWriter>::AnnotatedValueWriter<'a, A> + { + self.value_writer().with_annotations(annotations) + } + + /// Writes a value in the current context (list, s-expression, or stream) and upon success + /// returns another reference to `self` to enable method chaining. + fn write(&mut self, value: V) -> IonResult<&mut Self> { + value.write_as_ion(self.value_writer())?; + Ok(self) + } + + // Creates functions that delegate to the ValueWriter method of the same name but which then + // return `self` so it can be re-used/chained. + delegate_and_return_self!( + IonType => write_null, + bool => write_bool, + i64 => write_i64, + &Int => write_int, + f32 => write_f32, + f64 => write_f64, + &Decimal => write_decimal, + &Timestamp => write_timestamp, + impl AsRef => write_string, + impl AsRawSymbolTokenRef => write_symbol, + impl AsRef<[u8]> => write_clob, + impl AsRef<[u8]> => write_blob, + ); + + // XXX: For now, it's not possible to offer versions of `write_list`, `write_sexp`, or + // `write_struct`. This is due to a point-in-time limitation in the borrow checker[1]. + // It is still possible to call (e.g.) + // self.value_writer().list_writer(...) + // as a workaround. + // [1]: https://blog.rust-lang.org/2022/10/28/gats-stabilization.html#implied-static-requirement-from-higher-ranked-trait-bounds +} + +impl<'value, W: Write + 'value, SymbolType: AsRawSymbolTokenRef> ValueWriter + for TextAnnotatedValueWriter_1_0<'value, W, SymbolType> +{ + type ListWriter<'a> = TextListWriter_1_0<'value, W>; + type SExpWriter<'a> = TextSExpWriter_1_0<'value, W>; + type StructWriter<'a> = TextStructWriter_1_0<'value, W>; + + delegate! { + to self.encode_annotations()? { + fn write_null(self, ion_type: IonType) -> IonResult<()>; + fn write_bool(self, value: bool) -> IonResult<()>; + fn write_i64(self, value: i64) -> IonResult<()>; + fn write_int(self, value: &Int) -> IonResult<()>; + fn write_f32(self, value: f32) -> IonResult<()>; + fn write_f64(self, value: f64) -> IonResult<()>; + fn write_decimal(self, value: &Decimal) -> IonResult<()>; + fn write_timestamp(self, value: &Timestamp) -> IonResult<()>; + fn write_string>(self, value: A) -> IonResult<()>; + fn write_symbol(self, value: A) -> IonResult<()>; + fn write_clob>(self, value: A) -> IonResult<()>; + fn write_blob>(self, value: A) -> IonResult<()>; + fn write_list FnOnce(&mut Self::ListWriter<'a>) -> IonResult<()>>( + self, + list_fn: F, + ) -> IonResult<()>; + fn write_sexp FnOnce(&mut Self::SExpWriter<'a>) -> IonResult<()>>( + self, + sexp_fn: F, + ) -> IonResult<()>; + fn write_struct< + F: for<'a> FnOnce(&mut Self::StructWriter<'a>) -> IonResult<()>, + >( + self, + struct_fn: F, + ) -> IonResult<()>; + } + } +} + +impl<'value, W: Write> ValueWriter for TextValueWriter_1_0<'value, W> { + type ListWriter<'a> = TextListWriter_1_0<'value, W>; + type SExpWriter<'a> = TextSExpWriter_1_0<'value, W>; + type StructWriter<'a> = TextStructWriter_1_0<'value, W>; + fn write_null(mut self, ion_type: IonType) -> IonResult<()> { + use crate::IonType::*; + let null_text = match ion_type { + Null => "null", + Bool => "null.bool", + Int => "null.int", + Float => "null.float", + Decimal => "null.decimal", + Timestamp => "null.timestamp", + Symbol => "null.symbol", + String => "null.string", + Blob => "null.blob", + Clob => "null.clob", + List => "null.list", + SExp => "null.sexp", + Struct => "null.struct", + }; + write!(self.output(), "{null_text}")?; + Ok(()) + } + + fn write_bool(mut self, value: bool) -> IonResult<()> { + let bool_text = match value { + true => "true", + false => "false", + }; + write!(self.output(), "{bool_text}")?; + Ok(()) + } + + fn write_i64(mut self, value: i64) -> IonResult<()> { + write!(self.output(), "{value}")?; + Ok(()) + } + + fn write_int(mut self, value: &Int) -> IonResult<()> { + write!(self.output(), "{value}")?; + Ok(()) + } + + fn write_f32(self, value: f32) -> IonResult<()> { + self.write_f64(value as f64) + } + + fn write_f64(mut self, value: f64) -> IonResult<()> { + if value.is_nan() { + write!(self.output(), "nan")?; + return Ok(()); + } + + if value.is_infinite() { + if value.is_sign_positive() { + write!(self.output(), "+inf")?; + } else { + write!(self.output(), "-inf")?; + } + return Ok(()); + } + + // The {:e} formatter provided by the Display trait writes floats using scientific + // notation. It works for all floating point values except -0.0 (it drops the sign). + // See: https://github.com/rust-lang/rust/issues/20596 + if value == 0.0f64 && value.is_sign_negative() { + write!(self.output(), "-0e0")?; + return Ok(()); + } + + write!(self.output(), "{value:e}")?; + Ok(()) + } + + fn write_decimal(mut self, value: &Decimal) -> IonResult<()> { + write!(self.output(), "{value}")?; + Ok(()) + } + + fn write_timestamp(mut self, value: &Timestamp) -> IonResult<()> { + write!(self.output(), "{value}")?; + Ok(()) + } + + fn write_string>(mut self, value: A) -> IonResult<()> { + write!(self.output(), "\"")?; + RawTextWriter::::write_escaped_text_body(self.output(), value)?; + write!(self.output(), "\"")?; + Ok(()) + } + + fn write_symbol(mut self, value: A) -> IonResult<()> { + RawTextWriter::::write_symbol_token(self.output(), value)?; + Ok(()) + } + + fn write_clob>(mut self, value: A) -> IonResult<()> { + // This type exists solely to enable using the IonValueFormatter (which operates on + // `std::fmt::Write`) to write to a `std::io::Write`. + struct ClobShim<'a>(&'a [u8]); + impl<'a> std::fmt::Display for ClobShim<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut formatter = IonValueFormatter { output: f }; + formatter.format_clob(self.0)?; + Ok(()) + } + } + + write!(self.output(), "{}", ClobShim(value.as_ref()))?; + Ok(()) + } + + fn write_blob>(mut self, value: A) -> IonResult<()> { + // Rust format strings escape curly braces by doubling them. The following string is: + // * The opening {{ from a text Ion blob, with each brace doubled to escape it. + // * A {} pair used by the format string to indicate where the base64-encoded bytes + // should be inserted. + // * The closing }} from a text Ion blob, with each brace doubled to escape it. + write!(self.output(), "{{{{{}}}}}", base64::encode(value))?; + Ok(()) + } + + fn write_list FnOnce(&mut Self::ListWriter<'a>) -> IonResult<()>>( + self, + list_fn: F, + ) -> IonResult<()> { + let mut list_writer = TextListWriter_1_0::new(self.writer, self.depth + 1)?; + list_fn(&mut list_writer)?; + list_writer.end() + } + fn write_sexp FnOnce(&mut Self::SExpWriter<'a>) -> IonResult<()>>( + self, + sexp_fn: F, + ) -> IonResult<()> { + let mut sexp_writer = TextSExpWriter_1_0::new(self.writer, self.depth + 1)?; + sexp_fn(&mut sexp_writer)?; + sexp_writer.end() + } + fn write_struct FnOnce(&mut Self::StructWriter<'a>) -> IonResult<()>>( + self, + struct_fn: F, + ) -> IonResult<()> { + let mut struct_writer = TextStructWriter_1_0::new(self.writer, self.depth + 1)?; + struct_fn(&mut struct_writer)?; + struct_writer.end() + } +} diff --git a/src/lazy/encoder/write_as_ion.rs b/src/lazy/encoder/write_as_ion.rs index 48925eb7..f2135e7d 100644 --- a/src/lazy/encoder/write_as_ion.rs +++ b/src/lazy/encoder/write_as_ion.rs @@ -12,31 +12,27 @@ //! //! Types that do not explicitly implement [`WriteAsIon`] will fall back to a blanket implementation //! that uses an empty annotations sequence. A custom annotations sequence can be set on a per-value -//! basis by using the [`annotate`](crate::lazy::encoder::annotate::Annotate::annotate) method +//! basis by using the [`annotate`](crate::lazy::encoder::annotate::Annotate::annotated_with) method //! provided by the [`Annotate`](crate::lazy::encoder::annotate::Annotate) trait. -use crate::lazy::encoder::{AnnotatedValueWriter, LazyEncoder, SequenceWriter, ValueWriter}; +use std::marker::PhantomData; + +use crate::lazy::encoder::value_writer::{AnnotatableValueWriter, SequenceWriter, ValueWriter}; use crate::{ Blob, Clob, Decimal, Int, IonResult, Null, RawSymbolToken, RawSymbolTokenRef, Symbol, SymbolRef, Timestamp, }; -use std::io::Write; /// Defines how a Rust type should be serialized as Ion in terms of the methods available -/// on [`ValueWriter`]. +/// on [`ValueWriter`]. To annotate instances of your type with a sequence of text values, +/// implement the [`WriteAsIon`] trait instaed. pub trait WriteAsIonValue { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()>; + fn write_as_ion_value(&self, writer: V) -> IonResult<()>; } /// Defines how a Rust type should be serialized as Ion in terms of the methods available -/// on [`AnnotatedValueWriter`] and [`ValueWriter`]. +/// on [`AnnotatableValueWriter`] and [`ValueWriter`]. pub trait WriteAsIon { - fn write_as_ion<'a, W: Write + 'a, E: LazyEncoder, V: AnnotatedValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()>; + fn write_as_ion(&self, writer: V) -> IonResult<()>; } // Any type that does not define `WriteAsIon` itself will use this blanket implementation that does @@ -45,229 +41,210 @@ impl WriteAsIon for T where T: WriteAsIonValue, { - fn write_as_ion<'a, W: Write + 'a, E: LazyEncoder, V: AnnotatedValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { - self.write_as_ion_value(writer.no_annotations()) + fn write_as_ion(&self, writer: V) -> IonResult<()> { + self.write_as_ion_value(writer.without_annotations()) } } // ===== WriteAsIonValue implementations for common types ===== impl WriteAsIonValue for Null { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_null(self.0) } } impl WriteAsIonValue for bool { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_bool(*self) } } impl WriteAsIonValue for i32 { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_i64(*self as i64) } } impl WriteAsIonValue for i64 { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_i64(*self) } } impl WriteAsIonValue for usize { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_int(&Int::from(*self)) } } impl WriteAsIonValue for f32 { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_f32(*self) } } impl WriteAsIonValue for f64 { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_f64(*self) } } impl WriteAsIonValue for Decimal { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_decimal(self) } } impl WriteAsIonValue for Timestamp { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_timestamp(self) } } impl WriteAsIonValue for Symbol { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_symbol(self) } } impl<'b> WriteAsIonValue for RawSymbolTokenRef<'b> { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_symbol(self) } } impl<'b> WriteAsIonValue for SymbolRef<'b> { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_symbol(self) } } impl WriteAsIonValue for RawSymbolToken { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_symbol(self) } } impl<'b> WriteAsIonValue for &'b str { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_string(self) } } impl WriteAsIonValue for String { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_string(self) } } impl<'b> WriteAsIonValue for &'b [u8] { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_blob(self) } } impl WriteAsIonValue for [u8; N] { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_blob(self) } } impl WriteAsIonValue for Blob { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_blob(self) } } impl WriteAsIonValue for Clob { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_clob(self) } } -impl WriteAsIonValue for &T -where - T: WriteAsIonValue, -{ - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { +impl WriteAsIonValue for &T { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { (*self).write_as_ion_value(writer) } } -impl WriteAsIonValue for &[T] -where - for<'x> &'x T: WriteAsIon, -{ - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { - let mut list = writer.list_writer()?; - for value in *self { - list.write(value)?; +macro_rules! impl_write_as_ion_value_for_iterable { + ($iterable:ty, $item:ident $(, const $n:ident: $n_type:ty)?) => { + impl<$item $(, const $n: $n_type)?> WriteAsIonValue for $iterable + where + for<'a> &'a $item: WriteAsIon, + { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { + writer.write_list(|list| { + for value in self.iter() { + list.write(value)?; + } + Ok(()) + }) + } } - list.end()?; - Ok(()) - } + }; } -impl WriteAsIonValue for [T; N] +impl_write_as_ion_value_for_iterable!(Vec, T); +impl_write_as_ion_value_for_iterable!(&[T], T); +impl_write_as_ion_value_for_iterable!([T; N], T, const N: usize); + +pub trait WriteAsSExp: Sized where - T: WriteAsIonValue, + T: WriteAsIon, { - fn write_as_ion_value<'a, W: Write + 'a, E: LazyEncoder, V: ValueWriter<'a, W, E>>( - &self, - writer: V, - ) -> IonResult<()> { - let mut list = writer.list_writer()?; - for value in self { - list.write(value)?; + fn as_sexp(self) -> SExpTypeHint; +} +macro_rules! impl_write_as_sexp_for_iterable { + ($iterable:ty, $item:ident $(, const $n:ident: $n_type:ty)?) => { + impl<$item $(, const $n: $n_type)?> WriteAsSExp<$item> for $iterable + where + $item: WriteAsIon, + { + fn as_sexp(self) -> SExpTypeHint { + SExpTypeHint::new(self) + } + } + }; +} + +impl_write_as_sexp_for_iterable!(Vec, T); +impl_write_as_sexp_for_iterable!(&[T], T); +impl_write_as_sexp_for_iterable!([T; N], T, const N: usize); + +pub struct SExpTypeHint { + values: S, + spooky: PhantomData, +} + +impl SExpTypeHint { + pub fn new(values: S) -> Self { + Self { + values, + spooky: PhantomData, } - list.end()?; - Ok(()) } } + +macro_rules! impl_write_as_ion_value_for_sexp_type_hint { + ($iterable:ty, $item:ident $(, const $n:ident: $n_type:ty)?) => { + impl<$item $(, const $n: $n_type)?> WriteAsIonValue for SExpTypeHint<$iterable, $item> + where + for<'a> &'a $item: WriteAsIon, + { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { + writer.write_sexp(|sexp| { + for value in self.values.iter() { + sexp.write(value)?; + } + Ok(()) + }) + } + } + }; +} + +impl_write_as_ion_value_for_sexp_type_hint!(Vec, T); +impl_write_as_ion_value_for_sexp_type_hint!(&[T], T); +impl_write_as_ion_value_for_sexp_type_hint!([T; N], T, const N: usize); From 93c824a0cf729900ff32ae8f2f145024788a66f3 Mon Sep 17 00:00:00 2001 From: Zack Slayton Date: Fri, 8 Dec 2023 10:11:26 -0500 Subject: [PATCH 2/6] Moved code into more cohesive modules --- src/lazy/encoder/annotate.rs | 2 +- src/lazy/encoder/binary/mod.rs | 9 +- src/lazy/encoder/mod.rs | 403 +------------------- src/lazy/encoder/text/mod.rs | 92 +++++ src/lazy/encoder/text/value_writer.rs | 516 ++++++++++++++++++++++++++ src/lazy/encoder/value_writer.rs | 213 +---------- 6 files changed, 631 insertions(+), 604 deletions(-) create mode 100644 src/lazy/encoder/text/mod.rs create mode 100644 src/lazy/encoder/text/value_writer.rs diff --git a/src/lazy/encoder/annotate.rs b/src/lazy/encoder/annotate.rs index 455eeb26..6ba5c710 100644 --- a/src/lazy/encoder/annotate.rs +++ b/src/lazy/encoder/annotate.rs @@ -18,7 +18,7 @@ pub trait Annotate { ///# use ion_rs::IonResult; ///# fn main() -> IonResult<()> { /// use ion_rs::{Element, IonData}; - /// use ion_rs::lazy::encoder::LazyRawTextWriter_1_0; + /// use ion_rs::lazy::encoder::text::LazyRawTextWriter_1_0; /// use ion_rs::lazy::encoder::annotate::Annotate; /// /// let mut buffer = vec![]; diff --git a/src/lazy/encoder/binary/mod.rs b/src/lazy/encoder/binary/mod.rs index 5d0bd211..66ad41c5 100644 --- a/src/lazy/encoder/binary/mod.rs +++ b/src/lazy/encoder/binary/mod.rs @@ -9,7 +9,8 @@ use crate::lazy::encoder::binary::value_writer::{ BinaryAnnotatableValueWriter_1_0, MAX_INLINE_LENGTH, }; use crate::lazy::encoder::private::Sealed; -use crate::lazy::encoder::value_writer::{MakeValueWriter, SequenceWriter, StructWriter}; +use crate::lazy::encoder::value_writer::internal::MakeValueWriter; +use crate::lazy::encoder::value_writer::{SequenceWriter, StructWriter}; use crate::lazy::encoder::write_as_ion::WriteAsIon; use crate::lazy::encoder::{LazyEncoder, LazyRawWriter}; use crate::lazy::encoding::BinaryEncoding_1_0; @@ -71,7 +72,7 @@ impl LazyRawBinaryWriter_1_0 { } /// Writes the given Rust value to the output stream as a top-level value. - fn write(&mut self, value: V) -> IonResult<&mut Self> { + pub fn write(&mut self, value: V) -> IonResult<&mut Self> { value.write_as_ion(self.value_writer())?; Ok(self) } @@ -80,7 +81,7 @@ impl LazyRawBinaryWriter_1_0 { /// /// Calling `flush` also releases memory used for bookkeeping and storage, but calling it /// frequently can reduce overall throughput. - fn flush(&mut self) -> IonResult<()> { + pub fn flush(&mut self) -> IonResult<()> { // Temporarily break apart `self` to get simultaneous references to its innards. let Self { output, @@ -103,7 +104,7 @@ impl LazyRawBinaryWriter_1_0 { Ok(()) } - fn value_writer(&mut self) -> BinaryAnnotatableValueWriter_1_0<'_, '_> { + pub fn value_writer(&mut self) -> BinaryAnnotatableValueWriter_1_0<'_, '_> { let top_level = match self.encoding_buffer_ptr { // If the `encoding_buffer_ptr` is set, we already allocated an encoding buffer on // a previous call to `value_writer()`. Dereference the pointer and continue encoding diff --git a/src/lazy/encoder/mod.rs b/src/lazy/encoder/mod.rs index 22bdbc39..bd8a0711 100644 --- a/src/lazy/encoder/mod.rs +++ b/src/lazy/encoder/mod.rs @@ -3,20 +3,14 @@ use std::fmt::Debug; use std::io::Write; -use delegate::delegate; -use value_writer::{AnnotatableValueWriter, MakeValueWriter, SequenceWriter, StructWriter}; +use value_writer::SequenceWriter; -use write_as_ion::WriteAsIon; - -use crate::lazy::encoder::private::Sealed; -use crate::lazy::encoding::TextEncoding_1_0; -use crate::raw_symbol_token_ref::AsRawSymbolTokenRef; -use crate::text::raw_text_writer::{WhitespaceConfig, PRETTY_WHITESPACE_CONFIG}; -use crate::{IonResult, IonType, RawSymbolTokenRef, RawTextWriter}; +use crate::IonResult; pub mod annotate; pub mod binary; -mod value_writer; +pub mod text; +pub mod value_writer; pub mod write_as_ion; /// A family of types that collectively comprise the writer API for an Ion serialization @@ -36,15 +30,12 @@ pub trait LazyEncoder: 'static + Sized + Debug + Clone + Copy { type Writer: LazyRawWriter; } -impl LazyEncoder for TextEncoding_1_0 { - type Writer = LazyRawTextWriter_1_0; -} - pub(crate) mod private { /// Prevents types outside the crate from implementing traits that extend it. pub trait Sealed {} } +/// An Ion writer without an encoding context (that is: symbol/macro tables). pub trait LazyRawWriter: SequenceWriter { fn new(output: W) -> IonResult where @@ -52,392 +43,10 @@ pub trait LazyRawWriter: SequenceWriter { fn flush(&mut self) -> IonResult<()>; } -/// A raw text Ion 1.0 writer. -pub struct LazyRawTextWriter_1_0 { - output: W, - whitespace_config: &'static WhitespaceConfig, -} - -impl LazyRawTextWriter_1_0 { - /// Constructs a new writer that will emit encoded data to the specified `output`. - pub fn new(output: W) -> Self { - Self { - output, - whitespace_config: &PRETTY_WHITESPACE_CONFIG, - } - } - - /// Writes the provided data as a top-level value. - pub fn write(&mut self, value: V) -> IonResult<&mut Self> { - value.write_as_ion(self.annotatable_value_writer())?; - write!( - self.output, - "{}", - self.whitespace_config.space_between_top_level_values - )?; - Ok(self) - } - - /// Writes any pending data to the output stream and then calls [`Write::flush`] on it. - pub fn flush(&mut self) -> IonResult<()> { - self.output.flush()?; - Ok(()) - } - - /// Helper method to construct this format's `ValueWriter` implementation. - #[inline] - fn value_writer(&mut self) -> TextValueWriter_1_0<'_, W> { - TextValueWriter_1_0 { - writer: self, - depth: 0, - } - } - - /// Helper method to construct this format's `AnnotatedValueWriter` implementation. - #[inline] - fn annotatable_value_writer(&mut self) -> TextAnnotatableValueWriter_1_0<'_, W> { - TextAnnotatableValueWriter_1_0 { - value_writer: self.value_writer(), - } - } -} - -impl SequenceWriter for LazyRawTextWriter_1_0 { - // All default method impls -} - -impl MakeValueWriter for LazyRawTextWriter_1_0 { - type ValueWriter<'a> = TextAnnotatableValueWriter_1_0<'a, W> - where - Self: 'a; - - fn value_writer(&mut self) -> Self::ValueWriter<'_> { - let value_writer = TextValueWriter_1_0 { - writer: self, - depth: 0, - }; - TextAnnotatableValueWriter_1_0 { value_writer } - } -} - -impl LazyRawWriter for LazyRawTextWriter_1_0 { - fn new(output: W) -> IonResult { - Ok(LazyRawTextWriter_1_0::new(output)) - } - - // Delegate the trait methods to the inherent methods; this allows a version of these - // methods to be called on the concrete type even when the trait is not in scope. - delegate! { - to self { - fn flush(&mut self) -> IonResult<()>; - } - } -} - -pub struct TextValueWriter_1_0<'value, W: Write + 'value> { - writer: &'value mut LazyRawTextWriter_1_0, - depth: usize, -} - -impl<'value, W: Write> TextValueWriter_1_0<'value, W> { - fn output(&mut self) -> &mut W { - &mut self.writer.output - } - - fn whitespace_config(&self) -> &WhitespaceConfig { - self.writer.whitespace_config - } -} - -pub struct TextAnnotatableValueWriter_1_0<'value, W: Write> { - value_writer: TextValueWriter_1_0<'value, W>, -} - -impl<'value, W: Write> AnnotatableValueWriter for TextAnnotatableValueWriter_1_0<'value, W> { - type ValueWriter = TextValueWriter_1_0<'value, W>; - type AnnotatedValueWriter<'a, SymbolType: AsRawSymbolTokenRef + 'a> = - TextAnnotatedValueWriter_1_0<'a, W, SymbolType> where Self: 'a; - fn with_annotations<'a, SymbolType: AsRawSymbolTokenRef>( - self, - annotations: &'a [SymbolType], - ) -> Self::AnnotatedValueWriter<'a, SymbolType> - where - Self: 'a, - { - TextAnnotatedValueWriter_1_0 { - annotations, - value_writer: self.value_writer, - } - } - - fn without_annotations(self) -> TextValueWriter_1_0<'value, W> { - self.value_writer - } -} - -pub struct TextAnnotatedValueWriter_1_0<'value, W: Write, SymbolType: AsRawSymbolTokenRef + 'value> -{ - annotations: &'value [SymbolType], - value_writer: TextValueWriter_1_0<'value, W>, -} - -impl<'value, W: Write, SymbolType: AsRawSymbolTokenRef> - TextAnnotatedValueWriter_1_0<'value, W, SymbolType> -{ - fn encode_annotations(self) -> IonResult> { - let output = &mut self.value_writer.writer.output; - for annotation in self.annotations { - match annotation.as_raw_symbol_token_ref() { - RawSymbolTokenRef::Text(token) => write!(output, "{}::", token.as_ref()), - RawSymbolTokenRef::SymbolId(sid) => write!(output, "${sid}::"), - }?; - } - - Ok(self.value_writer) - } -} - -impl<'value, W: Write + 'value, SymbolType: AsRawSymbolTokenRef> Sealed - for TextAnnotatedValueWriter_1_0<'value, W, SymbolType> -{ -} - -impl<'value, W: Write> Sealed for TextValueWriter_1_0<'value, W> {} - -/// Helper type that is home to information and behavior common to the list writer, s-expression writer, -/// and struct writer. -struct TextContainerWriter_1_0<'a, W: Write> { - // Holds a reference to the output stream and a whitespace config - writer: &'a mut LazyRawTextWriter_1_0, - // The depth at which this container's child values appear. This value is used for formatting - // indentation where applicable. - depth: usize, - // Tracks whether the `end()` method was called (thereby emitting a closing delimiter) before - // this value was dropped. This scenario is a contract violation and results in a panic. - has_been_closed: bool, - // The Ion type of the container using this TextContainerWriter_1_0. This value is only - // used for more informative error messages. - ion_type: IonType, -} - -impl<'a, W: Write> Drop for TextContainerWriter_1_0<'a, W> { - fn drop(&mut self) { - // If the user didn't call `end`, the closing delimiter was not written to output. - // It's too late to call it here because we can't return a `Result`. - if !self.has_been_closed { - panic!( - "Container writer ({}) was dropped without calling `end()`.", - self.ion_type - ); - } - } -} - -impl<'a, W: Write> TextContainerWriter_1_0<'a, W> { - pub fn new( - writer: &'a mut LazyRawTextWriter_1_0, - depth: usize, - ion_type: IonType, - opening_delimiter: &str, - ) -> IonResult { - let space_after_container_start = writer.whitespace_config.space_after_container_start; - write!( - writer.output, - "{opening_delimiter}{space_after_container_start}" - )?; - Ok(Self { - writer, - depth, - ion_type, - has_been_closed: false, - }) - } - - /// Writes the `indentation` string set in the whitespace config to output `depth` times. - fn write_indentation(&mut self) -> IonResult<()> { - let indentation = self.whitespace_config().indentation; - if !indentation.is_empty() { - for _ in 0..self.depth { - write!(self.output(), "{indentation}")?; - } - } - Ok(()) - } - - /// Writes the provided value to output using its implementation of `WriteAsIon`, then writes - /// the whitespace config's `space_between_nested_values`. - fn write_value( - &mut self, - value: V, - delimiter_between_values: &str, - ) -> IonResult<&mut Self> { - self.write_indentation()?; - value.write_as_ion(self.annotatable_value_writer())?; - let space_between_nested_values = self.whitespace_config().space_between_nested_values; - write!( - self.output(), - "{delimiter_between_values}{space_between_nested_values}" - )?; - Ok(self) - } - - /// Finalizes the container, preventing further values from being written. - fn end(mut self, closing_delimiter: &str) -> IonResult<()> { - let space_between_top_level_values = - self.whitespace_config().space_between_top_level_values; - write!( - self.output(), - "{closing_delimiter}{space_between_top_level_values}" - )?; - self.has_been_closed = true; - Ok(()) - } - - fn output(&mut self) -> &mut W { - &mut self.writer.output - } - - fn whitespace_config(&self) -> &WhitespaceConfig { - self.writer.whitespace_config - } - - #[inline] - fn value_writer(&mut self) -> TextValueWriter_1_0<'_, W> { - TextValueWriter_1_0 { - writer: self.writer, - depth: self.depth, - } - } - - #[inline] - fn annotatable_value_writer(&mut self) -> TextAnnotatableValueWriter_1_0<'_, W> { - TextAnnotatableValueWriter_1_0 { - value_writer: self.value_writer(), - } - } -} - -/// A superset of the functionality offered by the user-facing `TextListWriter_1_0`. In particular, -/// this type exposes an `end` function that MUST be called to guarantee correct output. -pub struct TextListWriter_1_0<'top, W: Write> { - container_writer: TextContainerWriter_1_0<'top, W>, -} - -impl<'top, W: Write> TextListWriter_1_0<'top, W> { - pub(crate) fn new(writer: &'top mut LazyRawTextWriter_1_0, depth: usize) -> IonResult { - let container_writer = TextContainerWriter_1_0::new(writer, depth, IonType::List, "[")?; - Ok(Self { container_writer }) - } - - /// Writes the provided data as a nested value. - fn write(&mut self, value: V) -> IonResult<&mut Self> { - self.container_writer.write_value(value, ",")?; - Ok(self) - } - - /// Finalizes the list, preventing further values from being written. - pub(crate) fn end(self) -> IonResult<()> { - self.container_writer.end("]")?; - Ok(()) - } -} - -impl<'top, W: Write> MakeValueWriter for TextListWriter_1_0<'top, W> { - type ValueWriter<'a> = TextAnnotatableValueWriter_1_0<'a, W> where Self: 'a; - - fn value_writer(&mut self) -> Self::ValueWriter<'_> { - self.container_writer.annotatable_value_writer() - } -} - -impl<'top, W: Write> SequenceWriter for TextListWriter_1_0<'top, W> { - fn write(&mut self, value: V) -> IonResult<&mut Self> { - self.write(value) - } -} - -/// Incrementally encodes a potentially heterogeneously typed Ion s-expression. -pub struct TextSExpWriter_1_0<'a, W: Write> { - container_writer: TextContainerWriter_1_0<'a, W>, -} - -impl<'a, W: Write> TextSExpWriter_1_0<'a, W> { - pub(crate) fn new(writer: &'a mut LazyRawTextWriter_1_0, depth: usize) -> IonResult { - let container_writer = TextContainerWriter_1_0::new(writer, depth, IonType::SExp, "(")?; - Ok(Self { container_writer }) - } - - /// Writes the provided data as a nested value. - pub(crate) fn write(&mut self, value: V) -> IonResult<&mut Self> { - self.container_writer.write_value(value, " ")?; - Ok(self) - } - - /// Finalizes the sexp, preventing further values from being written. - pub(crate) fn end(self) -> IonResult<()> { - self.container_writer.end(")")?; - Ok(()) - } -} - -impl<'value, W: Write> MakeValueWriter for TextSExpWriter_1_0<'value, W> { - type ValueWriter<'a> = TextAnnotatableValueWriter_1_0<'a, W> where Self: 'a; - - fn value_writer(&mut self) -> Self::ValueWriter<'_> { - self.container_writer.annotatable_value_writer() - } -} - -impl<'a, W: Write> SequenceWriter for TextSExpWriter_1_0<'a, W> { - delegate! { - to self { - fn write(&mut self, value: V) -> IonResult<&mut Self>; - } - } -} - -/// Incrementally encodes an Ion struct. -pub struct TextStructWriter_1_0<'a, W: Write> { - container_writer: TextContainerWriter_1_0<'a, W>, -} - -impl<'a, W: Write> TextStructWriter_1_0<'a, W> { - pub(crate) fn new(writer: &'a mut LazyRawTextWriter_1_0, depth: usize) -> IonResult { - let container_writer = TextContainerWriter_1_0::new(writer, depth, IonType::Struct, "{")?; - Ok(Self { container_writer }) - } - - pub(crate) fn end(self) -> IonResult<()> { - self.container_writer.end("}")?; - Ok(()) - } -} - -impl<'a, W: Write> StructWriter for TextStructWriter_1_0<'a, W> { - fn write( - &mut self, - name: A, - value: V, - ) -> IonResult<&mut Self> { - // Write the field name - RawTextWriter::::write_symbol_token(self.container_writer.output(), name)?; - - let space_after_field_name = self - .container_writer - .whitespace_config() - .space_after_field_name; - // Write a `:` and configured trailing whitespace - write!(self.container_writer.output(), ":{space_after_field_name}",)?; - // Write the field value - self.container_writer.write_value(value, ",")?; - Ok(self) - } -} - #[cfg(test)] mod tests { use crate::lazy::encoder::annotate::Annotate; - use crate::lazy::encoder::LazyRawTextWriter_1_0; + use crate::lazy::encoder::text::LazyRawTextWriter_1_0; use crate::symbol_ref::AsSymbolRef; use crate::{Element, IonData, IonResult, Timestamp}; diff --git a/src/lazy/encoder/text/mod.rs b/src/lazy/encoder/text/mod.rs new file mode 100644 index 00000000..f2def672 --- /dev/null +++ b/src/lazy/encoder/text/mod.rs @@ -0,0 +1,92 @@ +use crate::lazy::encoder::text::value_writer::{ + TextAnnotatableValueWriter_1_0, TextValueWriter_1_0, +}; +use crate::lazy::encoder::value_writer::internal::MakeValueWriter; +use crate::lazy::encoder::value_writer::SequenceWriter; +use crate::lazy::encoder::write_as_ion::WriteAsIon; +use crate::lazy::encoder::{LazyEncoder, LazyRawWriter}; +use crate::lazy::encoding::TextEncoding_1_0; +use crate::text::raw_text_writer::{WhitespaceConfig, PRETTY_WHITESPACE_CONFIG}; +use crate::IonResult; +use delegate::delegate; +use std::io::Write; + +pub mod value_writer; + +/// A raw text Ion 1.0 writer. +pub struct LazyRawTextWriter_1_0 { + output: W, + whitespace_config: &'static WhitespaceConfig, +} + +impl LazyRawTextWriter_1_0 { + /// Constructs a new writer that will emit encoded data to the specified `output`. + pub fn new(output: W) -> Self { + Self { + output, + whitespace_config: &PRETTY_WHITESPACE_CONFIG, + } + } + + /// Writes the provided data as a top-level value. + pub fn write(&mut self, value: V) -> IonResult<&mut Self> { + value.write_as_ion(self.annotatable_value_writer())?; + write!( + self.output, + "{}", + self.whitespace_config.space_between_top_level_values + )?; + Ok(self) + } + + /// Writes any pending data to the output stream and then calls [`Write::flush`] on it. + pub fn flush(&mut self) -> IonResult<()> { + self.output.flush()?; + Ok(()) + } + + /// Helper method to construct this format's `ValueWriter` implementation. + #[inline] + fn value_writer(&mut self) -> TextValueWriter_1_0<'_, W> { + TextValueWriter_1_0::new(self, 0) + } + + /// Helper method to construct this format's `AnnotatedValueWriter` implementation. + #[inline] + fn annotatable_value_writer(&mut self) -> TextAnnotatableValueWriter_1_0<'_, W> { + TextAnnotatableValueWriter_1_0::new(self.value_writer()) + } +} + +impl SequenceWriter for LazyRawTextWriter_1_0 { + // All default method impls +} + +impl MakeValueWriter for LazyRawTextWriter_1_0 { + type ValueWriter<'a> = TextAnnotatableValueWriter_1_0<'a, W> + where + Self: 'a; + + fn value_writer(&mut self) -> Self::ValueWriter<'_> { + let value_writer = TextValueWriter_1_0::new(self, 0); + TextAnnotatableValueWriter_1_0::new(value_writer) + } +} + +impl LazyRawWriter for LazyRawTextWriter_1_0 { + fn new(output: W) -> IonResult { + Ok(LazyRawTextWriter_1_0::new(output)) + } + + // Delegate the trait methods to the inherent methods; this allows a version of these + // methods to be called on the concrete type even when the trait is not in scope. + delegate! { + to self { + fn flush(&mut self) -> IonResult<()>; + } + } +} + +impl LazyEncoder for TextEncoding_1_0 { + type Writer = LazyRawTextWriter_1_0; +} diff --git a/src/lazy/encoder/text/value_writer.rs b/src/lazy/encoder/text/value_writer.rs new file mode 100644 index 00000000..5b0b17fd --- /dev/null +++ b/src/lazy/encoder/text/value_writer.rs @@ -0,0 +1,516 @@ +use crate::lazy::encoder::private::Sealed; +use crate::lazy::encoder::text::LazyRawTextWriter_1_0; +use crate::lazy::encoder::value_writer::internal::MakeValueWriter; +use crate::lazy::encoder::value_writer::{ + AnnotatableValueWriter, SequenceWriter, StructWriter, ValueWriter, +}; +use crate::lazy::encoder::write_as_ion::WriteAsIon; +use crate::raw_symbol_token_ref::{AsRawSymbolTokenRef, RawSymbolTokenRef}; +use crate::result::IonResult; +use crate::text::raw_text_writer::{RawTextWriter, WhitespaceConfig}; +use crate::text::text_formatter::IonValueFormatter; +use crate::types::IonType; +use crate::{Decimal, Int, Timestamp}; +use delegate::delegate; +use std::fmt::Formatter; +use std::io::Write; + +pub struct TextValueWriter_1_0<'value, W: Write + 'value> { + writer: &'value mut LazyRawTextWriter_1_0, + depth: usize, +} + +impl<'value, W: Write + 'value> TextValueWriter_1_0<'value, W> { + pub fn new(writer: &'value mut LazyRawTextWriter_1_0, depth: usize) -> Self { + Self { writer, depth } + } +} + +impl<'value, W: Write> TextValueWriter_1_0<'value, W> { + fn output(&mut self) -> &mut W { + &mut self.writer.output + } + + fn whitespace_config(&self) -> &WhitespaceConfig { + self.writer.whitespace_config + } +} + +pub struct TextAnnotatableValueWriter_1_0<'value, W: Write> { + value_writer: TextValueWriter_1_0<'value, W>, +} + +impl<'value, W: Write> TextAnnotatableValueWriter_1_0<'value, W> { + pub fn new(value_writer: TextValueWriter_1_0<'value, W>) -> Self { + Self { value_writer } + } +} + +impl<'value, W: Write> AnnotatableValueWriter for TextAnnotatableValueWriter_1_0<'value, W> { + type ValueWriter = TextValueWriter_1_0<'value, W>; + type AnnotatedValueWriter<'a, SymbolType: AsRawSymbolTokenRef + 'a> = + TextAnnotatedValueWriter_1_0<'a, W, SymbolType> where Self: 'a; + fn with_annotations<'a, SymbolType: AsRawSymbolTokenRef>( + self, + annotations: &'a [SymbolType], + ) -> Self::AnnotatedValueWriter<'a, SymbolType> + where + Self: 'a, + { + TextAnnotatedValueWriter_1_0 { + annotations, + value_writer: self.value_writer, + } + } + + fn without_annotations(self) -> TextValueWriter_1_0<'value, W> { + self.value_writer + } +} + +pub struct TextAnnotatedValueWriter_1_0<'value, W: Write, SymbolType: AsRawSymbolTokenRef + 'value> +{ + annotations: &'value [SymbolType], + value_writer: TextValueWriter_1_0<'value, W>, +} + +impl<'value, W: Write, SymbolType: AsRawSymbolTokenRef> + TextAnnotatedValueWriter_1_0<'value, W, SymbolType> +{ + fn encode_annotations(self) -> IonResult> { + let output = &mut self.value_writer.writer.output; + for annotation in self.annotations { + match annotation.as_raw_symbol_token_ref() { + RawSymbolTokenRef::Text(token) => write!(output, "{}::", token.as_ref()), + RawSymbolTokenRef::SymbolId(sid) => write!(output, "${sid}::"), + }?; + } + + Ok(self.value_writer) + } +} + +impl<'value, W: Write + 'value, SymbolType: AsRawSymbolTokenRef> Sealed + for TextAnnotatedValueWriter_1_0<'value, W, SymbolType> +{ +} + +impl<'value, W: Write> Sealed for TextValueWriter_1_0<'value, W> {} + +/// Helper type that is home to information and behavior common to the list writer, s-expression writer, +/// and struct writer. +struct TextContainerWriter_1_0<'a, W: Write> { + // Holds a reference to the output stream and a whitespace config + writer: &'a mut LazyRawTextWriter_1_0, + // The depth at which this container's child values appear. This value is used for formatting + // indentation where applicable. + depth: usize, + // Tracks whether the `end()` method was called (thereby emitting a closing delimiter) before + // this value was dropped. This scenario is a contract violation and results in a panic. + has_been_closed: bool, + // The Ion type of the container using this TextContainerWriter_1_0. This value is only + // used for more informative error messages. + ion_type: IonType, +} + +impl<'a, W: Write> Drop for TextContainerWriter_1_0<'a, W> { + fn drop(&mut self) { + // If the user didn't call `end`, the closing delimiter was not written to output. + // It's too late to call it here because we can't return a `Result`. + if !self.has_been_closed { + panic!( + "Container writer ({}) was dropped without calling `end()`.", + self.ion_type + ); + } + } +} + +impl<'a, W: Write> TextContainerWriter_1_0<'a, W> { + pub fn new( + writer: &'a mut LazyRawTextWriter_1_0, + depth: usize, + ion_type: IonType, + opening_delimiter: &str, + ) -> IonResult { + let space_after_container_start = writer.whitespace_config.space_after_container_start; + write!( + writer.output, + "{opening_delimiter}{space_after_container_start}" + )?; + Ok(Self { + writer, + depth, + ion_type, + has_been_closed: false, + }) + } + + /// Writes the `indentation` string set in the whitespace config to output `depth` times. + fn write_indentation(&mut self) -> IonResult<()> { + let indentation = self.whitespace_config().indentation; + if !indentation.is_empty() { + for _ in 0..self.depth { + write!(self.output(), "{indentation}")?; + } + } + Ok(()) + } + + /// Writes the provided value to output using its implementation of `WriteAsIon`, then writes + /// the whitespace config's `space_between_nested_values`. + fn write_value( + &mut self, + value: V, + delimiter_between_values: &str, + ) -> IonResult<&mut Self> { + self.write_indentation()?; + value.write_as_ion(self.annotatable_value_writer())?; + let space_between_nested_values = self.whitespace_config().space_between_nested_values; + write!( + self.output(), + "{delimiter_between_values}{space_between_nested_values}" + )?; + Ok(self) + } + + /// Finalizes the container, preventing further values from being written. + fn end(mut self, closing_delimiter: &str) -> IonResult<()> { + let space_between_top_level_values = + self.whitespace_config().space_between_top_level_values; + write!( + self.output(), + "{closing_delimiter}{space_between_top_level_values}" + )?; + self.has_been_closed = true; + Ok(()) + } + + fn output(&mut self) -> &mut W { + &mut self.writer.output + } + + fn whitespace_config(&self) -> &WhitespaceConfig { + self.writer.whitespace_config + } + + #[inline] + fn value_writer(&mut self) -> TextValueWriter_1_0<'_, W> { + TextValueWriter_1_0 { + writer: self.writer, + depth: self.depth, + } + } + + #[inline] + fn annotatable_value_writer(&mut self) -> TextAnnotatableValueWriter_1_0<'_, W> { + TextAnnotatableValueWriter_1_0 { + value_writer: self.value_writer(), + } + } +} + +/// A superset of the functionality offered by the user-facing `TextListWriter_1_0`. In particular, +/// this type exposes an `end` function that MUST be called to guarantee correct output. +pub struct TextListWriter_1_0<'top, W: Write> { + container_writer: TextContainerWriter_1_0<'top, W>, +} + +impl<'top, W: Write> TextListWriter_1_0<'top, W> { + pub fn new(writer: &'top mut LazyRawTextWriter_1_0, depth: usize) -> IonResult { + let container_writer = TextContainerWriter_1_0::new(writer, depth, IonType::List, "[")?; + Ok(Self { container_writer }) + } + + /// Writes the provided data as a nested value. + fn write(&mut self, value: V) -> IonResult<&mut Self> { + self.container_writer.write_value(value, ",")?; + Ok(self) + } + + /// Finalizes the list, preventing further values from being written. + pub fn end(self) -> IonResult<()> { + self.container_writer.end("]")?; + Ok(()) + } +} + +impl<'top, W: Write> MakeValueWriter for TextListWriter_1_0<'top, W> { + type ValueWriter<'a> = TextAnnotatableValueWriter_1_0<'a, W> where Self: 'a; + + fn value_writer(&mut self) -> Self::ValueWriter<'_> { + self.container_writer.annotatable_value_writer() + } +} + +impl<'top, W: Write> SequenceWriter for TextListWriter_1_0<'top, W> { + fn write(&mut self, value: V) -> IonResult<&mut Self> { + self.write(value) + } +} + +/// Incrementally encodes a potentially heterogeneously typed Ion s-expression. +pub struct TextSExpWriter_1_0<'a, W: Write> { + container_writer: TextContainerWriter_1_0<'a, W>, +} + +impl<'a, W: Write> TextSExpWriter_1_0<'a, W> { + pub fn new(writer: &'a mut LazyRawTextWriter_1_0, depth: usize) -> IonResult { + let container_writer = TextContainerWriter_1_0::new(writer, depth, IonType::SExp, "(")?; + Ok(Self { container_writer }) + } + + /// Writes the provided data as a nested value. + pub fn write(&mut self, value: V) -> IonResult<&mut Self> { + self.container_writer.write_value(value, " ")?; + Ok(self) + } + + /// Finalizes the sexp, preventing further values from being written. + pub fn end(self) -> IonResult<()> { + self.container_writer.end(")")?; + Ok(()) + } +} + +impl<'value, W: Write> MakeValueWriter for TextSExpWriter_1_0<'value, W> { + type ValueWriter<'a> = TextAnnotatableValueWriter_1_0<'a, W> where Self: 'a; + + fn value_writer(&mut self) -> Self::ValueWriter<'_> { + self.container_writer.annotatable_value_writer() + } +} + +impl<'a, W: Write> SequenceWriter for TextSExpWriter_1_0<'a, W> { + delegate! { + to self { + fn write(&mut self, value: V) -> IonResult<&mut Self>; + } + } +} + +/// Incrementally encodes an Ion struct. +pub struct TextStructWriter_1_0<'a, W: Write> { + container_writer: TextContainerWriter_1_0<'a, W>, +} + +impl<'a, W: Write> TextStructWriter_1_0<'a, W> { + pub fn new(writer: &'a mut LazyRawTextWriter_1_0, depth: usize) -> IonResult { + let container_writer = TextContainerWriter_1_0::new(writer, depth, IonType::Struct, "{")?; + Ok(Self { container_writer }) + } + + pub fn end(self) -> IonResult<()> { + self.container_writer.end("}")?; + Ok(()) + } +} + +impl<'a, W: Write> StructWriter for TextStructWriter_1_0<'a, W> { + fn write( + &mut self, + name: A, + value: V, + ) -> IonResult<&mut Self> { + // Write the field name + RawTextWriter::::write_symbol_token(self.container_writer.output(), name)?; + + let space_after_field_name = self + .container_writer + .whitespace_config() + .space_after_field_name; + // Write a `:` and configured trailing whitespace + write!(self.container_writer.output(), ":{space_after_field_name}",)?; + // Write the field value + self.container_writer.write_value(value, ",")?; + Ok(self) + } +} + +impl<'value, W: Write + 'value, SymbolType: AsRawSymbolTokenRef> ValueWriter + for TextAnnotatedValueWriter_1_0<'value, W, SymbolType> +{ + type ListWriter<'a> = TextListWriter_1_0<'value, W>; + type SExpWriter<'a> = TextSExpWriter_1_0<'value, W>; + type StructWriter<'a> = TextStructWriter_1_0<'value, W>; + + delegate! { + to self.encode_annotations()? { + fn write_null(self, ion_type: IonType) -> IonResult<()>; + fn write_bool(self, value: bool) -> IonResult<()>; + fn write_i64(self, value: i64) -> IonResult<()>; + fn write_int(self, value: &Int) -> IonResult<()>; + fn write_f32(self, value: f32) -> IonResult<()>; + fn write_f64(self, value: f64) -> IonResult<()>; + fn write_decimal(self, value: &Decimal) -> IonResult<()>; + fn write_timestamp(self, value: &Timestamp) -> IonResult<()>; + fn write_string>(self, value: A) -> IonResult<()>; + fn write_symbol(self, value: A) -> IonResult<()>; + fn write_clob>(self, value: A) -> IonResult<()>; + fn write_blob>(self, value: A) -> IonResult<()>; + fn write_list FnOnce(&mut Self::ListWriter<'a>) -> IonResult<()>>( + self, + list_fn: F, + ) -> IonResult<()>; + fn write_sexp FnOnce(&mut Self::SExpWriter<'a>) -> IonResult<()>>( + self, + sexp_fn: F, + ) -> IonResult<()>; + fn write_struct< + F: for<'a> FnOnce(&mut Self::StructWriter<'a>) -> IonResult<()>, + >( + self, + struct_fn: F, + ) -> IonResult<()>; + } + } +} + +impl<'value, W: Write> ValueWriter for TextValueWriter_1_0<'value, W> { + type ListWriter<'a> = TextListWriter_1_0<'value, W>; + type SExpWriter<'a> = TextSExpWriter_1_0<'value, W>; + type StructWriter<'a> = TextStructWriter_1_0<'value, W>; + fn write_null(mut self, ion_type: IonType) -> IonResult<()> { + use crate::IonType::*; + let null_text = match ion_type { + Null => "null", + Bool => "null.bool", + Int => "null.int", + Float => "null.float", + Decimal => "null.decimal", + Timestamp => "null.timestamp", + Symbol => "null.symbol", + String => "null.string", + Blob => "null.blob", + Clob => "null.clob", + List => "null.list", + SExp => "null.sexp", + Struct => "null.struct", + }; + write!(self.output(), "{null_text}")?; + Ok(()) + } + + fn write_bool(mut self, value: bool) -> IonResult<()> { + let bool_text = match value { + true => "true", + false => "false", + }; + write!(self.output(), "{bool_text}")?; + Ok(()) + } + + fn write_i64(mut self, value: i64) -> IonResult<()> { + write!(self.output(), "{value}")?; + Ok(()) + } + + fn write_int(mut self, value: &Int) -> IonResult<()> { + write!(self.output(), "{value}")?; + Ok(()) + } + + fn write_f32(self, value: f32) -> IonResult<()> { + self.write_f64(value as f64) + } + + fn write_f64(mut self, value: f64) -> IonResult<()> { + if value.is_nan() { + write!(self.output(), "nan")?; + return Ok(()); + } + + if value.is_infinite() { + if value.is_sign_positive() { + write!(self.output(), "+inf")?; + } else { + write!(self.output(), "-inf")?; + } + return Ok(()); + } + + // The {:e} formatter provided by the Display trait writes floats using scientific + // notation. It works for all floating point values except -0.0 (it drops the sign). + // See: https://github.com/rust-lang/rust/issues/20596 + if value == 0.0f64 && value.is_sign_negative() { + write!(self.output(), "-0e0")?; + return Ok(()); + } + + write!(self.output(), "{value:e}")?; + Ok(()) + } + + fn write_decimal(mut self, value: &Decimal) -> IonResult<()> { + write!(self.output(), "{value}")?; + Ok(()) + } + + fn write_timestamp(mut self, value: &Timestamp) -> IonResult<()> { + write!(self.output(), "{value}")?; + Ok(()) + } + + fn write_string>(mut self, value: A) -> IonResult<()> { + write!(self.output(), "\"")?; + RawTextWriter::::write_escaped_text_body(self.output(), value)?; + write!(self.output(), "\"")?; + Ok(()) + } + + fn write_symbol(mut self, value: A) -> IonResult<()> { + RawTextWriter::::write_symbol_token(self.output(), value)?; + Ok(()) + } + + fn write_clob>(mut self, value: A) -> IonResult<()> { + // This type exists solely to enable using the IonValueFormatter (which operates on + // `std::fmt::Write`) to write to a `std::io::Write`. + struct ClobShim<'a>(&'a [u8]); + impl<'a> std::fmt::Display for ClobShim<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut formatter = IonValueFormatter { output: f }; + formatter.format_clob(self.0)?; + Ok(()) + } + } + + write!(self.output(), "{}", ClobShim(value.as_ref()))?; + Ok(()) + } + + fn write_blob>(mut self, value: A) -> IonResult<()> { + // Rust format strings escape curly braces by doubling them. The following string is: + // * The opening {{ from a text Ion blob, with each brace doubled to escape it. + // * A {} pair used by the format string to indicate where the base64-encoded bytes + // should be inserted. + // * The closing }} from a text Ion blob, with each brace doubled to escape it. + write!(self.output(), "{{{{{}}}}}", base64::encode(value))?; + Ok(()) + } + + fn write_list FnOnce(&mut Self::ListWriter<'a>) -> IonResult<()>>( + self, + list_fn: F, + ) -> IonResult<()> { + let mut list_writer = TextListWriter_1_0::new(self.writer, self.depth + 1)?; + list_fn(&mut list_writer)?; + list_writer.end() + } + fn write_sexp FnOnce(&mut Self::SExpWriter<'a>) -> IonResult<()>>( + self, + sexp_fn: F, + ) -> IonResult<()> { + let mut sexp_writer = TextSExpWriter_1_0::new(self.writer, self.depth + 1)?; + sexp_fn(&mut sexp_writer)?; + sexp_writer.end() + } + fn write_struct FnOnce(&mut Self::StructWriter<'a>) -> IonResult<()>>( + self, + struct_fn: F, + ) -> IonResult<()> { + let mut struct_writer = TextStructWriter_1_0::new(self.writer, self.depth + 1)?; + struct_fn(&mut struct_writer)?; + struct_writer.end() + } +} diff --git a/src/lazy/encoder/value_writer.rs b/src/lazy/encoder/value_writer.rs index 43f39c39..6cc2068e 100644 --- a/src/lazy/encoder/value_writer.rs +++ b/src/lazy/encoder/value_writer.rs @@ -1,22 +1,19 @@ +use crate::lazy::encoder::value_writer::internal::MakeValueWriter; use crate::lazy::encoder::write_as_ion::WriteAsIon; -use crate::lazy::encoder::{ - TextAnnotatedValueWriter_1_0, TextListWriter_1_0, TextSExpWriter_1_0, TextStructWriter_1_0, - TextValueWriter_1_0, -}; use crate::raw_symbol_token_ref::AsRawSymbolTokenRef; -use crate::text::text_formatter::IonValueFormatter; -use crate::{Decimal, Int, IonResult, IonType, RawTextWriter, Timestamp}; +use crate::{Decimal, Int, IonResult, IonType, Timestamp}; use delegate::delegate; -use std::fmt::Formatter; -use std::io::Write; -// TODO: Make this private -pub trait MakeValueWriter { - type ValueWriter<'a>: AnnotatableValueWriter - where - Self: 'a; +pub(crate) mod internal { + use crate::lazy::encoder::value_writer::AnnotatableValueWriter; + + pub trait MakeValueWriter { + type ValueWriter<'a>: AnnotatableValueWriter + where + Self: 'a; - fn value_writer(&mut self) -> Self::ValueWriter<'_>; + fn value_writer(&mut self) -> Self::ValueWriter<'_>; + } } /// One-shot methods that take a (possibly empty) sequence of annotations to encode and return a ValueWriter. @@ -172,191 +169,3 @@ pub trait SequenceWriter: MakeValueWriter { // as a workaround. // [1]: https://blog.rust-lang.org/2022/10/28/gats-stabilization.html#implied-static-requirement-from-higher-ranked-trait-bounds } - -impl<'value, W: Write + 'value, SymbolType: AsRawSymbolTokenRef> ValueWriter - for TextAnnotatedValueWriter_1_0<'value, W, SymbolType> -{ - type ListWriter<'a> = TextListWriter_1_0<'value, W>; - type SExpWriter<'a> = TextSExpWriter_1_0<'value, W>; - type StructWriter<'a> = TextStructWriter_1_0<'value, W>; - - delegate! { - to self.encode_annotations()? { - fn write_null(self, ion_type: IonType) -> IonResult<()>; - fn write_bool(self, value: bool) -> IonResult<()>; - fn write_i64(self, value: i64) -> IonResult<()>; - fn write_int(self, value: &Int) -> IonResult<()>; - fn write_f32(self, value: f32) -> IonResult<()>; - fn write_f64(self, value: f64) -> IonResult<()>; - fn write_decimal(self, value: &Decimal) -> IonResult<()>; - fn write_timestamp(self, value: &Timestamp) -> IonResult<()>; - fn write_string>(self, value: A) -> IonResult<()>; - fn write_symbol(self, value: A) -> IonResult<()>; - fn write_clob>(self, value: A) -> IonResult<()>; - fn write_blob>(self, value: A) -> IonResult<()>; - fn write_list FnOnce(&mut Self::ListWriter<'a>) -> IonResult<()>>( - self, - list_fn: F, - ) -> IonResult<()>; - fn write_sexp FnOnce(&mut Self::SExpWriter<'a>) -> IonResult<()>>( - self, - sexp_fn: F, - ) -> IonResult<()>; - fn write_struct< - F: for<'a> FnOnce(&mut Self::StructWriter<'a>) -> IonResult<()>, - >( - self, - struct_fn: F, - ) -> IonResult<()>; - } - } -} - -impl<'value, W: Write> ValueWriter for TextValueWriter_1_0<'value, W> { - type ListWriter<'a> = TextListWriter_1_0<'value, W>; - type SExpWriter<'a> = TextSExpWriter_1_0<'value, W>; - type StructWriter<'a> = TextStructWriter_1_0<'value, W>; - fn write_null(mut self, ion_type: IonType) -> IonResult<()> { - use crate::IonType::*; - let null_text = match ion_type { - Null => "null", - Bool => "null.bool", - Int => "null.int", - Float => "null.float", - Decimal => "null.decimal", - Timestamp => "null.timestamp", - Symbol => "null.symbol", - String => "null.string", - Blob => "null.blob", - Clob => "null.clob", - List => "null.list", - SExp => "null.sexp", - Struct => "null.struct", - }; - write!(self.output(), "{null_text}")?; - Ok(()) - } - - fn write_bool(mut self, value: bool) -> IonResult<()> { - let bool_text = match value { - true => "true", - false => "false", - }; - write!(self.output(), "{bool_text}")?; - Ok(()) - } - - fn write_i64(mut self, value: i64) -> IonResult<()> { - write!(self.output(), "{value}")?; - Ok(()) - } - - fn write_int(mut self, value: &Int) -> IonResult<()> { - write!(self.output(), "{value}")?; - Ok(()) - } - - fn write_f32(self, value: f32) -> IonResult<()> { - self.write_f64(value as f64) - } - - fn write_f64(mut self, value: f64) -> IonResult<()> { - if value.is_nan() { - write!(self.output(), "nan")?; - return Ok(()); - } - - if value.is_infinite() { - if value.is_sign_positive() { - write!(self.output(), "+inf")?; - } else { - write!(self.output(), "-inf")?; - } - return Ok(()); - } - - // The {:e} formatter provided by the Display trait writes floats using scientific - // notation. It works for all floating point values except -0.0 (it drops the sign). - // See: https://github.com/rust-lang/rust/issues/20596 - if value == 0.0f64 && value.is_sign_negative() { - write!(self.output(), "-0e0")?; - return Ok(()); - } - - write!(self.output(), "{value:e}")?; - Ok(()) - } - - fn write_decimal(mut self, value: &Decimal) -> IonResult<()> { - write!(self.output(), "{value}")?; - Ok(()) - } - - fn write_timestamp(mut self, value: &Timestamp) -> IonResult<()> { - write!(self.output(), "{value}")?; - Ok(()) - } - - fn write_string>(mut self, value: A) -> IonResult<()> { - write!(self.output(), "\"")?; - RawTextWriter::::write_escaped_text_body(self.output(), value)?; - write!(self.output(), "\"")?; - Ok(()) - } - - fn write_symbol(mut self, value: A) -> IonResult<()> { - RawTextWriter::::write_symbol_token(self.output(), value)?; - Ok(()) - } - - fn write_clob>(mut self, value: A) -> IonResult<()> { - // This type exists solely to enable using the IonValueFormatter (which operates on - // `std::fmt::Write`) to write to a `std::io::Write`. - struct ClobShim<'a>(&'a [u8]); - impl<'a> std::fmt::Display for ClobShim<'a> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let mut formatter = IonValueFormatter { output: f }; - formatter.format_clob(self.0)?; - Ok(()) - } - } - - write!(self.output(), "{}", ClobShim(value.as_ref()))?; - Ok(()) - } - - fn write_blob>(mut self, value: A) -> IonResult<()> { - // Rust format strings escape curly braces by doubling them. The following string is: - // * The opening {{ from a text Ion blob, with each brace doubled to escape it. - // * A {} pair used by the format string to indicate where the base64-encoded bytes - // should be inserted. - // * The closing }} from a text Ion blob, with each brace doubled to escape it. - write!(self.output(), "{{{{{}}}}}", base64::encode(value))?; - Ok(()) - } - - fn write_list FnOnce(&mut Self::ListWriter<'a>) -> IonResult<()>>( - self, - list_fn: F, - ) -> IonResult<()> { - let mut list_writer = TextListWriter_1_0::new(self.writer, self.depth + 1)?; - list_fn(&mut list_writer)?; - list_writer.end() - } - fn write_sexp FnOnce(&mut Self::SExpWriter<'a>) -> IonResult<()>>( - self, - sexp_fn: F, - ) -> IonResult<()> { - let mut sexp_writer = TextSExpWriter_1_0::new(self.writer, self.depth + 1)?; - sexp_fn(&mut sexp_writer)?; - sexp_writer.end() - } - fn write_struct FnOnce(&mut Self::StructWriter<'a>) -> IonResult<()>>( - self, - struct_fn: F, - ) -> IonResult<()> { - let mut struct_writer = TextStructWriter_1_0::new(self.writer, self.depth + 1)?; - struct_fn(&mut struct_writer)?; - struct_writer.end() - } -} From 9cce623839a768ce27e2a0224f6d91f4231cd7fa Mon Sep 17 00:00:00 2001 From: Zack Slayton Date: Fri, 8 Dec 2023 11:17:03 -0500 Subject: [PATCH 3/6] DRY via macros, some cleanup --- src/lazy/encoder/binary/mod.rs | 10 +- src/lazy/encoder/binary/value_writer.rs | 92 +++++-------- src/lazy/encoder/mod.rs | 176 ++++++++++++------------ src/lazy/encoder/text/value_writer.rs | 18 +-- src/lazy/encoder/value_writer.rs | 10 +- 5 files changed, 144 insertions(+), 162 deletions(-) diff --git a/src/lazy/encoder/binary/mod.rs b/src/lazy/encoder/binary/mod.rs index 66ad41c5..50898982 100644 --- a/src/lazy/encoder/binary/mod.rs +++ b/src/lazy/encoder/binary/mod.rs @@ -59,9 +59,9 @@ impl LazyRawBinaryWriter_1_0 { } /// Helper function that turns a raw pointer into a mutable reference of the specified type. - fn ptr_to_mut_ref<'a, T>(ptr: *mut ()) -> &'a mut T { + unsafe fn ptr_to_mut_ref<'a, T>(ptr: *mut ()) -> &'a mut T { let typed_ptr: *mut T = ptr.cast(); - unsafe { &mut *typed_ptr } + &mut *typed_ptr } /// Helper function that turns a mutable reference into a raw pointer. @@ -91,7 +91,7 @@ impl LazyRawBinaryWriter_1_0 { let encoding_buffer = match encoding_buffer_ptr { // If `encoding_buffer_ptr` is set, get the slice of bytes to which it refers. - Some(ptr) => Self::ptr_to_mut_ref::<'_, BumpVec<'_, u8>>(*ptr).as_slice(), + Some(ptr) => unsafe { Self::ptr_to_mut_ref::<'_, BumpVec<'_, u8>>(*ptr).as_slice() }, // Otherwise, there's nothing in the buffer. Use an empty slice. None => &[], }; @@ -104,12 +104,12 @@ impl LazyRawBinaryWriter_1_0 { Ok(()) } - pub fn value_writer(&mut self) -> BinaryAnnotatableValueWriter_1_0<'_, '_> { + fn value_writer(&mut self) -> BinaryAnnotatableValueWriter_1_0<'_, '_> { let top_level = match self.encoding_buffer_ptr { // If the `encoding_buffer_ptr` is set, we already allocated an encoding buffer on // a previous call to `value_writer()`. Dereference the pointer and continue encoding // to that buffer. - Some(ptr) => Self::ptr_to_mut_ref::<'_, BumpVec<'_, u8>>(ptr), + Some(ptr) => unsafe { Self::ptr_to_mut_ref::<'_, BumpVec<'_, u8>>(ptr) }, // Otherwise, allocate a new encoding buffer and set the pointer to refer to it. None => { let buffer = self diff --git a/src/lazy/encoder/binary/value_writer.rs b/src/lazy/encoder/binary/value_writer.rs index 91e643ad..a84b4da5 100644 --- a/src/lazy/encoder/binary/value_writer.rs +++ b/src/lazy/encoder/binary/value_writer.rs @@ -314,10 +314,10 @@ impl<'value, 'top> ValueWriter for BinaryValueWriter_1_0<'value, 'top> { fn write_f64(self, value: f64) -> IonResult<()>; fn write_decimal(self, value: &Decimal) -> IonResult<()>; fn write_timestamp(self, value: &Timestamp) -> IonResult<()>; - fn write_string>(self, value: A) -> IonResult<()>; - fn write_symbol(self, value: A) -> IonResult<()>; - fn write_clob>(self, value: A) -> IonResult<()>; - fn write_blob>(self, value: A) -> IonResult<()>; + fn write_string(self, value: impl AsRef) -> IonResult<()>; + fn write_symbol(self, value: impl AsRawSymbolTokenRef) -> IonResult<()>; + fn write_clob(self, value: impl AsRef<[u8]>) -> IonResult<()>; + fn write_blob(self, value: impl AsRef<[u8]>) -> IonResult<()>; fn write_list FnOnce(&mut Self::ListWriter<'a>) -> IonResult<()>>( self, list_fn: F, @@ -463,6 +463,20 @@ impl<'value, 'top, SymbolType: AsRawSymbolTokenRef> Sealed // No methods, precludes implementations outside the crate. } +/// Takes a series of `TYPE => METHOD` pairs, generating a function for each that calls the +/// corresponding value writer method and then prefixes the encoded result with an annotations wrapper. +macro_rules! delegate_and_annotate { + // End of iteration + () => {}; + // Recurses one argument pair at a time + ($value_type:ty => $method:ident, $($rest:tt)*) => { + fn $method(self, value: $value_type) -> IonResult<()> { + self.encode_annotated(|value_writer| value_writer.$method(value)) + } + delegate_and_annotate!($($rest)*); + }; +} + impl<'value, 'top, SymbolType: AsRawSymbolTokenRef> ValueWriter for BinaryAnnotationsWrapperWriter<'value, 'top, SymbolType> { @@ -471,53 +485,20 @@ impl<'value, 'top, SymbolType: AsRawSymbolTokenRef> ValueWriter type StructWriter<'a> = BinaryStructFieldsWriter_1_0<'a>; - fn write_null(self, ion_type: IonType) -> IonResult<()> { - self.encode_annotated(|value_writer| value_writer.write_null(ion_type)) - } - - fn write_bool(self, value: bool) -> IonResult<()> { - self.encode_annotated(|value_writer| value_writer.write_bool(value)) - } - - fn write_i64(self, value: i64) -> IonResult<()> { - self.encode_annotated(|value_writer| value_writer.write_i64(value)) - } - - fn write_int(self, value: &Int) -> IonResult<()> { - self.encode_annotated(|value_writer| value_writer.write_int(value)) - } - - fn write_f32(self, value: f32) -> IonResult<()> { - self.encode_annotated(|value_writer| value_writer.write_f32(value)) - } - - fn write_f64(self, value: f64) -> IonResult<()> { - self.encode_annotated(|value_writer| value_writer.write_f64(value)) - } - - fn write_decimal(self, value: &Decimal) -> IonResult<()> { - self.encode_annotated(|value_writer| value_writer.write_decimal(value)) - } - - fn write_timestamp(self, value: &Timestamp) -> IonResult<()> { - self.encode_annotated(|value_writer| value_writer.write_timestamp(value)) - } - - fn write_string>(self, value: A) -> IonResult<()> { - self.encode_annotated(|value_writer| value_writer.write_string(&value)) - } - - fn write_symbol(self, value: A) -> IonResult<()> { - self.encode_annotated(|value_writer| value_writer.write_symbol(&value)) - } - - fn write_clob>(self, value: A) -> IonResult<()> { - self.encode_annotated(|value_writer| value_writer.write_clob(&value)) - } - - fn write_blob>(self, value: A) -> IonResult<()> { - self.encode_annotated(|value_writer| value_writer.write_blob(&value)) - } + delegate_and_annotate!( + IonType => write_null, + bool => write_bool, + i64 => write_i64, + &Int => write_int, + f32 => write_f32, + f64 => write_f64, + &Decimal => write_decimal, + &Timestamp => write_timestamp, + impl AsRef => write_string, + impl AsRawSymbolTokenRef => write_symbol, + impl AsRef<[u8]> => write_clob, + impl AsRef<[u8]> => write_blob, + ); fn write_list FnOnce(&mut Self::ListWriter<'a>) -> IonResult<()>>( self, @@ -539,7 +520,6 @@ impl<'value, 'top, SymbolType: AsRawSymbolTokenRef> ValueWriter } } -// TODO: Doc comment. Holds its own buffer to limit the type to a single lifetime. pub struct BinaryAnnotatedValueWriter_1_0<'value, 'top> { allocator: &'top BumpAllocator, // Note that unlike the BinaryValueWriter_1_0, the borrow and the BumpVec here have the same @@ -576,10 +556,10 @@ impl<'value, 'top: 'value> ValueWriter for BinaryAnnotatedValueWriter_1_0<'value fn write_f64(mut self, value: f64) -> IonResult<()>; fn write_decimal(mut self, value: &Decimal) -> IonResult<()>; fn write_timestamp(mut self, value: &Timestamp) -> IonResult<()>; - fn write_string>(mut self, value: A) -> IonResult<()>; - fn write_symbol(mut self, value: A) -> IonResult<()>; - fn write_clob>(mut self, value: A) -> IonResult<()>; - fn write_blob>(mut self, value: A) -> IonResult<()>; + fn write_string(mut self, value: impl AsRef) -> IonResult<()>; + fn write_symbol(mut self, value: impl AsRawSymbolTokenRef) -> IonResult<()>; + fn write_clob(mut self, value: impl AsRef<[u8]>) -> IonResult<()>; + fn write_blob(mut self, value: impl AsRef<[u8]>) -> IonResult<()>; fn write_list FnOnce(&mut Self::ListWriter<'a>) -> IonResult<()>>( mut self, list_fn: F, diff --git a/src/lazy/encoder/mod.rs b/src/lazy/encoder/mod.rs index bd8a0711..15510e79 100644 --- a/src/lazy/encoder/mod.rs +++ b/src/lazy/encoder/mod.rs @@ -47,6 +47,8 @@ pub trait LazyRawWriter: SequenceWriter { mod tests { use crate::lazy::encoder::annotate::Annotate; use crate::lazy::encoder::text::LazyRawTextWriter_1_0; + use crate::lazy::encoder::value_writer::internal::MakeValueWriter; + use crate::lazy::encoder::value_writer::{AnnotatableValueWriter, StructWriter}; use crate::symbol_ref::AsSymbolRef; use crate::{Element, IonData, IonResult, Timestamp}; @@ -123,91 +125,91 @@ mod tests { writer_test(expected, test) } - // #[test] - // fn write_list() -> IonResult<()> { - // let expected = r#" - // [ - // 1, - // false, - // 3e0, - // "foo", - // bar, - // 2023-11-09T, - // {{4AEA6g==}}, - // ] - // "#; - // let test = |writer: &mut LazyRawTextWriter_1_0<&mut Vec>| { - // writer.value_writer().write_list(|list| { - // list.write(1)? - // .write(false)? - // .write(3f32)? - // .write("foo")? - // .write("bar".as_symbol_ref())? - // .write(Timestamp::with_ymd(2023, 11, 9).build()?)? - // .write(&[0xE0u8, 0x01, 0x00, 0xEA][..])?; - // Ok(()) - // }) - // }; - // writer_test(expected, test) - // } - // - // #[test] - // fn write_sexp() -> IonResult<()> { - // let expected = r#" - // ( - // 1 - // false - // 3e0 - // "foo" - // bar - // 2023-11-09T - // {{4AEA6g==}} - // // Nested list - // [1, 2, 3] - // ) - // "#; - // let test = |writer: &mut LazyRawTextWriter_1_0<&mut Vec>| { - // writer.value_writer().write_sexp(|sexp| { - // sexp.write(1)? - // .write(false)? - // .write(3f32)? - // .write("foo")? - // .write("bar".as_symbol_ref())? - // .write(Timestamp::with_ymd(2023, 11, 9).build()?)? - // .write([0xE0u8, 0x01, 0x00, 0xEA])? - // .write([1, 2, 3])?; - // Ok(()) - // }) - // }; - // writer_test(expected, test) - // } - // - // #[test] - // fn write_struct() -> IonResult<()> { - // let expected = r#" - // { - // a: 1, - // b: false, - // c: 3e0, - // d: "foo", - // e: bar, - // f: 2023-11-09T, - // g: {{4AEA6g==}}, - // } - // "#; - // let test = |writer: &mut LazyRawTextWriter_1_0<&mut Vec>| { - // writer.value_writer().write_struct(|struct_| { - // struct_ - // .write("a", 1)? - // .write("b", false)? - // .write("c", 3f32)? - // .write("d", "foo")? - // .write("e", "bar".as_symbol_ref())? - // .write("f", Timestamp::with_ymd(2023, 11, 9).build()?)? - // .write("g", [0xE0u8, 0x01, 0x00, 0xEA])?; - // Ok(()) - // }) - // }; - // writer_test(expected, test) - // } + #[test] + fn write_list() -> IonResult<()> { + let expected = r#" + [ + 1, + false, + 3e0, + "foo", + bar, + 2023-11-09T, + {{4AEA6g==}}, + ] + "#; + let test = |writer: &mut LazyRawTextWriter_1_0<&mut Vec>| { + writer.value_writer().write_list(|list| { + list.write(1)? + .write(false)? + .write(3f32)? + .write("foo")? + .write("bar".as_symbol_ref())? + .write(Timestamp::with_ymd(2023, 11, 9).build()?)? + .write(&[0xE0u8, 0x01, 0x00, 0xEA][..])?; + Ok(()) + }) + }; + writer_test(expected, test) + } + + #[test] + fn write_sexp() -> IonResult<()> { + let expected = r#" + ( + 1 + false + 3e0 + "foo" + bar + 2023-11-09T + {{4AEA6g==}} + // Nested list + [1, 2, 3] + ) + "#; + let test = |writer: &mut LazyRawTextWriter_1_0<&mut Vec>| { + writer.value_writer().write_sexp(|sexp| { + sexp.write(1)? + .write(false)? + .write(3f32)? + .write("foo")? + .write("bar".as_symbol_ref())? + .write(Timestamp::with_ymd(2023, 11, 9).build()?)? + .write([0xE0u8, 0x01, 0x00, 0xEA])? + .write([1, 2, 3])?; + Ok(()) + }) + }; + writer_test(expected, test) + } + + #[test] + fn write_struct() -> IonResult<()> { + let expected = r#" + { + a: 1, + b: false, + c: 3e0, + d: "foo", + e: bar, + f: 2023-11-09T, + g: {{4AEA6g==}}, + } + "#; + let test = |writer: &mut LazyRawTextWriter_1_0<&mut Vec>| { + writer.value_writer().write_struct(|struct_| { + struct_ + .write("a", 1)? + .write("b", false)? + .write("c", 3f32)? + .write("d", "foo")? + .write("e", "bar".as_symbol_ref())? + .write("f", Timestamp::with_ymd(2023, 11, 9).build()?)? + .write("g", [0xE0u8, 0x01, 0x00, 0xEA])?; + Ok(()) + }) + }; + writer_test(expected, test) + } } diff --git a/src/lazy/encoder/text/value_writer.rs b/src/lazy/encoder/text/value_writer.rs index 5b0b17fd..1056d2ee 100644 --- a/src/lazy/encoder/text/value_writer.rs +++ b/src/lazy/encoder/text/value_writer.rs @@ -223,7 +223,7 @@ impl<'top, W: Write> TextListWriter_1_0<'top, W> { } /// Writes the provided data as a nested value. - fn write(&mut self, value: V) -> IonResult<&mut Self> { + pub fn write(&mut self, value: V) -> IonResult<&mut Self> { self.container_writer.write_value(value, ",")?; Ok(self) } @@ -344,10 +344,10 @@ impl<'value, W: Write + 'value, SymbolType: AsRawSymbolTokenRef> ValueWriter fn write_f64(self, value: f64) -> IonResult<()>; fn write_decimal(self, value: &Decimal) -> IonResult<()>; fn write_timestamp(self, value: &Timestamp) -> IonResult<()>; - fn write_string>(self, value: A) -> IonResult<()>; - fn write_symbol(self, value: A) -> IonResult<()>; - fn write_clob>(self, value: A) -> IonResult<()>; - fn write_blob>(self, value: A) -> IonResult<()>; + fn write_string(self, value: impl AsRef) -> IonResult<()>; + fn write_symbol(self, value: impl AsRawSymbolTokenRef) -> IonResult<()>; + fn write_clob(self, value: impl AsRef<[u8]>) -> IonResult<()>; + fn write_blob(self, value: impl AsRef<[u8]>) -> IonResult<()>; fn write_list FnOnce(&mut Self::ListWriter<'a>) -> IonResult<()>>( self, list_fn: F, @@ -451,19 +451,19 @@ impl<'value, W: Write> ValueWriter for TextValueWriter_1_0<'value, W> { Ok(()) } - fn write_string>(mut self, value: A) -> IonResult<()> { + fn write_string(mut self, value: impl AsRef) -> IonResult<()> { write!(self.output(), "\"")?; RawTextWriter::::write_escaped_text_body(self.output(), value)?; write!(self.output(), "\"")?; Ok(()) } - fn write_symbol(mut self, value: A) -> IonResult<()> { + fn write_symbol(mut self, value: impl AsRawSymbolTokenRef) -> IonResult<()> { RawTextWriter::::write_symbol_token(self.output(), value)?; Ok(()) } - fn write_clob>(mut self, value: A) -> IonResult<()> { + fn write_clob(mut self, value: impl AsRef<[u8]>) -> IonResult<()> { // This type exists solely to enable using the IonValueFormatter (which operates on // `std::fmt::Write`) to write to a `std::io::Write`. struct ClobShim<'a>(&'a [u8]); @@ -479,7 +479,7 @@ impl<'value, W: Write> ValueWriter for TextValueWriter_1_0<'value, W> { Ok(()) } - fn write_blob>(mut self, value: A) -> IonResult<()> { + fn write_blob(mut self, value: impl AsRef<[u8]>) -> IonResult<()> { // Rust format strings escape curly braces by doubling them. The following string is: // * The opening {{ from a text Ion blob, with each brace doubled to escape it. // * A {} pair used by the format string to indicate where the base64-encoded bytes diff --git a/src/lazy/encoder/value_writer.rs b/src/lazy/encoder/value_writer.rs index 6cc2068e..c76c908d 100644 --- a/src/lazy/encoder/value_writer.rs +++ b/src/lazy/encoder/value_writer.rs @@ -85,10 +85,10 @@ pub trait ValueWriter { fn write_f64(self, value: f64) -> IonResult<()>; fn write_decimal(self, value: &Decimal) -> IonResult<()>; fn write_timestamp(self, value: &Timestamp) -> IonResult<()>; - fn write_string>(self, value: A) -> IonResult<()>; - fn write_symbol(self, value: A) -> IonResult<()>; - fn write_clob>(self, value: A) -> IonResult<()>; - fn write_blob>(self, value: A) -> IonResult<()>; + fn write_string(self, value: impl AsRef) -> IonResult<()>; + fn write_symbol(self, value: impl AsRawSymbolTokenRef) -> IonResult<()>; + fn write_clob(self, value: impl AsRef<[u8]>) -> IonResult<()>; + fn write_blob(self, value: impl AsRef<[u8]>) -> IonResult<()>; fn write_list FnOnce(&mut Self::ListWriter<'a>) -> IonResult<()>>( self, @@ -121,7 +121,7 @@ macro_rules! delegate_and_return_self { () => {}; // Recurses one argument pair at a time ($value_type:ty => $method:ident, $($rest:tt)*) => { - fn $method(&mut self, value: $value_type) -> IonResult<&mut Self> { + fn $method(&mut self, value: $value_type) -> IonResult<&mut Self> { self.value_writer().without_annotations().$method(value)?; Ok(self) } From aafd9b219ea96d1f4bb715ac0878636177d96abc Mon Sep 17 00:00:00 2001 From: Zack Slayton Date: Fri, 8 Dec 2023 11:24:19 -0500 Subject: [PATCH 4/6] clippy suggestions --- src/lazy/encoder/binary/mod.rs | 6 +++--- src/lazy/encoder/binary/value_writer.rs | 10 +++++----- src/lazy/encoder/write_as_ion.rs | 6 ++++++ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/lazy/encoder/binary/mod.rs b/src/lazy/encoder/binary/mod.rs index 50898982..8318f45b 100644 --- a/src/lazy/encoder/binary/mod.rs +++ b/src/lazy/encoder/binary/mod.rs @@ -216,7 +216,7 @@ impl<'value> BinaryContainerValuesWriter_1_0<'value> { pub fn write(&mut self, value: V) -> IonResult<&mut Self> { let annotated_value_writer = - BinaryAnnotatableValueWriter_1_0::new(&self.allocator, &mut self.buffer); + BinaryAnnotatableValueWriter_1_0::new(self.allocator, &mut self.buffer); value.write_as_ion(annotated_value_writer)?; Ok(self) } @@ -241,7 +241,7 @@ impl<'value> MakeValueWriter for BinaryListValuesWriter_1_0<'value> { fn value_writer(&mut self) -> Self::ValueWriter<'_> { BinaryAnnotatableValueWriter_1_0::new( - &*self.values_writer.allocator, + self.values_writer.allocator, &mut self.values_writer.buffer, ) } @@ -298,7 +298,7 @@ impl<'value> MakeValueWriter for BinarySExpValuesWriter_1_0<'value> { fn value_writer(&mut self) -> Self::ValueWriter<'_> { BinaryAnnotatableValueWriter_1_0::new( - &*self.values_writer.allocator, + self.values_writer.allocator, &mut self.values_writer.buffer, ) } diff --git a/src/lazy/encoder/binary/value_writer.rs b/src/lazy/encoder/binary/value_writer.rs index a84b4da5..bf20e74b 100644 --- a/src/lazy/encoder/binary/value_writer.rs +++ b/src/lazy/encoder/binary/value_writer.rs @@ -266,7 +266,7 @@ impl<'value, 'top> BinaryValueWriter_1_0<'value, 'top> { const STRUCT_TYPE_CODE: u8 = 0xD0; Ok(BinaryStructWriter_1_0::new(BinaryContainerWriter_1_0::new( STRUCT_TYPE_CODE, - &self.allocator, + self.allocator, self.encoding_buffer, ))) } @@ -404,7 +404,7 @@ impl<'value, 'top, SymbolType: AsRawSymbolTokenRef> where F: for<'a> FnOnce(BinaryAnnotatedValueWriter_1_0<'a, 'top>) -> IonResult<()>, { - let allocator = &*self.allocator; + let allocator = self.allocator; let buffer = allocator.alloc_with(|| BumpVec::new_in(allocator)); { let annotated_value_writer = @@ -415,10 +415,10 @@ impl<'value, 'top, SymbolType: AsRawSymbolTokenRef> } fn annotate_encoded_value(self, encoded_value: &[u8]) -> IonResult<()> { - let mut encoded_annotations_sequence = BumpVec::new_in(&self.allocator); + let mut encoded_annotations_sequence = BumpVec::new_in(self.allocator); self.encode_annotations_sequence(&mut encoded_annotations_sequence)?; - let mut encoded_annotations_sequence_length = BumpVec::new_in(&self.allocator); + let mut encoded_annotations_sequence_length = BumpVec::new_in(self.allocator); VarUInt::write_u64( &mut encoded_annotations_sequence_length, encoded_annotations_sequence.len() as u64, @@ -532,7 +532,7 @@ impl<'value, 'top> BinaryAnnotatedValueWriter_1_0<'value, 'top> { Self { allocator, buffer } } pub(crate) fn value_writer(&mut self) -> BinaryValueWriter_1_0<'_, 'top> { - BinaryValueWriter_1_0::new(self.allocator, &mut self.buffer) + BinaryValueWriter_1_0::new(self.allocator, self.buffer) } pub(crate) fn buffer(&self) -> &[u8] { diff --git a/src/lazy/encoder/write_as_ion.rs b/src/lazy/encoder/write_as_ion.rs index f2135e7d..e50c33a2 100644 --- a/src/lazy/encoder/write_as_ion.rs +++ b/src/lazy/encoder/write_as_ion.rs @@ -194,6 +194,12 @@ pub trait WriteAsSExp: Sized where T: WriteAsIon, { + // The name `as_sexp` makes common cases read as a short sentence: + // writer.write([1, 2, 3].as_sexp())?; + // Clippy complains because in most contexts, `as_*` methods borrow by reference. + #[allow(clippy::wrong_self_convention)] + /// Wraps `self` (which may be a reference) in a [`SExpTypeHint`], causing the value to be + /// serialized as an Ion S-expression instead of a list. fn as_sexp(self) -> SExpTypeHint; } macro_rules! impl_write_as_sexp_for_iterable { From 0d0cabc6ed93fa7b90c3ca80b7693e44668ca4f9 Mon Sep 17 00:00:00 2001 From: Zack Slayton Date: Fri, 8 Dec 2023 12:23:08 -0500 Subject: [PATCH 5/6] Added impl_write_as_ion_value! macro --- src/lazy/encoder/mod.rs | 4 +- src/lazy/encoder/write_as_ion.rs | 132 +++++++++---------------------- 2 files changed, 41 insertions(+), 95 deletions(-) diff --git a/src/lazy/encoder/mod.rs b/src/lazy/encoder/mod.rs index 15510e79..cbeabb23 100644 --- a/src/lazy/encoder/mod.rs +++ b/src/lazy/encoder/mod.rs @@ -119,7 +119,7 @@ mod tests { .build()? .annotated_with(&["Saturn"]), )? - .write((&[0xE0u8, 0x01, 0x00, 0xEA][..]).annotated_with(&["Uranus"]))?; + .write([0xE0u8, 0x01, 0x00, 0xEA].annotated_with(&["Uranus"]))?; Ok(()) }; writer_test(expected, test) @@ -146,7 +146,7 @@ mod tests { .write("foo")? .write("bar".as_symbol_ref())? .write(Timestamp::with_ymd(2023, 11, 9).build()?)? - .write(&[0xE0u8, 0x01, 0x00, 0xEA][..])?; + .write([0xE0u8, 0x01, 0x00, 0xEA])?; Ok(()) }) }; diff --git a/src/lazy/encoder/write_as_ion.rs b/src/lazy/encoder/write_as_ion.rs index e50c33a2..b7e65e14 100644 --- a/src/lazy/encoder/write_as_ion.rs +++ b/src/lazy/encoder/write_as_ion.rs @@ -48,65 +48,47 @@ where // ===== WriteAsIonValue implementations for common types ===== -impl WriteAsIonValue for Null { - fn write_as_ion_value(&self, writer: V) -> IonResult<()> { - writer.write_null(self.0) - } -} - -impl WriteAsIonValue for bool { - fn write_as_ion_value(&self, writer: V) -> IonResult<()> { - writer.write_bool(*self) - } -} - -impl WriteAsIonValue for i32 { - fn write_as_ion_value(&self, writer: V) -> IonResult<()> { - writer.write_i64(*self as i64) - } -} - -impl WriteAsIonValue for i64 { - fn write_as_ion_value(&self, writer: V) -> IonResult<()> { - writer.write_i64(*self) - } -} - -impl WriteAsIonValue for usize { - fn write_as_ion_value(&self, writer: V) -> IonResult<()> { - writer.write_int(&Int::from(*self)) - } -} - -impl WriteAsIonValue for f32 { - fn write_as_ion_value(&self, writer: V) -> IonResult<()> { - writer.write_f32(*self) - } -} - -impl WriteAsIonValue for f64 { - fn write_as_ion_value(&self, writer: V) -> IonResult<()> { - writer.write_f64(*self) - } -} - -impl WriteAsIonValue for Decimal { - fn write_as_ion_value(&self, writer: V) -> IonResult<()> { - writer.write_decimal(self) - } -} - -impl WriteAsIonValue for Timestamp { - fn write_as_ion_value(&self, writer: V) -> IonResult<()> { - writer.write_timestamp(self) - } +macro_rules! impl_write_as_ion_value { + // End of iteration + () => {}; + // The caller defined an expression to write other than `self` (e.g. `*self`, `*self.0`, etc) + ($target_type:ty => $method:ident with $self:ident as $value:expr, $($rest:tt)*) => { + impl WriteAsIonValue for $target_type { + fn write_as_ion_value(&$self, writer: V) -> IonResult<()> { + writer.$method($value) + } + } + impl_write_as_ion_value!($($rest)*); + }; + // We're writing the expression `self` + ($target_type:ty => $method:ident, $($rest:tt)*) => { + impl WriteAsIonValue for $target_type { + fn write_as_ion_value(&self, writer: V) -> IonResult<()> { + writer.$method(self) + } + } + impl_write_as_ion_value!($($rest)*); + }; } -impl WriteAsIonValue for Symbol { - fn write_as_ion_value(&self, writer: V) -> IonResult<()> { - writer.write_symbol(self) - } -} +impl_write_as_ion_value!( + Null => write_null with self as self.0, + bool => write_bool with self as *self, + i32 => write_i64 with self as *self as i64, + i64 => write_i64 with self as *self, + usize => write_int with self as &Int::from(*self), + f32 => write_f32 with self as *self, + f64 => write_f64 with self as *self, + Decimal => write_decimal, + Timestamp => write_timestamp, + Symbol => write_symbol, + RawSymbolToken => write_symbol, + &str => write_string, + String => write_string, + &[u8] => write_blob, + Blob => write_blob, + Clob => write_clob, +); impl<'b> WriteAsIonValue for RawSymbolTokenRef<'b> { fn write_as_ion_value(&self, writer: V) -> IonResult<()> { @@ -120,48 +102,12 @@ impl<'b> WriteAsIonValue for SymbolRef<'b> { } } -impl WriteAsIonValue for RawSymbolToken { - fn write_as_ion_value(&self, writer: V) -> IonResult<()> { - writer.write_symbol(self) - } -} - -impl<'b> WriteAsIonValue for &'b str { - fn write_as_ion_value(&self, writer: V) -> IonResult<()> { - writer.write_string(self) - } -} - -impl WriteAsIonValue for String { - fn write_as_ion_value(&self, writer: V) -> IonResult<()> { - writer.write_string(self) - } -} - -impl<'b> WriteAsIonValue for &'b [u8] { - fn write_as_ion_value(&self, writer: V) -> IonResult<()> { - writer.write_blob(self) - } -} - impl WriteAsIonValue for [u8; N] { fn write_as_ion_value(&self, writer: V) -> IonResult<()> { writer.write_blob(self) } } -impl WriteAsIonValue for Blob { - fn write_as_ion_value(&self, writer: V) -> IonResult<()> { - writer.write_blob(self) - } -} - -impl WriteAsIonValue for Clob { - fn write_as_ion_value(&self, writer: V) -> IonResult<()> { - writer.write_clob(self) - } -} - impl WriteAsIonValue for &T { fn write_as_ion_value(&self, writer: V) -> IonResult<()> { (*self).write_as_ion_value(writer) From d150ee18b94e67716363fd8aea90da42991f275c Mon Sep 17 00:00:00 2001 From: Zack Slayton Date: Wed, 20 Dec 2023 07:31:18 -0500 Subject: [PATCH 6/6] removed old test code --- src/lazy/encoder/binary/value_writer.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lazy/encoder/binary/value_writer.rs b/src/lazy/encoder/binary/value_writer.rs index bf20e74b..7c4433ed 100644 --- a/src/lazy/encoder/binary/value_writer.rs +++ b/src/lazy/encoder/binary/value_writer.rs @@ -595,7 +595,6 @@ mod tests { let mut writer = LazyRawBinaryWriter_1_0::new(&mut buffer)?; test(&mut writer)?; writer.flush()?; - std::fs::write("/tmp/output.ion", &buffer)?; let actual = Element::read_all(buffer)?; assert!( IonData::eq(&expected, &actual),