diff --git a/src/layout/tree.rs b/src/layout/tree.rs index f039d9b09..714cfe274 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -63,6 +63,10 @@ impl<'a> TreeLayouter<'a> { match &node.v { SyntaxNode::Spacing => self.layout_space(), SyntaxNode::Linebreak => self.layouter.finish_line(), + SyntaxNode::Parbreak => self.layouter.add_secondary_spacing( + self.style.text.paragraph_spacing(), + SpacingKind::PARAGRAPH, + ), SyntaxNode::ToggleItalic => { self.style.text.italic = !self.style.text.italic; @@ -81,7 +85,7 @@ impl<'a> TreeLayouter<'a> { SyntaxNode::Raw(lines) => self.layout_raw(lines).await, SyntaxNode::CodeBlock(block) => self.layout_code(block).await, - SyntaxNode::Par(par) => self.layout_par(par).await, + SyntaxNode::Call(call) => { self.layout_call(Spanned::new(call, node.span)).await; } @@ -144,14 +148,6 @@ impl<'a> TreeLayouter<'a> { self.style.text.fallback = fallback; } - async fn layout_par(&mut self, par: &SyntaxTree) { - self.layout_tree(par).await; - self.layouter.add_secondary_spacing( - self.style.text.paragraph_spacing(), - SpacingKind::PARAGRAPH, - ); - } - async fn layout_call(&mut self, call: Spanned<&CallExpr>) { let ctx = LayoutContext { style: &self.style, diff --git a/src/library/align.rs b/src/library/align.rs index 14692ecaf..55e0f65e7 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -5,16 +5,19 @@ use super::*; /// # Positional arguments /// - At most two of `left`, `right`, `top`, `bottom`, `center`. /// +/// When `center` is used as a positional argument, it is automatically inferred +/// which axis it should apply to depending on further arguments, defaulting +/// to the axis, text is set along. +/// /// # Keyword arguments /// - `horizontal`: Any of `left`, `right` or `center`. /// - `vertical`: Any of `top`, `bottom` or `center`. /// /// There may not be two alignment specifications for the same axis. -pub async fn align(_: Span, mut args: TableValue, mut ctx: LayoutContext<'_>) -> Pass { +pub async fn align(_: Span, mut args: TableValue, ctx: LayoutContext<'_>) -> Pass { let mut f = Feedback::new(); let content = args.take::(); - let h = args.take_key::>("horizontal", &mut f); let v = args.take_key::>("vertical", &mut f); let all = args @@ -23,40 +26,69 @@ pub async fn align(_: Span, mut args: TableValue, mut ctx: LayoutContext<'_>) -> .chain(h.into_iter().map(|align| (Some(Horizontal), align))) .chain(v.into_iter().map(|align| (Some(Vertical), align))); + let mut aligns = ctx.align; let mut had = [false; 2]; - for (axis, align) in all { - let axis = axis.unwrap_or_else(|| align.v.axis().unwrap_or_else(|| { - let primary = ctx.axes.primary.axis(); - if !had[primary as usize] { - primary - } else { - ctx.axes.secondary.axis() - } - })); + let mut deferred_center = false; - if align.v.axis().map(|a| a != axis).unwrap_or(false) { - error!( - @f, align.span, - "invalid alignment {} for {} axis", align.v, axis, - ); - } else if had[axis as usize] { - error!(@f, align.span, "duplicate alignment for {} axis", axis); + for (axis, align) in all { + // Check whether we know which axis this alignment belongs to. We don't + // if the alignment is `center` for a positional argument. Then we set + // `deferred_center` to true and handle the situation once we know more. + if let Some(axis) = axis { + if align.v.axis().map(|a| a != axis).unwrap_or(false) { + error!( + @f, align.span, + "invalid alignment {} for {} axis", align.v, axis, + ); + } else if had[axis as usize] { + error!(@f, align.span, "duplicate alignment for {} axis", axis); + } else { + let gen_align = align.v.to_generic(ctx.axes); + *aligns.get_mut(axis.to_generic(ctx.axes)) = gen_align; + had[axis as usize] = true; + } } else { + if had == [true, true] { + error!(@f, align.span, "duplicate alignment"); + } else if deferred_center { + // We have two unflushed centers, meaning we know that both axes + // are to be centered. + had = [true, true]; + aligns = LayoutAlign::new(Center, Center); + } else { + deferred_center = true; + } + } + + // Flush a deferred center alignment if we know have had at least one + // known alignment. + if deferred_center && had != [false, false] { + let axis = if !had[Horizontal as usize] { + Horizontal + } else { + Vertical + }; + + *aligns.get_mut(axis.to_generic(ctx.axes)) = Center; + had[axis as usize] = true; - let gen_axis = axis.to_generic(ctx.axes); - let gen_align = align.v.to_generic(ctx.axes); - *ctx.align.get_mut(gen_axis) = gen_align; + deferred_center = false; } } + // If center has not been flushed by known, it is the only argument and then + // we default to applying it to the primary axis. + if deferred_center { + aligns.primary = Center; + } + let commands = match content { - Some(tree) => { - ctx.base = ctx.spaces[0].size; - let layouted = layout(&tree, ctx).await; - f.extend(layouted.feedback); - vec![AddMultiple(layouted.output)] - } - None => vec![SetAlignment(ctx.align)], + Some(tree) => vec![ + SetAlignment(aligns), + LayoutSyntaxTree(tree), + SetAlignment(ctx.align), + ], + None => vec![SetAlignment(aligns)], }; args.unexpected(&mut f); diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index e9bbf2e58..0e6fba5e6 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -50,27 +50,20 @@ impl<'s> Parser<'s> { impl Parser<'_> { fn parse_body_contents(&mut self) -> SyntaxTree { let mut tree = SyntaxTree::new(); - let mut par = SyntaxTree::new(); while let Some(token) = self.peek() { - par.push(match token.v { + tree.push(match token.v { // Starting from two newlines counts as a paragraph break, a single // newline does not. - Token::Space(newlines) => if newlines < 2 { - self.with_span(SyntaxNode::Spacing) + Token::Space(newlines) => self.with_span(if newlines < 2 { + SyntaxNode::Spacing } else { - // End the current paragraph if it is not empty. - if let (Some(first), Some(last)) = (par.first(), par.last()) { - let span = Span::merge(first.span, last.span); - let node = SyntaxNode::Par(std::mem::take(&mut par)); - tree.push(Spanned::new(node, span)); - } - self.eat(); - continue; - } + SyntaxNode::Parbreak + }), + Token::LineComment(_) | Token::BlockComment(_) => { self.eat(); - continue + continue; } Token::LeftBracket => { @@ -134,12 +127,6 @@ impl Parser<'_> { }); } - if let (Some(first), Some(last)) = (par.first(), par.last()) { - let span = Span::merge(first.span, last.span); - let node = SyntaxNode::Par(par); - tree.push(Spanned::new(node, span)); - } - tree } } @@ -753,6 +740,7 @@ mod tests { use SyntaxNode::{ Spacing as S, Linebreak as L, + Parbreak as P, ToggleItalic as I, ToggleBolder as B, }; @@ -765,6 +753,7 @@ mod tests { }; } + fn Lang(text: &str) -> Option> { Some(Spanned::zero(Ident(text.to_string()))) } macro_rules! C { @@ -772,11 +761,7 @@ mod tests { SyntaxNode::CodeBlock(CodeBlockExpr { raw: vec![$($line.to_string()) ,*], lang: $lang }) }; } - - macro_rules! P { - ($($tts:tt)*) => { SyntaxNode::Par(Tree![@$($tts)*]) }; - } - + macro_rules! F { ($($tts:tt)*) => { SyntaxNode::Call(Call!(@$($tts)*)) } } @@ -860,7 +845,7 @@ mod tests { // Test expressions. macro_rules! v { ($src:expr => $($tts:tt)*) => { - t!(concat!("[val: ", $src, "]") => P![F!("val"; $($tts)*)]); + t!(concat!("[val: ", $src, "]") => F!("val"; $($tts)*)); } } @@ -945,16 +930,17 @@ mod tests { #[test] fn test_parse_simple_nodes() { t!("" => ); - t!("hi" => P![T("hi")]); - t!("*hi" => P![B, T("hi")]); - t!("hi_" => P![T("hi"), I]); - t!("hi you" => P![T("hi"), S, T("you")]); - t!("\n\n\nhello" => P![T("hello")]); - t!(r"a\ b" => P![T("a"), L, S, T("b")]); - t!("`py`" => P![R!["py"]]); - t!("`hi\nyou" => P![R!["hi", "you"]]); + t!("hi" => T("hi")); + t!("*hi" => B, T("hi")); + t!("hi_" => T("hi"), I); + t!("hi you" => T("hi"), S, T("you")); + t!("\n\n\nhello" => P, T("hello")); + t!(r"a\ b" => T("a"), L, S, T("b")); + t!("`py`" => R!["py"]); + t!("`hi\nyou" => R!["hi", "you"]); e!("`hi\nyou" => s(1,3, 1,3, "expected backtick")); - t!("`hi\\`du`" => P![R!["hi`du"]]); + t!("`hi\\`du`" => R!["hi`du"]); + t!("```java System.out.print```" => P![ C![Lang("java"), "System.out.print"] ]); @@ -968,28 +954,23 @@ mod tests { e!("```🌍 hi\nyou```" => s(0,3, 0,4, "expected language to be a valid identifier")); t!("💜\n\n 🌍" => P![T("💜")], P![T("🌍")]); - ts!("hi" => s(0,0, 0,2, P![s(0,0, 0,2, T("hi"))])); - ts!("*Hi*" => s(0,0, 0,4, P![ - s(0,0, 0,1, B), s(0,1, 0,3, T("Hi")), s(0,3, 0,4, B), - ])); - ts!("💜\n\n 🌍" => - s(0,0, 0,1, P![s(0,0, 0,1, T("💜"))]), - s(2,1, 2,2, P![s(2,1, 2,2, T("🌍"))]), - ); + ts!("hi" => s(0,0, 0,2, T("hi"))); + ts!("*Hi*" => s(0,0, 0,1, B), s(0,1, 0,3, T("Hi")), s(0,3, 0,4, B)); + ts!("💜\n\n 🌍" => s(0,0, 0,1, T("💜")), s(0,1, 2,1, P), s(2,1, 2,2, T("🌍"))); } #[test] fn test_parse_comments() { // In body. - t!("hi// you\nw" => P![T("hi"), S, T("w")]); - t!("first//\n//\nsecond" => P![T("first"), S, S, T("second")]); - t!("first//\n \nsecond" => P![T("first")], P![T("second")]); - t!("first/*\n \n*/second" => P![T("first"), T("second")]); + t!("hi// you\nw" => T("hi"), S, T("w")); + t!("first//\n//\nsecond" => T("first"), S, S, T("second")); + t!("first//\n \nsecond" => T("first"), P, T("second")); + t!("first/*\n \n*/second" => T("first"), T("second")); e!("🌎\n*/n" => s(1,0, 1,2, "unexpected end of block comment")); // In header. - t!("[val:/*12pt*/]" => P![F!("val")]); - t!("[val \n /* \n */:]" => P![F!("val")]); + t!("[val:/*12pt*/]" => F!("val")); + t!("[val \n /* \n */:]" => F!("val")); e!("[val \n /* \n */:]" => ); e!("[val : 12, /* \n */ 14]" => ); } @@ -1006,7 +987,7 @@ mod tests { #[test] fn test_parse_function_names() { // No closing bracket. - t!("[" => P![F!("")]); + t!("[" => F!("")); e!("[" => s(0,1, 0,1, "expected function name"), s(0,1, 0,1, "expected closing bracket")); @@ -1016,8 +997,8 @@ mod tests { s(0,3, 0,3, "expected closing bracket")); // A valid name. - t!("[hi]" => P![F!("hi")]); - t!("[ f]" => P![F!("f")]); + t!("[hi]" => F!("hi")); + t!("[ f]" => F!("f")); // An invalid name. e!("[12]" => s(0,1, 0,3, "expected function name, found number")); @@ -1025,12 +1006,16 @@ mod tests { } #[test] - fn test_parse_subgroups() { + fn test_parse_chaining() { // Things the parser has to make sense of - t!("[hi: (5.0, 2.1 >> you]" => P![F!("hi"; Table![Num(5.0), Num(2.1)], Tree![F!("you")])]); - t!("[bold: 400, >> emph >> sub: 1cm]" => P![F!("bold"; Num(400.0), Tree![F!("emph"; Tree!(F!("sub"; Len(Length::cm(1.0)))))])]); - t!("[box >> pad: 1pt][Hi]" => P![F!("box"; Tree![F!("pad"; Len(Length::pt(1.0)), Tree!(P![T("Hi")]))])]); - t!("[box >>][Hi]" => P![F!("box"; Tree![P![T("Hi")]])]); + t!("[hi: (5.0, 2.1 >> you]" => F!("hi"; Table![Num(5.0), Num(2.1)], Tree![F!("you")])); + t!("[box >>][Hi]" => F!("box"; Tree![T("Hi")])); + t!("[box >> pad: 1pt][Hi]" => F!("box"; Tree![ + F!("pad"; Len(Length::pt(1.0)), Tree!(T("Hi"))) + ])); + t!("[bold: 400, >> emph >> sub: 1cm]" => F!("bold"; Num(400.0), Tree![ + F!("emph"; Tree!(F!("sub"; Len(Length::cm(1.0))))) + ])); // Errors for unclosed / empty predecessor groups e!("[hi: (5.0, 2.1 >> you]" => s(0, 15, 0, 15, "expected closing paren")); @@ -1043,7 +1028,7 @@ mod tests { e!("[val:]" => ); // Wrong token. - t!("[val=]" => P![F!("val")]); + t!("[val=]" => F!("val")); e!("[val=]" => s(0,4, 0,4, "expected colon")); e!("[val/🌎:$]" => s(0,4, 0,4, "expected colon")); @@ -1056,27 +1041,25 @@ mod tests { #[test] fn test_parse_function_bodies() { - t!("[val: 1][*Hi*]" => P![F!("val"; Num(1.0), Tree![P![B, T("Hi"), B]])]); + t!("[val: 1][*Hi*]" => F!("val"; Num(1.0), Tree![B, T("Hi"), B])); e!(" [val][ */ ]" => s(0,8, 0,10, "unexpected end of block comment")); // Raw in body. - t!("[val][`Hi]`" => P![F!("val"; Tree![P![R!["Hi]"]]])]); + t!("[val][`Hi]`" => F!("val"; Tree![R!["Hi]"]])); e!("[val][`Hi]`" => s(0,11, 0,11, "expected closing bracket")); // Crazy. - t!("[v][[v][v][v]]" => P![F!("v"; Tree![P![ - F!("v"; Tree![P![T("v")]]), F!("v") - ]])]); + t!("[v][[v][v][v]]" => F!("v"; Tree![F!("v"; Tree![T("v")]), F!("v")])); // Spanned. - ts!(" [box][Oh my]" => s(0,0, 0,13, P![ + ts!(" [box][Oh my]" => s(0,0, 0,1, S), s(0,1, 0,13, F!(s(0,2, 0,5, "box"); - s(0,6, 0,13, Tree![s(0,7, 0,12, P![ + s(0,6, 0,13, Tree![ s(0,7, 0,9, T("Oh")), s(0,9, 0,10, S), s(0,10, 0,12, T("my")) - ])]) + ]) )) - ])); + ); } #[test] @@ -1097,7 +1080,7 @@ mod tests { v!("\"a\n[]\\\"string\"" => Str("a\n[]\"string")); // Content. - v!("{_hi_}" => Tree![P![I, T("hi"), I]]); + v!("{_hi_}" => Tree![I, T("hi"), I]); e!("[val: {_hi_}]" => ); v!("[hi]" => Tree![F!["hi"]]); e!("[val: [hi]]" => ); @@ -1115,9 +1098,7 @@ mod tests { s(0,13, 0,13, "expected closing bracket")); // Spanned. - ts!("[val: 1.4]" => s(0,0, 0,10, P![ - s(0,0, 0,10, F!(s(0,1, 0,4, "val"); s(0,6, 0,9, Num(1.4)))) - ])); + ts!("[val: 1.4]" => s(0,0, 0,10, F!(s(0,1, 0,4, "val"); s(0,6, 0,9, Num(1.4))))); } #[test] @@ -1147,17 +1128,15 @@ mod tests { v!("3/4*5" => Mul(Div(Num(3.0), Num(4.0)), Num(5.0))); // Spanned. - ts!("[val: 1 + 3]" => s(0,0, 0,12, P![s(0,0, 0,12, F!( + ts!("[val: 1 + 3]" => s(0,0, 0,12, F!( s(0,1, 0,4, "val"); s(0,6, 0,11, Add( s(0,6, 0,7, Num(1.0)), s(0,10, 0,11, Num(3.0)), )) - ))])); + ))); // Span of parenthesized expression contains parens. - ts!("[val: (1)]" => s(0,0, 0,10, P![ - s(0,0, 0,10, F!(s(0,1, 0,4, "val"); s(0,6, 0,9, Num(1.0)))) - ])); + ts!("[val: (1)]" => s(0,0, 0,10, F!(s(0,1, 0,4, "val"); s(0,6, 0,9, Num(1.0))))); // Invalid expressions. v!("4pt--" => Len(Length::pt(4.0))); @@ -1184,12 +1163,10 @@ mod tests { d!("[val: f(key=hi)]" => s(0,8, 0,11, TableKey)); // Spanned with spacing around keyword arguments. - ts!("[val: \n hi \n = /* //\n */ \"s\n\"]" => s(0,0, 4,2, P![ - s(0,0, 4,2, F!( - s(0,1, 0,4, "val"); - s(1,1, 1,3, "hi") => s(3,4, 4,1, Str("s\n")) - )) - ])); + ts!("[val: \n hi \n = /* //\n */ \"s\n\"]" => s(0,0, 4,2, F!( + s(0,1, 0,4, "val"); + s(1,1, 1,3, "hi") => s(3,4, 4,1, Str("s\n")) + ))); e!("[val: \n hi \n = /* //\n */ \"s\n\"]" => ); } diff --git a/src/syntax/tree.rs b/src/syntax/tree.rs index ace5ad8ec..313e76a4a 100644 --- a/src/syntax/tree.rs +++ b/src/syntax/tree.rs @@ -23,6 +23,8 @@ pub enum SyntaxNode { Spacing, /// A forced line break. Linebreak, + /// A paragraph break. + Parbreak, /// Italics were enabled / disabled. ToggleItalic, /// Bolder was enabled / disabled. @@ -33,8 +35,6 @@ pub enum SyntaxNode { Raw(Vec), /// An optionally highlighted multi-line code block. CodeBlock(CodeBlockExpr), - /// A paragraph of child nodes. - Par(SyntaxTree), /// A function call. Call(CallExpr), } diff --git a/tests/coma.typ b/tests/coma.typ index 404a81b30..3a886699c 100644 --- a/tests/coma.typ +++ b/tests/coma.typ @@ -7,7 +7,7 @@ Dr. Max Mustermann \ Ola Nordmann, John Doe ] -[align: right][ +[align: right >> box][ *WiSe 2019/2020* \ Woche 3 ]