From 93c824a0cf729900ff32ae8f2f145024788a66f3 Mon Sep 17 00:00:00 2001 From: Zack Slayton Date: Fri, 8 Dec 2023 10:11:26 -0500 Subject: [PATCH] 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() - } -}