diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 4483ed765..5fdaa4ce0 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -34,11 +34,9 @@ fn tree(p: &mut Parser) -> Tree { let mut at_start = true; let mut tree = vec![]; while !p.eof() { - if let Some(node) = p.span_if(|p| node(p, at_start)) { - match node.v { - Node::Parbreak => at_start = true, - Node::Space => {} - _ => at_start = false, + if let Some(node) = p.span_if(|p| node(p, &mut at_start)) { + if !matches!(node.v, Node::Parbreak | Node::Space) { + at_start = false; } tree.push(node); } @@ -47,7 +45,7 @@ fn tree(p: &mut Parser) -> Tree { } /// Parse a syntax node. -fn node(p: &mut Parser, at_start: bool) -> Option { +fn node(p: &mut Parser, at_start: &mut bool) -> Option { let node = match p.peek()? { // Bracket call. Token::LeftBracket => { @@ -64,7 +62,7 @@ fn node(p: &mut Parser, at_start: bool) -> Option { Token::Underscore => Node::Emph, Token::Tilde => Node::Text("\u{00A0}".into()), Token::Hash => { - if at_start { + if *at_start { return Some(Node::Heading(heading(p))); } else { Node::Text(p.get(p.peek_span()).into()) @@ -72,11 +70,8 @@ fn node(p: &mut Parser, at_start: bool) -> Option { } Token::Backslash => Node::Linebreak, Token::Space(newlines) => { - if newlines < 2 { - Node::Space - } else { - Node::Parbreak - } + *at_start |= newlines > 0; + if newlines < 2 { Node::Space } else { Node::Parbreak } } Token::Text(text) => Node::Text(text.into()), Token::Raw(t) => Node::Raw(raw(p, t)), @@ -122,8 +117,8 @@ fn heading(p: &mut Parser) -> NodeHeading { // Parse the heading contents. let mut contents = vec![]; - while p.check(|t| !matches!(t, Token::Space(n) if n >= 1)) { - if let Some(node) = p.span_if(|p| node(p, false)) { + while p.check(|t| !matches!(t, Token::Space(n) if n > 0)) { + if let Some(node) = p.span_if(|p| node(p, &mut false)) { contents.push(node); } } diff --git a/src/parse/tests.rs b/src/parse/tests.rs index 2e7a2af36..f8b9dcbb1 100644 --- a/src/parse/tests.rs +++ b/src/parse/tests.rs @@ -87,10 +87,6 @@ fn Text(text: &str) -> Node { Node::Text(text.into()) } -fn Heading(level: impl Into>, contents: Tree) -> Node { - Node::Heading(NodeHeading { level: level.into(), contents }) -} - fn Raw(lang: Option<&str>, lines: &[&str], inline: bool) -> Node { Node::Raw(NodeRaw { lang: lang.map(|id| Ident(id.into())), @@ -246,47 +242,6 @@ fn test_parse_simple_nodes() { S(1..2, "unexpected closing brace")]); } -#[test] -fn test_parse_headings() { - // Basics with spans. - t!("# a" - 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, Template![@Space, Text("three")])); - t!("###### six" Heading(5, Template![@Space, Text("six")])); - - // Start of heading. - 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, Template![@Space, Text("a")]), Space, Text("b")); - - // Continued heading. - t!("# a{\n1\n}b" Heading(0, Template![ - @Space, Text("a"), Block!(Int(1)), Text("b") - ])); - t!("# a[f][\n\n]d" Heading(0, Template![@ - Space, Text("a"), Call!("f", Args![Template![Parbreak]]), Text("d"), - ])); - - // No heading. - t!(r"\#" Text("#")); - t!("Nr. #1" Text("Nr."), Space, Text("#"), Text("1")); - t!("[v]#" Call!("v"), Text("#")); - - // Too many hashtags. - t!("####### seven" - nodes: [Heading(5, Template![@Space, Text("seven")])], - warnings: [S(0..7, "section depth should not exceed 6")]); -} - #[test] fn test_parse_raw() { // Basic, mostly tested in tokenizer and resolver. diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index 32cc11d9c..7741d27f8 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -567,6 +567,7 @@ mod tests { // Test markup tokens. t!(Markup[" a1"]: "*" => Star); t!(Markup: "_" => Underscore); + t!(Markup[""]: "###" => Hash, Hash, Hash); t!(Markup["a1/"]: "# " => Hash, Space(0)); t!(Markup: "~" => Tilde); t!(Markup[" "]: r"\" => Backslash); diff --git a/tests/ref/headings.png b/tests/ref/headings.png new file mode 100644 index 000000000..b16e38a6b Binary files /dev/null and b/tests/ref/headings.png differ diff --git a/tests/typ/headings.typ b/tests/typ/headings.typ new file mode 100644 index 000000000..88c76ad38 --- /dev/null +++ b/tests/typ/headings.typ @@ -0,0 +1,40 @@ +// Number of hashtags. +// +// warning: 5:1-5:8 section depth should not exceed 6 + +# One +### Three +###### Six +####### Seven + +--- +// Is a heading. + +/**/ # Heading +{[## Heading]} +[box][### Heading] + +--- +// Is no heading. +// +// error: 4:1-4:6 unexpected invalid token + +\# No heading + +Text with # hashtag + +#nope + +--- +// Heading continues over linebreak. + +# This { + "works" +} + +# [box][ + This +] too + +# This +does not