From 84cdc85ca7494368e7ce2039fcef06ac2d3bd2ed Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 17 Feb 2021 23:07:28 +0100 Subject: [PATCH] =?UTF-8?q?Refresh=20parser=20=F0=9F=8C=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parse/collection.rs | 2 +- src/parse/mod.rs | 261 +++++++++++++------------------ src/parse/parser.rs | 132 +++++++++------- src/pretty.rs | 105 +++++-------- src/syntax/expr.rs | 11 ++ src/syntax/token.rs | 3 - tests/lang/ref/call-invalid.png | Bin 1766 -> 1729 bytes tests/lang/ref/for-invalid.png | Bin 1484 -> 1653 bytes tests/lang/typ/block-invalid.typ | 2 +- tests/lang/typ/call-invalid.typ | 4 +- tests/lang/typ/call-value.typ | 2 +- tests/lang/typ/comment.typ | 2 +- tests/lang/typ/emph.typ | 2 +- tests/lang/typ/expr-invalid.typ | 10 +- tests/lang/typ/expr-prec.typ | 2 +- tests/lang/typ/for-invalid.typ | 4 +- tests/lang/typ/if-invalid.typ | 2 +- tests/library/typ/image.typ | 4 +- 18 files changed, 250 insertions(+), 298 deletions(-) diff --git a/src/parse/collection.rs b/src/parse/collection.rs index 7ffc4539f..ab358f76a 100644 --- a/src/parse/collection.rs +++ b/src/parse/collection.rs @@ -4,7 +4,7 @@ use super::*; pub fn args(p: &mut Parser) -> ExprArgs { let start = p.start(); let items = collection(p, vec![]); - ExprArgs { span: p.span_from(start), items } + ExprArgs { span: p.span(start), items } } /// Parse a parenthesized group, which can be either of: diff --git a/src/parse/mod.rs b/src/parse/mod.rs index e9cf2a609..1d3b8be7e 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -70,8 +70,8 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option { Token::Raw(t) => raw(p, t), Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)), - // Keywords. - Token::Let | Token::If | Token::For => { + // Hashtag + keyword / identifier. + Token::Ident(_) | Token::Let | Token::If | Token::For => { *at_start = false; let stmt = token == Token::Let; let group = if stmt { Group::Stmt } else { Group::Expr }; @@ -100,12 +100,6 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option { return Some(Node::Expr(template(p))); } - // Bracket function. - Token::HashBracket => { - *at_start = false; - return Some(Node::Expr(bracket_call(p)?)); - } - // Comments. Token::LineComment(_) | Token::BlockComment(_) => { p.eat(); @@ -125,7 +119,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option { /// Parse a heading. fn heading(p: &mut Parser) -> Node { let start = p.start(); - p.assert(&[Token::Eq]); + p.assert(Token::Eq); // Count depth. let mut level: usize = 0; @@ -174,62 +168,94 @@ fn unicode_escape(p: &mut Parser, token: TokenUnicodeEscape) -> String { text } -/// Parse a bracketed function call. -fn bracket_call(p: &mut Parser) -> Option { - p.start_group(Group::Bracket, TokenMode::Code); +/// Parse a primary expression. +fn primary(p: &mut Parser) -> Option { + if let Some(expr) = literal(p) { + return Some(expr); + } - // One header is guaranteed, but there may be more (through chaining). - let mut outer = vec![]; - let mut inner = bracket_subheader(p); - while p.eat_if(Token::Pipe) { - if let Some(new) = bracket_subheader(p) { - outer.extend(inner); - inner = Some(new); + match p.peek() { + // Function or identifier. + Some(Token::Ident(string)) => { + let ident = Ident { + span: p.eat_span(), + string: string.into(), + }; + + match p.peek_direct() { + Some(Token::LeftParen) | Some(Token::LeftBracket) => Some(call(p, ident)), + _ => Some(Expr::Ident(ident)), + } + } + + // Keywords. + Some(Token::Let) => expr_let(p), + Some(Token::If) => expr_if(p), + Some(Token::For) => expr_for(p), + + // Structures. + Some(Token::LeftBrace) => block(p, true), + Some(Token::LeftBracket) => Some(template(p)), + Some(Token::LeftParen) => Some(parenthesized(p)), + + // Nothing. + _ => { + p.expected("expression"); + None } } +} - p.end_group(); - - let body = match p.peek() { - Some(Token::LeftBracket) => Some(bracket_body(p)), - _ => None, +/// Parse a literal. +fn literal(p: &mut Parser) -> Option { + let kind = match p.peek()? { + // Basic values. + Token::None => LitKind::None, + Token::Bool(b) => LitKind::Bool(b), + Token::Int(i) => LitKind::Int(i), + Token::Float(f) => LitKind::Float(f), + Token::Length(val, unit) => LitKind::Length(val, unit), + Token::Angle(val, unit) => LitKind::Angle(val, unit), + Token::Percent(p) => LitKind::Percent(p), + Token::Color(color) => LitKind::Color(color), + Token::Str(token) => LitKind::Str({ + if !token.terminated { + p.expected_at("quote", p.peek_span().end); + } + resolve::resolve_string(token.string) + }), + _ => return None, }; - - let mut inner = inner?; - if let Some(body) = body { - inner.span.expand(body.span()); - inner.args.items.push(ExprArg::Pos(body)); - } - - while let Some(mut top) = outer.pop() { - top.args.items.push(ExprArg::Pos(Expr::Call(inner))); - inner = top; - } - - Some(Expr::Call(inner)) + Some(Expr::Lit(Lit { span: p.eat_span(), kind })) } -/// Parse one subheader of a bracketed function call. -fn bracket_subheader(p: &mut Parser) -> Option { - p.start_group(Group::Subheader, TokenMode::Code); - let name = ident(p); - let args = args(p); - let span = p.end_group(); - Some(ExprCall { - span, - callee: Box::new(Expr::Ident(name?)), - args, - }) -} - -/// Parse the body of a bracketed function call. -fn bracket_body(p: &mut Parser) -> Expr { +// Parse a template value: `[...]`. +fn template(p: &mut Parser) -> Expr { p.start_group(Group::Bracket, TokenMode::Markup); let tree = Rc::new(tree(p)); let span = p.end_group(); Expr::Template(ExprTemplate { span, tree }) } +/// Parse a block expression: `{...}`. +fn block(p: &mut Parser, scopes: bool) -> Option { + p.start_group(Group::Brace, TokenMode::Code); + let mut exprs = vec![]; + while !p.eof() { + p.start_group(Group::Stmt, TokenMode::Code); + if let Some(expr) = expr(p) { + exprs.push(expr); + if !p.eof() { + p.expected_at("semicolon or line break", p.end()); + } + } + p.end_group(); + p.skip_white(); + } + let span = p.end_group(); + Some(Expr::Block(ExprBlock { span, exprs, scoping: scopes })) +} + /// Parse an expression. fn expr(p: &mut Parser) -> Option { expr_with(p, 0) @@ -242,7 +268,7 @@ fn expr_with(p: &mut Parser, min_prec: usize) -> Option { Some(op) => { let prec = op.precedence(); let expr = Box::new(expr_with(p, prec)?); - Expr::Unary(ExprUnary { span: p.span_from(start), op, expr }) + Expr::Unary(ExprUnary { span: p.span(start), op, expr }) } None => primary(p)?, }; @@ -276,114 +302,37 @@ fn expr_with(p: &mut Parser, min_prec: usize) -> Option { Some(lhs) } -/// Parse a primary expression. -fn primary(p: &mut Parser) -> Option { - if let Some(expr) = literal(p) { - return Some(expr); - } - - match p.peek() { - // Function or identifier. - Some(Token::Ident(string)) => { - let ident = Ident { - span: p.eat_span(), - string: string.into(), - }; - if p.peek() == Some(Token::LeftParen) { - Some(paren_call(p, ident)) - } else { - Some(Expr::Ident(ident)) - } +/// Parse a function call. +fn call(p: &mut Parser, name: Ident) -> Expr { + let mut args = match p.peek_direct() { + Some(Token::LeftParen) => { + p.start_group(Group::Paren, TokenMode::Code); + let args = args(p); + p.end_group(); + args } - - // Keywords. - Some(Token::Let) => expr_let(p), - Some(Token::If) => expr_if(p), - Some(Token::For) => expr_for(p), - - // Structures. - Some(Token::LeftBrace) => block(p, true), - Some(Token::LeftBracket) => Some(template(p)), - Some(Token::HashBracket) => bracket_call(p), - Some(Token::LeftParen) => Some(parenthesized(p)), - - // Nothing. - _ => { - p.expected("expression"); - None - } - } -} - -/// Parse a literal. -fn literal(p: &mut Parser) -> Option { - let kind = match p.peek()? { - // Basic values. - Token::None => LitKind::None, - Token::Bool(b) => LitKind::Bool(b), - Token::Int(i) => LitKind::Int(i), - Token::Float(f) => LitKind::Float(f), - Token::Length(val, unit) => LitKind::Length(val, unit), - Token::Angle(val, unit) => LitKind::Angle(val, unit), - Token::Percent(p) => LitKind::Percent(p), - Token::Color(color) => LitKind::Color(color), - Token::Str(token) => LitKind::Str(string(p, token)), - _ => return None, + _ => ExprArgs { + span: Span::at(name.span.end), + items: vec![], + }, }; - Some(Expr::Lit(Lit { span: p.eat_span(), kind })) -} -// Parse a template value: `[...]`. -fn template(p: &mut Parser) -> Expr { - p.start_group(Group::Bracket, TokenMode::Markup); - let tree = Rc::new(tree(p)); - let span = p.end_group(); - Expr::Template(ExprTemplate { span, tree }) -} - -/// Parse a block expression: `{...}`. -fn block(p: &mut Parser, scopes: bool) -> Option { - p.start_group(Group::Brace, TokenMode::Code); - let mut exprs = vec![]; - while !p.eof() { - p.start_group(Group::Stmt, TokenMode::Code); - if let Some(expr) = expr(p) { - exprs.push(expr); - if !p.eof() { - p.expected_at("semicolon or line break", p.end()); - } - } - p.end_group(); - p.skip_white(); + if p.peek_direct() == Some(Token::LeftBracket) { + let body = template(p); + args.items.push(ExprArg::Pos(body)); } - let span = p.end_group(); - Some(Expr::Block(ExprBlock { span, exprs, scoping: scopes })) -} -/// Parse a parenthesized function call. -fn paren_call(p: &mut Parser, name: Ident) -> Expr { - p.start_group(Group::Paren, TokenMode::Code); - let args = args(p); - p.end_group(); Expr::Call(ExprCall { - span: p.span_from(name.span.start), + span: p.span(name.span.start), callee: Box::new(Expr::Ident(name)), args, }) } -/// Parse a string. -fn string(p: &mut Parser, token: TokenStr) -> String { - if !token.terminated { - p.expected_at("quote", p.peek_span().end); - } - resolve::resolve_string(token.string) -} - /// Parse a let expression. fn expr_let(p: &mut Parser) -> Option { let start = p.start(); - p.assert(&[Token::Let]); + p.assert(Token::Let); let mut expr_let = None; if let Some(binding) = ident(p) { @@ -393,7 +342,7 @@ fn expr_let(p: &mut Parser) -> Option { } expr_let = Some(Expr::Let(ExprLet { - span: p.span_from(start), + span: p.span(start), binding, init: init.map(Box::new), })) @@ -405,18 +354,24 @@ fn expr_let(p: &mut Parser) -> Option { /// Parse an if expresion. fn expr_if(p: &mut Parser) -> Option { let start = p.start(); - p.assert(&[Token::If]); + p.assert(Token::If); let mut expr_if = None; if let Some(condition) = expr(p) { if let Some(if_body) = body(p) { let mut else_body = None; - if p.eat_if(Token::Else) { + + // We are in code mode but still want to react to `#else` if the + // outer mode is markup. + if match p.outer_mode() { + TokenMode::Markup => p.eat_if(Token::Invalid("#else")), + TokenMode::Code => p.eat_if(Token::Else), + } { else_body = body(p); } expr_if = Some(Expr::If(ExprIf { - span: p.span_from(start), + span: p.span(start), condition: Box::new(condition), if_body: Box::new(if_body), else_body: else_body.map(Box::new), @@ -430,7 +385,7 @@ fn expr_if(p: &mut Parser) -> Option { /// Parse a for expression. fn expr_for(p: &mut Parser) -> Option { let start = p.start(); - p.assert(&[Token::For]); + p.assert(Token::For); let mut expr_for = None; if let Some(pattern) = for_pattern(p) { @@ -438,7 +393,7 @@ fn expr_for(p: &mut Parser) -> Option { if let Some(iter) = expr(p) { if let Some(body) = body(p) { expr_for = Some(Expr::For(ExprFor { - span: p.span_from(start), + span: p.span(start), pattern, iter: Box::new(iter), body: Box::new(body), diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 7c6601828..5d390bc10 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -10,6 +10,8 @@ pub struct Parser<'s> { pub diags: DiagSet, /// An iterator over the source tokens. tokens: Tokens<'s>, + /// The stack of open groups. + groups: Vec, /// The next token. next: Option>, /// The peeked token. @@ -19,21 +21,20 @@ pub struct Parser<'s> { next_start: Pos, /// The end position of the last (non-whitespace if in code mode) token. last_end: Pos, - /// The stack of open groups. - groups: Vec, } /// A logical group of tokens, e.g. `[...]`. +#[derive(Debug, Copy, Clone)] struct GroupEntry { /// The start position of the group. Used by `Parser::end_group` to return /// The group's full span. - start: Pos, + pub start: Pos, /// The kind of group this is. This decides which tokens will end the group. - /// For example, a [`GroupKind::Paren`] will be ended by + /// For example, a [`Group::Paren`] will be ended by /// [`Token::RightParen`]. - kind: Group, + pub kind: Group, /// The mode the parser was in _before_ the group started. - prev_mode: TokenMode, + pub outer_mode: TokenMode, } /// A group, confined by optional start and end delimiters. @@ -60,13 +61,13 @@ impl<'s> Parser<'s> { let mut tokens = Tokens::new(src, TokenMode::Markup); let next = tokens.next(); Self { - tokens, + diags: DiagSet::new(), next, + tokens, + last_end: Pos::ZERO, peeked: next, next_start: Pos::ZERO, - last_end: Pos::ZERO, groups: vec![], - diags: DiagSet::new(), } } @@ -118,16 +119,16 @@ impl<'s> Parser<'s> { self.groups.push(GroupEntry { start: self.next_start, kind, - prev_mode: self.tokens.mode(), + outer_mode: self.tokens.mode(), }); self.tokens.set_mode(mode); self.repeek(); match kind { - Group::Paren => self.assert(&[Token::LeftParen]), - Group::Bracket => self.assert(&[Token::HashBracket, Token::LeftBracket]), - Group::Brace => self.assert(&[Token::LeftBrace]), + Group::Paren => self.assert(Token::LeftParen), + Group::Bracket => self.assert(Token::LeftBracket), + Group::Brace => self.assert(Token::LeftBrace), Group::Subheader => {} Group::Stmt => {} Group::Expr => {} @@ -141,7 +142,7 @@ impl<'s> Parser<'s> { pub fn end_group(&mut self) -> Span { let prev_mode = self.tokens.mode(); let group = self.groups.pop().expect("no started group"); - self.tokens.set_mode(group.prev_mode); + self.tokens.set_mode(group.outer_mode); self.repeek(); let mut rescan = self.tokens.mode() != prev_mode; @@ -173,6 +174,62 @@ impl<'s> Parser<'s> { Span::new(group.start, self.last_end) } + /// The tokenization mode outside of the current group. + /// + /// For example, this would be [`Markup`] if we are in a [`Code`] group that + /// is embedded in a [`Markup`] group. + /// + /// [`Markup`]: TokenMode::Markup + /// [`Code`]: TokenMode::Code + pub fn outer_mode(&mut self) -> TokenMode { + self.groups.last().map_or(TokenMode::Markup, |group| group.outer_mode) + } + + /// Whether the end of the source string or group is reached. + pub fn eof(&self) -> bool { + self.peek().is_none() + } + + /// Peek at the next token without consuming it. + pub fn peek(&self) -> Option> { + self.peeked + } + + /// Peek at the next token if it follows immediately after the last one + /// without any whitespace in between. + pub fn peek_direct(&self) -> Option> { + if self.next_start == self.last_end { + self.peeked + } else { + None + } + } + + /// Peek at the span of the next token. + /// + /// Has length zero if `peek()` returns `None`. + pub fn peek_span(&self) -> Span { + Span::new( + self.next_start, + if self.eof() { self.next_start } else { self.tokens.pos() }, + ) + } + + /// Peek at the source of the next token. + pub fn peek_src(&self) -> &'s str { + self.get(self.peek_span()) + } + + /// Checks whether the next token fulfills a condition. + /// + /// Returns `false` if there is no next token. + pub fn check(&self, f: F) -> bool + where + F: FnOnce(Token<'s>) -> bool, + { + self.peek().map_or(false, f) + } + /// Consume the next token. pub fn eat(&mut self) -> Option> { let token = self.peek()?; @@ -210,8 +267,8 @@ impl<'s> Parser<'s> { Span::new(start, self.last_end) } - /// Consume the next token if it is the given one and produce an error if - /// not. + /// Consume the next token if it is the given one and produce a diagnostic + /// if not. pub fn expect(&mut self, t: Token) -> bool { let eaten = self.eat_if(t); if !eaten { @@ -221,9 +278,9 @@ impl<'s> Parser<'s> { } /// Consume the next token, debug-asserting that it is one of the given ones. - pub fn assert(&mut self, ts: &[Token]) { + pub fn assert(&mut self, t: Token) { let next = self.eat(); - debug_assert!(next.map_or(false, |n| ts.contains(&n))); + debug_assert_eq!(next, Some(t)); } /// Skip whitespace and comment tokens. @@ -238,41 +295,6 @@ impl<'s> Parser<'s> { } } - /// Peek at the next token without consuming it. - pub fn peek(&self) -> Option> { - self.peeked - } - - /// Peek at the span of the next token. - /// - /// Has length zero if `peek()` returns `None`. - pub fn peek_span(&self) -> Span { - Span::new( - self.next_start, - if self.eof() { self.next_start } else { self.tokens.pos() }, - ) - } - - /// Peek at the source of the next token. - pub fn peek_src(&self) -> &'s str { - self.get(self.peek_span()) - } - - /// Checks whether the next token fulfills a condition. - /// - /// Returns `false` if there is no next token. - pub fn check(&self, f: F) -> bool - where - F: FnOnce(Token<'s>) -> bool, - { - self.peek().map_or(false, f) - } - - /// Whether the end of the source string or group is reached. - pub fn eof(&self) -> bool { - self.peek().is_none() - } - /// The position at which the next token starts. pub fn start(&self) -> Pos { self.next_start @@ -285,8 +307,8 @@ impl<'s> Parser<'s> { self.last_end } - /// The span from - pub fn span_from(&self, start: Pos) -> Span { + /// The span from `start` to the end of the last token. + pub fn span(&self, start: Pos) -> Span { Span::new(start, self.last_end) } diff --git a/src/pretty.rs b/src/pretty.rs index e040d3ae8..2ed6e80da 100644 --- a/src/pretty.rs +++ b/src/pretty.rs @@ -127,10 +127,10 @@ impl PrettyWithMap for Node { if let Some(map) = map { let value = &map[&(expr as *const _)]; value.pretty(p); - } else if let Expr::Call(call) = expr { - // Format bracket functions appropriately. - pretty_bracketed(call, p, false) } else { + if expr.has_short_form() { + p.push('#'); + } expr.pretty(p); } } @@ -287,13 +287,9 @@ impl Pretty for Named { impl Pretty for ExprTemplate { fn pretty(&self, p: &mut Printer) { - if let [Node::Expr(Expr::Call(call))] = self.tree.as_slice() { - pretty_bracketed(call, p, false); - } else { - p.push('['); - self.tree.pretty_with_map(p, None); - p.push(']'); - } + p.push('['); + self.tree.pretty_with_map(p, None); + p.push(']'); } } @@ -354,51 +350,25 @@ impl Pretty for BinOp { impl Pretty for ExprCall { fn pretty(&self, p: &mut Printer) { self.callee.pretty(p); - p.push('('); - self.args.pretty(p); - p.push(')'); - } -} -/// Pretty print a bracket function, with body or chaining when possible. -pub fn pretty_bracketed(call: &ExprCall, p: &mut Printer, chained: bool) { - if chained { - p.push_str(" | "); - } else { - p.push_str("#["); - } - - // Function name. - call.callee.pretty(p); - - let mut write_args = |items: &[ExprArg]| { - if !items.is_empty() { - p.push(' '); + let mut write_args = |items: &[ExprArg]| { + p.push('('); p.join(items, ", ", |item, p| item.pretty(p)); - } - }; + p.push(')'); + }; - match call.args.items.as_slice() { - // This can written as a chain. - // - // Example: Transforms "#[v][[f]]" => "#[v | f]". - [head @ .., ExprArg::Pos(Expr::Call(call))] => { - write_args(head); - pretty_bracketed(call, p, true); - } + match self.args.items.as_slice() { + // This can be moved behind the arguments. + // + // Example: Transforms "#v(a, [b])" => "#v(a)[b]". + [head @ .., ExprArg::Pos(Expr::Template(template))] => { + if !head.is_empty() { + write_args(head); + } + template.pretty(p); + } - // This can be written with a body. - // - // Example: Transforms "#[v [Hi]]" => "#[v][Hi]". - [head @ .., ExprArg::Pos(Expr::Template(template))] => { - write_args(head); - p.push(']'); - template.pretty(p); - } - - items => { - write_args(items); - p.push(']'); + items => write_args(items), } } } @@ -420,7 +390,7 @@ impl Pretty for ExprArg { impl Pretty for ExprLet { fn pretty(&self, p: &mut Printer) { - p.push_str("#let "); + p.push_str("let "); self.binding.pretty(p); if let Some(init) = &self.init { p.push_str(" = "); @@ -431,12 +401,13 @@ impl Pretty for ExprLet { impl Pretty for ExprIf { fn pretty(&self, p: &mut Printer) { - p.push_str("#if "); + p.push_str("if "); self.condition.pretty(p); p.push(' '); self.if_body.pretty(p); if let Some(expr) = &self.else_body { - p.push_str(" #else "); + // FIXME: Hashtag in markup. + p.push_str(" else "); expr.pretty(p); } } @@ -444,9 +415,9 @@ impl Pretty for ExprIf { impl Pretty for ExprFor { fn pretty(&self, p: &mut Printer) { - p.push_str("#for "); + p.push_str("for "); self.pattern.pretty(p); - p.push_str(" #in "); + p.push_str(" in "); self.iter.pretty(p); p.push(' '); self.body.pretty(p); @@ -728,7 +699,7 @@ mod tests { // Blocks. roundtrip("{}"); roundtrip("{1}"); - roundtrip("{ #let x = 1; x += 2; x + 1 }"); + roundtrip("{ let x = 1; x += 2; x + 1 }"); roundtrip("[{}]"); // Operators. @@ -736,24 +707,20 @@ mod tests { roundtrip("{not true}"); roundtrip("{1 + 3}"); - // Parenthesized calls. + // Function calls. roundtrip("{v()}"); roundtrip("{v(1)}"); roundtrip("{v(a: 1, b)}"); - - // Bracket functions. - roundtrip("#[v]"); - roundtrip("#[v 1]"); - roundtrip("#[v 1, 2][*Ok*]"); - roundtrip("#[v 1 | f 2]"); - test_parse("{#[v]}", "{v()}"); - test_parse("#[v 1, #[f 2]]", "#[v 1 | f 2]"); + roundtrip("#v()"); + roundtrip("#v(1)"); + roundtrip("#v(1, 2)[*Ok*]"); + roundtrip("#v(1, f[2])"); // Keywords. roundtrip("#let x = 1 + 2"); - roundtrip("#if x [y] #else [z]"); - roundtrip("#for x #in y {z}"); - roundtrip("#for k, x #in y {z}"); + roundtrip("#for x in y {z}"); + roundtrip("#for k, x in y {z}"); + test_parse("#if x [y] #else [z]", "#if x [y] else [z]"); } #[test] diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index d18d34048..5b37bb568 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -54,6 +54,17 @@ impl Expr { Self::For(v) => v.span, } } + + /// Whether the expression can be shorten in markup with a hashtag. + pub fn has_short_form(&self) -> bool { + matches!(self, + Expr::Ident(_) + | Expr::Call(_) + | Expr::Let(_) + | Expr::If(_) + | Expr::For(_) + ) + } } /// A literal. diff --git a/src/syntax/token.rs b/src/syntax/token.rs index fe429e24a..e57620af4 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -6,8 +6,6 @@ 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: `{`. @@ -191,7 +189,6 @@ impl<'s> Token<'s> { pub fn name(self) -> &'static str { match self { Self::LeftBracket => "opening bracket", - Self::HashBracket => "start of bracket function", Self::RightBracket => "closing bracket", Self::LeftBrace => "opening brace", Self::RightBrace => "closing brace", diff --git a/tests/lang/ref/call-invalid.png b/tests/lang/ref/call-invalid.png index 0bceebc188b3f086271019d86992218f36c1df02..05db4b7eac92e365f4fa726e5b6d3f7b8eb31b96 100644 GIT binary patch literal 1729 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 literal 1484 zcma)+c~FuG7{)(XbiW7{Mya(t5VR@G)LGUNWk9@39YQ?N@>r=fEpao=$u#OiH^VE; zT0ElCbal-R32QxT3|k{xNZl-;OYvCCL<==#ZFc|Mop$D#XMTUZ^S<7eY{vlhNo07(=G?B<-Zj2dTAWo9>B8KQj9u} z5~1FpxW*~QB`~{m5So5#Hb!KBw^-Qbv25J!PTfUJ3GE40MGzMnO08sRpQU|p?dCV# zpr6YLj35p{`2Pq=aNCMrf7YYsSHD`;!8yef-eHdb9;*22`Y8pa} z4X6~^KwjqV{P;@|;#5&7%ia>NJgU2nDk{EWnHMi#YL4})C-snQ6`c)r5lb87lV!U4 z$F=gBtlK#c!>q!WI~K9fpYS@8t+<&rx8&oDEe+42)1F>*sD1*QkB9p1L@!kP0%XZY zRdUj9QOw%o^aT2OjStyqEUbUYdc9Y@1@-Vj2dLJ?cy3%$Zc|OPj#J5B^y2~cASw9r zCpHvg6h!2?N#@(g^m92LIYvD3<1|p-$${!p^meeFj!+~XLX2oJh?0i+E=7+8SmIMtSP%b)Vy!x~k zs<_ck=sJR;8TM;V8Hz-42=NY(+bNnErZlbW+b7uLwl&#H7&tA^8a3^d%^Va9jHKTPiDDxKyM`1vuKc3h20wh;+7cX(?%eQ8-rQm{}v-m6BB z9?KZD`cMFrkQbhAqRX_^r`zp-spp*o6NMu>g&KW4kgXIjHl>Pe*`41)*AroPE}J@W zJ+{kSyrSC{WC#t*_1~`fJFF0u546bD7{$rlx8^r>E3q7ciJvN8huC1^1YNBovP+a( zS1I_Kw&5)t1D3ZO<(7moO^iysO03ubfA62Q1pF9WnNya$bZa)(w{;?&HB8`)N1g9~ z?0wF+0rxuIWJE5TC2`xD$T?U?!HD diff --git a/tests/lang/typ/block-invalid.typ b/tests/lang/typ/block-invalid.typ index 288748612..d98bf06bf 100644 --- a/tests/lang/typ/block-invalid.typ +++ b/tests/lang/typ/block-invalid.typ @@ -20,7 +20,7 @@ // Error: 9-12 expected identifier, found string for "v" - // Error: 10 expected keyword `#in` + // Error: 10 expected keyword `in` for v let z = 1 + 2 z diff --git a/tests/lang/typ/call-invalid.typ b/tests/lang/typ/call-invalid.typ index a04f59592..153f7a392 100644 --- a/tests/lang/typ/call-invalid.typ +++ b/tests/lang/typ/call-invalid.typ @@ -7,7 +7,7 @@ --- #let x = "string" -// Error: 2-3 expected function, found string +// Error: 1-3 expected function, found string #x() --- @@ -20,5 +20,5 @@ --- // Error: 3:1 expected quote -// Error: 2:1 expected closing bracket +// Error: 2:1 expected closing paren #f("] diff --git a/tests/lang/typ/call-value.typ b/tests/lang/typ/call-value.typ index 0e2577170..26e48d0f2 100644 --- a/tests/lang/typ/call-value.typ +++ b/tests/lang/typ/call-value.typ @@ -2,7 +2,7 @@ // Ref: false --- -// Whitespace is insignificant. +// Whitespace is significant. #test(type(1), "integer") #test (type (1), "integer") diff --git a/tests/lang/typ/comment.typ b/tests/lang/typ/comment.typ index e58924532..25180211f 100644 --- a/tests/lang/typ/comment.typ +++ b/tests/lang/typ/comment.typ @@ -11,7 +11,7 @@ C/* */D // Works in code. -#test(type /*1*/ (1) // +#test(type(/*1*/ 1) // , "integer") --- diff --git a/tests/lang/typ/emph.typ b/tests/lang/typ/emph.typ index 8e5812a87..772e15abf 100644 --- a/tests/lang/typ/emph.typ +++ b/tests/lang/typ/emph.typ @@ -8,7 +8,7 @@ _Emphasized!_ Partly em_phas_ized. // Scoped to body. -#[box][_Scoped] to body. +#box[_Scoped] to body. // Unterminated is fine. _The End diff --git a/tests/lang/typ/expr-invalid.typ b/tests/lang/typ/expr-invalid.typ index 329a26162..f760ae312 100644 --- a/tests/lang/typ/expr-invalid.typ +++ b/tests/lang/typ/expr-invalid.typ @@ -35,22 +35,22 @@ // Error: 1:31-1:39 cannot divide integer by length {(1 + "2", 40% - 1, 2 * true, 3 / 12pt)} -// Error: 15-23 cannot apply '+=' to integer and string +// Error: 14-22 cannot apply '+=' to integer and string { let x = 1; x += "2" } --- // Bad left-hand sides of assignment. -// Error: 1:3-1:6 cannot assign to this expression +// Error: 3-6 cannot assign to this expression { (x) = "" } -// Error: 1:3-1:8 cannot assign to this expression +// Error: 3-8 cannot assign to this expression { 1 + 2 += 3 } -// Error: 1:3-1:4 unknown variable +// Error: 3-4 unknown variable { z = 1 } -// Error: 1:3-1:6 cannot assign to a constant +// Error: 3-6 cannot assign to a constant { box = "hi" } // Works if we define box beforehand diff --git a/tests/lang/typ/expr-prec.typ b/tests/lang/typ/expr-prec.typ index 738e8fdf9..e64e583cc 100644 --- a/tests/lang/typ/expr-prec.typ +++ b/tests/lang/typ/expr-prec.typ @@ -21,7 +21,7 @@ #test((1), 1) #test((1+2)*-3, -9) -// Error: 15 expected closing paren +// Error: 14 expected closing paren #test({(1 + 1}, 2) --- diff --git a/tests/lang/typ/for-invalid.typ b/tests/lang/typ/for-invalid.typ index 3866909ff..c8bdebdd2 100644 --- a/tests/lang/typ/for-invalid.typ +++ b/tests/lang/typ/for-invalid.typ @@ -4,7 +4,7 @@ // Error: 5-5 expected identifier #for -// Error: 7-7 expected keyword `#in` +// Error: 7-7 expected keyword `in` #for v // Error: 10-10 expected expression @@ -28,5 +28,5 @@ A#for "v" thing. #for "v" in iter {} // Should output `+ b in iter`. -// Error: 7 expected keyword `#in` +// Error: 7 expected keyword `in` #for a + b in iter {} diff --git a/tests/lang/typ/if-invalid.typ b/tests/lang/typ/if-invalid.typ index 166de1229..6d2deab15 100644 --- a/tests/lang/typ/if-invalid.typ +++ b/tests/lang/typ/if-invalid.typ @@ -10,7 +10,7 @@ // Error: 6 expected body #if x -// Error: 1-6 unexpected keyword `#else` +// Error: 1-6 unexpected keyword `else` #else {} --- diff --git a/tests/library/typ/image.typ b/tests/library/typ/image.typ index 8bf972b25..9c3cd4fe2 100644 --- a/tests/library/typ/image.typ +++ b/tests/library/typ/image.typ @@ -7,10 +7,10 @@ // Load an RGB JPEG image. #image("res/tiger.jpg") -// Error: 9-30 failed to load image +// Error: 8-29 failed to load image #image("path/does/not/exist") -// Error: 9-30 failed to load image +// Error: 8-29 failed to load image #image("typ/image-error.typ") ---