From d86a5e8a1f469dd79abf3137dba77a71fae2a774 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 3 Feb 2021 21:30:36 +0100 Subject: [PATCH] =?UTF-8?q?Tidy=20up=20raw=20blocks=20=F0=9F=A7=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Better trimming (only trim at the end if necessary) - Fixed block-level layouting - Improved pretty printing - Flip inline variable to block - Flip inline variable to display for math formulas --- src/eval/mod.rs | 12 +++- src/eval/value.rs | 30 ++++----- src/parse/mod.rs | 14 ++--- src/parse/parser.rs | 5 ++ src/parse/resolve.rs | 45 +++++++------ src/parse/tokens.rs | 44 ++++++------- src/pretty.rs | 71 ++++++++++++++++++++- src/syntax/expr.rs | 84 +++++++++++++------------ src/syntax/ident.rs | 8 +++ src/syntax/mod.rs | 21 +++++-- src/syntax/node.rs | 140 ++++++++++++++++++++++++++--------------- src/syntax/token.rs | 10 +-- tests/lang/ref/raw.png | Bin 6338 -> 24986 bytes tests/lang/typ/raw.typ | 25 +++++--- tests/typeset.rs | 2 +- 15 files changed, 337 insertions(+), 174 deletions(-) diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 1a2116704..4f8961cc0 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -142,6 +142,10 @@ impl Eval for Spanned<&NodeRaw> { })); } + if self.v.block { + ctx.apply_parbreak(); + } + ctx.push(NodeStack { dirs: ctx.state.dirs, align: ctx.state.align, @@ -149,6 +153,10 @@ impl Eval for Spanned<&NodeRaw> { children, }); + if self.v.block { + ctx.apply_parbreak(); + } + ctx.state.font.families = prev; } } @@ -466,8 +474,8 @@ impl Eval for Spanned<&ExprFor> { iterate!(for (k => key, v => value) in dict.into_iter()) } - (ForPattern::KeyValue(..), Value::Str(_)) - | (ForPattern::KeyValue(..), Value::Array(_)) => { + (ForPattern::KeyValue(_, _), Value::Str(_)) + | (ForPattern::KeyValue(_, _), Value::Array(_)) => { ctx.diag(error!(self.v.pat.span, "mismatched pattern")); Value::Error } diff --git a/src/eval/value.rs b/src/eval/value.rs index 860c06348..119a2f1b0 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -110,15 +110,15 @@ impl Pretty for Value { fn pretty(&self, p: &mut Printer) { match self { Value::None => p.push_str("none"), - Value::Bool(v) => write!(p, "{}", v).unwrap(), - Value::Int(v) => p.push_str(itoa::Buffer::new().format(*v)), - Value::Float(v) => p.push_str(ryu::Buffer::new().format(*v)), - Value::Length(v) => write!(p, "{}", v).unwrap(), - Value::Angle(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::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), + Value::Str(v) => v.pretty(p), Value::Array(v) => v.pretty(p), Value::Dict(v) => v.pretty(p), Value::Template(v) => pretty_template(v, p), @@ -134,12 +134,12 @@ pub type ValueArray = Vec; impl Pretty for ValueArray { fn pretty(&self, p: &mut Printer) { - p.push_str("("); + p.push('('); p.join(self, ", ", |item, p| item.pretty(p)); if self.len() == 1 { - p.push_str(","); + p.push(','); } - p.push_str(")"); + p.push(')'); } } @@ -148,9 +148,9 @@ pub type ValueDict = BTreeMap; impl Pretty for ValueDict { fn pretty(&self, p: &mut Printer) { - p.push_str("("); + p.push('('); if self.is_empty() { - p.push_str(":"); + p.push(':'); } else { p.join(self, ", ", |(key, value), p| { p.push_str(key); @@ -158,7 +158,7 @@ impl Pretty for ValueDict { value.pretty(p); }); } - p.push_str(")"); + p.push(')'); } } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 3fc7d483a..3fd2cca52 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -59,14 +59,14 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option { Token::Underscore => Node::Emph, Token::Eq => { if *at_start { - return Some(Node::Heading(heading(p))); + return Some(heading(p)); } else { - Node::Text(p.get(p.peek_span()).into()) + Node::Text(p.peek_src().into()) } } Token::Tilde => Node::Text("\u{00A0}".into()), Token::Backslash => Node::Linebreak, - Token::Raw(t) => Node::Raw(raw(p, t)), + Token::Raw(t) => raw(p, t), Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)), // Keywords. @@ -122,7 +122,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option { } /// Parse a heading. -fn heading(p: &mut Parser) -> NodeHeading { +fn heading(p: &mut Parser) -> Node { // Count depth. let mut level = p.span(|p| { p.assert(&[Token::Eq]); @@ -147,16 +147,16 @@ fn heading(p: &mut Parser) -> NodeHeading { } } - NodeHeading { level, contents } + Node::Heading(NodeHeading { level, contents }) } /// Handle a raw block. -fn raw(p: &mut Parser, token: TokenRaw) -> NodeRaw { +fn raw(p: &mut Parser, token: TokenRaw) -> Node { let raw = resolve::resolve_raw(token.text, token.backticks); if !token.terminated { p.diag(error!(p.peek_span().end, "expected backtick(s)")); } - raw + Node::Raw(raw) } /// Handle a unicode escape sequence. diff --git a/src/parse/parser.rs b/src/parse/parser.rs index b77677727..986a36b09 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -243,6 +243,11 @@ impl<'s> Parser<'s> { ) } + /// Peek at the source of the next token. + pub fn peek_src(&self) -> &'s str { + self.get(self.peek_span()) + } + /// Checks whether the next token fulfills a condition. /// /// Returns `false` if there is no next token. diff --git a/src/parse/resolve.rs b/src/parse/resolve.rs index 3adbf11f4..a5e831daa 100644 --- a/src/parse/resolve.rs +++ b/src/parse/resolve.rs @@ -54,13 +54,13 @@ pub fn resolve_raw(text: &str, backticks: usize) -> NodeRaw { NodeRaw { lang: Ident::new(tag), lines, - inline: !had_newline, + block: had_newline, } } else { NodeRaw { lang: None, lines: split_lines(text), - inline: true, + block: false, } } } @@ -77,10 +77,14 @@ fn split_at_lang_tag(raw: &str) -> (&str, &str) { /// Trim raw text and splits it into lines. /// /// Returns whether at least one newline was contained in `raw`. -fn trim_and_split_raw(raw: &str) -> (Vec, bool) { - // Trims one whitespace at end and start. - let raw = raw.strip_prefix(' ').unwrap_or(raw); - let raw = raw.strip_suffix(' ').unwrap_or(raw); +fn trim_and_split_raw(mut raw: &str) -> (Vec, bool) { + // Trims one space at the start. + raw = raw.strip_prefix(' ').unwrap_or(raw); + + // Trim one space at the end if the last non-whitespace char is a backtick. + if raw.trim_end().ends_with('`') { + raw = raw.strip_suffix(' ').unwrap_or(raw); + } let mut lines = split_lines(raw); let had_newline = lines.len() > 1; @@ -167,29 +171,29 @@ mod tests { backticks: usize, lang: Option<&str>, lines: &[&str], - inline: bool, + block: bool, ) { assert_eq!(resolve_raw(raw, backticks), NodeRaw { lang: lang.map(|id| Ident(id.into())), lines: lines.iter().map(ToString::to_string).collect(), - inline, + block, }); } // Just one backtick. - test("py", 1, None, &["py"], true); - test("1\n2", 1, None, &["1", "2"], true); - test("1\r\n2", 1, None, &["1", "2"], true); + test("py", 1, None, &["py"], false); + test("1\n2", 1, None, &["1", "2"], false); + test("1\r\n2", 1, None, &["1", "2"], false); // More than one backtick with lang tag. - test("js alert()", 2, Some("js"), &["alert()"], true); - test("py quit(\n\n) ", 3, Some("py"), &["quit(", "", ")"], false); - test("♥", 2, None, &[], true); + test("js alert()", 2, Some("js"), &["alert()"], false); + test("py quit(\n\n)", 3, Some("py"), &["quit(", "", ")"], true); + test("♥", 2, None, &[], false); // Trimming of whitespace (tested more thoroughly in separate test). - test(" a", 2, None, &["a"], true); - test(" a", 2, None, &[" a"], true); - test(" \na", 2, None, &["a"], false); + test(" a", 2, None, &["a"], false); + test(" a", 2, None, &[" a"], false); + test(" \na", 2, None, &["a"], true); } #[test] @@ -203,8 +207,11 @@ mod tests { test(" hi", vec![" hi"]); test("\nhi", vec!["hi"]); test(" \n hi", vec![" hi"]); - test("hi ", vec!["hi"]); - test("hi ", vec!["hi "]); + test("hi` ", vec!["hi`"]); + test("hi` ", vec!["hi` "]); + test("hi` ", vec!["hi` "]); + test("hi ", vec!["hi "]); + test("hi ", vec!["hi "]); test("hi\n", vec!["hi"]); test("hi \n ", vec!["hi "]); test(" \n hi \n ", vec![" hi "]); diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index 405352c3a..e3550707d 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -254,22 +254,22 @@ impl<'s> Tokens<'s> { } fn math(&mut self) -> Token<'s> { - let mut inline = true; + let mut display = false; if self.s.eat_if('[') { - inline = false; + display = true; } let start = self.s.index(); let mut escaped = false; - let mut dollar = inline; + let mut dollar = !display; let terminated = loop { match self.s.eat() { Some('$') if !escaped && dollar => break true, Some(']') if !escaped => dollar = true, Some(c) => { - dollar = inline; + dollar = !display; escaped = c == '\\' && !escaped; } None => break false, @@ -277,15 +277,15 @@ impl<'s> Tokens<'s> { }; let end = self.s.index() - - match (terminated, inline) { + - match (terminated, display) { (false, _) => 0, - (true, true) => 1, - (true, false) => 2, + (true, false) => 1, + (true, true) => 2, }; Token::Math(TokenMath { formula: self.s.get(start .. end), - inline, + display, terminated, }) } @@ -470,8 +470,8 @@ mod tests { Token::Raw(TokenRaw { text, backticks, terminated }) } - const fn Math(formula: &str, inline: bool, terminated: bool) -> Token { - Token::Math(TokenMath { formula, inline, terminated }) + const fn Math(formula: &str, display: bool, terminated: bool) -> Token { + Token::Math(TokenMath { formula, display, terminated }) } const fn UnicodeEscape(sequence: &str, terminated: bool) -> Token { @@ -527,7 +527,7 @@ mod tests { ('/', None, "//", LineComment("")), ('/', None, "/**/", BlockComment("")), ('/', Some(Markup), "*", Star), - ('/', Some(Markup), "$ $", Math(" ", true, true)), + ('/', Some(Markup), "$ $", Math(" ", false, true)), ('/', Some(Markup), r"\\", Text(r"\")), ('/', Some(Markup), "#let", Let), ('/', Some(Code), "#if", If), @@ -752,21 +752,21 @@ mod tests { #[test] fn test_tokenize_math_formulas() { // Test basic formula. - t!(Markup: "$$" => Math("", true, true)); - t!(Markup: "$x$" => Math("x", true, true)); - t!(Markup: r"$\\$" => Math(r"\\", true, true)); - t!(Markup: "$[x + y]$" => Math("x + y", false, true)); - t!(Markup: r"$[\\]$" => Math(r"\\", false, true)); + t!(Markup: "$$" => Math("", false, true)); + t!(Markup: "$x$" => Math("x", false, true)); + t!(Markup: r"$\\$" => Math(r"\\", false, true)); + t!(Markup: "$[x + y]$" => Math("x + y", true, true)); + t!(Markup: r"$[\\]$" => Math(r"\\", true, true)); // Test unterminated. - t!(Markup[""]: "$x" => Math("x", true, false)); - t!(Markup[""]: "$[x" => Math("x", false, false)); - t!(Markup[""]: "$[x]\n$" => Math("x]\n$", false, false)); + t!(Markup[""]: "$x" => Math("x", false, false)); + t!(Markup[""]: "$[x" => Math("x", true, false)); + t!(Markup[""]: "$[x]\n$" => Math("x]\n$", true, false)); // Test escape sequences. - t!(Markup: r"$\$x$" => Math(r"\$x", true, true)); - t!(Markup: r"$[\\\]$]$" => Math(r"\\\]$", false, true)); - t!(Markup[""]: r"$[ ]\\$" => Math(r" ]\\$", false, false)); + t!(Markup: r"$\$x$" => Math(r"\$x", false, true)); + t!(Markup: r"$[\\\]$]$" => Math(r"\\\]$", true, true)); + t!(Markup[""]: r"$[ ]\\$" => Math(r" ]\\$", true, false)); } #[test] diff --git a/src/pretty.rs b/src/pretty.rs index f1c3cfb7d..0123e9a75 100644 --- a/src/pretty.rs +++ b/src/pretty.rs @@ -2,10 +2,13 @@ use std::fmt::{Arguments, Result, Write}; +use crate::color::{Color, RgbaColor}; +use crate::geom::{Angle, Length, Linear, Relative}; + /// Pretty print an item and return the resulting string. pub fn pretty(item: &T) -> String where - T: Pretty, + T: Pretty + ?Sized, { let mut p = Printer::new(); item.pretty(&mut p); @@ -29,6 +32,11 @@ impl Printer { 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); @@ -67,3 +75,64 @@ impl Write for Printer { Ok(()) } } + +impl Pretty for i64 { + fn pretty(&self, p: &mut Printer) { + p.push_str(itoa::Buffer::new().format(*self)); + } +} + +impl Pretty for f64 { + fn pretty(&self, p: &mut Printer) { + p.push_str(ryu::Buffer::new().format(*self)); + } +} + +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! impl_pretty_display { + ($($type:ty),* $(,)?) => { + $(impl Pretty for $type { + fn pretty(&self, p: &mut Printer) { + write!(p, "{}", self).unwrap(); + } + })* + }; +} + +impl_pretty_display! { + bool, + Length, + Angle, + Relative, + Linear, + RgbaColor, + Color, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_pretty_print_str() { + assert_eq!(pretty("\n"), r#""\n""#); + assert_eq!(pretty("\\"), r#""\\""#); + assert_eq!(pretty("\""), r#""\"""#); + } +} diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index ebe821995..a681aa32a 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -62,24 +62,28 @@ impl Pretty for Expr { fn pretty(&self, p: &mut Printer) { match self { Self::None => p.push_str("none"), - Self::Ident(v) => p.push_str(&v), - Self::Bool(v) => write!(p, "{}", v).unwrap(), - Self::Int(v) => p.push_str(itoa::Buffer::new().format(*v)), - Self::Float(v) => p.push_str(ryu::Buffer::new().format(*v)), - 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) => write!(p, "{}", v).unwrap(), - // TODO: Debug escapes a bit more than we want (e.g. apostrophes). - // We probably need to do the escaping ourselves. - Self::Str(v) => write!(p, "{:?}", &v).unwrap(), + Self::Ident(v) => v.pretty(p), + Self::Bool(v) => v.pretty(p), + Self::Int(v) => v.pretty(p), + Self::Float(v) => v.pretty(p), + Self::Length(v, u) => { + write!(p, "{}{}", ryu::Buffer::new().format(*v), u).unwrap(); + } + Self::Angle(v, u) => { + write!(p, "{}{}", ryu::Buffer::new().format(*v), u).unwrap(); + } + Self::Percent(v) => { + write!(p, "{}%", ryu::Buffer::new().format(*v)).unwrap(); + } + 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) => pretty_template(v, p), Self::Group(v) => { - p.push_str("("); + p.push('('); v.v.pretty(p); - p.push_str(")"); + p.push(')'); } Self::Block(v) => v.pretty(p), Self::Unary(v) => v.pretty(p), @@ -98,12 +102,12 @@ pub type ExprArray = SpanVec; impl Pretty for ExprArray { fn pretty(&self, p: &mut Printer) { - p.push_str("("); + p.push('('); p.join(self, ", ", |item, p| item.v.pretty(p)); if self.len() == 1 { - p.push_str(","); + p.push(','); } - p.push_str(")"); + p.push(')'); } } @@ -112,13 +116,13 @@ pub type ExprDict = Vec; impl Pretty for ExprDict { fn pretty(&self, p: &mut Printer) { - p.push_str("("); + p.push('('); if self.is_empty() { - p.push_str(":"); + p.push(':'); } else { p.join(self, ", ", |named, p| named.pretty(p)); } - p.push_str(")"); + p.push(')'); } } @@ -133,7 +137,7 @@ pub struct Named { impl Pretty for Named { fn pretty(&self, p: &mut Printer) { - p.push_str(&self.name.v); + self.name.v.pretty(p); p.push_str(": "); self.expr.v.pretty(p); } @@ -147,9 +151,9 @@ pub fn pretty_template(template: &ExprTemplate, p: &mut Printer) { if let [Spanned { v: Node::Expr(Expr::Call(call)), .. }] = template.as_slice() { pretty_func_template(call, p, false) } else { - p.push_str("["); + p.push('['); template.pretty(p); - p.push_str("]"); + p.push(']'); } } @@ -167,15 +171,15 @@ pub struct ExprBlock { impl Pretty for ExprBlock { fn pretty(&self, p: &mut Printer) { - p.push_str("{"); + p.push('{'); if self.exprs.len() > 1 { - p.push_str(" "); + p.push(' '); } p.join(&self.exprs, "; ", |expr, p| expr.v.pretty(p)); if self.exprs.len() > 1 { - p.push_str(" "); + p.push(' '); } - p.push_str("}"); + p.push('}'); } } @@ -192,7 +196,7 @@ impl Pretty for ExprUnary { fn pretty(&self, p: &mut Printer) { self.op.v.pretty(p); if self.op.v == UnOp::Not { - p.push_str(" "); + p.push(' '); } self.expr.v.pretty(p); } @@ -258,9 +262,9 @@ pub struct ExprBinary { impl Pretty for ExprBinary { fn pretty(&self, p: &mut Printer) { self.lhs.v.pretty(p); - p.push_str(" "); + p.push(' '); self.op.v.pretty(p); - p.push_str(" "); + p.push(' '); self.rhs.v.pretty(p); } } @@ -419,9 +423,9 @@ pub struct ExprCall { impl Pretty for ExprCall { fn pretty(&self, p: &mut Printer) { self.callee.v.pretty(p); - p.push_str("("); + p.push('('); self.args.v.pretty(p); - p.push_str(")"); + p.push(')'); } } @@ -444,7 +448,7 @@ pub fn pretty_func_template(call: &ExprCall, p: &mut Printer, chained: bool) { { // Previous arguments. if !head.is_empty() { - p.push_str(" "); + p.push(' '); p.join(head, ", ", |item, p| item.pretty(p)); } @@ -458,12 +462,12 @@ pub fn pretty_func_template(call: &ExprCall, p: &mut Printer, chained: bool) { template.pretty(p); } } else if !call.args.v.is_empty() { - p.push_str(" "); + p.push(' '); call.args.v.pretty(p); } // Either end of header or end of body. - p.push_str("]"); + p.push(']'); } /// The arguments to a function: `12, draw: false`. @@ -508,7 +512,7 @@ pub struct ExprLet { impl Pretty for ExprLet { fn pretty(&self, p: &mut Printer) { p.push_str("#let "); - p.push_str(&self.pat.v); + self.pat.v.pretty(p); if let Some(init) = &self.init { p.push_str(" = "); init.v.pretty(p); @@ -531,7 +535,7 @@ impl Pretty for ExprIf { fn pretty(&self, p: &mut Printer) { p.push_str("#if "); self.condition.v.pretty(p); - p.push_str(" "); + p.push(' '); self.if_body.v.pretty(p); if let Some(expr) = &self.else_body { p.push_str(" #else "); @@ -557,7 +561,7 @@ impl Pretty for ExprFor { self.pat.v.pretty(p); p.push_str(" #in "); self.iter.v.pretty(p); - p.push_str(" "); + p.push(' '); self.body.v.pretty(p); } } @@ -574,11 +578,11 @@ pub enum ForPattern { impl Pretty for ForPattern { fn pretty(&self, p: &mut Printer) { match self { - Self::Value(v) => p.push_str(&v), + Self::Value(v) => v.pretty(p), Self::KeyValue(k, v) => { - p.push_str(&k); + k.pretty(p); p.push_str(", "); - p.push_str(&v); + v.pretty(p); } } } diff --git a/src/syntax/ident.rs b/src/syntax/ident.rs index 3cb47c47d..c4cc19bc4 100644 --- a/src/syntax/ident.rs +++ b/src/syntax/ident.rs @@ -2,6 +2,8 @@ use std::ops::Deref; use unicode_xid::UnicodeXID; +use crate::pretty::{Pretty, Printer}; + /// An Unicode identifier with a few extra permissible characters. /// /// In addition to what is specified in the [Unicode Standard][uax31], we allow: @@ -28,6 +30,12 @@ impl Ident { } } +impl Pretty for Ident { + fn pretty(&self, p: &mut Printer) { + p.push_str(self.as_str()); + } +} + impl AsRef for Ident { fn as_ref(&self) -> &str { self diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 409e8cbf2..2a8c4dbbd 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -64,9 +64,18 @@ mod tests { // Raw. roundtrip("``"); - roundtrip("`lang 1`"); - test("``` hi```", "`hi`"); - test("``` ` ```", "```"); + roundtrip("`nolang 1`"); + roundtrip("```lang 1```"); + roundtrip("```lang 1 ```"); + roundtrip("```hi line ```"); + roundtrip("```py\ndef\n```"); + roundtrip("```\n line \n```"); + roundtrip("```\n`\n```"); + roundtrip("``` ` ```"); + test("```1 ```", "``"); + test("``` 1```", "`1`"); + test("``` 1 ```", "`1 `"); + test("```` ` ````", "``` ` ```"); } #[test] @@ -77,12 +86,12 @@ mod tests { roundtrip("{true}"); roundtrip("{10}"); roundtrip("{3.14}"); - roundtrip("{10pt}"); + roundtrip("{10.0pt}"); roundtrip("{14.1deg}"); - roundtrip("{20%}"); + roundtrip("{20.0%}"); roundtrip("{#abcdef}"); roundtrip(r#"{"hi"}"#); - test(r#"{"let's go"}"#, r#"{"let\'s go"}"#); + test(r#"{"let's \" go"}"#, r#"{"let's \" go"}"#); // Arrays. roundtrip("{()}"); diff --git a/src/syntax/node.rs b/src/syntax/node.rs index f7625036e..7b6aa728f 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -26,9 +26,9 @@ pub enum Node { impl Pretty for Node { fn pretty(&self, p: &mut Printer) { match self { - Self::Strong => p.push_str("*"), - Self::Emph => p.push_str("_"), - Self::Space => p.push_str(" "), + Self::Strong => p.push('*'), + Self::Emph => p.push('_'), + Self::Space => p.push(' '), Self::Linebreak => p.push_str(r"\"), Self::Parbreak => p.push_str("\n\n"), Self::Text(text) => p.push_str(&text), @@ -46,10 +46,10 @@ impl Pretty for Node { } } -/// A section heading: `# Introduction`. +/// A section heading: `= Introduction`. #[derive(Debug, Clone, PartialEq)] pub struct NodeHeading { - /// The section depth (numer of hashtags minus 1, capped at 5). + /// The section depth (numer of equals signs minus 1, capped at 5). pub level: Spanned, /// The contents of the heading. pub contents: Tree, @@ -58,7 +58,7 @@ pub struct NodeHeading { impl Pretty for NodeHeading { fn pretty(&self, p: &mut Printer) { for _ in 0 ..= self.level.v { - p.push_str("="); + p.push('='); } self.contents.pretty(p); } @@ -67,8 +67,7 @@ impl Pretty for NodeHeading { /// A raw block with optional syntax highlighting: `` `raw` ``. /// /// Raw blocks start with 1 or 3+ backticks and end with the same number of -/// backticks. If you want to include a sequence of backticks in a raw block, -/// simply surround the block with more backticks. +/// backticks. /// /// When using at least three backticks, an optional language tag may follow /// directly after the backticks. This tag defines which language to @@ -86,7 +85,7 @@ impl Pretty for NodeHeading { /// ````typst /// ```rust println!("hello!")```; /// ```` -/// - Blocks can span multiple lines. +/// - Blocks can span multiple lines. /// ````typst /// ```rust /// loop { @@ -94,34 +93,40 @@ impl Pretty for NodeHeading { /// } /// ``` /// ```` -/// - Start with a space to omit the language tag (the space will be trimmed -/// from the output) and use more backticks to allow backticks in the raw -/// text. +/// - Start with a space to omit the language tag (the space will be trimmed +/// from the output). /// `````typst -/// ```` This contains ```backticks``` and has no leading & trailing spaces. ```` +/// ```` This has no leading space.```` +/// ````` +/// - Use more backticks to allow backticks in the raw text. +/// `````typst +/// ```` This contains ```backticks```.```` /// ````` /// -/// # Trimming -/// If we would always render the raw text between the backticks exactly as -/// given, a few things would become problematic or even impossible: -/// - Typical multiline code blocks (like in the example above) would have an -/// additional newline before and after the code. -/// - The first word of text wrapped in more than three backticks would always -/// be interpreted as a language tag which means that text without leading -/// space would be impossible. -/// - A single backtick without surrounding spaces could not exist as raw text -/// since it would be interpreted as belonging to the opening or closing -/// backticks. +/// # Trimming +/// If we would always render the raw text between the backticks exactly as +/// given, some things would become cumbersome/impossible to write: +/// - Typical multiline code blocks (like in the example above) would have an +/// additional newline before and after the code. +/// - Multi-line blocks would need to start with a space since a word would be +/// interpreted as a language tag. +/// - Text ending with a backtick would be impossible since the backtick would +/// be interpreted as belonging to the closing backticks. /// -/// To fix these problems, we trim blocks with 3+ backticks as follows: -/// - A single space or a sequence of whitespace followed by a newline at the start. -/// - A single space or a newline followed by a sequence of whitespace at the end. +/// To fix these problems, we sometimes trim a bit of space from blocks with 3+ +/// backticks: +/// - At the start, we trim a single space or a sequence of whitespace followed +/// by a newline. +/// - At the end, we trim +/// - a single space if the raw text ends with a backtick followed only by +/// whitespace, +/// - a newline followed by a sequence of whitespace. /// -/// With these rules, a single raw backtick can be produced by the sequence -/// ```` ``` ` ``` ````, ```` ``` unhighlighted text ``` ```` has no -/// surrounding spaces and multiline code blocks don't have extra empty lines. -/// Note that you can always force leading or trailing whitespace simply by -/// adding more spaces. +/// You can thus produce a single backtick without surrounding spaces with the +/// sequence ```` ``` ` ``` ````. +/// +/// Note that with these rules you can always force leading or trailing +/// whitespace simply by adding more spaces. #[derive(Debug, Clone, PartialEq)] pub struct NodeRaw { /// An optional identifier specifying the language to syntax-highlight in. @@ -129,28 +134,65 @@ pub struct NodeRaw { /// The lines of raw text, determined as the raw string between the /// backticks trimmed according to the above rules and split at newlines. pub lines: Vec, - /// Whether the element can be layouted inline. - /// - /// - When true, it will be layouted integrated within the surrounding - /// paragraph. - /// - When false, it will be separated into its own paragraph. - /// - /// Single-backtick blocks are always inline-level. Multi-backtick blocks - /// are inline-level when they contain no newlines. - pub inline: bool, + /// Whether the element is block-level, that is, it has 3+ backticks + /// and contains at least one newline. + pub block: 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(" "); + // 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; } - // TODO: Technically, we should handle backticks in the lines by - // wrapping with more backticks, and we should add space before the - // first and/or after the last line if necessary. + + // More backticks may be required if there are lots of consecutive + // backticks in the lines. + let mut count = 0; + for line in &self.lines { + for c in line.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.join(&self.lines, "\n", |line, p| p.push_str(line)); - p.push_str("`"); + + // End untrimming. + if self.block { + p.push('\n'); + } else if self.lines.last().map_or(false, |line| line.trim_end().ends_with('`')) { + p.push(' '); + } + + // Ending backticks. + for _ in 0 .. backticks { + p.push('`'); + } } } diff --git a/src/syntax/token.rs b/src/syntax/token.rs index c4b9ec8fd..5e69a350a 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -170,9 +170,9 @@ pub struct TokenRaw<'s> { pub struct TokenMath<'s> { /// The formula between the dollars. pub formula: &'s str, - /// Whether the formula was surrounded by one dollar (true) or two dollars - /// (false). - pub inline: bool, + /// Whether the formula is display-level, that is, it is surrounded by + /// `$[..]`. + pub display: bool, /// Whether the closing dollars were present. pub terminated: bool, } @@ -243,8 +243,8 @@ impl<'s> Token<'s> { Self::Bool(_) => "boolean", Self::Int(_) => "integer", Self::Float(_) => "float", - Self::Length(..) => "length", - Self::Angle(..) => "angle", + Self::Length(_, _) => "length", + Self::Angle(_, _) => "angle", Self::Percent(_) => "percentage", Self::Color(_) => "color", Self::Str(_) => "string", diff --git a/tests/lang/ref/raw.png b/tests/lang/ref/raw.png index 0da49c1b8a071c10804eab5a103fa90f05b361b5..fbf02760864556b8fc9856191e6a46cd8d6e4f6f 100644 GIT binary patch literal 24986 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 XxJTWG)aV2Lb72og*BD)>?Lhnws>c16 literal 6338 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 F{s%u)a;E?Q diff --git a/tests/lang/typ/raw.typ b/tests/lang/typ/raw.typ index 753f1a09c..a783216ea 100644 --- a/tests/lang/typ/raw.typ +++ b/tests/lang/typ/raw.typ @@ -9,6 +9,21 @@ `#let x = 1` \ `#[f 1]` +--- +// Multiline block splits paragraphs. + +First +``` +Second +``` +Third + +--- +// Lots of backticks inside. +```` +```backticks``` +```` + --- // Trimming. @@ -18,7 +33,9 @@ The keyword ```rust let```. // Trimming depends on number backticks. <``> \ <` untrimmed `> \ -<``` trimmed ```> +<``` trimmed` ```> \ +<``` trimmed ```> \ +<``` trimmed```> // Multiline trimming. ```py @@ -28,12 +45,6 @@ def hi(): print("Hi!") ``` ---- -// Lots of backticks inside. -```` -```backticks``` -```` - --- // Unterminated. // Error: 2:1-2:1 expected backtick(s) diff --git a/tests/typeset.rs b/tests/typeset.rs index 63831c229..807d55d9b 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -335,7 +335,7 @@ fn register_helpers(scope: &mut Scope, panics: Rc>>) { } value.pretty(p); }); - p.push_str(")"); + p.push(')'); Value::Str(p.finish()) }