preserves/implementations/rust/preserves/src/value/text/writer.rs

359 lines
10 KiB
Rust

use crate::hex::HexFormatter;
use crate::value::DomainEncode;
use crate::value::IOValue;
use crate::value::IOValueDomainCodec;
use crate::value::NestedValue;
use crate::value::Writer;
use crate::value::suspendable::Suspendable;
use crate::value::writer::CompoundWriter;
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: Suspendable<W>,
pub comma_style: CommaStyle,
pub indentation: usize,
pub escape_spaces: bool,
indent: String,
}
impl std::default::Default for CommaStyle {
fn default() -> Self {
CommaStyle::Separating
}
}
impl TextWriter<&mut Vec<u8>> {
pub fn fmt_value<N: NestedValue, Enc: DomainEncode<N::Embedded>>(
f: &mut std::fmt::Formatter<'_>,
enc: &mut Enc,
v: &crate::value::Value<N>,
) -> io::Result<()> {
let mut buf: Vec<u8> = Vec::new();
let mut w = TextWriter::new(&mut buf);
if f.alternate() { w.indentation = 4 }
w.write_value(enc, v)?;
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<N: NestedValue, Enc: DomainEncode<N::Embedded>>(
enc: &mut Enc,
v: &N,
) -> io::Result<String> {
let mut buf: Vec<u8> = Vec::new();
TextWriter::new(&mut buf).write(enc, v)?;
Ok(String::from_utf8(buf).expect("valid UTF-8 from TextWriter"))
}
pub fn encode_iovalue(v: &IOValue) -> io::Result<String> {
Self::encode(&mut IOValueDomainCodec, v)
}
}
impl<W: io::Write> TextWriter<W> {
pub fn new(w: W) -> Self {
TextWriter {
w: Suspendable::new(w),
comma_style: CommaStyle::default(),
indentation: 0,
escape_spaces: false,
indent: "\n".to_owned(),
}
}
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 suspend(&mut self) -> Self {
TextWriter { w: self.w.suspend(), indent: self.indent.clone(), .. *self }
}
pub fn resume(&mut self, other: Self) {
self.w.resume(other.w)
}
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> CompoundWriter 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, " ")
}
(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(())
}
}
macro_rules! simple_writer_method {
($n:ident, $argty:ty) =>
(fn $n (&mut self, v: $argty) -> io::Result<()> {
write!(self.w, "{}", v)
});
}
impl<W: io::Write> Writer for TextWriter<W> {
type AnnWriter = Self;
type RecWriter = Self;
type SeqWriter = Self;
type SetWriter = Self;
type DictWriter = Self;
type EmbeddedWriter = Self;
fn start_annotations(&mut self) -> io::Result<Self::AnnWriter> {
Ok(self.suspend())
}
fn end_annotations(&mut self, ann: Self::AnnWriter) -> io::Result<()> {
self.resume(ann);
Ok(())
}
fn write_bool(&mut self, v: bool) -> io::Result<()> {
write!(self.w, "{}", if v { "#t" } else { "#f" })
}
fn write_f32(&mut self, v: f32) -> io::Result<()> {
if v.is_nan() || v.is_infinite() {
write!(self.w, "#xf\"{}\"",
HexFormatter::Packed.encode(&u32::to_be_bytes(f32::to_bits(v))))
} else {
dtoa::write(&mut *self.w, v)?;
write!(self.w, "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(())
}
}
simple_writer_method!(write_i8, i8);
simple_writer_method!(write_u8, u8);
simple_writer_method!(write_i16, i16);
simple_writer_method!(write_u16, u16);
simple_writer_method!(write_i32, i32);
simple_writer_method!(write_u32, u32);
simple_writer_method!(write_i64, i64);
simple_writer_method!(write_u64, u64);
simple_writer_method!(write_i128, i128);
simple_writer_method!(write_u128, u128);
simple_writer_method!(write_int, &BigInt);
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 RE: regex::Regex =
regex::Regex::new("^[-a-zA-Z0-9~!$%^&*?_=+/.]+$").unwrap();
}
if RE.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, _field_count: Option<usize>) -> io::Result<Self::RecWriter> {
write!(self.w, "<")?;
Ok(self.suspend())
}
fn end_record(&mut self, rec: Self::RecWriter) -> io::Result<()> {
self.resume(rec);
write!(self.w, ">")
}
fn start_sequence(&mut self, _item_count: Option<usize>) -> io::Result<Self::SeqWriter> {
write!(self.w, "[")?;
Ok(self.suspend())
}
fn end_sequence(&mut self, seq: Self::SeqWriter) -> io::Result<()> {
self.resume(seq);
write!(self.w, "]")
}
fn start_set(&mut self, _item_count: Option<usize>) -> io::Result<Self::SetWriter> {
write!(self.w, "#{{")?;
Ok(self.suspend())
}
fn end_set(&mut self, set: Self::SetWriter) -> io::Result<()> {
self.resume(set);
write!(self.w, "}}")
}
fn start_dictionary(&mut self, _entry_count: Option<usize>) -> io::Result<Self::DictWriter> {
write!(self.w, "{{")?;
Ok(self.suspend())
}
fn end_dictionary(&mut self, dict: Self::DictWriter) -> io::Result<()> {
self.resume(dict);
write!(self.w, "}}")
}
fn start_embedded(&mut self) -> io::Result<Self::EmbeddedWriter> {
write!(self.w, "#!")?;
Ok(self.suspend())
}
fn end_embedded(&mut self, ptr: Self::EmbeddedWriter) -> io::Result<()> {
self.resume(ptr);
Ok(())
}
fn flush(&mut self) -> io::Result<()> {
self.w.flush()
}
}