//! A library for emitting pretty-formatted structured source code. //! //! The main entry points are [Formatter::to_string] and [Formatter::write], plus the utilities //! in the [macros] submodule. use std::fmt::Write; use std::str; /// Default width for pretty-formatting, in columns. pub const DEFAULT_WIDTH: usize = 80; /// All pretty-formattable items must implement this trait. pub trait Emittable: std::fmt::Debug { /// Serializes `self`, as pretty-printed code, on `f`. fn write_on(&self, f: &mut Formatter); } /// Tailoring of behaviour for [Vertical] groupings. #[derive(Clone, PartialEq, Eq)] pub enum VerticalMode { Variable, Normal, ExtraNewline, } /// Vertical formatting for [Emittable]s. pub trait Vertical { fn set_vertical_mode(&mut self, mode: VerticalMode); fn write_vertically_on(&self, f: &mut Formatter); } /// Polymorphic [Emittable], used consistently in the API. pub type Item = std::rc::Rc; /// A possibly-vertical sequence of items with item-separating and -terminating text. #[derive(Clone)] pub struct Sequence { pub items: Vec, pub vertical_mode: VerticalMode, pub separator: &'static str, pub terminator: &'static str, } /// A sequence of items, indented when formatted vertically, surrounded by opening and closing /// text. #[derive(Clone)] pub struct Grouping { pub sequence: Sequence, pub open: &'static str, pub close: &'static str, } /// State needed for pretty-formatting of [Emittable]s. pub struct Formatter { /// Number of available columns. Used to decide between horizontal and vertical layouts. pub width: usize, indent_delta: String, current_indent: String, /// Mutable output buffer. Accumulates emitted text during writing. pub buffer: String, } impl Formatter { /// Construct a Formatter using [DEFAULT_WIDTH] and a four-space indent. pub fn new() -> Self { Formatter { width: DEFAULT_WIDTH, indent_delta: " ".to_owned(), current_indent: "\n".to_owned(), buffer: String::new(), } } /// Construct a Formatter just like `self` but with an empty `buffer`. pub fn copy_empty(&self) -> Formatter { Formatter { width: self.width, indent_delta: self.indent_delta.clone(), current_indent: self.current_indent.clone(), buffer: String::new(), } } /// Yields the indent size. pub fn indent_size(self) -> usize { self.indent_delta.len() } /// Updates the indent size. pub fn set_indent_size(&mut self, n: usize) { self.indent_delta = str::repeat(" ", n) } /// Accumulates a text serialization of `e` in `buffer`. pub fn write(&mut self, e: E) { e.write_on(self) } /// Emits a newline followed by indentation into `buffer`. pub fn newline(&mut self) { self.buffer.push_str(&self.current_indent) } /// Creates a default Formatter, uses it to [write][Formatter::write] `e`, and yields the /// contents of its `buffer`. pub fn to_string(e: E) -> String { let mut f = Formatter::new(); f.write(e); f.buffer } /// Calls `f` in a context where the indentation has been increased by /// [Formatter::indent_size] spaces. Restores the indentation level after `f` returns. /// Yields the result of the call to `f`. pub fn with_indent R>(&mut self, f: F) -> R { let old_indent = self.current_indent.clone(); self.current_indent += &self.indent_delta; let r = f(self); self.current_indent = old_indent; r } } impl Default for Formatter { fn default() -> Self { Self::new() } } impl Default for VerticalMode { fn default() -> Self { Self::Variable } } //--------------------------------------------------------------------------- impl Emittable for &str { fn write_on(&self, f: &mut Formatter) { f.buffer.push_str(self) } } impl Emittable for String { fn write_on(&self, f: &mut Formatter) { f.write(self.as_str()) } } impl<'a, E: Emittable> Emittable for &'a Vec where &'a E: Emittable, { fn write_on(&self, f: &mut Formatter) { for e in self.iter() { f.write(e) } } } impl Emittable for Sequence { fn write_on(&self, f: &mut Formatter) { if self.vertical_mode != VerticalMode::Variable { self.write_vertically_on(f) } else { let mut need_sep = false; for e in self.items.iter() { if need_sep { self.separator.write_on(f) } else { need_sep = true } e.write_on(f) } if !self.items.is_empty() { self.terminator.write_on(f) } } } } impl Vertical for Sequence { fn set_vertical_mode(&mut self, vertical_mode: VerticalMode) { self.vertical_mode = vertical_mode; } fn write_vertically_on(&self, f: &mut Formatter) { let mut i = self.items.len(); let mut first = true; for e in self.items.iter() { if !first { if self.vertical_mode == VerticalMode::ExtraNewline { f.write("\n"); } f.newline(); } first = false; e.write_on(f); let delim = if i == 1 { self.terminator } else { self.separator }; delim .trim_end_matches(|c: char| c.is_whitespace() && c != '\n') .write_on(f); i = i - 1; } } } impl Emittable for Grouping { fn write_on(&self, f: &mut Formatter) { if self.sequence.vertical_mode != VerticalMode::Variable { self.write_vertically_on(f) } else { let mut g = f.copy_empty(); self.open.write_on(&mut g); g.write(&self.sequence); self.close.write_on(&mut g); let s = g.buffer; if s.len() <= f.width { f.write(&s) } else { self.write_vertically_on(f) } } } } impl Vertical for Grouping { fn set_vertical_mode(&mut self, vertical_mode: VerticalMode) { self.sequence.set_vertical_mode(vertical_mode); } fn write_vertically_on(&self, f: &mut Formatter) { self.open.write_on(f); if !self.sequence.items.is_empty() { f.with_indent(|f| { f.newline(); self.sequence.write_vertically_on(f) }); f.newline() } self.close.write_on(f); } } impl<'a, E: Emittable> Emittable for &'a E { fn write_on(&self, f: &mut Formatter) { (*self).write_on(f) } } impl Emittable for Item { fn write_on(&self, f: &mut Formatter) { (**self).write_on(f) } } impl std::fmt::Debug for Sequence { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { f.write_str(&Formatter::to_string(self)) } } impl std::fmt::Debug for Grouping { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { f.write_str(&Formatter::to_string(self)) } } //--------------------------------------------------------------------------- /// Escapes `s` by substituting `\\` for `\`, `\"` for `"`, and `\u{...}` for characters /// outside the range 32..126, inclusive. /// /// This process is intended to generate literals compatible with `rustc`; see [the language /// reference on "Character and string /// literals"](https://doc.rust-lang.org/reference/tokens.html#character-and-string-literals). pub fn escape_string(s: &str) -> String { let mut buf = String::new(); buf.push('"'); for c in s.chars() { match c { '\\' => buf.push_str("\\\\"), '"' => buf.push_str("\\\""), _ if c >= ' ' && c <= '~' => buf.push(c), _ => write!(&mut buf, "\\u{{{:x}}}", c as i32).expect("no IO errors building a string"), } } buf.push('"'); buf } /// Escapes `bs` into a Rust byte string literal, treating each byte as its ASCII equivalent /// except producing `\\` for 0x5c, `\"` for 0x22, and `\x..` for bytes outside the range /// 0x20..0x7e, inclusive. /// /// This process is intended to generate literals compatible with `rustc`; see [the language /// reference on "Byte string /// literals"](https://doc.rust-lang.org/reference/tokens.html#byte-string-literals). pub fn escape_bytes(bs: &[u8]) -> String { let mut buf = String::new(); buf.push_str("b\""); for b in bs { let c = *b as char; match c { '\\' => buf.push_str("\\\\"), '"' => buf.push_str("\\\""), _ if c >= ' ' && c <= '~' => buf.push(c), _ => write!(&mut buf, "\\x{:02x}", b).expect("no IO errors building a string"), } } buf.push('"'); buf } //--------------------------------------------------------------------------- /// Utilities for constructing many useful kinds of [Sequence] and [Grouping]. pub mod constructors { use super::Emittable; use super::Grouping; use super::Item; use super::Sequence; use super::Vertical; use super::VerticalMode; /// Produces a polymorphic, reference-counted [Item] from some generic [Emittable]. pub fn item(i: E) -> Item { std::rc::Rc::new(i) } /// *a*`::`*b*`::`*...*`::`*z* pub fn name(pieces: Vec) -> Sequence { Sequence { items: pieces, vertical_mode: VerticalMode::default(), separator: "::", terminator: "", } } /// *ab...z* (directly adjacent, no separators or terminators) pub fn seq(items: Vec) -> Sequence { Sequence { items: items, vertical_mode: VerticalMode::default(), separator: "", terminator: "", } } /// *a*`, `*b*`, `*...*`, `*z* pub fn commas(items: Vec) -> Sequence { Sequence { items: items, vertical_mode: VerticalMode::default(), separator: ", ", terminator: "", } } /// `(`*a*`, `*b*`, `*...*`, `*z*`)` pub fn parens(items: Vec) -> Grouping { Grouping { sequence: commas(items), open: "(", close: ")", } } /// `[`*a*`, `*b*`, `*...*`, `*z*`]` pub fn brackets(items: Vec) -> Grouping { Grouping { sequence: commas(items), open: "[", close: "]", } } /// `<`*a*`, `*b*`, `*...*`, `*z*`>` pub fn anglebrackets(items: Vec) -> Grouping { Grouping { sequence: commas(items), open: "<", close: ">", } } /// `{`*a*`, `*b*`, `*...*`, `*z*`}` pub fn braces(items: Vec) -> Grouping { Grouping { sequence: commas(items), open: "{", close: "}", } } /// `{`*a*` `*b*` `*...*` `*z*`}` pub fn block(items: Vec) -> Grouping { Grouping { sequence: Sequence { items: items, vertical_mode: VerticalMode::default(), separator: " ", terminator: "", }, open: "{", close: "}", } } /// As [block], but always vertical pub fn codeblock(items: Vec) -> Grouping { vertical(false, block(items)) } /// `{`*a*`; `*b*`; `*...*`; `*z*`}` pub fn semiblock(items: Vec) -> Grouping { Grouping { sequence: Sequence { items: items, vertical_mode: VerticalMode::default(), separator: "; ", terminator: "", }, open: "{", close: "}", } } /// Overrides `v` to be always vertical. /// /// If `spaced` is true, inserts an extra newline between items. pub fn vertical(spaced: bool, mut v: V) -> V { v.set_vertical_mode(if spaced { VerticalMode::ExtraNewline } else { VerticalMode::Normal }); v } /// Adds a layer of indentation to the given [Sequence]. pub fn indented(sequence: Sequence) -> Grouping { Grouping { sequence, open: "", close: "", } } } /// Ergonomic syntax for using the constructors in submodule [constructors]; see the /// documentation for the macros, which appears on the [page for the crate /// itself][crate#macros]. pub mod macros { /// `name!(`*a*`, `*b*`, `*...*`, `*z*`)` ⟶ *a*`::`*b*`::`*...*`::`*z* /// /// See [super::constructors::name]. #[macro_export] macro_rules! name { ($($item:expr),*) => {$crate::syntax::block::constructors::name(vec![$(std::rc::Rc::new($item)),*])} } /// `seq!(`*a*`, `*b*`, `*...*`, `*z*`)` ⟶ *ab...z* /// /// See [super::constructors::seq]. #[macro_export] macro_rules! seq { ($($item:expr),*) => {$crate::syntax::block::constructors::seq(vec![$(std::rc::Rc::new($item)),*])} } /// `commas!(`*a*`, `*b*`, `*...*`, `*z*`)` ⟶ *a*`, `*b*`, `*...*`, `*z* /// /// See [super::constructors::commas]. #[macro_export] macro_rules! commas { ($($item:expr),*) => {$crate::syntax::block::constructors::commas(vec![$(std::rc::Rc::new($item)),*])} } /// `parens!(`*a*`, `*b*`, `*...*`, `*z*`)` ⟶ `(`*a*`, `*b*`, `*...*`, `*z*`)` /// /// See [super::constructors::parens]. #[macro_export] macro_rules! parens { ($($item:expr),*) => {$crate::syntax::block::constructors::parens(vec![$(std::rc::Rc::new($item)),*])} } /// `brackets!(`*a*`, `*b*`, `*...*`, `*z*`)` ⟶ `[`*a*`, `*b*`, `*...*`, `*z*`]` /// /// See [super::constructors::brackets]. #[macro_export] macro_rules! brackets { ($($item:expr),*) => {$crate::syntax::block::constructors::brackets(vec![$(std::rc::Rc::new($item)),*])} } /// `anglebrackets!(`*a*`, `*b*`, `*...*`, `*z*`)` ⟶ `<`*a*`, `*b*`, `*...*`, `*z*`>` /// /// See [super::constructors::anglebrackets]. #[macro_export] macro_rules! anglebrackets { ($($item:expr),*) => {$crate::syntax::block::constructors::anglebrackets(vec![$(std::rc::Rc::new($item)),*])} } /// `braces!(`*a*`, `*b*`, `*...*`, `*z*`)` ⟶ `{`*a*`, `*b*`, `*...*`, `*z*`}` /// /// See [super::constructors::braces]. #[macro_export] macro_rules! braces { ($($item:expr),*) => {$crate::syntax::block::constructors::braces(vec![$(std::rc::Rc::new($item)),*])} } /// `block!(`*a*`, `*b*`, `*...*`, `*z*`)` ⟶ `{`*a*` `*b*` `*...*` `*z*`}` /// /// See [super::constructors::block]. #[macro_export] macro_rules! block { ($($item:expr),*) => {$crate::syntax::block::constructors::block(vec![$(std::rc::Rc::new($item)),*])} } /// As [`block`]`!`, but always vertical. See /// [constructors::codeblock][super::constructors::codeblock]. #[macro_export] macro_rules! codeblock { ($($item:expr),*) => {$crate::syntax::block::constructors::codeblock(vec![$(std::rc::Rc::new($item)),*])} } /// `semiblock!(`*a*`, `*b*`, `*...*`, `*z*`)` ⟶ `{`*a*`; `*b*`; `*...*`; `*z*`}` /// /// See [super::constructors::semiblock]. #[macro_export] macro_rules! semiblock { ($($item:expr),*) => {$crate::syntax::block::constructors::semiblock(vec![$(std::rc::Rc::new($item)),*])} } }