539 lines
16 KiB
Rust
539 lines
16 KiB
Rust
//! 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<dyn Emittable>;
|
|
|
|
/// A possibly-vertical sequence of items with item-separating and -terminating text.
|
|
#[derive(Clone)]
|
|
pub struct Sequence {
|
|
pub items: Vec<Item>,
|
|
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<E: Emittable>(&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: Emittable>(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, F: FnOnce(&mut Self) -> 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<E>
|
|
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<E: 'static + Emittable>(i: E) -> Item {
|
|
std::rc::Rc::new(i)
|
|
}
|
|
|
|
/// *a*`::`*b*`::`*...*`::`*z*
|
|
pub fn name(pieces: Vec<Item>) -> Sequence {
|
|
Sequence {
|
|
items: pieces,
|
|
vertical_mode: VerticalMode::default(),
|
|
separator: "::",
|
|
terminator: "",
|
|
}
|
|
}
|
|
|
|
/// *ab...z* (directly adjacent, no separators or terminators)
|
|
pub fn seq(items: Vec<Item>) -> Sequence {
|
|
Sequence {
|
|
items: items,
|
|
vertical_mode: VerticalMode::default(),
|
|
separator: "",
|
|
terminator: "",
|
|
}
|
|
}
|
|
|
|
/// *a*`, `*b*`, `*...*`, `*z*
|
|
pub fn commas(items: Vec<Item>) -> Sequence {
|
|
Sequence {
|
|
items: items,
|
|
vertical_mode: VerticalMode::default(),
|
|
separator: ", ",
|
|
terminator: "",
|
|
}
|
|
}
|
|
|
|
/// `(`*a*`, `*b*`, `*...*`, `*z*`)`
|
|
pub fn parens(items: Vec<Item>) -> Grouping {
|
|
Grouping {
|
|
sequence: commas(items),
|
|
open: "(",
|
|
close: ")",
|
|
}
|
|
}
|
|
|
|
/// `[`*a*`, `*b*`, `*...*`, `*z*`]`
|
|
pub fn brackets(items: Vec<Item>) -> Grouping {
|
|
Grouping {
|
|
sequence: commas(items),
|
|
open: "[",
|
|
close: "]",
|
|
}
|
|
}
|
|
|
|
/// `<`*a*`, `*b*`, `*...*`, `*z*`>`
|
|
pub fn anglebrackets(items: Vec<Item>) -> Grouping {
|
|
Grouping {
|
|
sequence: commas(items),
|
|
open: "<",
|
|
close: ">",
|
|
}
|
|
}
|
|
|
|
/// `{`*a*`, `*b*`, `*...*`, `*z*`}`
|
|
pub fn braces(items: Vec<Item>) -> Grouping {
|
|
Grouping {
|
|
sequence: commas(items),
|
|
open: "{",
|
|
close: "}",
|
|
}
|
|
}
|
|
|
|
/// `{`*a*` `*b*` `*...*` `*z*`}`
|
|
pub fn block(items: Vec<Item>) -> Grouping {
|
|
Grouping {
|
|
sequence: Sequence {
|
|
items: items,
|
|
vertical_mode: VerticalMode::default(),
|
|
separator: " ",
|
|
terminator: "",
|
|
},
|
|
open: "{",
|
|
close: "}",
|
|
}
|
|
}
|
|
|
|
/// As [block], but always vertical
|
|
pub fn codeblock(items: Vec<Item>) -> Grouping {
|
|
vertical(false, block(items))
|
|
}
|
|
|
|
/// `{`*a*`; `*b*`; `*...*`; `*z*`}`
|
|
pub fn semiblock(items: Vec<Item>) -> 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<V: 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)),*])}
|
|
}
|
|
}
|