Pretty printing 🦋

- Syntax tree and value pretty printing
- Better value evaluation (top-level strings and content are evaluated plainly, everything else is pretty printed)
This commit is contained in:
Laurenz 2021-01-06 01:32:59 +01:00
parent 2e77b1c836
commit 7b4d4d6002
9 changed files with 533 additions and 46 deletions

View File

@ -126,6 +126,7 @@ mod tests {
#[test]
fn test_parse_color_strings() {
#[track_caller]
fn test(hex: &str, r: u8, g: u8, b: u8, a: u8) {
assert_eq!(RgbaColor::from_str(hex), Ok(RgbaColor::new(r, g, b, a)));
}
@ -139,6 +140,7 @@ mod tests {
#[test]
fn test_parse_invalid_colors() {
#[track_caller]
fn test(hex: &str) {
assert_eq!(RgbaColor::from_str(hex), Err(ParseRgbaError));
}

View File

@ -169,7 +169,7 @@ impl Eval for Spanned<&Lit> {
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
match *self.v {
Lit::Ident(ref v) => match ctx.state.scope.get(v.as_str()) {
Lit::Ident(ref v) => match ctx.state.scope.get(&v) {
Some(value) => value.clone(),
None => {
ctx.diag(error!(self.span, "unknown variable"));
@ -286,6 +286,7 @@ fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
// Complex data types to themselves.
(Str(a), Str(b)) => Str(a + &b),
(Array(a), Array(b)) => Array(concat(a, b)),
(Dict(a), Dict(b)) => Dict(concat(a, b)),
(Content(a), Content(b)) => Content(concat(a, b)),

View File

@ -1,5 +1,5 @@
use std::any::Any;
use std::collections::HashMap;
use std::collections::BTreeMap;
use std::fmt::{self, Debug, Display, Formatter};
use std::ops::Deref;
use std::rc::Rc;
@ -7,10 +7,11 @@ use std::rc::Rc;
use super::{Args, Eval, EvalContext};
use crate::color::Color;
use crate::geom::{Length, Linear, Relative};
use crate::syntax::{Spanned, Tree, WithSpan};
use crate::pretty::{pretty, Pretty, Printer};
use crate::syntax::{pretty_content_expr, Spanned, Tree, WithSpan};
/// A computational value.
#[derive(Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
/// The value that indicates the absence of a meaningful value.
None,
@ -82,21 +83,9 @@ impl Eval for &Value {
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
ctx.push(ctx.make_text_node(match self {
Value::None => return,
Value::Bool(v) => v.to_string(),
Value::Int(v) => v.to_string(),
Value::Float(v) => v.to_string(),
Value::Length(v) => v.to_string(),
Value::Relative(v) => v.to_string(),
Value::Linear(v) => v.to_string(),
Value::Color(v) => v.to_string(),
Value::Str(v) => v.clone(),
// TODO: Find good representation for composite types.
Value::Array(_v) => "(array)".into(),
Value::Dict(_v) => "(dictionary)".into(),
Value::Str(s) => s.clone(),
Value::Content(tree) => return tree.eval(ctx),
Value::Func(v) => v.to_string(),
Value::Any(v) => v.to_string(),
Value::Error => "(error)".into(),
other => pretty(other),
}));
}
}
@ -107,24 +96,24 @@ impl Default for Value {
}
}
impl Debug for Value {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
impl Pretty for Value {
fn pretty(&self, p: &mut Printer) {
match self {
Self::None => f.pad("None"),
Self::Bool(v) => Debug::fmt(v, f),
Self::Int(v) => Debug::fmt(v, f),
Self::Float(v) => Debug::fmt(v, f),
Self::Length(v) => Debug::fmt(v, f),
Self::Relative(v) => Debug::fmt(v, f),
Self::Linear(v) => Debug::fmt(v, f),
Self::Color(v) => Debug::fmt(v, f),
Self::Str(v) => Debug::fmt(v, f),
Self::Array(v) => Debug::fmt(v, f),
Self::Dict(v) => Debug::fmt(v, f),
Self::Content(v) => Debug::fmt(v, f),
Self::Func(v) => Debug::fmt(v, f),
Self::Any(v) => Debug::fmt(v, f),
Self::Error => f.pad("Error"),
Value::None => p.push_str("none"),
Value::Bool(v) => write!(p, "{}", v).unwrap(),
Value::Int(v) => write!(p, "{}", v).unwrap(),
Value::Float(v) => write!(p, "{}", v).unwrap(),
Value::Length(v) => write!(p, "{}", v).unwrap(),
Value::Relative(v) => write!(p, "{}", v).unwrap(),
Value::Linear(v) => write!(p, "{}", v).unwrap(),
Value::Color(v) => write!(p, "{}", v).unwrap(),
Value::Str(v) => write!(p, "{:?}", v).unwrap(),
Value::Array(array) => array.pretty(p),
Value::Dict(dict) => dict.pretty(p),
Value::Content(content) => pretty_content_expr(content, p),
Value::Func(v) => v.pretty(p),
Value::Any(v) => v.pretty(p),
Value::Error => p.push_str("(error)"),
}
}
}
@ -132,8 +121,35 @@ impl Debug for Value {
/// An array value: `(1, "hi", 12cm)`.
pub type ValueArray = Vec<Value>;
impl Pretty for ValueArray {
fn pretty(&self, p: &mut Printer) {
p.push_str("(");
p.join(self, ", ", |item, p| item.pretty(p));
if self.len() == 1 {
p.push_str(",");
}
p.push_str(")");
}
}
/// A dictionary value: `(color: #f79143, pattern: dashed)`.
pub type ValueDict = HashMap<String, Value>;
pub type ValueDict = BTreeMap<String, Value>;
impl Pretty for ValueDict {
fn pretty(&self, p: &mut Printer) {
p.push_str("(");
if self.is_empty() {
p.push_str(":");
} else {
p.join(self, ", ", |(key, value), p| {
p.push_str(key);
p.push_str(": ");
value.pretty(p);
});
}
p.push_str(")");
}
}
/// A content value: `{*Hi* there}`.
pub type ValueContent = Tree;
@ -169,14 +185,15 @@ impl Deref for ValueFunc {
}
}
impl Display for ValueFunc {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "<function {}>", self.name)
impl Pretty for ValueFunc {
fn pretty(&self, p: &mut Printer) {
write!(p, "(function {})", self.name).unwrap();
}
}
impl Debug for ValueFunc {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
f.debug_struct("ValueFunc").field("name", &self.name).finish()
}
}
@ -229,15 +246,15 @@ impl PartialEq for ValueAny {
}
}
impl Display for ValueAny {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(&self.0, f)
impl Pretty for ValueAny {
fn pretty(&self, p: &mut Printer) {
write!(p, "{}", self.0).unwrap();
}
}
impl Debug for ValueAny {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(&self.0, f)
f.debug_tuple("ValueAny").field(&self.0).finish()
}
}
@ -465,3 +482,47 @@ macro_rules! impl_type {
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::color::RgbaColor;
use crate::pretty::pretty;
use crate::syntax::Node;
#[track_caller]
fn test_pretty(value: impl Into<Value>, exp: &str) {
assert_eq!(pretty(&value.into()), exp);
}
#[test]
fn test_pretty_print_values() {
test_pretty(Value::None, "none");
test_pretty(false, "false");
test_pretty(12.4, "12.4");
test_pretty(Length::ZERO, "0pt");
test_pretty(Relative::ONE, "100%");
test_pretty(Relative::new(0.3) + Length::cm(2.0), "30% + 2cm");
test_pretty(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101");
test_pretty("hello", r#""hello""#);
test_pretty(vec![Spanned::zero(Node::Strong)], "{*}");
test_pretty(ValueFunc::new("nil", |_, _| Value::None), "(function nil)");
test_pretty(ValueAny::new(1), "1");
test_pretty(Value::Error, "(error)");
}
#[test]
fn test_pretty_print_collections() {
// Array.
test_pretty(Value::Array(vec![]), "()");
test_pretty(vec![Value::None], "(none,)");
test_pretty(vec![Value::Int(1), Value::Int(2)], "(1, 2)");
// Dictionary.
let mut dict = BTreeMap::new();
dict.insert("one".into(), Value::Int(1));
dict.insert("two".into(), Value::Int(2));
test_pretty(BTreeMap::new(), "(:)");
test_pretty(dict, "(one: 1, two: 2)");
}
}

View File

@ -38,6 +38,7 @@ pub mod library;
pub mod paper;
pub mod parse;
pub mod prelude;
pub mod pretty;
pub mod shaping;
pub mod syntax;

View File

@ -124,6 +124,7 @@ mod tests {
#[test]
fn test_resolve_strings() {
#[track_caller]
fn test(string: &str, expected: &str) {
assert_eq!(resolve_string(string), expected.to_string());
}
@ -144,6 +145,7 @@ mod tests {
#[test]
fn test_split_at_lang_tag() {
#[track_caller]
fn test(text: &str, lang: &str, inner: &str) {
assert_eq!(split_at_lang_tag(text), (lang, inner));
}
@ -158,6 +160,7 @@ mod tests {
#[test]
fn test_resolve_raw() {
#[track_caller]
fn test(
raw: &str,
backticks: usize,
@ -190,6 +193,7 @@ mod tests {
#[test]
fn test_trim_raw() {
#[track_caller]
fn test(text: &str, expected: Vec<&str>) {
assert_eq!(trim_and_split_raw(text).0, expected);
}
@ -207,6 +211,7 @@ mod tests {
#[test]
fn test_split_lines() {
#[track_caller]
fn test(text: &str, expected: Vec<&str>) {
assert_eq!(split_lines(text), expected);
}

68
src/pretty.rs Normal file
View File

@ -0,0 +1,68 @@
//! Pretty printing.
use std::fmt::{Arguments, Result, Write};
/// Pretty print an item and return the resulting string.
pub fn pretty<T>(item: &T) -> String
where
T: Pretty,
{
let mut p = Printer::new();
item.pretty(&mut p);
p.finish()
}
/// Pretty printing.
pub trait Pretty {
/// Pretty print this item into the given printer.
fn pretty(&self, p: &mut Printer);
}
/// A buffer into which items are printed.
pub struct Printer {
buf: String,
}
impl Printer {
/// Create a new pretty printer.
pub fn new() -> Self {
Self { buf: String::new() }
}
/// Push a string into the buffer.
pub fn push_str(&mut self, string: &str) {
self.buf.push_str(string);
}
/// Write formatted items into the buffer.
pub fn write_fmt(&mut self, fmt: Arguments<'_>) -> Result {
Write::write_fmt(self, fmt)
}
/// Write a comma-separated list of items.
pub fn join<T, I, F>(&mut self, items: I, joiner: &str, mut write_item: F)
where
I: IntoIterator<Item = T>,
F: FnMut(T, &mut Self),
{
let mut iter = items.into_iter();
if let Some(first) = iter.next() {
write_item(first, self);
}
for item in iter {
self.push_str(joiner);
write_item(item, self);
}
}
/// Finish pretty printing and return the underlying buffer.
pub fn finish(self) -> String {
self.buf
}
}
impl Write for Printer {
fn write_str(&mut self, s: &str) -> Result {
Ok(self.push_str(s))
}
}

View File

@ -21,6 +21,35 @@ pub enum Expr {
Content(ExprContent),
}
impl Pretty for Expr {
fn pretty(&self, p: &mut Printer) {
match self {
Self::Lit(lit) => lit.pretty(p),
Self::Call(call) => call.pretty(p),
Self::Unary(unary) => unary.pretty(p),
Self::Binary(binary) => binary.pretty(p),
Self::Array(array) => array.pretty(p),
Self::Dict(dict) => dict.pretty(p),
Self::Content(content) => pretty_content_expr(content, p),
}
}
}
/// Pretty print content in an expression context.
pub fn pretty_content_expr(tree: &Tree, p: &mut Printer) {
if let [Spanned { v: Node::Expr(Expr::Call(call)), .. }] = tree.as_slice() {
// Remove unncessary braces from content expression containing just a
// single function call.
//
// Example: Transforms "{(call: {[f]})}" => "{(call: [f])}"
pretty_bracket_call(call, p, false);
} else {
p.push_str("{");
tree.pretty(p);
p.push_str("}");
}
}
/// An invocation of a function: `[foo ...]`, `foo(...)`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprCall {
@ -30,12 +59,68 @@ pub struct ExprCall {
pub args: Spanned<ExprArgs>,
}
impl Pretty for ExprCall {
fn pretty(&self, p: &mut Printer) {
p.push_str(&self.name.v);
p.push_str("(");
self.args.v.pretty(p);
p.push_str(")");
}
}
/// Pretty print a bracketed function call, with body or chaining when possible.
pub fn pretty_bracket_call(call: &ExprCall, p: &mut Printer, chained: bool) {
if chained {
p.push_str(" | ");
} else {
p.push_str("[");
}
// Function name.
p.push_str(&call.name.v);
// Find out whether this can be written as body or chain.
//
// Example: Transforms "[v {Hi}]" => "[v][Hi]".
if let [head @ .., Argument::Pos(Spanned { v: Expr::Content(content), .. })] =
call.args.v.as_slice()
{
// Previous arguments.
if !head.is_empty() {
p.push_str(" ");
p.join(head, ", ", |item, p| item.pretty(p));
}
// Find out whether this can written as a chain.
//
// Example: Transforms "[v][[f]]" => "[v | f]".
if let [Spanned { v: Node::Expr(Expr::Call(call)), .. }] = content.as_slice() {
return pretty_bracket_call(call, p, true);
} else {
p.push_str("][");
content.pretty(p);
}
} else if !call.args.v.is_empty() {
p.push_str(" ");
call.args.v.pretty(p);
}
// Either end of header or end of body.
p.push_str("]");
}
/// The arguments to a function: `12, draw: false`.
///
/// In case of a bracketed invocation with a body, the body is _not_
/// included in the span for the sake of clearer error messages.
pub type ExprArgs = Vec<Argument>;
impl Pretty for Vec<Argument> {
fn pretty(&self, p: &mut Printer) {
p.join(self, ", ", |item, p| item.pretty(p));
}
}
/// An argument to a function call: `12` or `draw: false`.
#[derive(Debug, Clone, PartialEq)]
pub enum Argument {
@ -45,6 +130,15 @@ pub enum Argument {
Named(Named),
}
impl Pretty for Argument {
fn pretty(&self, p: &mut Printer) {
match self {
Self::Pos(expr) => expr.v.pretty(p),
Self::Named(named) => named.pretty(p),
}
}
}
/// A pair of a name and an expression: `pattern: dashed`.
#[derive(Debug, Clone, PartialEq)]
pub struct Named {
@ -54,6 +148,14 @@ pub struct Named {
pub expr: Spanned<Expr>,
}
impl Pretty for Named {
fn pretty(&self, p: &mut Printer) {
p.push_str(&self.name.v);
p.push_str(": ");
self.expr.v.pretty(p);
}
}
/// A unary operation: `-x`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprUnary {
@ -63,6 +165,13 @@ pub struct ExprUnary {
pub expr: Box<Spanned<Expr>>,
}
impl Pretty for ExprUnary {
fn pretty(&self, p: &mut Printer) {
self.op.v.pretty(p);
self.expr.v.pretty(p);
}
}
/// A unary operator.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum UnOp {
@ -70,6 +179,14 @@ pub enum UnOp {
Neg,
}
impl Pretty for UnOp {
fn pretty(&self, p: &mut Printer) {
p.push_str(match self {
Self::Neg => "-",
});
}
}
/// A binary operation: `a + b`, `a / b`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprBinary {
@ -81,6 +198,16 @@ pub struct ExprBinary {
pub rhs: Box<Spanned<Expr>>,
}
impl Pretty for ExprBinary {
fn pretty(&self, p: &mut Printer) {
self.lhs.v.pretty(p);
p.push_str(" ");
self.op.v.pretty(p);
p.push_str(" ");
self.rhs.v.pretty(p);
}
}
/// A binary operator.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum BinOp {
@ -94,12 +221,46 @@ pub enum BinOp {
Div,
}
impl Pretty for BinOp {
fn pretty(&self, p: &mut Printer) {
p.push_str(match self {
Self::Add => "+",
Self::Sub => "-",
Self::Mul => "*",
Self::Div => "/",
});
}
}
/// An array expression: `(1, "hi", 12cm)`.
pub type ExprArray = SpanVec<Expr>;
impl Pretty for ExprArray {
fn pretty(&self, p: &mut Printer) {
p.push_str("(");
p.join(self, ", ", |item, p| item.v.pretty(p));
if self.len() == 1 {
p.push_str(",");
}
p.push_str(")");
}
}
/// A dictionary expression: `(color: #f79143, pattern: dashed)`.
pub type ExprDict = Vec<Named>;
impl Pretty for ExprDict {
fn pretty(&self, p: &mut Printer) {
p.push_str("(");
if self.is_empty() {
p.push_str(":");
} else {
p.join(self, ", ", |named, p| named.pretty(p));
}
p.push_str(")");
}
}
/// A content expression: `{*Hello* there!}`.
pub type ExprContent = Tree;
@ -128,3 +289,65 @@ pub enum Lit {
/// A string literal: `"hello!"`.
Str(String),
}
impl Pretty for Lit {
fn pretty(&self, p: &mut Printer) {
match self {
Self::Ident(v) => p.push_str(&v),
Self::None => p.push_str("none"),
Self::Bool(v) => write!(p, "{}", v).unwrap(),
Self::Int(v) => write!(p, "{}", v).unwrap(),
Self::Float(v) => write!(p, "{}", v).unwrap(),
Self::Length(v, u) => write!(p, "{}{}", v, u).unwrap(),
Self::Percent(v) => write!(p, "{}%", v).unwrap(),
Self::Color(v) => write!(p, "{}", v).unwrap(),
Self::Str(s) => write!(p, "{:?}", &s).unwrap(),
}
}
}
#[cfg(test)]
mod tests {
use super::super::tests::test_pretty;
#[test]
fn test_pretty_print_chaining() {
// All equivalent.
test_pretty("[v [f]]", "[v | f]");
test_pretty("[v {[f]}]", "[v | f]");
test_pretty("[v][[f]]", "[v | f]");
test_pretty("[v | f]", "[v | f]");
}
#[test]
fn test_pretty_print_expressions() {
// Unary and binary operations.
test_pretty("{1 +}", "{1}");
test_pretty("{1 + func(-2)}", "{1 + func(-2)}");
// Array.
test_pretty("(-5,)", "(-5,)");
test_pretty("(1, 2, 3)", "(1, 2, 3)");
// Dictionary.
test_pretty("{(:)}", "{(:)}");
test_pretty("{(percent: 5%)}", "{(percent: 5%)}");
// Content expression without unncessary braces.
test_pretty("[v [f], 1]", "[v [f], 1]");
test_pretty("(func: {[f]})", "(func: [f])");
}
#[test]
fn test_pretty_print_literals() {
test_pretty("{none}", "{none}");
test_pretty("{true}", "{true}");
test_pretty("{25}", "{25}");
test_pretty("{2.50}", "{2.5}");
test_pretty("{1e2}", "{100}");
test_pretty("{12pt}", "{12pt}");
test_pretty("{50%}", "{50%}");
test_pretty("{#fff}", "{#ffffff}");
test_pretty(r#"{"hi\n"}"#, r#"{"hi\n"}"#);
}
}

View File

@ -12,5 +12,33 @@ pub use node::*;
pub use span::*;
pub use token::*;
/// A collection of nodes which form a tree together with their children.
use crate::pretty::{Pretty, Printer};
/// The abstract syntax tree.
pub type Tree = SpanVec<Node>;
impl Pretty for Tree {
fn pretty(&self, p: &mut Printer) {
for node in self {
node.v.pretty(p);
}
}
}
#[cfg(test)]
mod tests {
use crate::parse::parse;
use crate::pretty::pretty;
#[track_caller]
pub fn test_pretty(src: &str, exp: &str) {
let tree = parse(src).output;
let found = pretty(&tree);
if exp != found {
println!("tree: {:#?}", tree);
println!("expected: {}", exp);
println!("found: {}", found);
panic!("test failed");
}
}
}

View File

@ -27,15 +27,62 @@ pub enum Node {
Expr(Expr),
}
impl Pretty for Node {
fn pretty(&self, p: &mut Printer) {
match self {
Self::Text(text) => p.push_str(&text),
Self::Space => p.push_str(" "),
Self::Linebreak => p.push_str(r"\"),
Self::Parbreak => p.push_str("\n\n"),
Self::Strong => p.push_str("*"),
Self::Emph => p.push_str("_"),
Self::Heading(heading) => heading.pretty(p),
Self::Raw(raw) => raw.pretty(p),
Self::Expr(expr) => pretty_expr_node(expr, p),
}
}
}
/// Pretty print an expression in a node context.
pub fn pretty_expr_node(expr: &Expr, p: &mut Printer) {
match expr {
// Prefer bracket calls over expression blocks with just a single paren
// call.
//
// Example: Transforms "{v()}" => "[v]".
Expr::Call(call) => pretty_bracket_call(call, p, false),
// Remove unncessary nesting of content and expression blocks.
//
// Example: Transforms "{{Hi}}" => "Hi".
Expr::Content(content) => content.pretty(p),
_ => {
p.push_str("{");
expr.pretty(p);
p.push_str("}");
}
}
}
/// A section heading: `# Introduction`.
#[derive(Debug, Clone, PartialEq)]
pub struct NodeHeading {
/// The section depth (numer of hashtags minus 1).
/// The section depth (numer of hashtags minus 1, capped at 5).
pub level: Spanned<u8>,
/// The contents of the heading.
pub contents: Tree,
}
impl Pretty for NodeHeading {
fn pretty(&self, p: &mut Printer) {
for _ in 0 ..= self.level.v {
p.push_str("#");
}
self.contents.pretty(p);
}
}
/// A raw block with optional syntax highlighting: `` `raw` ``.
///
/// Raw blocks start with an arbitrary number of backticks and end with the same
@ -114,3 +161,54 @@ pub struct NodeRaw {
/// are inline-level when they contain no newlines.
pub inline: bool,
}
impl Pretty for NodeRaw {
fn pretty(&self, p: &mut Printer) {
p.push_str("`");
if let Some(lang) = &self.lang {
p.push_str(&lang);
p.push_str(" ");
}
// TODO: Technically, we should handle backticks in the lines
// by wrapping with more backticks and possibly adding space
// before the first or after the last line.
p.join(&self.lines, "\n", |line, p| p.push_str(line));
p.push_str("`");
}
}
#[cfg(test)]
mod tests {
use super::super::tests::test_pretty;
#[test]
fn test_pretty_print_removes_nesting() {
// Even levels of nesting do not matter.
test_pretty("{{Hi}}", "Hi");
test_pretty("{{{{Hi}}}}", "Hi");
}
#[test]
fn test_pretty_print_prefers_bracket_calls() {
// All reduces to a simple bracket call.
test_pretty("{v()}", "[v]");
test_pretty("[v]", "[v]");
test_pretty("{[v]}", "[v]");
test_pretty("{{[v]}}", "[v]");
}
#[test]
fn test_pretty_print_nodes() {
// Basic text and markup.
test_pretty(r"*Hi_\", r"*Hi_\");
// Whitespace.
test_pretty(" ", " ");
test_pretty("\n\n\n", "\n\n");
// Heading and raw.
test_pretty("# Ok", "# Ok");
test_pretty("``\none\ntwo\n``", "`one\ntwo`");
test_pretty("`lang one\ntwo`", "`lang one\ntwo`");
}
}