preserves 5.0.0-rc.9

Implementation of the Preserves serialization format.
Documentation
use crate::Domain;
use crate::DomainEncode;
use crate::IOValue;
use crate::IOValueDomainEncode;
use crate::ValueObject;
use crate::Writer;
use crate::hex::HexFormatter;

use lazy_static::lazy_static;

use num_bigint::BigInt;

use std::io;

use super::super::boundary as B;

#[derive(Clone, Copy, Debug)]
pub enum CommaStyle {
    None,
    Separating,
    Terminating,
}

pub struct TextWriter<W: io::Write> {
    w: W,
    pub comma_style: CommaStyle,
    pub indentation: usize,
    pub escape_spaces: bool,
    pub write_annotations: bool,
    indent: String,
}

impl std::default::Default for CommaStyle {
    fn default() -> Self {
        CommaStyle::Separating
    }
}

impl TextWriter<&mut Vec<u8>> {
    pub fn fmt_value<D: Domain, Enc: DomainEncode<D>>(
        f: &mut std::fmt::Formatter<'_>,
        enc: &mut Enc,
        v: &ValueObject<D>,
    ) -> io::Result<()> {
        let mut buf: Vec<u8> = Vec::new();
        let mut w = TextWriter::new(&mut buf);
        if f.alternate() { w.indentation = 4 }
        v.write(&mut w, enc)?;
        f.write_str(std::str::from_utf8(&buf).expect("valid UTF-8 from TextWriter")).map_err(
            |_| io::Error::new(io::ErrorKind::Other, "could not append to Formatter"))
    }

    pub fn encode<D: Domain, Enc: DomainEncode<D>>(
        enc: &mut Enc,
        v: &ValueObject<D>,
    ) -> io::Result<String> {
        let mut buf: Vec<u8> = Vec::new();
        v.write(&mut TextWriter::new(&mut buf), enc)?;
        Ok(String::from_utf8(buf).expect("valid UTF-8 from TextWriter"))
    }

    pub fn encode_iovalue<D: Domain>(
        v: &IOValue,
    ) -> io::Result<String> {
        Self::encode(&mut IOValueDomainEncode, v)
    }
}

impl<W: io::Write> TextWriter<W> {
    pub fn new(w: W) -> Self {
        TextWriter {
            w,
            comma_style: CommaStyle::default(),
            indentation: 0,
            escape_spaces: false,
            write_annotations: true,
            indent: "\n".to_owned(),
        }
    }

    pub fn set_write_annotations(mut self, v: bool) -> Self {
        self.write_annotations = v;
        self
    }

    pub fn set_comma_style(mut self, v: CommaStyle) -> Self {
        self.comma_style = v;
        self
    }

    pub fn set_escape_spaces(mut self, v: bool) -> Self {
        self.escape_spaces = v;
        self
    }

    pub fn write_stringlike_char_fallback<F>(
        &mut self,
        c: char,
        f: F,
    ) -> io::Result<()> where
        F: FnOnce(&mut W, char) -> io::Result<()>
    {
        match c {
            '\\' => write!(self.w, "\\\\"),
            '\x08' => write!(self.w, "\\b"),
            '\x0c' => write!(self.w, "\\f"),
            '\x0a' => write!(self.w, "\\n"),
            '\x0d' => write!(self.w, "\\r"),
            '\x09' => write!(self.w, "\\t"),
            _ => f(&mut self.w, c),
        }
    }

    pub fn write_stringlike_char(&mut self, c: char) -> io::Result<()> {
        self.write_stringlike_char_fallback(c, |w, c| write!(w, "{}", c))
    }

    pub fn add_indent(&mut self) {
        for _ in 0 .. self.indentation {
            self.indent.push(' ')
        }
    }

    pub fn del_indent(&mut self) {
        if self.indentation > 0 {
            self.indent.truncate(self.indent.len() - self.indentation)
        }
    }

    pub fn indent(&mut self) -> io::Result<()> {
        if self.indentation > 0 {
            write!(self.w, "{}", &self.indent)
        } else {
            Ok(())
        }
    }

    pub fn indent_sp(&mut self) -> io::Result<()> {
        if self.indentation > 0 {
            write!(self.w, "{}", &self.indent)
        } else {
            write!(self.w, " ")
        }
    }

    pub fn borrow_write(&mut self) -> &mut W {
        &mut self.w
    }
}

