summaryrefslogtreecommitdiff
path: root/src/pretty.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-08-14 15:24:59 +0200
committerLaurenz <laurmaedje@gmail.com>2021-08-14 15:55:39 +0200
commit6ae6d86b9c6fefe6c5379ac1b20ea90634c09c81 (patch)
tree2504c3b46807be148b9efbadf9b23e57bb77b8f3 /src/pretty.rs
parentfcb4e451186067cdf6efe3c14cbfa7561b366a6c (diff)
Separate type for string values
Diffstat (limited to 'src/pretty.rs')
-rw-r--r--src/pretty.rs786
1 files changed, 0 insertions, 786 deletions
diff --git a/src/pretty.rs b/src/pretty.rs
deleted file mode 100644
index 573dc6e9..00000000
--- a/src/pretty.rs
+++ /dev/null
@@ -1,786 +0,0 @@
-//! Pretty printing.
-
-use std::fmt::{self, Arguments, Write};
-
-use crate::color::{Color, RgbaColor};
-use crate::eval::*;
-use crate::geom::{Angle, Fractional, 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.
-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 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 Pretty for SyntaxTree {
- fn pretty(&self, p: &mut Printer) {
- for node in self {
- node.pretty(p);
- }
- }
-}
-
-impl Pretty for SyntaxNode {
- fn pretty(&self, p: &mut Printer) {
- match self {
- // TODO: Handle escaping.
- Self::Space => p.push(' '),
- Self::Text(text) => p.push_str(text),
- Self::Linebreak(_) => p.push_str(r"\"),
- Self::Parbreak(_) => p.push_str("\n\n"),
- Self::Strong(_) => p.push('*'),
- Self::Emph(_) => p.push('_'),
- Self::Raw(raw) => raw.pretty(p),
- Self::Heading(n) => n.pretty(p),
- Self::List(n) => n.pretty(p),
- Self::Enum(n) => n.pretty(p),
- Self::Expr(n) => {
- if n.has_short_form() {
- p.push('#');
- }
- n.pretty(p);
- }
- }
- }
-}
-
-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 HeadingNode {
- fn pretty(&self, p: &mut Printer) {
- for _ in 0 .. self.level {
- p.push('=');
- }
- p.push(' ');
- self.body.pretty(p);
- }
-}
-
-impl Pretty for ListItem {
- fn pretty(&self, p: &mut Printer) {
- p.push_str("- ");
- self.body.pretty(p);
- }
-}
-
-impl Pretty for EnumItem {
- fn pretty(&self, p: &mut Printer) {
- if let Some(number) = self.number {
- write!(p, "{}", number).unwrap();
- }
- p.push_str(". ");
- self.body.pretty(p);
- }
-}
-
-impl Pretty for Expr {
- fn pretty(&self, p: &mut Printer) {
- match self {
- Self::Ident(v) => v.pretty(p),
- Self::Lit(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::With(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),
- Self::Import(v) => v.pretty(p),
- Self::Include(v) => v.pretty(p),
- }
- }
-}
-
-impl Pretty for Lit {
- fn pretty(&self, p: &mut Printer) {
- match self {
- Self::None(_) => p.push_str("none"),
- Self::Auto(_) => p.push_str("auto"),
- 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::Fractional(_, v) => write!(p, "{}fr", v).unwrap(),
- Self::Str(_, 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(p);
- 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),
- Self::Spread(expr) => {
- p.push_str("..");
- expr.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 ClosureParam {
- fn pretty(&self, p: &mut Printer) {
- match self {
- Self::Pos(ident) => ident.pretty(p),
- Self::Named(named) => named.pretty(p),
- Self::Sink(ident) => {
- p.push_str("..");
- ident.pretty(p);
- }
- }
- }
-}
-
-impl Pretty for WithExpr {
- fn pretty(&self, p: &mut Printer) {
- self.callee.pretty(p);
- p.push_str(" with (");
- self.args.pretty(p);
- p.push(')');
- }
-}
-
-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 ImportExpr {
- fn pretty(&self, p: &mut Printer) {
- p.push_str("import ");
- self.imports.pretty(p);
- p.push_str(" from ");
- self.path.pretty(p);
- }
-}
-
-impl Pretty for Imports {
- fn pretty(&self, p: &mut Printer) {
- match self {
- Self::Wildcard => p.push('*'),
- Self::Idents(idents) => p.join(idents, ", ", |item, p| item.pretty(p)),
- }
- }
-}
-
-impl Pretty for IncludeExpr {
- fn pretty(&self, p: &mut Printer) {
- p.push_str("include ");
- self.path.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 {
- Self::None => p.push_str("none"),
- Self::Auto => p.push_str("auto"),
- Self::Bool(v) => v.pretty(p),
- Self::Int(v) => v.pretty(p),
- Self::Float(v) => v.pretty(p),
- Self::Length(v) => v.pretty(p),
- Self::Angle(v) => v.pretty(p),
- Self::Relative(v) => v.pretty(p),
- Self::Linear(v) => v.pretty(p),
- Self::Fractional(v) => v.pretty(p),
- Self::Color(v) => v.pretty(p),
- Self::Str(v) => v.pretty(p),
- Self::Array(v) => v.pretty(p),
- Self::Dict(v) => v.pretty(p),
- Self::Template(v) => v.pretty(p),
- Self::Func(v) => v.pretty(p),
- Self::Args(v) => v.pretty(p),
- Self::Dyn(v) => v.pretty(p),
- }
- }
-}
-
-impl Pretty for Array {
- 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 Dict {
- 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 Template {
- fn pretty(&self, p: &mut Printer) {
- p.push_str("<template>");
- }
-}
-
-impl Pretty for Function {
- 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);
- p.push_str(": ");
- }
- self.value.v.pretty(p);
- }
-}
-
-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! {
- i64,
- f64,
- bool,
- Length,
- Angle,
- Relative,
- Linear,
- Fractional,
- RgbaColor,
- Color,
- Dynamic,
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::parse::parse;
- use crate::source::SourceFile;
-
- #[track_caller]
- fn roundtrip(src: &str) {
- test_parse(src, src);
- }
-
- #[track_caller]
- fn test_parse(src: &str, exp: &str) {
- let source = SourceFile::detached(src);
- let ast = parse(&source).unwrap();
- let found = pretty(&ast);
- if exp != found {
- println!("tree: {:#?}", ast);
- 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");
- roundtrip("= *Ok*");
- 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("{auto}");
- roundtrip("{true}");
- roundtrip("{10}");
- roundtrip("{3.14}");
- roundtrip("{10pt}");
- roundtrip("{14.1deg}");
- roundtrip("{20%}");
- roundtrip("{0.5fr}");
- roundtrip(r#"{"hi"}"#);
- test_parse(r#"{"let's \" go"}"#, r#"{"let's \" go"}"#);
- roundtrip("{hi}");
-
- // 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}");
-
- // Functions.
- 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])");
- roundtrip("{(a, b) => a + b}");
-
- // Control flow.
- 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}");
- roundtrip("#import * from \"file.typ\"");
- roundtrip("#include \"chapter1.typ\"");
- }
-
- #[test]
- fn test_pretty_print_value() {
- // Primitives.
- 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(Fractional::one() * 7.55, "7.55fr");
- test_value(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101");
-
- // Collections.
- test_value("hello", r#""hello""#);
- test_value("\n", r#""\n""#);
- test_value("\\", r#""\\""#);
- test_value("\"", r#""\"""#);
- test_value(array![], "()");
- test_value(array![Value::None], "(none,)");
- test_value(array![1, 2], "(1, 2)");
- test_value(dict![], "(:)");
- test_value(dict!["one" => 1], "(one: 1)");
- test_value(dict!["two" => false, "one" => 1], "(one: 1, two: false)");
-
- // Functions.
- test_value(Function::new(None, |_, _| Ok(Value::None)), "<function>");
- test_value(
- Function::new(Some("nil".into()), |_, _| Ok(Value::None)),
- "<function nil>",
- );
-
- // Dynamics.
- test_value(Dynamic::new(1), "1");
- }
-}