319 lines
8.7 KiB
Rust
319 lines
8.7 KiB
Rust
use crate::DomainEncode;
|
|
use crate::ValueImpl;
|
|
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,
|
|
indent: String,
|
|
}
|
|
|
|
impl std::default::Default for CommaStyle {
|
|
fn default() -> Self {
|
|
CommaStyle::Separating
|
|
}
|
|
}
|
|
|
|
impl TextWriter<&mut Vec<u8>> {
|
|
pub fn fmt_value<'de, V: ValueImpl<'de>, Enc: DomainEncode<'de, V::Embedded>>(
|
|
f: &mut std::fmt::Formatter<'_>,
|
|
enc: &mut Enc,
|
|
v: &V,
|
|
) -> 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<'de, V: ValueImpl<'de>, Enc: DomainEncode<'de, V::Embedded>>(
|
|
enc: &mut Enc,
|
|
v: &V,
|
|
) -> 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"))
|
|
}
|
|
}
|
|
|
|
impl<W: io::Write> TextWriter<W> {
|
|
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<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 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<()> {
|
|
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(())
|
|
}
|
|
}
|
|
|
|
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 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) -> 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()
|
|
}
|
|
}
|