diff --git a/src/parse/mod.rs b/src/parse/mod.rs index bdecc31f8..1d893ad9d 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -102,14 +102,12 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option { Token::Star => Node::Strong(span), Token::Underscore => Node::Emph(span), Token::Raw(t) => raw(p, t), - Token::Hashtag if *at_start => return Some(heading(p)), + Token::Eq if *at_start => return Some(heading(p)), Token::Hyph if *at_start => return Some(list_item(p)), Token::Numbering(number) if *at_start => return Some(enum_item(p, number)), // Line-based markup that is not currently at the start of the line. - Token::Hashtag | Token::Hyph | Token::Numbering(_) => { - Node::Text(p.peek_src().into()) - } + Token::Eq | Token::Hyph | Token::Numbering(_) => Node::Text(p.peek_src().into()), // Hashtag + keyword / identifier. Token::Ident(_) @@ -183,11 +181,11 @@ fn raw(p: &mut Parser, token: RawToken) -> Node { /// Parse a heading. fn heading(p: &mut Parser) -> Node { let start = p.next_start(); - p.assert(Token::Hashtag); + p.assert(Token::Eq); // Count depth. let mut level: usize = 1; - while p.eat_if(Token::Hashtag) { + while p.eat_if(Token::Eq) { level += 1; } diff --git a/src/parse/scanner.rs b/src/parse/scanner.rs index fad44e89f..af88aa684 100644 --- a/src/parse/scanner.rs +++ b/src/parse/scanner.rs @@ -88,12 +88,12 @@ impl<'s> Scanner<'s> { /// Checks whether the next char fulfills a condition. /// - /// Returns `false` if there is no next char. - pub fn check(&self, f: F) -> bool + /// Returns `default` if there is no next char. + pub fn check_or(&self, default: bool, f: F) -> bool where F: FnOnce(char) -> bool, { - self.peek().map(f).unwrap_or(false) + self.peek().map_or(default, f) } /// The previous index in the source string. diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index f21967402..aebe7b70d 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -89,19 +89,22 @@ impl<'s> Iterator for Tokens<'s> { impl<'s> Tokens<'s> { fn markup(&mut self, start: usize, c: char) -> Token<'s> { match c { + // Escape sequences. + '\\' => self.backslash(), + + // Keywords and identifiers. + '#' => self.hash(), + // Markup. '~' => Token::Tilde, '*' => Token::Star, '_' => Token::Underscore, - '\\' => self.backslash(), '`' => self.raw(), '$' => self.math(), '-' => self.hyph(start), + '=' if self.s.check_or(true, |c| c == '=' || c.is_whitespace()) => Token::Eq, c if c == '.' || c.is_ascii_digit() => self.numbering(start, c), - // Headings, keywords and identifiers. - '#' => self.hash(start), - // Plain text. _ => self.text(start), } @@ -143,7 +146,7 @@ impl<'s> Tokens<'s> { // Numbers. c if c.is_ascii_digit() - || (c == '.' && self.s.check(|n| n.is_ascii_digit())) => + || (c == '.' && self.s.check_or(false, |n| n.is_ascii_digit())) => { self.number(start, c) } @@ -157,7 +160,7 @@ impl<'s> Tokens<'s> { fn whitespace(&mut self, first: char) -> Token<'s> { // Fast path for just a single space - if first == ' ' && !self.s.check(char::is_whitespace) { + if first == ' ' && self.s.check_or(true, |c| !c.is_whitespace()) { Token::Space(0) } else { self.s.uneat(); @@ -188,8 +191,10 @@ impl<'s> Tokens<'s> { '/' => true, // Parentheses. '[' | ']' | '{' | '}' => true, + // Code. + '#' => true, // Markup. - '#' | '~' | '*' | '_' | '`' | '$' | '-' => true, + '~' | '*' | '_' | '`' | '$' | '-' => true, // Escaping. '\\' => true, // Just text. @@ -233,18 +238,16 @@ impl<'s> Tokens<'s> { } } - fn hash(&mut self, start: usize) -> Token<'s> { - if self.s.check(is_id_start) { + fn hash(&mut self) -> Token<'s> { + if self.s.check_or(false, is_id_start) { let read = self.s.eat_while(is_id_continue); if let Some(keyword) = keyword(read) { keyword } else { Token::Ident(read) } - } else if self.s.check(|c| c != '#' && !c.is_whitespace()) { - Token::Text(self.s.eaten_from(start)) } else { - Token::Hashtag + Token::Invalid("#") } } @@ -255,10 +258,10 @@ impl<'s> Tokens<'s> { } else { Token::HyphHyph } - } else if self.s.check(|c| !c.is_whitespace()) { - Token::Text(self.s.eaten_from(start)) - } else { + } else if self.s.check_or(true, char::is_whitespace) { Token::Hyph + } else { + Token::Text(self.s.eaten_from(start)) } } @@ -274,11 +277,11 @@ impl<'s> Tokens<'s> { None }; - if self.s.check(|c| !c.is_whitespace()) { - return Token::Text(self.s.eaten_from(start)); + if self.s.check_or(true, char::is_whitespace) { + Token::Numbering(number) + } else { + Token::Text(self.s.eaten_from(start)) } - - Token::Numbering(number) } fn raw(&mut self) -> Token<'s> { @@ -663,8 +666,8 @@ mod tests { // Test code symbols in text. t!(Markup[" /"]: "a():\"b" => Text("a():\"b")); t!(Markup[" /"]: ";:,|/+" => Text(";:,|"), Text("/+")); - t!(Markup[" /"]: "#-a" => Text("#"), Text("-"), Text("a")); - t!(Markup[" "]: "#123" => Text("#"), Text("123")); + t!(Markup[" /"]: "=-a" => Text("="), Text("-"), Text("a")); + t!(Markup[" "]: "#123" => Invalid("#"), Text("123")); // Test text ends. t!(Markup[""]: "hello " => Text("hello"), Space(0)); @@ -712,8 +715,8 @@ mod tests { // Test markup tokens. t!(Markup[" a1"]: "*" => Star); t!(Markup: "_" => Underscore); - t!(Markup[""]: "###" => Hashtag, Hashtag, Hashtag); - t!(Markup["a1/"]: "# " => Hashtag, Space(0)); + t!(Markup[""]: "===" => Eq, Eq, Eq); + t!(Markup["a1/"]: "= " => Eq, Space(0)); t!(Markup: "~" => Tilde); t!(Markup[" "]: r"\" => Backslash); t!(Markup["a "]: r"a--" => Text("a"), HyphHyph); @@ -776,7 +779,7 @@ mod tests { for &(s, t) in &list { t!(Markup[" "]: format!("#{}", s) => t); t!(Markup[" "]: format!("#{0}#{0}", s) => t, t); - t!(Markup[" /"]: format!("# {}", s) => Token::Hashtag, Space(0), Text(s)); + t!(Markup[" /"]: format!("# {}", s) => Token::Invalid("#"), Space(0), Text(s)); } for &(s, t) in &list { diff --git a/src/pretty.rs b/src/pretty.rs index 17609ceaf..ff8841f3e 100644 --- a/src/pretty.rs +++ b/src/pretty.rs @@ -169,7 +169,7 @@ impl Pretty for RawNode { impl Pretty for HeadingNode { fn pretty(&self, p: &mut Printer) { for _ in 0 .. self.level { - p.push('#'); + p.push('='); } p.push(' '); self.body.pretty(p); @@ -653,7 +653,7 @@ mod tests { roundtrip("\\ "); roundtrip("\n\n"); roundtrip("hi"); - roundtrip("# *Ok*"); + roundtrip("= *Ok*"); roundtrip("- Ok"); // Raw. diff --git a/src/syntax/token.rs b/src/syntax/token.rs index 32a14d58f..425dac108 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -19,8 +19,6 @@ pub enum Token<'s> { Star, /// An underscore: `_`. Underscore, - /// A single hashtag: `#`. - Hashtag, /// A tilde: `~`. Tilde, /// Two hyphens: `--`. @@ -215,7 +213,6 @@ impl<'s> Token<'s> { Self::RightParen => "closing paren", Self::Star => "star", Self::Underscore => "underscore", - Self::Hashtag => "hashtag", Self::Tilde => "tilde", Self::HyphHyph => "en dash", Self::HyphHyphHyph => "em dash", diff --git a/tests/ref/code/let.png b/tests/ref/code/let.png index ae360b6b6..e9c000644 100644 Binary files a/tests/ref/code/let.png and b/tests/ref/code/let.png differ diff --git a/tests/ref/markup/heading.png b/tests/ref/markup/heading.png index f69bb887d..35a589bd8 100644 Binary files a/tests/ref/markup/heading.png and b/tests/ref/markup/heading.png differ diff --git a/tests/typ/code/call-wide.typ b/tests/typ/code/call-wide.typ index 54da540bd..996a052d1 100644 --- a/tests/typ/code/call-wide.typ +++ b/tests/typ/code/call-wide.typ @@ -7,7 +7,7 @@ --- // Test in heading. -# A #align!(right) B += A #align!(right) B C --- diff --git a/tests/typ/code/importable/chap1.typ b/tests/typ/code/importable/chap1.typ index a0f38d0d6..06a4c1a1a 100644 --- a/tests/typ/code/importable/chap1.typ +++ b/tests/typ/code/importable/chap1.typ @@ -2,7 +2,7 @@ #let name = "Klaus" -## Chapter 1 +== Chapter 1 #name stood in a field of wheat. There was nothing of particular interest about the field #name just casually surveyed for any paths on which the corn would not totally ruin his semi-new outdorsy jacket but then again, most of us spend diff --git a/tests/typ/code/importable/chap2.typ b/tests/typ/code/importable/chap2.typ index 51e116aa8..d4aedc60d 100644 --- a/tests/typ/code/importable/chap2.typ +++ b/tests/typ/code/importable/chap2.typ @@ -2,7 +2,7 @@ #let name = "Klaus" -## Chapter 2 +== Chapter 2 Their motivations, however, were pretty descript, so to speak. #name had not yet conceptualized their consequences, but that should change pretty quickly. #name approached the center of the field and picked up a 4-foot long disk made from diff --git a/tests/typ/code/include.typ b/tests/typ/code/include.typ index 8080c6e85..166c3945c 100644 --- a/tests/typ/code/include.typ +++ b/tests/typ/code/include.typ @@ -1,7 +1,7 @@ // Test include statements. --- -# Document += Document // Include a file #include "importable/chap1.typ" diff --git a/tests/typ/code/let.typ b/tests/typ/code/let.typ index 1a0bb10ea..d788a5639 100644 --- a/tests/typ/code/let.typ +++ b/tests/typ/code/let.typ @@ -79,6 +79,6 @@ Three // Error: 9 expected expression #let v = -// Should output `= 1`. +// Should output a heading `1`. // Error: 6-9 expected identifier, found string #let "v" = 1 diff --git a/tests/typ/coma.typ b/tests/typ/coma.typ index 5074bd9ea..161ac832d 100644 --- a/tests/typ/coma.typ +++ b/tests/typ/coma.typ @@ -30,7 +30,7 @@ // the parentheses. #align(center)[ // Markdown-like syntax for headings. - #### 3. Übungsblatt Computerorientierte Mathematik II #v(4mm) + ==== 3. Übungsblatt Computerorientierte Mathematik II #v(4mm) *Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) #v(4mm) *Alle Antworten sind zu beweisen.* ] diff --git a/tests/typ/markup/heading.typ b/tests/typ/markup/heading.typ index 8787d959a..e0dbd999a 100644 --- a/tests/typ/markup/heading.typ +++ b/tests/typ/markup/heading.typ @@ -4,39 +4,39 @@ // Different number of hashtags. // Valid levels. -# Level 1 -### Level 2 -###### Level 6 += Level 1 +=== Level 2 +====== Level 6 // Too many hashtags. // Warning: 1-8 should not exceed depth 6 -####### Level 7 +======= Level 7 --- // Heading vs. no heading. // Parsed as headings if at start of the context. -/**/ # Level 1 -{[## Level 2]} -#box[### Level 3] +/**/ = Level 1 +{[== Level 2]} +#box[=== Level 3] // Not at the start of the context. -No # heading +No = heading // Escaped. -\# No heading +\= No heading --- // While indented at least as much as the start, the heading continues. -# This += This is indented. -# This += This is not. // Code blocks continue heading. -# A { += A { "B" } diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json index e1f416535..33e4c3db7 100644 --- a/tools/support/typst.tmLanguage.json +++ b/tools/support/typst.tmLanguage.json @@ -75,7 +75,7 @@ { "name": "markup.heading.typst", "contentName": "entity.name.section.typst", - "begin": "^\\s*#{1,6}\\s+", + "begin": "^\\s*={1,6}\\s+", "end": "\n", "beginCaptures": { "0": { "name": "punctuation.definition.heading.typst" } }, "patterns": [{ "include": "#markup" }]