mirror of https://github.com/stelzo/typst.git
813 lines
20 KiB
Rust
813 lines
20 KiB
Rust
//! Pretty printing.
|
|
|
|
use std::fmt::{self, Arguments, Write};
|
|
|
|
use crate::color::{Color, RgbaColor};
|
|
use crate::eval::*;
|
|
use crate::geom::{Angle, Length, Linear, Relative};
|
|
use crate::syntax::*;
|
|
|
|
/// Pretty print an item and return the resulting string.
|
|
pub fn pretty<T>(item: &T) -> String
|
|
where
|
|
T: Pretty + ?Sized,
|
|
{
|
|
let mut p = Printer::new();
|
|
item.pretty(&mut p);
|
|
p.finish()
|
|
}
|
|
|
|
/// Pretty print an item with a node map and return the resulting string.
|
|
pub fn pretty_with_map<T>(item: &T, map: &NodeMap) -> String
|
|
where
|
|
T: PrettyWithMap + ?Sized,
|
|
{
|
|
let mut p = Printer::new();
|
|
item.pretty_with_map(&mut p, Some(map));
|
|
p.finish()
|
|
}
|
|
|
|
/// Pretty print an item.
|
|
pub trait Pretty {
|
|
/// Pretty print this item into the given printer.
|
|
fn pretty(&self, p: &mut Printer);
|
|
}
|
|
|
|
/// Pretty print an item with a node map that applies to it.
|
|
pub trait PrettyWithMap {
|
|
/// Pretty print this item into the given printer.
|
|
fn pretty_with_map(&self, p: &mut Printer, map: Option<&NodeMap>);
|
|
}
|
|
|
|
impl<T> Pretty for T
|
|
where
|
|
T: PrettyWithMap,
|
|
{
|
|
fn pretty(&self, p: &mut Printer) {
|
|
self.pretty_with_map(p, None);
|
|
}
|
|
}
|
|
|
|
/// 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 character into the buffer.
|
|
pub fn push(&mut self, c: char) {
|
|
self.buf.push(c);
|
|
}
|
|
|
|
/// 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<'_>) -> fmt::Result {
|
|
Write::write_fmt(self, fmt)
|
|
}
|
|
|
|
/// Write a list of items joined by a joiner.
|
|
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) -> fmt::Result {
|
|
self.push_str(s);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl PrettyWithMap for Tree {
|
|
fn pretty_with_map(&self, p: &mut Printer, map: Option<&NodeMap>) {
|
|
for node in self {
|
|
node.pretty_with_map(p, map);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PrettyWithMap for Node {
|
|
fn pretty_with_map(&self, p: &mut Printer, map: Option<&NodeMap>) {
|
|
match self {
|
|
// TODO: Handle escaping.
|
|
Self::Text(text) => p.push_str(text),
|
|
Self::Space => p.push(' '),
|
|
Self::Strong(_) => p.push('*'),
|
|
Self::Emph(_) => p.push('_'),
|
|
Self::Linebreak(_) => p.push_str(r"\"),
|
|
Self::Parbreak(_) => p.push_str("\n\n"),
|
|
Self::Heading(heading) => heading.pretty_with_map(p, map),
|
|
Self::Raw(raw) => raw.pretty(p),
|
|
Self::Expr(expr) => {
|
|
if let Some(map) = map {
|
|
let value = &map[&(self as *const _)];
|
|
value.pretty(p);
|
|
} else {
|
|
if expr.has_short_form() {
|
|
p.push('#');
|
|
}
|
|
expr.pretty(p);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PrettyWithMap for HeadingNode {
|
|
fn pretty_with_map(&self, p: &mut Printer, map: Option<&NodeMap>) {
|
|
for _ in 0 .. self.level {
|
|
p.push('=');
|
|
}
|
|
self.contents.pretty_with_map(p, map);
|
|
}
|
|
}
|
|
|
|
impl Pretty for RawNode {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
// Find out how many backticks we need.
|
|
let mut backticks = 1;
|
|
|
|
// Language tag and block-level are only possible with 3+ backticks.
|
|
if self.lang.is_some() || self.block {
|
|
backticks = 3;
|
|
}
|
|
|
|
// More backticks may be required if there are lots of consecutive
|
|
// backticks.
|
|
let mut count = 0;
|
|
for c in self.text.chars() {
|
|
if c == '`' {
|
|
count += 1;
|
|
backticks = backticks.max(3).max(count + 1);
|
|
} else {
|
|
count = 0;
|
|
}
|
|
}
|
|
|
|
// Starting backticks.
|
|
for _ in 0 .. backticks {
|
|
p.push('`');
|
|
}
|
|
|
|
// Language tag.
|
|
if let Some(lang) = &self.lang {
|
|
lang.pretty(p);
|
|
}
|
|
|
|
// Start untrimming.
|
|
if self.block {
|
|
p.push('\n');
|
|
} else if backticks >= 3 {
|
|
p.push(' ');
|
|
}
|
|
|
|
// The lines.
|
|
p.push_str(&self.text);
|
|
|
|
// End untrimming.
|
|
if self.block {
|
|
p.push('\n');
|
|
} else if self.text.trim_end().ends_with('`') {
|
|
p.push(' ');
|
|
}
|
|
|
|
// Ending backticks.
|
|
for _ in 0 .. backticks {
|
|
p.push('`');
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for Expr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
match self {
|
|
Self::None(_) => p.push_str("none"),
|
|
Self::Bool(_, v) => v.pretty(p),
|
|
Self::Int(_, v) => v.pretty(p),
|
|
Self::Float(_, v) => v.pretty(p),
|
|
Self::Length(_, v, u) => write!(p, "{}{}", v, u).unwrap(),
|
|
Self::Angle(_, v, u) => write!(p, "{}{}", v, u).unwrap(),
|
|
Self::Percent(_, v) => write!(p, "{}%", v).unwrap(),
|
|
Self::Color(_, v) => v.pretty(p),
|
|
Self::Str(_, v) => v.pretty(p),
|
|
Self::Ident(v) => v.pretty(p),
|
|
Self::Array(v) => v.pretty(p),
|
|
Self::Dict(v) => v.pretty(p),
|
|
Self::Template(v) => v.pretty(p),
|
|
Self::Group(v) => v.pretty(p),
|
|
Self::Block(v) => v.pretty(p),
|
|
Self::Unary(v) => v.pretty(p),
|
|
Self::Binary(v) => v.pretty(p),
|
|
Self::Call(v) => v.pretty(p),
|
|
Self::Closure(v) => v.pretty(p),
|
|
Self::Let(v) => v.pretty(p),
|
|
Self::If(v) => v.pretty(p),
|
|
Self::While(v) => v.pretty(p),
|
|
Self::For(v) => v.pretty(p),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for ArrayExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('(');
|
|
p.join(&self.items, ", ", |item, p| item.pretty(p));
|
|
if self.items.len() == 1 {
|
|
p.push(',');
|
|
}
|
|
p.push(')');
|
|
}
|
|
}
|
|
|
|
impl Pretty for DictExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('(');
|
|
if self.items.is_empty() {
|
|
p.push(':');
|
|
} else {
|
|
p.join(&self.items, ", ", |named, p| named.pretty(p));
|
|
}
|
|
p.push(')');
|
|
}
|
|
}
|
|
|
|
impl Pretty for Named {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
self.name.pretty(p);
|
|
p.push_str(": ");
|
|
self.expr.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for TemplateExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('[');
|
|
self.tree.pretty_with_map(p, None);
|
|
p.push(']');
|
|
}
|
|
}
|
|
|
|
impl Pretty for GroupExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('(');
|
|
self.expr.pretty(p);
|
|
p.push(')');
|
|
}
|
|
}
|
|
|
|
impl Pretty for BlockExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('{');
|
|
if self.exprs.len() > 1 {
|
|
p.push(' ');
|
|
}
|
|
p.join(&self.exprs, "; ", |expr, p| expr.pretty(p));
|
|
if self.exprs.len() > 1 {
|
|
p.push(' ');
|
|
}
|
|
p.push('}');
|
|
}
|
|
}
|
|
|
|
impl Pretty for UnaryExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
self.op.pretty(p);
|
|
if self.op == UnOp::Not {
|
|
p.push(' ');
|
|
}
|
|
self.expr.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for UnOp {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str(self.as_str());
|
|
}
|
|
}
|
|
|
|
impl Pretty for BinaryExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
self.lhs.pretty(p);
|
|
p.push(' ');
|
|
self.op.pretty(p);
|
|
p.push(' ');
|
|
self.rhs.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for BinOp {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str(self.as_str());
|
|
}
|
|
}
|
|
|
|
impl Pretty for CallExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
self.callee.pretty(p);
|
|
|
|
let mut write_args = |items: &[CallArg]| {
|
|
p.push('(');
|
|
p.join(items, ", ", |item, p| item.pretty(p));
|
|
p.push(')');
|
|
};
|
|
|
|
match self.args.items.as_slice() {
|
|
// This can be moved behind the arguments.
|
|
//
|
|
// Example: Transforms "#v(a, [b])" => "#v(a)[b]".
|
|
[head @ .., CallArg::Pos(Expr::Template(template))] => {
|
|
if !head.is_empty() {
|
|
write_args(head);
|
|
}
|
|
template.pretty(p);
|
|
}
|
|
|
|
items => write_args(items),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for CallArgs {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.join(&self.items, ", ", |item, p| item.pretty(p));
|
|
}
|
|
}
|
|
|
|
impl Pretty for CallArg {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
match self {
|
|
Self::Pos(expr) => expr.pretty(p),
|
|
Self::Named(named) => named.pretty(p),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for ClosureExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('(');
|
|
p.join(self.params.iter(), ", ", |item, p| item.pretty(p));
|
|
p.push_str(") => ");
|
|
self.body.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for LetExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str("let ");
|
|
self.binding.pretty(p);
|
|
if let Some(init) = &self.init {
|
|
p.push_str(" = ");
|
|
init.pretty(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for IfExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str("if ");
|
|
self.condition.pretty(p);
|
|
p.push(' ');
|
|
self.if_body.pretty(p);
|
|
if let Some(expr) = &self.else_body {
|
|
// FIXME: Hashtag in markup.
|
|
p.push_str(" else ");
|
|
expr.pretty(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for WhileExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str("while ");
|
|
self.condition.pretty(p);
|
|
p.push(' ');
|
|
self.body.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for ForExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str("for ");
|
|
self.pattern.pretty(p);
|
|
p.push_str(" in ");
|
|
self.iter.pretty(p);
|
|
p.push(' ');
|
|
self.body.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for ForPattern {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
match self {
|
|
Self::Value(v) => v.pretty(p),
|
|
Self::KeyValue(k, v) => {
|
|
k.pretty(p);
|
|
p.push_str(", ");
|
|
v.pretty(p);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for Ident {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str(self.as_str());
|
|
}
|
|
}
|
|
|
|
impl Pretty for Value {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
match self {
|
|
Value::None => p.push_str("none"),
|
|
Value::Bool(v) => v.pretty(p),
|
|
Value::Int(v) => v.pretty(p),
|
|
Value::Float(v) => v.pretty(p),
|
|
Value::Length(v) => v.pretty(p),
|
|
Value::Angle(v) => v.pretty(p),
|
|
Value::Relative(v) => v.pretty(p),
|
|
Value::Linear(v) => v.pretty(p),
|
|
Value::Color(v) => v.pretty(p),
|
|
// TODO: Handle like text when directly in template.
|
|
Value::Str(v) => v.pretty(p),
|
|
Value::Array(v) => v.pretty(p),
|
|
Value::Dict(v) => v.pretty(p),
|
|
Value::Template(v) => v.pretty(p),
|
|
Value::Func(v) => v.pretty(p),
|
|
Value::Any(v) => v.pretty(p),
|
|
Value::Error => p.push_str("<error>"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for ArrayValue {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('(');
|
|
p.join(self, ", ", |item, p| item.pretty(p));
|
|
if self.len() == 1 {
|
|
p.push(',');
|
|
}
|
|
p.push(')');
|
|
}
|
|
}
|
|
|
|
impl Pretty for DictValue {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('(');
|
|
if self.is_empty() {
|
|
p.push(':');
|
|
} else {
|
|
p.join(self, ", ", |(key, value), p| {
|
|
p.push_str(key);
|
|
p.push_str(": ");
|
|
value.pretty(p);
|
|
});
|
|
}
|
|
p.push(')');
|
|
}
|
|
}
|
|
|
|
impl Pretty for TemplateValue {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('[');
|
|
for part in self {
|
|
part.pretty(p);
|
|
}
|
|
p.push(']');
|
|
}
|
|
}
|
|
|
|
impl Pretty for TemplateNode {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
match self {
|
|
Self::Tree { tree, map } => tree.pretty_with_map(p, Some(map)),
|
|
Self::Str(s) => p.push_str(s),
|
|
Self::Func(func) => func.pretty(p),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for TemplateFunc {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str("<node ");
|
|
p.push_str(self.name());
|
|
p.push('>');
|
|
}
|
|
}
|
|
|
|
impl Pretty for FuncValue {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str("<function");
|
|
if let Some(name) = self.name() {
|
|
p.push(' ');
|
|
p.push_str(name);
|
|
}
|
|
p.push('>');
|
|
}
|
|
}
|
|
|
|
impl Pretty for FuncArgs {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('(');
|
|
p.join(&self.items, ", ", |item, p| item.pretty(p));
|
|
p.push(')');
|
|
}
|
|
}
|
|
|
|
impl Pretty for FuncArg {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
if let Some(name) = &self.name {
|
|
p.push_str(&name.v);
|
|
p.push_str(": ");
|
|
}
|
|
self.value.v.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for i64 {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
write!(p, "{}", self).unwrap();
|
|
}
|
|
}
|
|
|
|
impl Pretty for f64 {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
write!(p, "{}", self).unwrap();
|
|
}
|
|
}
|
|
|
|
impl Pretty for str {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('"');
|
|
for c in self.chars() {
|
|
match c {
|
|
'\\' => p.push_str(r"\\"),
|
|
'"' => p.push_str(r#"\""#),
|
|
'\n' => p.push_str(r"\n"),
|
|
'\r' => p.push_str(r"\r"),
|
|
'\t' => p.push_str(r"\t"),
|
|
_ => p.push(c),
|
|
}
|
|
}
|
|
p.push('"');
|
|
}
|
|
}
|
|
|
|
macro_rules! pretty_display {
|
|
($($type:ty),* $(,)?) => {
|
|
$(impl Pretty for $type {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
write!(p, "{}", self).unwrap();
|
|
}
|
|
})*
|
|
};
|
|
}
|
|
|
|
pretty_display! {
|
|
bool,
|
|
Length,
|
|
Angle,
|
|
Relative,
|
|
Linear,
|
|
RgbaColor,
|
|
Color,
|
|
AnyValue,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::collections::{BTreeMap, HashMap};
|
|
use std::rc::Rc;
|
|
|
|
use super::*;
|
|
use crate::env::Env;
|
|
use crate::parse::parse;
|
|
|
|
#[track_caller]
|
|
fn roundtrip(src: &str) {
|
|
test_parse(src, src);
|
|
}
|
|
|
|
#[track_caller]
|
|
fn test_parse(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");
|
|
}
|
|
}
|
|
|
|
#[track_caller]
|
|
fn test_value(value: impl Into<Value>, exp: &str) {
|
|
assert_eq!(pretty(&value.into()), exp);
|
|
}
|
|
|
|
#[test]
|
|
fn test_pretty_print_node() {
|
|
// Basic text and markup.
|
|
roundtrip("*");
|
|
roundtrip("_");
|
|
roundtrip(" ");
|
|
roundtrip("\\ ");
|
|
roundtrip("\n\n");
|
|
roundtrip("hi");
|
|
|
|
// Heading.
|
|
roundtrip("= *Ok*");
|
|
|
|
// Raw.
|
|
roundtrip("``");
|
|
roundtrip("`nolang 1`");
|
|
roundtrip("```lang 1```");
|
|
roundtrip("```lang 1 ```");
|
|
roundtrip("```hi line ```");
|
|
roundtrip("```py\ndef\n```");
|
|
roundtrip("```\n line \n```");
|
|
roundtrip("```\n`\n```");
|
|
roundtrip("``` ` ```");
|
|
roundtrip("````\n```\n```\n````");
|
|
test_parse("```lang```", "```lang ```");
|
|
test_parse("```1 ```", "``");
|
|
test_parse("``` 1```", "`1`");
|
|
test_parse("``` 1 ```", "`1 `");
|
|
test_parse("```` ` ````", "``` ` ```");
|
|
}
|
|
|
|
#[test]
|
|
fn test_pretty_print_expr() {
|
|
// Basic expressions.
|
|
roundtrip("{none}");
|
|
roundtrip("{hi}");
|
|
roundtrip("{true}");
|
|
roundtrip("{10}");
|
|
roundtrip("{3.14}");
|
|
roundtrip("{10pt}");
|
|
roundtrip("{14.1deg}");
|
|
roundtrip("{20%}");
|
|
roundtrip("{#abcdef}");
|
|
roundtrip(r#"{"hi"}"#);
|
|
test_parse(r#"{"let's \" go"}"#, r#"{"let's \" go"}"#);
|
|
|
|
// Arrays.
|
|
roundtrip("{()}");
|
|
roundtrip("{(1)}");
|
|
roundtrip("{(1, 2, 3)}");
|
|
|
|
// Dictionaries.
|
|
roundtrip("{(:)}");
|
|
roundtrip("{(key: value)}");
|
|
roundtrip("{(a: 1, b: 2)}");
|
|
|
|
// Templates.
|
|
roundtrip("[]");
|
|
roundtrip("[*Ok*]");
|
|
roundtrip("{[f]}");
|
|
|
|
// Groups.
|
|
roundtrip("{(1)}");
|
|
|
|
// Blocks.
|
|
roundtrip("{}");
|
|
roundtrip("{1}");
|
|
roundtrip("{ let x = 1; x += 2; x + 1 }");
|
|
roundtrip("[{}]");
|
|
|
|
// Operators.
|
|
roundtrip("{-x}");
|
|
roundtrip("{not true}");
|
|
roundtrip("{1 + 3}");
|
|
|
|
// Function calls.
|
|
roundtrip("{v()}");
|
|
roundtrip("{v()()}");
|
|
roundtrip("{v(1)}");
|
|
roundtrip("{v(a: 1, b)}");
|
|
roundtrip("#v()");
|
|
roundtrip("#v(1)");
|
|
roundtrip("#v(1, 2)[*Ok*]");
|
|
roundtrip("#v(1, f[2])");
|
|
|
|
// Closures.
|
|
roundtrip("{(a, b) => a + b}");
|
|
|
|
// Keywords.
|
|
roundtrip("#let x = 1 + 2");
|
|
test_parse("#let f(x) = y", "#let f = (x) => y");
|
|
test_parse("#if x [y] #else [z]", "#if x [y] else [z]");
|
|
roundtrip("#while x {y}");
|
|
roundtrip("#for x in y {z}");
|
|
roundtrip("#for k, x in y {z}");
|
|
}
|
|
|
|
#[test]
|
|
fn test_pretty_print_with_map() {
|
|
let tree = parse("*[{1+2}[{4}]]*{2+3}").output;
|
|
let map = eval(&mut Env::blank(), &tree, &Default::default()).output;
|
|
assert_eq!(pretty_with_map(&tree, &map), "*[3[4]]*5");
|
|
}
|
|
|
|
#[test]
|
|
fn test_pretty_print_value() {
|
|
// Simple values.
|
|
test_value(Value::None, "none");
|
|
test_value(false, "false");
|
|
test_value(12i64, "12");
|
|
test_value(3.14, "3.14");
|
|
test_value(Length::pt(5.5), "5.5pt");
|
|
test_value(Angle::deg(90.0), "90deg");
|
|
test_value(Relative::one() / 2.0, "50%");
|
|
test_value(Relative::new(0.3) + Length::cm(2.0), "30% + 2cm");
|
|
test_value(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101");
|
|
test_value("hello", r#""hello""#);
|
|
test_value("\n", r#""\n""#);
|
|
test_value("\\", r#""\\""#);
|
|
test_value("\"", r#""\"""#);
|
|
|
|
// Array.
|
|
test_value(Value::Array(vec![]), "()");
|
|
test_value(vec![Value::None], "(none,)");
|
|
test_value(vec![Value::Int(1), Value::Int(2)], "(1, 2)");
|
|
|
|
// Dictionary.
|
|
let mut dict = BTreeMap::new();
|
|
test_value(dict.clone(), "(:)");
|
|
dict.insert("one".into(), Value::Int(1));
|
|
test_value(dict.clone(), "(one: 1)");
|
|
dict.insert("two".into(), Value::Bool(false));
|
|
test_value(dict, "(one: 1, two: false)");
|
|
|
|
// Template.
|
|
test_value(
|
|
vec![
|
|
TemplateNode::Tree {
|
|
tree: Rc::new(vec![Node::Strong(Span::ZERO)]),
|
|
map: HashMap::new(),
|
|
},
|
|
TemplateNode::Func(TemplateFunc::new("example", |_| {})),
|
|
],
|
|
"[*<node example>]",
|
|
);
|
|
|
|
// Function.
|
|
test_value(FuncValue::new(None, |_, _| Value::None), "<function>");
|
|
test_value(
|
|
FuncValue::new(Some("nil".into()), |_, _| Value::None),
|
|
"<function nil>",
|
|
);
|
|
|
|
// Any.
|
|
test_value(AnyValue::new(1), "1");
|
|
|
|
// Error.
|
|
test_value(Value::Error, "<error>");
|
|
}
|
|
|
|
#[test]
|
|
fn test_pretty_print_args() {
|
|
// Arguments.
|
|
assert_eq!(
|
|
pretty(&FuncArgs {
|
|
span: Span::ZERO,
|
|
items: vec![
|
|
FuncArg {
|
|
name: Some(Spanned::zero("a".into())),
|
|
value: Spanned::zero(Value::Int(1)),
|
|
},
|
|
FuncArg {
|
|
name: None,
|
|
value: Spanned::zero(Value::Int(2)),
|
|
},
|
|
],
|
|
}),
|
|
"(a: 1, 2)",
|
|
);
|
|
}
|
|
}
|