mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
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:
parent
2e77b1c836
commit
7b4d4d6002
@ -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));
|
||||
}
|
||||
|
@ -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)),
|
||||
|
||||
|
@ -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)");
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
68
src/pretty.rs
Normal 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))
|
||||
}
|
||||
}
|
@ -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"}"#);
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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`");
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user