diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-01-30 12:09:26 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-01-30 12:09:26 +0100 |
| commit | 89eb8bae49edb71d9a9279a2210bb1ccaf4dd707 (patch) | |
| tree | 160b1a2ff41b5bba8a58f882df9d10c9eb036cf2 /src | |
| parent | ac24075469f171fe83a976b9a97b9b1ea078a7e3 (diff) | |
New syntax 💎
- Everything everywhere!
- Blocks with curly braces: {}
- Templates with brackets: []
- Function templates with hashtag: `#[f]`
- Headings with equals sign: `= Introduction`
Diffstat (limited to 'src')
| -rw-r--r-- | src/eval/value.rs | 12 | ||||
| -rw-r--r-- | src/parse/mod.rs | 114 | ||||
| -rw-r--r-- | src/parse/parser.rs | 12 | ||||
| -rw-r--r-- | src/parse/tokens.rs | 89 | ||||
| -rw-r--r-- | src/syntax/expr.rs | 33 | ||||
| -rw-r--r-- | src/syntax/mod.rs | 25 | ||||
| -rw-r--r-- | src/syntax/node.rs | 6 | ||||
| -rw-r--r-- | src/syntax/token.rs | 10 |
8 files changed, 160 insertions, 141 deletions
diff --git a/src/eval/value.rs b/src/eval/value.rs index 6e838f6c..860c0634 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::{Spanned, Tree, WithSpan}; +use crate::syntax::{pretty_template, Spanned, Tree, WithSpan}; /// A computational value. #[derive(Debug, Clone, PartialEq)] @@ -121,11 +121,7 @@ impl Pretty for Value { Value::Str(v) => write!(p, "{:?}", v).unwrap(), Value::Array(v) => v.pretty(p), Value::Dict(v) => v.pretty(p), - Value::Template(v) => { - p.push_str("["); - v.pretty(p); - p.push_str("]"); - } + Value::Template(v) => pretty_template(v, p), Value::Func(v) => v.pretty(p), Value::Any(v) => v.pretty(p), Value::Error => p.push_str("(error)"), @@ -537,8 +533,8 @@ mod tests { // Dictionary. let mut dict = BTreeMap::new(); dict.insert("one".into(), Value::Int(1)); - dict.insert("two".into(), Value::Template(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/parse/mod.rs b/src/parse/mod.rs index ff75a563..3fc7d483 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -45,38 +45,33 @@ fn tree(p: &mut Parser) -> Tree { fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> { let token = p.peek()?; let node = match token { - // Bracket call. - Token::LeftBracket => { - return Some(Node::Expr(bracket_call(p)?)); + // Whitespace. + Token::Space(newlines) => { + *at_start |= newlines > 0; + if newlines < 2 { Node::Space } else { Node::Parbreak } } - // Code block. - Token::LeftBrace => { - return Some(Node::Expr(block(p, false)?)); - } + // Text. + Token::Text(text) => Node::Text(text.into()), // Markup. Token::Star => Node::Strong, Token::Underscore => Node::Emph, - Token::Tilde => Node::Text("\u{00A0}".into()), - Token::Hash => { + Token::Eq => { if *at_start { return Some(Node::Heading(heading(p))); } else { Node::Text(p.get(p.peek_span()).into()) } } + Token::Tilde => Node::Text("\u{00A0}".into()), Token::Backslash => Node::Linebreak, - Token::Space(newlines) => { - *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)), Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)), // Keywords. Token::Let | Token::If | Token::For => { + *at_start = false; let stmt = token == Token::Let; let group = if stmt { Group::Stmt } else { Group::Expr }; @@ -92,6 +87,24 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> { return expr.map(Node::Expr); } + // Block. + Token::LeftBrace => { + *at_start = false; + return Some(Node::Expr(block(p, false)?)); + } + + // Template. + Token::LeftBracket => { + *at_start = false; + return Some(Node::Expr(template(p))); + } + + // Function template. + Token::HashBracket => { + *at_start = false; + return Some(Node::Expr(bracket_call(p)?)); + } + // Comments. Token::LineComment(_) | Token::BlockComment(_) => { p.eat(); @@ -99,6 +112,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> { } _ => { + *at_start = false; p.unexpected(); return None; } @@ -109,12 +123,12 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> { /// Parse a heading. fn heading(p: &mut Parser) -> NodeHeading { - // Count hashtags. + // Count depth. let mut level = p.span(|p| { - p.assert(Token::Hash); + p.assert(&[Token::Eq]); let mut level = 0u8; - while p.eat_if(Token::Hash) { + while p.eat_if(Token::Eq) { level = level.saturating_add(1); } level @@ -278,22 +292,18 @@ fn expr_with(p: &mut Parser, min_prec: usize) -> Option<Expr> { /// Parse a primary expression. fn primary(p: &mut Parser) -> Option<Expr> { let expr = match p.peek() { - // Template. - Some(Token::LeftBracket) => { - return Some(template(p)); - } - - // Nested block. - Some(Token::LeftBrace) => { - return block(p, true); - } - - // Dictionary or just a parenthesized expression. - Some(Token::LeftParen) => { - return Some(parenthesized(p)); - } + // Basic values. + Some(Token::None) => Expr::None, + Some(Token::Bool(b)) => Expr::Bool(b), + Some(Token::Int(i)) => Expr::Int(i), + Some(Token::Float(f)) => Expr::Float(f), + Some(Token::Length(val, unit)) => Expr::Length(val, unit), + Some(Token::Angle(val, unit)) => Expr::Angle(val, unit), + Some(Token::Percent(p)) => Expr::Percent(p), + Some(Token::Color(color)) => Expr::Color(color), + Some(Token::Str(token)) => Expr::Str(string(p, token)), - // Function or just ident. + // Function or identifier. Some(Token::Ident(id)) => { p.eat(); let ident = Ident(id.into()); @@ -305,23 +315,33 @@ fn primary(p: &mut Parser) -> Option<Expr> { } } - // Basic values. - Some(Token::None) => Expr::None, - Some(Token::Bool(b)) => Expr::Bool(b), - Some(Token::Int(i)) => Expr::Int(i), - Some(Token::Float(f)) => Expr::Float(f), - Some(Token::Length(val, unit)) => Expr::Length(val, unit), - Some(Token::Angle(val, unit)) => Expr::Angle(val, unit), - Some(Token::Percent(p)) => Expr::Percent(p), - Some(Token::Color(color)) => Expr::Color(color), - Some(Token::Str(token)) => Expr::Str(string(p, token)), - // Keywords. Some(Token::Let) => return expr_let(p), Some(Token::If) => return expr_if(p), Some(Token::For) => return expr_for(p), - // No value. + // Block. + Some(Token::LeftBrace) => { + return block(p, true); + } + + // Template. + Some(Token::LeftBracket) => { + return Some(template(p)); + } + + // Function template. + Some(Token::HashBracket) => { + let call = p.span_if(bracket_call)?.map(Node::Expr); + return Some(Expr::Template(vec![call])); + } + + // Array, dictionary or parenthesized expression. + Some(Token::LeftParen) => { + return Some(parenthesized(p)); + } + + // Nothing. _ => { p.expected("expression"); return None; @@ -380,7 +400,7 @@ fn string(p: &mut Parser, token: TokenStr) -> String { /// Parse a let expression. fn expr_let(p: &mut Parser) -> Option<Expr> { - p.assert(Token::Let); + p.assert(&[Token::Let]); let mut expr_let = None; if let Some(pat) = p.span_if(ident) { @@ -397,7 +417,7 @@ fn expr_let(p: &mut Parser) -> Option<Expr> { /// Parse an if expresion. fn expr_if(p: &mut Parser) -> Option<Expr> { - p.assert(Token::If); + p.assert(&[Token::If]); let mut expr_if = None; if let Some(condition) = p.span_if(expr) { @@ -420,7 +440,7 @@ fn expr_if(p: &mut Parser) -> Option<Expr> { /// Parse a for expression. fn expr_for(p: &mut Parser) -> Option<Expr> { - p.assert(Token::For); + p.assert(&[Token::For]); let mut expr_for = None; if let Some(pat) = p.span_if(for_pattern) { diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 906d9e62..2ca8eb10 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -105,9 +105,9 @@ impl<'s> Parser<'s> { self.repeek(); match group { - Group::Paren => self.assert(Token::LeftParen), - Group::Bracket => self.assert(Token::LeftBracket), - Group::Brace => self.assert(Token::LeftBrace), + Group::Paren => self.assert(&[Token::LeftParen]), + Group::Bracket => self.assert(&[Token::HashBracket, Token::LeftBracket]), + Group::Brace => self.assert(&[Token::LeftBrace]), Group::Subheader => {} Group::Stmt => {} Group::Expr => {} @@ -210,10 +210,10 @@ impl<'s> Parser<'s> { eaten } - /// Consume the next token, debug-asserting that it is the given one. - pub fn assert(&mut self, t: Token) { + /// Consume the next token, debug-asserting that it is one of the given ones. + pub fn assert(&mut self, ts: &[Token]) { let next = self.eat(); - debug_assert_eq!(next, Some(t)); + debug_assert!(next.map_or(false, |n| ts.contains(&n))); } /// Skip whitespace and comment tokens. diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index 056bbbbb..4f5d8ab4 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -67,12 +67,15 @@ impl<'s> Iterator for Tokens<'s> { loop { // Common elements. return Some(match c { - // Functions, blocks and terminators. + // Blocks and templates. '[' => Token::LeftBracket, ']' => Token::RightBracket, '{' => Token::LeftBrace, '}' => Token::RightBrace, + // Keywords, function templates, colors. + '#' => self.hash(start), + // Whitespace. c if c.is_whitespace() => self.whitespace(c), @@ -90,8 +93,8 @@ impl<'s> Iterator for Tokens<'s> { // Markup. '*' => Token::Star, '_' => Token::Underscore, + '=' => Token::Eq, '~' => Token::Tilde, - '#' => self.hash(start), '`' => self.raw(), '$' => self.math(), '\\' => self.backslash(), @@ -140,8 +143,7 @@ impl<'s> Iterator for Tokens<'s> { self.number(start, c) } - // Hex values and strings. - '#' => self.hex(start), + // Strings. '"' => self.string(), _ => Token::Invalid(self.s.eaten_from(start)), @@ -151,6 +153,27 @@ impl<'s> Iterator for Tokens<'s> { } impl<'s> Tokens<'s> { + fn hash(&mut self, start: usize) -> Token<'s> { + if self.s.eat_if('[') { + return Token::HashBracket; + } + + self.s.eat_while(is_id_continue); + let read = self.s.eaten_from(start); + + if let Some(keyword) = keyword(read) { + return keyword; + } + + if self.mode == TokenMode::Code { + if let Ok(color) = RgbaColor::from_str(read) { + return Token::Color(color); + } + } + + Token::Invalid(read) + } + fn whitespace(&mut self, first: char) -> Token<'s> { // Fast path for just a single space if first == ' ' && !self.s.check(|c| c.is_whitespace()) { @@ -182,10 +205,10 @@ impl<'s> Tokens<'s> { c if c.is_whitespace() => true, // Comments. '/' if self.s.check(|c| c == '/' || c == '*') => true, - // Parenthesis. - '[' | ']' | '{' | '}' => true, + // Parenthesis and hashtag. + '[' | ']' | '{' | '}' | '#' => true, // Markup. - '*' | '_' | '#' | '~' | '`' | '$' => true, + '*' | '_' | '=' | '~' | '`' | '$' => true, // Escaping. '\\' => true, _ => false, @@ -198,21 +221,6 @@ impl<'s> Tokens<'s> { Token::Text(self.s.eaten_from(start)) } - fn hash(&mut self, start: usize) -> Token<'s> { - if self.s.check(is_id_start) { - self.s.eat(); - self.s.eat_while(is_id_continue); - let read = self.s.eaten_from(start); - if let Some(keyword) = keyword(read) { - keyword - } else { - Token::Invalid(read) - } - } else { - Token::Hash - } - } - fn raw(&mut self) -> Token<'s> { let mut backticks = 1; while self.s.eat_if('`') { @@ -276,10 +284,10 @@ impl<'s> Tokens<'s> { match c { // Backslash and comments. '\\' | '/' | - // Parenthesis. - '[' | ']' | '{' | '}' | + // Parenthesis and hashtag. + '[' | ']' | '{' | '}' | '#' | // Markup. - '*' | '_' | '#' | '~' | '`' | '$' => { + '*' | '_' | '=' | '~' | '`' | '$' => { let start = self.s.index(); self.s.eat_assert(c); Token::Text(&self.s.eaten_from(start)) @@ -367,18 +375,6 @@ impl<'s> Tokens<'s> { } } - fn hex(&mut self, start: usize) -> Token<'s> { - self.s.eat_while(is_id_continue); - let read = self.s.eaten_from(start); - if let Some(keyword) = keyword(read) { - keyword - } else if let Ok(color) = RgbaColor::from_str(read) { - Token::Color(color) - } else { - Token::Invalid(read) - } - } - fn string(&mut self) -> Token<'s> { let mut escaped = false; Token::Str(TokenStr { @@ -596,8 +592,8 @@ 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[""]: "===" => Eq, Eq, Eq); + t!(Markup["a1/"]: "= " => Eq, Space(0)); t!(Markup: "~" => Tilde); t!(Markup[" "]: r"\" => Backslash); } @@ -655,10 +651,9 @@ mod tests { ]; for &(s, t) in &both { - t!(Code[" "]: format!("#{}", s) => t); - t!(Markup[" "]: format!("#{}", s) => t); - t!(Markup[" "]: format!("#{0}#{0}", s) => t, t); - t!(Markup[" /"]: format!("# {}", s) => Hash, Space(0), Text(s)); + t!(Both[" "]: format!("#{}", s) => t); + t!(Both[" "]: format!("#{0}#{0}", s) => t, t); + t!(Markup[" /"]: format!("# {}", s) => Token::Invalid("#"), Space(0), Text(s)); } let code = [ @@ -713,7 +708,7 @@ mod tests { // Test code symbols in text. t!(Markup[" /"]: "a():\"b" => Text("a():\"b")); - t!(Markup[" /"]: ";:,=|/+-" => Text(";:,=|/+-")); + t!(Markup[" /"]: ";:,|/+-" => Text(";:,|/+-")); // Test text ends. t!(Markup[""]: "hello " => Text("hello"), Space(0)); @@ -765,17 +760,17 @@ mod tests { t!(Markup: r"\}" => Text("}")); t!(Markup: r"\*" => Text("*")); t!(Markup: r"\_" => Text("_")); - t!(Markup: r"\#" => Text("#")); + t!(Markup: r"\=" => Text("=")); t!(Markup: r"\~" => Text("~")); t!(Markup: r"\`" => Text("`")); t!(Markup: r"\$" => Text("$")); + t!(Markup: r"\#" => Text("#")); // Test unescapable symbols. t!(Markup[" /"]: r"\a" => Text(r"\"), Text("a")); t!(Markup[" /"]: r"\u" => Text(r"\"), Text("u")); t!(Markup[" /"]: r"\1" => Text(r"\"), Text("1")); t!(Markup[" /"]: r"\:" => Text(r"\"), Text(":")); - t!(Markup[" /"]: r"\=" => Text(r"\"), Text("=")); t!(Markup[" /"]: r#"\""# => Text(r"\"), Text("\"")); // Test basic unicode escapes. @@ -947,7 +942,7 @@ mod tests { t!(Code: "1%%" => Percent(1.0), Invalid("%")); // Test invalid keyword. - t!(Markup[" /"]: "#-" => Hash, Text("-")); + t!(Markup[" /"]: "#-" => Invalid("#-")); t!(Markup[" /"]: "#do" => Invalid("#do")); t!(Code[" /"]: r"#letter" => Invalid(r"#letter")); } diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index ca55bdd0..7a055cc7 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -43,7 +43,7 @@ pub enum Expr { Unary(ExprUnary), /// A binary operation: `a + b`. Binary(ExprBinary), - /// An invocation of a function: `foo(...)`, `[foo ...]`. + /// An invocation of a function: `foo(...)`, `#[foo ...]`. Call(ExprCall), /// A let expression: `#let x = 1`. Let(ExprLet), @@ -75,11 +75,7 @@ impl Pretty for Expr { Self::Str(v) => write!(p, "{:?}", &v).unwrap(), Self::Array(v) => v.pretty(p), Self::Dict(v) => v.pretty(p), - Self::Template(v) => { - p.push_str("["); - v.pretty(p); - p.push_str("]"); - } + Self::Template(v) => pretty_template(v, p), Self::Group(v) => { p.push_str("("); v.v.pretty(p); @@ -146,6 +142,17 @@ impl Pretty for Named { /// A template expression: `[*Hi* there!]`. pub type ExprTemplate = Tree; +/// Pretty print a template. +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("["); + template.pretty(p); + p.push_str("]"); + } +} + /// A grouped expression: `(1 + 2)`. pub type ExprGroup = SpanBox<Expr>; @@ -400,7 +407,7 @@ pub enum Associativity { Right, } -/// An invocation of a function: `foo(...)`, `[foo ...]`. +/// An invocation of a function: `foo(...)`, `#[foo ...]`. #[derive(Debug, Clone, PartialEq)] pub struct ExprCall { /// The callee of the function. @@ -418,12 +425,12 @@ impl Pretty for ExprCall { } } -/// Pretty print a bracketed function call, with body or chaining when possible. -pub fn pretty_bracket_call(call: &ExprCall, p: &mut Printer, chained: bool) { +/// Pretty print a function template, with body or chaining when possible. +pub fn pretty_func_template(call: &ExprCall, p: &mut Printer, chained: bool) { if chained { p.push_str(" | "); } else { - p.push_str("["); + p.push_str("#["); } // Function name. @@ -431,7 +438,7 @@ 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]". + // Example: Transforms "#[v [Hi]]" => "#[v][Hi]". if let [head @ .., Argument::Pos(Spanned { v: Expr::Template(template), .. })] = call.args.v.as_slice() { @@ -443,9 +450,9 @@ 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]". + // Example: Transforms "#[v][[f]]" => "#[v | f]". if let [Spanned { v: Node::Expr(Expr::Call(call)), .. }] = template.as_slice() { - return pretty_bracket_call(call, p, true); + return pretty_func_template(call, p, true); } else { p.push_str("]["); template.pretty(p); diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index d1fc6b77..41fba134 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -60,7 +60,7 @@ mod tests { roundtrip("hi"); // Heading. - roundtrip("# *Ok*"); + roundtrip("= *Ok*"); // Raw. roundtrip("`lang 1`"); @@ -94,9 +94,9 @@ mod tests { roundtrip("{(a: 1, b: 2)}"); // Templates. - roundtrip("{[]}"); - roundtrip("{[*Ok*]}"); - roundtrip("{[[f]]}"); + roundtrip("[]"); + roundtrip("[*Ok*]"); + roundtrip("{[f]}"); // Groups. roundtrip("{(1)}"); @@ -105,6 +105,7 @@ mod tests { roundtrip("{}"); roundtrip("{1}"); roundtrip("{ #let x = 1; x += 2; x + 1 }"); + roundtrip("[{}]"); // Operators. roundtrip("{-x}"); @@ -116,14 +117,14 @@ mod tests { roundtrip("{v(1)}"); roundtrip("{v(a: 1, b)}"); - // Bracket calls. - roundtrip("[v]"); - roundtrip("[v 1]"); - roundtrip("[v 1, 2][*Ok*]"); - roundtrip("[v 1 | f 2]"); - roundtrip("{[[v]]}"); - test("[v 1, [[f 2]]]", "[v 1 | f 2]"); - test("[v 1, 2][[f 3]]", "[v 1, 2 | f 3]"); + // Function templates. + roundtrip("#[v]"); + roundtrip("#[v 1]"); + roundtrip("#[v 1, 2][*Ok*]"); + roundtrip("#[v 1 | f 2]"); + roundtrip("{#[v]}"); + test("#[v 1, #[f 2]]", "#[v 1 | f 2]"); + test("#[v 1, 2][#[f 3]]", "#[v 1, 2 | f 3]"); // Keywords. roundtrip("#let x = 1 + 2"); diff --git a/src/syntax/node.rs b/src/syntax/node.rs index db3b6510..d45e5952 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -36,8 +36,8 @@ impl Pretty for Node { Self::Raw(raw) => raw.pretty(p), Self::Expr(expr) => { if let Expr::Call(call) = expr { - // Format bracket calls appropriately. - pretty_bracket_call(call, p, false) + // Format function templates appropriately. + pretty_func_template(call, p, false) } else { expr.pretty(p); } @@ -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_str("="); } self.contents.pretty(p); } diff --git a/src/syntax/token.rs b/src/syntax/token.rs index 43797f75..c4b9ec8f 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -6,6 +6,8 @@ use crate::geom::{AngularUnit, LengthUnit}; pub enum Token<'s> { /// A left square bracket: `[`. LeftBracket, + /// A hashtag followed by a left square bracket: `#[`. + HashBracket, /// A right square bracket: `]`. RightBracket, /// A left curly brace: `{`. @@ -20,8 +22,8 @@ pub enum Token<'s> { Star, /// An underscore: `_`. Underscore, - /// A hashtag: `#`. - Hash, + /// A single equals sign: `=`. + Eq, /// A tilde: `~`. Tilde, /// A backslash followed by nothing or whitespace: `\`. @@ -40,8 +42,6 @@ pub enum Token<'s> { Hyph, /// A slash: `/`. Slash, - /// A single equals sign: `=`. - Eq, /// Two equals signs: `==`. EqEq, /// An exclamation mark followed by an equals sign: `!=`. @@ -191,6 +191,7 @@ impl<'s> Token<'s> { pub fn name(self) -> &'static str { match self { Self::LeftBracket => "opening bracket", + Self::HashBracket => "start of function template", Self::RightBracket => "closing bracket", Self::LeftBrace => "opening brace", Self::RightBrace => "closing brace", @@ -198,7 +199,6 @@ impl<'s> Token<'s> { Self::RightParen => "closing paren", Self::Star => "star", Self::Underscore => "underscore", - Self::Hash => "hashtag", Self::Tilde => "tilde", Self::Backslash => "backslash", Self::Comma => "comma", |
