diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-08-14 15:24:59 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-08-14 15:55:39 +0200 |
| commit | 6ae6d86b9c6fefe6c5379ac1b20ea90634c09c81 (patch) | |
| tree | 2504c3b46807be148b9efbadf9b23e57bb77b8f3 /src/pretty.rs | |
| parent | fcb4e451186067cdf6efe3c14cbfa7561b366a6c (diff) | |
Separate type for string values
Diffstat (limited to 'src/pretty.rs')
| -rw-r--r-- | src/pretty.rs | 786 |
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"); - } -} |
