From 105cda0e698fe86266d706f4e3bacc081e65c2aa Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 10 Jan 2021 21:38:58 +0100 Subject: [PATCH] =?UTF-8?q?Braced=20content=20->=20Bracketed=20templates?= =?UTF-8?q?=20=E2=9C=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/eval/mod.rs | 4 +- src/eval/value.rs | 42 +++++++++---------- src/library/layout.rs | 6 +-- src/library/style.rs | 2 +- src/parse/mod.rs | 96 ++++++++++++++++++++++--------------------- src/parse/tests.rs | 87 +++++++++++++++++++-------------------- src/parse/tokens.rs | 46 ++++++++++----------- src/prelude.rs | 4 +- src/syntax/expr.rs | 44 ++++++++------------ src/syntax/node.rs | 31 +++++++------- 10 files changed, 175 insertions(+), 187 deletions(-) diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 403fa58f9..efa74596e 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -170,7 +170,7 @@ impl Eval for Spanned<&Expr> { Expr::Binary(v) => v.with_span(self.span).eval(ctx), Expr::Array(v) => Value::Array(v.with_span(self.span).eval(ctx)), Expr::Dict(v) => Value::Dict(v.with_span(self.span).eval(ctx)), - Expr::Content(v) => Value::Content(v.clone()), + Expr::Template(v) => Value::Template(v.clone()), } } } @@ -275,7 +275,7 @@ fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value { (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)), + (Template(a), Template(b)) => Template(concat(a, b)), (a, b) => { ctx.diag(error!( diff --git a/src/eval/value.rs b/src/eval/value.rs index 67c468b06..d3b2688b0 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -8,7 +8,7 @@ use super::{Args, Eval, EvalContext}; use crate::color::Color; use crate::geom::{Angle, Length, Linear, Relative}; use crate::pretty::{pretty, Pretty, Printer}; -use crate::syntax::{pretty_content_expr, Spanned, Tree, WithSpan}; +use crate::syntax::{pretty_template_expr, Spanned, Tree, WithSpan}; /// A computational value. #[derive(Debug, Clone, PartialEq)] @@ -37,8 +37,8 @@ pub enum Value { Array(ValueArray), /// A dictionary value: `(color: #f79143, pattern: dashed)`. Dict(ValueDict), - /// A content value: `{*Hi* there}`. - Content(ValueContent), + /// A template value: `[*Hi* there]`. + Template(ValueTemplate), /// An executable function. Func(ValueFunc), /// Any object. @@ -71,7 +71,7 @@ impl Value { Self::Str(_) => String::TYPE_NAME, Self::Array(_) => ValueArray::TYPE_NAME, Self::Dict(_) => ValueDict::TYPE_NAME, - Self::Content(_) => ValueContent::TYPE_NAME, + Self::Template(_) => ValueTemplate::TYPE_NAME, Self::Func(_) => ValueFunc::TYPE_NAME, Self::Any(v) => v.type_name(), Self::Error => "error", @@ -87,7 +87,7 @@ impl Eval for &Value { ctx.push(ctx.make_text_node(match self { Value::None => return, Value::Str(s) => s.clone(), - Value::Content(tree) => return tree.eval(ctx), + Value::Template(tree) => return tree.eval(ctx), other => pretty(other), })); } @@ -114,7 +114,7 @@ impl Pretty for Value { 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::Template(template) => pretty_template_expr(template, p), Value::Func(v) => v.pretty(p), Value::Any(v) => v.pretty(p), Value::Error => p.push_str("(error)"), @@ -155,8 +155,8 @@ impl Pretty for ValueDict { } } -/// A content value: `{*Hi* there}`. -pub type ValueContent = Tree; +/// A template value: `[*Hi* there]`. +pub type ValueTemplate = Tree; /// A wrapper around a reference-counted executable function. #[derive(Clone)] @@ -407,28 +407,26 @@ macro_rules! impl_primitive { impl_primitive! { bool: "boolean", Value::Bool } impl_primitive! { i64: "integer", Value::Int } -impl_primitive! { Length: "length", Value::Length } -impl_primitive! { Angle: "angle", Value::Angle } -impl_primitive! { Relative: "relative", Value::Relative } -impl_primitive! { Color: "color", Value::Color } -impl_primitive! { String: "string", Value::Str } -impl_primitive! { ValueArray: "array", Value::Array } -impl_primitive! { ValueDict: "dictionary", Value::Dict } -impl_primitive! { ValueContent: "content", Value::Content } -impl_primitive! { ValueFunc: "function", Value::Func } - impl_primitive! { f64: "float", Value::Float, Value::Int(v) => v as f64, } - +impl_primitive! { Length: "length", Value::Length } +impl_primitive! { Angle: "angle", Value::Angle } +impl_primitive! { Relative: "relative", Value::Relative } impl_primitive! { Linear: "linear", Value::Linear, Value::Length(v) => v.into(), Value::Relative(v) => v.into(), } +impl_primitive! { Color: "color", Value::Color } +impl_primitive! { String: "string", Value::Str } +impl_primitive! { ValueArray: "array", Value::Array } +impl_primitive! { ValueDict: "dictionary", Value::Dict } +impl_primitive! { ValueTemplate: "template", Value::Template } +impl_primitive! { ValueFunc: "function", Value::Func } impl From<&str> for Value { fn from(v: &str) -> Self { @@ -512,7 +510,7 @@ mod tests { 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(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)"); @@ -528,8 +526,8 @@ mod tests { // Dictionary. let mut dict = BTreeMap::new(); dict.insert("one".into(), Value::Int(1)); - dict.insert("two".into(), Value::Content(parse("[f]").output)); + dict.insert("two".into(), Value::Template(parse("[f]").output)); test_pretty(BTreeMap::new(), "(:)"); - test_pretty(dict, "(one: 1, two: [f])"); + test_pretty(dict, "(one: 1, two: [[f]])"); } } diff --git a/src/library/layout.rs b/src/library/layout.rs index 774e67817..59c1954b6 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -101,7 +101,7 @@ pub fn align(ctx: &mut EvalContext, args: &mut Args) -> Value { ctx.start_par_group(); } - if let Some(body) = args.find::(ctx) { + if let Some(body) = args.find::(ctx) { body.eval(ctx); ctx.state = snapshot; } @@ -190,7 +190,7 @@ pub fn box_(ctx: &mut EvalContext, args: &mut Args) -> Value { ctx.start_content_group(); - if let Some(body) = args.find::(ctx) { + if let Some(body) = args.find::(ctx) { body.eval(ctx); } @@ -317,7 +317,7 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> Value { ctx.set_dirs(Gen::new(main, cross)); let mut softness = ctx.end_page_group(|_| false); - if let Some(body) = args.find::(ctx) { + if let Some(body) = args.find::(ctx) { // TODO: Restrict body to a single page? ctx.start_page_group(Softness::Hard); body.eval(ctx); diff --git a/src/library/style.rs b/src/library/style.rs index 3bdcdfd4b..7b2772ccf 100644 --- a/src/library/style.rs +++ b/src/library/style.rs @@ -95,7 +95,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> Value { } } - if let Some(body) = args.find::(ctx) { + if let Some(body) = args.find::(ctx) { body.eval(ctx); ctx.state = snapshot; } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index cce34d8af..ee7991590 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -49,32 +49,20 @@ fn tree(p: &mut Parser) -> Tree { /// Parse a syntax node. fn node(p: &mut Parser, at_start: bool) -> Option { let node = match p.peek()? { - Token::Text(text) => Node::Text(text.into()), - Token::Space(newlines) => { - if newlines < 2 { - Node::Space - } else { - Node::Parbreak - } - } - - Token::LineComment(_) | Token::BlockComment(_) => { - p.eat(); - return None; - } - + // Bracket call. Token::LeftBracket => { return Some(Node::Expr(Expr::Call(bracket_call(p)))); } + // Code block. Token::LeftBrace => { - return Some(Node::Expr(block_expr(p)?)); + return Some(Node::Expr(code_block(p)?)); } + // Markup. Token::Star => Node::Strong, Token::Underscore => Node::Emph, Token::Tilde => Node::Text("\u{00A0}".into()), - Token::Backslash => Node::Linebreak, Token::Hash => { if at_start { return Some(Node::Heading(heading(p))); @@ -82,9 +70,24 @@ fn node(p: &mut Parser, at_start: bool) -> Option { Node::Text(p.get(p.peek_span()).into()) } } + Token::Backslash => Node::Linebreak, + Token::Space(newlines) => { + if newlines < 2 { + Node::Space + } else { + Node::Parbreak + } + } + Token::Text(text) => Node::Text(text.into()), Token::Raw(t) => Node::Raw(raw(p, t)), Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)), + // Comments. + Token::LineComment(_) | Token::BlockComment(_) => { + p.eat(); + return None; + } + _ => { p.diag_unexpected(); return None; @@ -150,27 +153,6 @@ fn unicode_escape(p: &mut Parser, token: TokenUnicodeEscape) -> String { text } -/// Parse a block expression. -fn block_expr(p: &mut Parser) -> Option { - p.push_mode(TokenMode::Code); - p.start_group(Group::Brace); - let expr = expr(p); - while !p.eof() { - p.diag_unexpected(); - } - p.pop_mode(); - p.end_group(); - expr -} - -/// Parse a parenthesized function call. -fn paren_call(p: &mut Parser, name: Spanned) -> ExprCall { - p.start_group(Group::Paren); - let args = p.span(arguments); - p.end_group(); - ExprCall { name, args } -} - /// Parse a bracketed function call. fn bracket_call(p: &mut Parser) -> ExprCall { p.push_mode(TokenMode::Code); @@ -189,7 +171,7 @@ fn bracket_call(p: &mut Parser) -> ExprCall { p.end_group(); if p.peek() == Some(Token::LeftBracket) { - let body = p.span(|p| Expr::Content(bracket_body(p))); + let body = p.span(|p| Expr::Template(bracket_body(p))); inner.span.expand(body.span); inner.v.args.v.push(Argument::Pos(body)); } @@ -197,7 +179,7 @@ fn bracket_call(p: &mut Parser) -> ExprCall { while let Some(mut top) = outer.pop() { let span = inner.span; let node = inner.map(|c| Node::Expr(Expr::Call(c))); - let expr = Expr::Content(vec![node]).with_span(span); + let expr = Expr::Template(vec![node]).with_span(span); top.v.args.v.push(Argument::Pos(expr)); inner = top; } @@ -236,6 +218,19 @@ fn bracket_body(p: &mut Parser) -> Tree { tree } +/// Parse a code block: `{...}`. +fn code_block(p: &mut Parser) -> Option { + p.push_mode(TokenMode::Code); + p.start_group(Group::Brace); + let expr = expr(p); + while !p.eof() { + p.diag_unexpected(); + } + p.pop_mode(); + p.end_group(); + expr +} + /// Parse an expression: `term (+ term)*`. fn expr(p: &mut Parser) -> Option { binops(p, term, |token| match token { @@ -297,15 +292,14 @@ fn factor(p: &mut Parser) -> Option { /// Parse a value. fn value(p: &mut Parser) -> Option { let expr = match p.peek() { - // Bracketed function call. + // Template. Some(Token::LeftBracket) => { - let node = p.span(|p| Node::Expr(Expr::Call(bracket_call(p)))); - return Some(Expr::Content(vec![node])); + return Some(Expr::Template(template(p))); } - // Content expression. + // Nested block. Some(Token::LeftBrace) => { - return Some(Expr::Content(content(p))); + return code_block(p); } // Dictionary or just a parenthesized expression. @@ -346,16 +340,24 @@ fn value(p: &mut Parser) -> Option { Some(expr) } -// Parse a content value: `{...}`. -fn content(p: &mut Parser) -> Tree { +// Parse a template value: `[...]`. +fn template(p: &mut Parser) -> Tree { p.push_mode(TokenMode::Markup); - p.start_group(Group::Brace); + p.start_group(Group::Bracket); let tree = tree(p); p.pop_mode(); p.end_group(); tree } +/// Parse a parenthesized function call. +fn paren_call(p: &mut Parser, name: Spanned) -> ExprCall { + p.start_group(Group::Paren); + let args = p.span(arguments); + p.end_group(); + ExprCall { name, args } +} + /// Parse an identifier. fn ident(p: &mut Parser) -> Option { p.eat_map(|token| match token { diff --git a/src/parse/tests.rs b/src/parse/tests.rs index 3c57fa1f3..a8897c1cb 100644 --- a/src/parse/tests.rs +++ b/src/parse/tests.rs @@ -26,7 +26,7 @@ macro_rules! t { $(spans = $spans;)? let Pass { output, feedback } = parse($src); - check($src, Content![@$($node),*], output, spans); + check($src, Template![@$($node),*], output, spans); check( $src, vec![ @@ -158,9 +158,9 @@ macro_rules! Args { }; } -macro_rules! Content { +macro_rules! Template { (@$($node:expr),* $(,)?) => (vec![$(into!($node)),*]); - ($($tts:tt)*) => (Expr::Content(Content![@$($tts)*])); + ($($tts:tt)*) => (Expr::Template(Template![@$($tts)*])); } macro_rules! Call { @@ -227,30 +227,30 @@ fn test_parse_simple_nodes() { fn test_parse_headings() { // Basics with spans. t!("# a" - nodes: [S(0..3, Heading(S(0..1, 0), Content![ + nodes: [S(0..3, Heading(S(0..1, 0), Template![ @S(1..2, Space), S(2..3, Text("a")) ]))], spans: true); // Multiple hashtags. - t!("### three" Heading(2, Content![@Space, Text("three")])); - t!("###### six" Heading(5, Content![@Space, Text("six")])); + t!("### three" Heading(2, Template![@Space, Text("three")])); + t!("###### six" Heading(5, Template![@Space, Text("six")])); // Start of heading. - t!("/**/#" Heading(0, Content![@])); - t!("[f][# ok]" Call!("f", Args![Content![Heading(0, Content![ + t!("/**/#" Heading(0, Template![@])); + t!("[f][# ok]" Call!("f", Args![Template![Heading(0, Template![ @Space, Text("ok") ])]])); // End of heading. - t!("# a\nb" Heading(0, Content![@Space, Text("a")]), Space, Text("b")); + t!("# a\nb" Heading(0, Template![@Space, Text("a")]), Space, Text("b")); // Continued heading. - t!("# a{\n1\n}b" Heading(0, Content![ + t!("# a{\n1\n}b" Heading(0, Template![ @Space, Text("a"), Block(Int(1)), Text("b") ])); - t!("# a[f][\n\n]d" Heading(0, Content![@ - Space, Text("a"), Call!("f", Args![Content![Parbreak]]), Text("d"), + t!("# a[f][\n\n]d" Heading(0, Template![@ + Space, Text("a"), Call!("f", Args![Template![Parbreak]]), Text("d"), ])); // No heading. @@ -260,7 +260,7 @@ fn test_parse_headings() { // Too many hashtags. t!("####### seven" - nodes: [Heading(5, Content![@Space, Text("seven")])], + nodes: [Heading(5, Template![@Space, Text("seven")])], warnings: [S(0..7, "section depth should not exceed 6")]); } @@ -293,13 +293,9 @@ fn test_parse_escape_sequences() { #[test] fn test_parse_groups() { // Test paren group. - t!("{([v 1) + 3}" - nodes: [Block(Binary( - Content![Call!("v", Args![Int(1)])], - Add, - Int(3), - ))], - errors: [S(6..6, "expected closing bracket")]); + t!("{({1) + 3}" + nodes: [Block(Binary(Int(1), Add, Int(3)))], + errors: [S(4..4, "expected closing brace")]); // Test bracket group. t!("[)" @@ -307,19 +303,18 @@ fn test_parse_groups() { errors: [S(1..2, "expected function name, found closing paren"), S(2..2, "expected closing bracket")]); - t!("[v {*]_" - nodes: [Call!("v", Args![Content![Strong]]), Emph], - errors: [S(5..5, "expected closing brace")]); + t!("[v [*]" + nodes: [Call!("v", Args![Template![Strong]])], + errors: [S(6..6, "expected closing bracket")]); // Test brace group. t!("{1 + [}" - nodes: [Block(Binary(Int(1), Add, Content![Call!("")]))], - errors: [S(6..6, "expected function name"), - S(6..6, "expected closing bracket")]); + nodes: [Block(Binary(Int(1), Add, Template![]))], + errors: [S(6..6, "expected closing bracket")]); // Test subheader group. t!("[v (|u )]" - nodes: [Call!("v", Args![Array![], Content![Call!("u")]])], + nodes: [Call!("v", Args![Array![], Template![Call!("u")]])], errors: [S(4..4, "expected closing paren"), S(7..8, "expected expression, found closing paren")]); } @@ -331,7 +326,7 @@ fn test_parse_blocks() { // Function calls. t!("{f()}" Call!("f")); - t!("{[f]}" Block(Content![Call!("f")])); + t!("{[[f]]}" Block(Template![Call!("f")])); // Missing or bad value. t!("{}{1u}" @@ -353,15 +348,15 @@ fn test_parse_bracket_funcs() { t!("[ v ]" Call!("v")); // Body and no body. - t!("[v][[f]]" Call!("v", Args![Content![Call!("f")]])); - t!("[v][v][v]" Call!("v", Args![Content![Text("v")]]), Call!("v")); + t!("[v][[f]]" Call!("v", Args![Template![Call!("f")]])); + t!("[v][v][v]" Call!("v", Args![Template![Text("v")]]), Call!("v")); t!("[v] [f]" Call!("v"), Space, Call!("f")); // Spans. t!("[v 1][📐]" nodes: [S(0..11, Call!(S(1..2, "v"), S(3..4, Args![ S(3..4, Int(1)), - S(5..11, Content![S(6..10, Text("📐"))]), + S(5..11, Template![S(6..10, Text("📐"))]), ])))], spans: true); @@ -389,35 +384,35 @@ fn test_parse_bracket_funcs() { // Raw in body eats closing bracket. t!("[v][`a]`" - nodes: [Call!("v", Args![Content![Raw(None, &["a]"], true)]])], + nodes: [Call!("v", Args![Template![Raw(None, &["a]"], true)]])], errors: [S(8..8, "expected closing bracket")]); } #[test] fn test_parse_chaining() { // Basic. - t!("[a | b]" Call!("a", Args![Content![Call!("b")]])); - t!("[a|b|c]" Call!("a", Args![Content![ - Call!("b", Args![Content![Call!("c")]]) + t!("[a | b]" Call!("a", Args![Template![Call!("b")]])); + t!("[a|b|c]" Call!("a", Args![Template![ + Call!("b", Args![Template![Call!("c")]]) ]])); // With body and spans. t!("[a|b][💕]" nodes: [S(0..11, Call!(S(1..2, "a"), S(2..2, Args![ - S(3..11, Content![S(3..11, Call!(S(3..4, "b"), S(4..4, Args![ - S(5..11, Content![S(6..10, Text("💕"))]) + S(3..11, Template![S(3..11, Call!(S(3..4, "b"), S(4..4, Args![ + S(5..11, Template![S(6..10, Text("💕"))]) ])))]) ])))], spans: true); // No name in second subheader. t!("[a 1|]" - nodes: [Call!("a", Args![Int(1), Content![Call!("")]])], + nodes: [Call!("a", Args![Int(1), Template![Call!("")]])], errors: [S(5..5, "expected function name")]); // No name in first subheader. t!("[|a true]" - nodes: [Call!("", Args![Content![Call!("a", Args![Bool(true)])]])], + nodes: [Call!("", Args![Template![Call!("a", Args![Bool(true)])]])], errors: [S(1..1, "expected function name")]); } @@ -534,12 +529,12 @@ fn test_parse_dictionaries() { errors: [S(8..9, "expected named pair, found expression")]); // Dictionary marker followed by more stuff. - t!("{(:1 b:2, true::)}" - nodes: [Block(Dict!["b" => Int(2)])], + t!("{(:1 b:[], true::)}" + nodes: [Block(Dict!["b" => Template![]])], errors: [S(3..4, "expected named pair, found expression"), S(4..4, "expected comma"), - S(10..14, "name must be identifier"), - S(15..16, "expected expression, found colon")]); + S(11..15, "name must be identifier"), + S(16..17, "expected expression, found colon")]); } #[test] @@ -615,7 +610,11 @@ fn test_parse_values() { errors: [S(1..4, "invalid color")]); // Content. - t!("{{*Hi*}}" Block(Content![Strong, Text("Hi"), Strong])); + t!("{[*Hi*]}" Block(Template![Strong, Text("Hi"), Strong])); + + // Nested blocks. + t!("{{1}}" Block(Int(1))); + t!("{{{1+2}}}" Block(Binary(Int(1), Add, Int(2)))); // Invalid tokens. t!("{1u}" diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index 28ca6e484..d16cf2cee 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -562,7 +562,7 @@ mod tests { } #[test] - fn test_tokenize_body_symbols() { + fn test_tokenize_markup_symbols() { // Test markup tokens. t!(Markup[" a1"]: "*" => Star); t!(Markup: "_" => Underscore); @@ -572,7 +572,7 @@ mod tests { } #[test] - fn test_tokenize_header_symbols() { + fn test_tokenize_code_symbols() { // Test all symbols. t!(Code: "," => Comma); t!(Code: ":" => Colon); @@ -653,6 +653,27 @@ mod tests { t!(Code[" /"]: "falser" => Ident("falser")); } + #[test] + fn test_tokenize_whitespace() { + // Test basic whitespace. + t!(Both["a1/"]: "" => ); + t!(Both["a1/"]: " " => Space(0)); + t!(Both["a1/"]: " " => Space(0)); + t!(Both["a1/"]: "\t" => Space(0)); + t!(Both["a1/"]: " \t" => Space(0)); + t!(Both["a1/"]: "\u{202F}" => Space(0)); + + // Test newline counting. + t!(Both["a1/"]: "\n" => Space(1)); + t!(Both["a1/"]: "\n " => Space(1)); + t!(Both["a1/"]: " \n" => Space(1)); + t!(Both["a1/"]: " \n " => Space(1)); + t!(Both["a1/"]: "\r\n" => Space(1)); + t!(Both["a1/"]: " \n\t \n " => Space(2)); + t!(Both["a1/"]: "\n\r" => Space(2)); + t!(Both["a1/"]: " \r\r\n \x0D" => Space(3)); + } + #[test] fn test_tokenize_text() { // Test basic text. @@ -738,27 +759,6 @@ mod tests { t!(Markup: r"\u{1🏕}" => UnicodeEscape("1", false), Text("🏕"), RightBrace); } - #[test] - fn test_tokenize_whitespace() { - // Test basic whitespace. - t!(Both["a1/"]: "" => ); - t!(Both["a1/"]: " " => Space(0)); - t!(Both["a1/"]: " " => Space(0)); - t!(Both["a1/"]: "\t" => Space(0)); - t!(Both["a1/"]: " \t" => Space(0)); - t!(Both["a1/"]: "\u{202F}" => Space(0)); - - // Test newline counting. - t!(Both["a1/"]: "\n" => Space(1)); - t!(Both["a1/"]: "\n " => Space(1)); - t!(Both["a1/"]: " \n" => Space(1)); - t!(Both["a1/"]: " \n " => Space(1)); - t!(Both["a1/"]: "\r\n" => Space(1)); - t!(Both["a1/"]: " \n\t \n " => Space(2)); - t!(Both["a1/"]: "\n\r" => Space(2)); - t!(Both["a1/"]: " \r\r\n \x0D" => Space(3)); - } - #[test] fn test_tokenize_idents() { // Test valid identifiers. diff --git a/src/prelude.rs b/src/prelude.rs index 1db0d5b00..9856a8377 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -3,8 +3,8 @@ pub use crate::diag::{Feedback, Pass}; #[doc(no_inline)] pub use crate::eval::{ - Args, CastResult, Eval, EvalContext, Value, ValueAny, ValueArray, ValueContent, - ValueDict, + Args, CastResult, Eval, EvalContext, Value, ValueAny, ValueArray, ValueDict, + ValueTemplate, }; pub use crate::geom::*; #[doc(no_inline)] diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 834c4393e..cb09041ce 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -38,8 +38,8 @@ pub enum Expr { Array(ExprArray), /// A dictionary expression: `(color: #f79143, pattern: dashed)`. Dict(ExprDict), - /// A content expression: `{*Hello* there!}`. - Content(ExprContent), + /// A template expression: `[*Hi* there!]`. + Template(ExprTemplate), } impl Pretty for Expr { @@ -60,24 +60,16 @@ impl Pretty for Expr { 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), + Self::Template(template) => pretty_template_expr(template, 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("}"); - } +/// Pretty print a template in an expression context. +pub fn pretty_template_expr(tree: &Tree, p: &mut Printer) { + p.push_str("["); + tree.pretty(p); + p.push_str("]"); } /// An invocation of a function: `[foo ...]`, `foo(...)`. @@ -111,8 +103,8 @@ pub fn pretty_bracket_call(call: &ExprCall, p: &mut Printer, chained: bool) { // Find out whether this can be written with a body or as a chain. // - // Example: Transforms "[v {Hi}]" => "[v][Hi]". - if let [head @ .., Argument::Pos(Spanned { v: Expr::Content(content), .. })] = + // Example: Transforms "[v [Hi]]" => "[v][Hi]". + if let [head @ .., Argument::Pos(Spanned { v: Expr::Template(template), .. })] = call.args.v.as_slice() { // Previous arguments. @@ -124,11 +116,11 @@ pub fn pretty_bracket_call(call: &ExprCall, p: &mut Printer, chained: bool) { // 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() { + if let [Spanned { v: Node::Expr(Expr::Call(call)), .. }] = template.as_slice() { return pretty_bracket_call(call, p, true); } else { p.push_str("]["); - content.pretty(p); + template.pretty(p); } } else if !call.args.v.is_empty() { p.push_str(" "); @@ -291,8 +283,8 @@ impl Pretty for ExprDict { } } -/// A content expression: `{*Hello* there!}`. -pub type ExprContent = Tree; +/// A template expression: `[*Hi* there!]`. +pub type ExprTemplate = Tree; #[cfg(test)] mod tests { @@ -301,8 +293,7 @@ mod tests { #[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_pretty("[v | f]", "[v | f]"); } @@ -321,9 +312,8 @@ mod tests { 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])"); + // Content expression. + test_pretty("[v [[f]], 1]", "[v [[f]], 1]"); } #[test] diff --git a/src/syntax/node.rs b/src/syntax/node.rs index 01b4ee42a..e7091a404 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -3,18 +3,18 @@ use super::*; /// A syntax node, encompassing a single logical entity of parsed source code. #[derive(Debug, Clone, PartialEq)] pub enum Node { - /// Plain text. - Text(String), + /// Strong text was enabled / disabled. + Strong, + /// Emphasized text was enabled / disabled. + Emph, /// Whitespace containing less than two newlines. Space, /// A forced line break. Linebreak, /// A paragraph break. Parbreak, - /// Strong text was enabled / disabled. - Strong, - /// Emphasized text was enabled / disabled. - Emph, + /// Plain text. + Text(String), /// A section heading. Heading(NodeHeading), /// An optionally syntax-highlighted raw block. @@ -26,12 +26,12 @@ pub enum Node { impl Pretty for Node { fn pretty(&self, p: &mut Printer) { match self { - Self::Text(text) => p.push_str(&text), + Self::Strong => p.push_str("*"), + Self::Emph => p.push_str("_"), 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::Text(text) => p.push_str(&text), Self::Heading(heading) => heading.pretty(p), Self::Raw(raw) => raw.pretty(p), Self::Expr(expr) => pretty_expr_node(expr, p), @@ -50,8 +50,8 @@ pub fn pretty_expr_node(expr: &Expr, p: &mut Printer) { // Remove unncessary nesting of content and expression blocks. // - // Example: Transforms "{{Hi}}" => "Hi". - Expr::Content(content) => content.pretty(p), + // Example: Transforms "{[Hi]}" => "Hi". + Expr::Template(template) => template.pretty(p), _ => { p.push_str("{"); @@ -179,9 +179,9 @@ mod tests { #[test] fn test_pretty_print_removes_nesting() { - // Even levels of nesting do not matter. - test_pretty("{{Hi}}", "Hi"); - test_pretty("{{{{Hi}}}}", "Hi"); + // Nesting does not matter. + test_pretty("{{x}}", "{x}"); + test_pretty("{{{x}}}", "{x}"); } #[test] @@ -189,8 +189,7 @@ mod tests { // All reduces to a simple bracket call. test_pretty("{v()}", "[v]"); test_pretty("[v]", "[v]"); - test_pretty("{[v]}", "[v]"); - test_pretty("{{[v]}}", "[v]"); + test_pretty("{[[v]]}", "[v]"); } #[test]