use crate::ffi::{PrinterCallError, PrinterHandle};
use std::cell::Cell;
use std::marker::PhantomData;
fn tolerate(result: Result<(), PrinterCallError>) {
match result {
Ok(()) | Err(PrinterCallError::DeadRegion) => {}
Err(PrinterCallError::Other(message)) => panic!("{message}"),
}
}
use crate::test_case::invalid_argument;
pub(crate) const DEFAULT_MAX_WIDTH: u64 = 79;
#[derive(Debug)]
pub struct Document {
max_width: u64,
printer: Option<PrettyPrinter>,
}
impl Document {
pub fn new() -> Self {
Document {
max_width: DEFAULT_MAX_WIDTH,
printer: None,
}
}
pub fn max_width(mut self, max_width: usize) -> Self {
if self.printer.is_some() {
invalid_argument!("max_width must be set before the document is printed to");
}
if max_width == 0 {
invalid_argument!("max_width must be positive");
}
self.max_width = max_width as u64;
self
}
pub fn printer(&mut self) -> &mut PrettyPrinter {
self.printer
.get_or_insert_with(|| PrettyPrinter::from_handle(PrinterHandle::new(self.max_width)))
}
pub fn finish(mut self) -> String {
match &mut self.printer {
Some(printer) => printer.value(),
None => String::new(),
}
}
}
impl Default for Document {
fn default() -> Self {
Document::new()
}
}
pub struct PrettyPrinter {
handle: Option<PrinterHandle>,
_single_owner: PhantomData<Cell<()>>,
}
impl std::fmt::Debug for PrettyPrinter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PrettyPrinter")
.field("handle", &self.handle)
.finish()
}
}
impl PrettyPrinter {
pub fn noop() -> Self {
PrettyPrinter {
handle: None,
_single_owner: PhantomData,
}
}
pub fn should_print(&self) -> bool {
self.handle.is_some()
}
pub(crate) fn from_handle(handle: PrinterHandle) -> Self {
PrettyPrinter {
handle: Some(handle),
_single_owner: PhantomData,
}
}
pub fn text(&mut self, s: &str) {
let Some(handle) = &self.handle else { return };
let mut first = true;
for segment in s.split('\n') {
if !first {
tolerate(handle.hard_break());
}
first = false;
if !segment.is_empty() {
tolerate(handle.text(segment));
}
}
}
pub fn breakable(&mut self, sep: &str) {
let Some(handle) = &self.handle else { return };
tolerate(handle.breakable(sep));
}
pub fn hard_break(&mut self) {
let Some(handle) = &self.handle else { return };
tolerate(handle.hard_break());
}
pub fn begin_group(&mut self, indent: usize, open: &str) {
let Some(handle) = &self.handle else { return };
tolerate(handle.begin_group(indent as u64, open));
}
pub fn end_group(&mut self, close: &str) {
let Some(handle) = &self.handle else { return };
tolerate(handle.end_group(close));
}
pub fn shift_indent(&mut self, delta: isize) {
let Some(handle) = &self.handle else { return };
tolerate(handle.shift_indent(delta as i64));
}
pub fn comment(&mut self, text: &str) {
let Some(handle) = &self.handle else { return };
tolerate(handle.comment(&format!(" // {text}")));
}
pub(crate) fn value(&mut self) -> String {
self.try_value()
.unwrap_or_else(|message| panic!("{message}"))
}
pub(crate) fn try_value(&mut self) -> Result<String, String> {
let Some(handle) = &self.handle else {
unreachable!("only rendering printers have their value read");
};
let _ = handle.resolve();
match handle.value() {
Ok(rendered) => Ok(rendered),
Err(PrinterCallError::Other(message)) => Err(message),
Err(PrinterCallError::DeadRegion) => {
unreachable!("a document's own region never dies before it renders")
}
}
}
pub fn speculate(&mut self) -> Speculation<'_> {
if let Some(handle) = &self.handle {
tolerate(handle.begin_speculative());
}
Speculation {
printer: self,
resolved: false,
}
}
}
impl Clone for PrettyPrinter {
fn clone(&self) -> Self {
let handle = match &self.handle {
None => None,
Some(handle) => match handle.deferred() {
Ok(child) => Some(child),
Err(PrinterCallError::DeadRegion) => None,
Err(PrinterCallError::Other(message)) => unreachable!("{message}"),
},
};
PrettyPrinter {
handle,
_single_owner: PhantomData,
}
}
}
#[derive(Debug)]
pub struct Speculation<'a> {
printer: &'a mut PrettyPrinter,
resolved: bool,
}
impl Speculation<'_> {
pub fn printer(&mut self) -> &mut PrettyPrinter {
self.printer
}
pub fn commit(mut self) {
self.resolved = true;
if let Some(handle) = &self.printer.handle {
tolerate(handle.commit_speculative());
}
}
pub fn abort(mut self) {
self.resolved = true;
if let Some(handle) = &self.printer.handle {
tolerate(handle.abort_speculative());
}
}
}
impl Drop for Speculation<'_> {
fn drop(&mut self) {
if !self.resolved {
if let Some(handle) = &self.printer.handle {
let _ = handle.abort_speculative();
}
}
}
}
pub fn print_debug_repr(repr: &str, printer: &mut PrettyPrinter) {
match DebugRepr::parse(repr) {
Some(nodes) => emit_debug_nodes(&nodes, printer),
None => printer.text(repr),
}
}
enum DebugNode {
Leaf(String),
Group {
prefix: String,
delimiter: char,
named: bool,
items: Vec<Vec<DebugNode>>,
},
}
struct DebugRepr {
chars: Vec<char>,
pos: usize,
depth: usize,
}
const MAX_DEBUG_DEPTH: usize = 64;
impl DebugRepr {
fn parse(repr: &str) -> Option<Vec<DebugNode>> {
if repr.contains('\n') {
return None;
}
let mut parser = DebugRepr {
chars: repr.chars().collect(),
pos: 0,
depth: 0,
};
let nodes = parser.parse_item()?;
if parser.pos != parser.chars.len() {
return None;
}
Some(nodes)
}
fn peek(&self) -> Option<char> {
self.chars.get(self.pos).copied()
}
fn peek_next(&self) -> Option<char> {
self.chars.get(self.pos + 1).copied()
}
fn bump(&mut self) -> Option<char> {
let c = self.peek()?;
self.pos += 1;
Some(c)
}
fn parse_item(&mut self) -> Option<Vec<DebugNode>> {
let mut nodes = Vec::new();
let mut text = String::new();
loop {
match self.peek() {
None | Some(']' | ')' | '}') => break,
Some(',') if self.peek_next() == Some(' ') => break,
Some(' ') if self.peek_next() == Some('}') => break,
Some('"' | '\'') => {
flush_text(&mut text, &mut nodes);
nodes.push(DebugNode::Leaf(self.lex_quoted()?));
}
Some(delimiter @ ('[' | '(' | '{')) => {
let prefix = take_group_prefix(&mut text, delimiter);
flush_text(&mut text, &mut nodes);
nodes.push(self.parse_group(prefix)?);
}
Some(c) => {
text.push(c);
self.bump();
}
}
}
flush_text(&mut text, &mut nodes);
Some(nodes)
}
fn parse_group(&mut self, prefix: String) -> Option<DebugNode> {
if self.depth == MAX_DEBUG_DEPTH {
return None;
}
self.depth += 1;
let delimiter = self.bump()?;
let close = match delimiter {
'[' => ']',
'(' => ')',
_ => '}',
};
let named = delimiter == '{' && !prefix.is_empty() && self.peek() == Some(' ');
if named {
self.bump();
}
let mut items = Vec::new();
if !named && self.peek() == Some(close) {
self.bump();
} else {
loop {
items.push(self.parse_item()?);
match self.peek() {
Some(',') if self.peek_next() == Some(' ') => {
self.bump();
self.bump();
}
Some(' ') if named && self.peek_next() == Some(close) => {
self.bump();
self.bump();
break;
}
Some(c) if !named && c == close => {
self.bump();
break;
}
_ => return None,
}
}
}
self.depth -= 1;
Some(DebugNode::Group {
prefix,
delimiter,
named,
items,
})
}
fn lex_quoted(&mut self) -> Option<String> {
let quote = self.bump()?;
let mut lit = String::new();
lit.push(quote);
loop {
let c = self.bump()?;
lit.push(c);
if c == '\\' {
lit.push(self.bump()?);
} else if c == quote {
return Some(lit);
}
}
}
}
fn flush_text(text: &mut String, nodes: &mut Vec<DebugNode>) {
if !text.is_empty() {
nodes.push(DebugNode::Leaf(std::mem::take(text)));
}
}
fn take_group_prefix(text: &mut String, delimiter: char) -> String {
if delimiter == '{' {
let Some(without_space) = text.strip_suffix(' ') else {
return String::new();
};
let start = without_space.rfind(' ').map(|index| index + 1).unwrap_or(0);
let prefix = without_space[start..].to_string();
if prefix.is_empty() {
return String::new();
}
text.truncate(text.len() - prefix.len() - 1);
prefix
} else {
let start = text.rfind(' ').map(|index| index + 1).unwrap_or(0);
let prefix = text[start..].to_string();
text.truncate(start);
prefix
}
}
fn emit_debug_nodes(nodes: &[DebugNode], printer: &mut PrettyPrinter) {
for node in nodes {
match node {
DebugNode::Leaf(text) => printer.text(text),
DebugNode::Group {
prefix,
delimiter,
named,
items,
} => {
let (open, close, indent) = match (delimiter, named) {
('{', true) => (format!("{prefix} {{"), " }", 4),
('{', false) if prefix.is_empty() => ("{".to_string(), "}", 1),
('{', false) => (format!("{prefix} {{"), "}", 1),
('[', _) => (format!("{prefix}["), "]", 1),
_ => (format!("{prefix}("), ")", 1),
};
printer.begin_group(indent, &open);
if *named {
printer.breakable(" ");
}
for (index, item) in items.iter().enumerate() {
if index > 0 {
printer.text(",");
printer.breakable(" ");
}
emit_debug_nodes(item, printer);
}
printer.end_group(close);
}
}
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` has no printed representation",
label = "`{Self}` does not implement `PrettyPrintable`",
note = "for your own type, add `#[derive(PrettyPrintable)]` (or `hegel::pretty_print_as_debug!` for a `Debug` type)",
note = "for a foreign type, make the generator printable instead: `.print_as_debug()` prints any `Debug` value, `.print_with(..)` prints a custom representation",
note = "or draw without reporting the value via `tc.draw_silent(..)`"
)]
pub trait PrettyPrintable {
fn pretty_print(&self, printer: &mut PrettyPrinter);
}
#[macro_export]
macro_rules! pretty_print_as_debug {
($($t:ty),+ $(,)?) => {$(
impl $crate::PrettyPrintable for $t {
fn pretty_print(&self, printer: &mut $crate::PrettyPrinter) {
$crate::pretty::print_debug_repr(&::std::format!("{:?}", self), printer);
}
}
)+};
}
macro_rules! pretty_via_display {
($($t:ty),+) => {$(
impl PrettyPrintable for $t {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
printer.text(&format!("{}", self));
}
}
)+};
}
pretty_via_display!(
i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, bool
);
macro_rules! pretty_via_debug {
($($t:ty),+) => {$(
impl PrettyPrintable for $t {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
printer.text(&format!("{:?}", self));
}
}
)+};
}
pretty_via_debug!(char, str);
impl PrettyPrintable for String {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
printer.text(&format!("{self:?}.to_string()"));
}
}
impl PrettyPrintable for std::time::Duration {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
printer.text(&format!(
"Duration::new({}, {})",
self.as_secs(),
self.subsec_nanos()
));
}
}
impl PrettyPrintable for std::net::Ipv4Addr {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
let [a, b, c, d] = self.octets();
printer.text(&format!("Ipv4Addr::new({a}, {b}, {c}, {d})"));
}
}
impl PrettyPrintable for std::net::Ipv6Addr {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
let segments = self
.segments()
.map(|segment| format!("{segment:#x}"))
.join(", ");
printer.text(&format!("Ipv6Addr::new({segments})"));
}
}
impl PrettyPrintable for std::net::IpAddr {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
match self {
std::net::IpAddr::V4(addr) => {
printer.text("IpAddr::V4(");
addr.pretty_print(printer);
printer.text(")");
}
std::net::IpAddr::V6(addr) => {
printer.text("IpAddr::V6(");
addr.pretty_print(printer);
printer.text(")");
}
}
}
}
macro_rules! pretty_float {
($t:ty, $name:literal) => {
impl PrettyPrintable for $t {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
if self.is_nan() {
if self.to_bits() == <$t>::NAN.to_bits() {
printer.text(concat!($name, "::NAN"));
} else {
printer.text(&format!(
concat!($name, "::from_bits(0x{:x})"),
self.to_bits()
));
}
} else if *self == <$t>::INFINITY {
printer.text(concat!($name, "::INFINITY"));
} else if *self == <$t>::NEG_INFINITY {
printer.text(concat!($name, "::NEG_INFINITY"));
} else {
printer.text(&format!("{:?}", self));
}
}
}
};
}
pretty_float!(f32, "f32");
pretty_float!(f64, "f64");
macro_rules! pretty_delegating {
($($t:ty),+) => {$(
impl<T: PrettyPrintable + ?Sized> PrettyPrintable for $t {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
(**self).pretty_print(printer);
}
}
)+};
}
pretty_delegating!(&T, &mut T);
macro_rules! pretty_smart_pointer {
($($t:ty, $open:literal);+) => {$(
impl<T: PrettyPrintable> PrettyPrintable for $t {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
printer.begin_group($open.len(), $open);
(**self).pretty_print(printer);
printer.end_group(")");
}
}
)+};
}
pretty_smart_pointer!(
Box<T>, "Box::new(";
std::rc::Rc<T>, "Rc::new(";
std::sync::Arc<T>, "Arc::new("
);
impl PrettyPrintable for Box<str> {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
(**self).pretty_print(printer);
}
}
fn pretty_seq<'a, T: PrettyPrintable + ?Sized + 'a>(
printer: &mut PrettyPrinter,
open: &str,
close: &str,
items: impl Iterator<Item = &'a T>,
) {
printer.begin_group(open.chars().count(), open);
for (index, item) in items.enumerate() {
if index > 0 {
printer.text(",");
printer.breakable(" ");
}
item.pretty_print(printer);
}
printer.end_group(close);
}
impl<T: PrettyPrintable> PrettyPrintable for [T] {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
pretty_seq(printer, "[", "]", self.iter());
}
}
impl<T: PrettyPrintable> PrettyPrintable for Vec<T> {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
pretty_seq(printer, "vec![", "]", self.iter());
}
}
impl<T: PrettyPrintable, const N: usize> PrettyPrintable for [T; N] {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
self.as_slice().pretty_print(printer);
}
}
impl<T: PrettyPrintable> PrettyPrintable for std::collections::HashSet<T> {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
pretty_seq(printer, "HashSet::from([", "])", self.iter());
}
}
impl<T: PrettyPrintable> PrettyPrintable for std::collections::BTreeSet<T> {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
pretty_seq(printer, "BTreeSet::from([", "])", self.iter());
}
}
fn pretty_map<'a, K: PrettyPrintable + 'a, V: PrettyPrintable + 'a>(
printer: &mut PrettyPrinter,
open: &str,
entries: impl Iterator<Item = (&'a K, &'a V)>,
) {
printer.begin_group(open.chars().count(), open);
for (index, (key, value)) in entries.enumerate() {
if index > 0 {
printer.text(",");
printer.breakable(" ");
}
printer.text("(");
key.pretty_print(printer);
printer.text(", ");
value.pretty_print(printer);
printer.text(")");
}
printer.end_group("])");
}
impl<K: PrettyPrintable, V: PrettyPrintable> PrettyPrintable for std::collections::HashMap<K, V> {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
pretty_map(printer, "HashMap::from([", self.iter());
}
}
impl<K: PrettyPrintable, V: PrettyPrintable> PrettyPrintable for std::collections::BTreeMap<K, V> {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
pretty_map(printer, "BTreeMap::from([", self.iter());
}
}
impl<T: PrettyPrintable> PrettyPrintable for Option<T> {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
match self {
None => printer.text("None"),
Some(value) => {
printer.begin_group(5, "Some(");
value.pretty_print(printer);
printer.end_group(")");
}
}
}
}
impl<T: PrettyPrintable, E: PrettyPrintable> PrettyPrintable for Result<T, E> {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
match self {
Ok(value) => {
printer.begin_group(3, "Ok(");
value.pretty_print(printer);
printer.end_group(")");
}
Err(error) => {
printer.begin_group(4, "Err(");
error.pretty_print(printer);
printer.end_group(")");
}
}
}
}
impl PrettyPrintable for () {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
printer.text("()");
}
}
impl<A: PrettyPrintable> PrettyPrintable for (A,) {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
printer.begin_group(1, "(");
self.0.pretty_print(printer);
printer.end_group(",)");
}
}
macro_rules! pretty_tuple {
($(($($name:ident),+)),+ $(,)?) => {$(
#[allow(non_snake_case)]
impl<$($name: PrettyPrintable),+> PrettyPrintable for ($($name,)+) {
fn pretty_print(&self, printer: &mut PrettyPrinter) {
let ($($name,)+) = self;
printer.begin_group(1, "(");
let mut index = 0usize;
$(
if index > 0 {
printer.text(",");
printer.breakable(" ");
}
index += 1;
$name.pretty_print(printer);
)+
let _ = index;
printer.end_group(")");
}
}
)+};
}
pretty_tuple!(
(A, B),
(A, B, C),
(A, B, C, D),
(A, B, C, D, E),
(A, B, C, D, E, F),
(A, B, C, D, E, F, G),
(A, B, C, D, E, F, G, H),
(A, B, C, D, E, F, G, H, I),
(A, B, C, D, E, F, G, H, I, J),
(A, B, C, D, E, F, G, H, I, J, K),
(A, B, C, D, E, F, G, H, I, J, K, L),
);