1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use crate::{RantValue, RantString};
use super::format::{WhitespaceNormalizationMode, OutputFormat};
use std::rc::Rc;

const INITIAL_CHAIN_CAPACITY: usize = 64;

/// Writes a stream of buffers that can be passed to a parent buffer or rendered to a string.

pub struct OutputWriter {
  buffers: Vec<OutputBuffer>,
  frag_buffer: Option<RantString>,
  format: Rc<OutputFormat>,
}

impl OutputWriter {
  pub fn new(prev_output: Option<&Self>) -> Self {
    Self {
      buffers: Vec::with_capacity(INITIAL_CHAIN_CAPACITY),
      frag_buffer: None,
      format: prev_output.map(|o| Rc::clone(&o.format)).unwrap_or_default(),
    }
  }

  #[inline]
  pub fn format(&self) -> &OutputFormat {
    &self.format
  }

  #[inline]
  pub fn format_mut(&mut self) -> &mut OutputFormat {
    Rc::make_mut(&mut self.format)
  }
  
  #[inline]
  pub fn write_buffer(&mut self, value: OutputBuffer) {
    self.flush_frag_buffer();
    self.buffers.push(value);
  }
  
  #[inline]
  pub fn write_frag(&mut self, value: &str) {
    // Consecutive string writes are buffered in a "frag buffer", which reduces the total number of buffer elements in the output

    if let Some(frag_buffer) = self.frag_buffer.as_mut() {
      frag_buffer.push_str(value);
    } else {
      self.frag_buffer = Some(RantString::from(value))
    }
  }
  
  #[inline]
  pub fn write_ws(&mut self, value: &str) {
    match &self.format.ws_norm_mode {
      WhitespaceNormalizationMode::Default => {
        self.write_frag(" ");
      },
      WhitespaceNormalizationMode::IgnoreAll => {},
      WhitespaceNormalizationMode::Verbatim => {
        self.write_frag(value);
      },
      WhitespaceNormalizationMode::Custom(val) => {
        self.write_frag(val.to_string().as_str())
      },
    }
  }
  
  #[inline]
  fn flush_frag_buffer(&mut self) {
    if let Some(frag_buffer) = self.frag_buffer.take() {
      self.buffers.push(OutputBuffer::String(frag_buffer));
    }
  }
}

impl OutputWriter {
  #[inline]
  pub fn render_value(mut self) -> RantValue {
    self.flush_frag_buffer();
    
    match self.buffers.len() {
      // An empty output always returns an empty value

      0 => RantValue::Empty,
      // Single buffer is always returned unchanged

      1 => {
        let buffer = self.buffers.pop().unwrap();
        match buffer {
          OutputBuffer::String(s) => RantValue::String(s.to_string()),
          OutputBuffer::Value(v) => v,
        }
      },
      // Multiple buffers are concatenated into a single string, unless they are all empty

      _ => {
        let mut has_any_nonempty = false;
        let mut output = RantString::new();
        for buf in self.buffers {
          if !matches!(buf, OutputBuffer::Value(RantValue::Empty)) {
            has_any_nonempty = true;
            output.push_str(buf.render().as_str())
          }
        }
        // If there is at least one non-empty, return the string; otherwise, return empty value

        if has_any_nonempty {
          RantValue::String(output.to_string())
        } else {
          RantValue::Empty
        }
      }
    }
  }
}

impl Default for OutputWriter {
  fn default() -> Self {
    OutputWriter::new(Default::default())
  }
}

/// A unit of output.

#[derive(Debug)]
pub enum OutputBuffer {
  String(RantString),
  Value(RantValue)
}

impl<'a> OutputBuffer {
  /// Consumes the buffer and returns its contents rendered as a single `String`.

  #[inline]
  pub(crate) fn render(self) -> RantString {
    match self {
      OutputBuffer::String(s) => s,
      OutputBuffer::Value(v) => RantString::from(v.to_string())
    }
  }
}