use crate::value::DomainEncode; use crate::value::IOValue; use crate::value::IOValueDomainCodec; use crate::value::NestedValue; use crate::value::Writer; 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: W, pub comma_style: CommaStyle, pub indentation: usize, pub escape_spaces: bool, indent: String, } impl std::default::Default for CommaStyle { fn default() -> Self { CommaStyle::None } } impl TextWriter<&mut Vec> { pub fn encode>( enc: &mut Enc, v: &N, ) -> io::Result { let mut buf: Vec = 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 { Self::encode(&mut IOValueDomainCodec, v) } } impl TextWriter { pub fn new(w: W) -> Self { TextWriter { 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 write_stringlike_char_fallback( &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 } } macro_rules! simple_writer_method { ($n:ident, $argty:ty) => (fn $n (&mut self, v: $argty) -> io::Result<()> { write!(self.w, "{}", v) }); } impl Writer for TextWriter { #[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(()) } 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_f32(&mut self, v: f32) -> io::Result<()> { dtoa::write(&mut self.w, v)?; write!(self.w, "f") } fn write_f64(&mut self, v: f64) -> io::Result<()> { 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<()> { // FIXME: This regular expression is conservatively correct, but Anglo-chauvinistic. let re = regex::Regex::new("^[a-zA-Z~!$%^&*?_=+/.][-a-zA-Z~!$%^&*?_=+/.0-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) -> io::Result<()> { write!(self.w, "<")?; Ok(()) } fn end_record(&mut self) -> io::Result<()> { write!(self.w, ">") } fn start_sequence(&mut self) -> io::Result<()> { write!(self.w, "[")?; Ok(()) } fn end_sequence(&mut self) -> io::Result<()> { write!(self.w, "]") } fn start_set(&mut self) -> io::Result<()> { write!(self.w, "#{{")?; Ok(()) } fn end_set(&mut self) -> io::Result<()> { write!(self.w, "}}") } fn start_dictionary(&mut self) -> io::Result<()> { write!(self.w, "{{")?; Ok(()) } fn end_dictionary(&mut self) -> io::Result<()> { write!(self.w, "}}") } fn start_embedded(&mut self) -> io::Result<()> { write!(self.w, "#!")?; Ok(()) } fn end_embedded(&mut self) -> io::Result<()> { Ok(()) } fn flush(&mut self) -> io::Result<()> { self.w.flush() } }