impl<W: io::Write> Writer for TextWriter<W> {
    #[inline]
    fn boundary(&mut self, b: &B::Type) -> io::Result<()> {
        match (b.closing.as_ref(), b.opening.as_ref()) {
            (None, Some(B::Item::RecordLabel)) |
            (Some(B::Item::RecordLabel), None) |
            (Some(B::Item::RecordField), None) =>
                return Ok(()),
            (_, Some(B::Item::RecordField)) =>
                return write!(self.w, " "),

            (Some(B::Item::DictionaryKey), Some(B::Item::DictionaryValue)) => {
                return write!(self.w, ": ")
            }

            (None, Some(B::Item::Annotation)) => {
                return write!(self.w, "@")
            }
            (Some(_), Some(B::Item::Annotation)) => {
                return write!(self.w, " @")
            }
            (Some(B::Item::Annotation), Some(B::Item::AnnotatedValue)) => {
                return write!(self.w, " ")
            }
            (None, Some(B::Item::AnnotatedValue)) |
            // ^ strictly speaking, this combination is not permitted; a ValueImpl that yields
            // a zero-length vector of annotations instead of `None` is in error.
            (Some(B::Item::AnnotatedValue), None) =>
                return Ok(()),

            _ => (),
        }

        match (b.closing.as_ref(), b.opening.as_ref()) {
            (None, None) => (),
            (None, Some(_)) => {
                self.add_indent();
                self.indent()?
            },
            (Some(_), Some(_)) => {
                match self.comma_style {
                    CommaStyle::Separating | CommaStyle::Terminating => write!(self.w, ",")?,
                    CommaStyle::None => (),
                }
                self.indent_sp()?
            }
            (Some(_), None) => {
                match self.comma_style {
                    CommaStyle::Terminating => write!(self.w, ",")?,
                    CommaStyle::Separating | CommaStyle::None => (),
                }
                self.del_indent();
                self.indent()?
            }
        }

        Ok(())
    }

    fn should_write_annotations(&self) -> bool {
        self.write_annotations
    }

    fn start_annotations(&mut self) -> io::Result<()> {
        Ok(())
    }

    fn end_annotations(&mut self) -> io::Result<()> {
        Ok(())
    }

    fn write_bool(&mut self, v: bool) -> io::Result<()> {
        write!(self.w, "{}", if v { "#t" } else { "#f" })
    }

    fn write_f64(&mut self, v: f64) -> io::Result<()> {
        if v.is_nan() || v.is_infinite() {
            write!(self.w, "#xd\"{}\"",
                   HexFormatter::Packed.encode(&u64::to_be_bytes(f64::to_bits(v))))
        } else {
            dtoa::write(&mut self.w, v)?;
            Ok(())
        }
    }

    fn write_i128(&mut self, v: i128) -> io::Result<()> {
        write!(self.w, "{}", v)
    }

    fn write_u128(&mut self, v: u128) -> io::Result<()> {
        write!(self.w, "{}", v)
    }

    fn write_int(&mut self, v: &BigInt) -> io::Result<()> {
        write!(self.w, "{}", v)
    }

    fn write_string(&mut self, v: &str) -> io::Result<()> {
        write!(self.w, "\"")?;
        for c in v.chars() {
            match c {
                '"' => write!(self.w, "\\\"")?,
                ' ' if self.escape_spaces => write!(self.w, "\\u0020")?,
                _ => self.write_stringlike_char(c)?,
            }
        }
        write!(self.w, "\"")
    }

    fn write_bytes(&mut self, v: &[u8]) -> io::Result<()> {
        write!(self.w, "#[{}]", base64::encode_config(v, base64::STANDARD_NO_PAD))
    }

    fn write_symbol(&mut self, v: &str) -> io::Result<()> {
        lazy_static! {
            // FIXME: This regular expression is conservatively correct, but Anglo-chauvinistic.
            static ref SYMBOL_OR_NUMBER: regex::Regex =
                regex::Regex::new("^[-a-zA-Z0-9~!$%^&*?_=+/.|]+$").unwrap();
            static ref NUMBER: regex::Regex =
                regex::Regex::new(r"^([-+]?\d+)((\.\d+([eE][-+]?\d+)?)|([eE][-+]?\d+))?$").unwrap();
        }
        if SYMBOL_OR_NUMBER.is_match(v) && !NUMBER.is_match(v) {
            write!(self.w, "{}", v)
        } else {
            write!(self.w, "'")?;
            for c in v.chars() {
                match c {
                    '\'' => write!(self.w, "\\'")?,
                    ' ' if self.escape_spaces => write!(self.w, "\\u0020")?,
                    _ => self.write_stringlike_char(c)?,
                }
            }
            write!(self.w, "'")
        }
    }

    fn start_record(&mut self) -> io::Result<()> {
        write!(self.w, "<")
    }

    fn end_record(&mut self) -> io::Result<()> {
        write!(self.w, ">")
    }

    fn start_sequence(&mut self) -> io::Result<()> {
        write!(self.w, "[")
    }

    fn end_sequence(&mut self) -> io::Result<()> {
        write!(self.w, "]")
    }

    fn start_set(&mut self) -> io::Result<()> {
        write!(self.w, "#{{")
    }

    fn end_set(&mut self) -> io::Result<()> {
        write!(self.w, "}}")
    }

    fn start_dictionary(&mut self) -> io::Result<()> {
        write!(self.w, "{{")
    }

    fn end_dictionary(&mut self) -> io::Result<()> {
        write!(self.w, "}}")
    }

    fn start_embedded(&mut self) -> io::Result<()> {
        write!(self.w, "#:")
    }

    fn end_embedded(&mut self) -> io::Result<()> {
        Ok(())
    }

    fn flush(&mut self) -> io::Result<()> {
        self.w.flush()
    }
}