diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index 65e860cf2..53e6fa613 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -67,7 +67,7 @@ impl Parser<'_> { } Token::LeftBracket => { - self.parse_bracket_call().map(|c| SyntaxNode::Call(c)) + self.parse_bracket_call(false).map(|c| SyntaxNode::Call(c)) } Token::Star => self.with_span(SyntaxNode::ToggleBolder), @@ -111,11 +111,15 @@ impl Parser<'_> { // Function calls. impl Parser<'_> { - fn parse_bracket_call(&mut self) -> Spanned { - self.start_group(Delimiter::Bracket); - self.tokens.push_mode(TokenMode::Header); + fn parse_bracket_call(&mut self, chained: bool) -> Spanned { + let before_bracket = self.pos(); + if !chained { + self.start_group(Delimiter::Bracket); + self.tokens.push_mode(TokenMode::Header); + } let after_bracket = self.pos(); + self.start_group(Delimiter::Subheader); self.skip_white(); let name = self.parse_ident().unwrap_or_else(|| { self.expected_found_or_at("function name", after_bracket); @@ -123,8 +127,12 @@ impl Parser<'_> { }); self.skip_white(); + let mut last_was_chain = false; + let mut args = match self.eatv() { - Some(Token::Colon) => self.parse_table_contents().0, + Some(Token::Colon) => { + self.parse_table_contents().0 + }, Some(_) => { self.expected_at("colon", name.span.end); while self.eat().is_some() {} @@ -133,10 +141,30 @@ impl Parser<'_> { None => TableExpr::new(), }; - self.tokens.pop_mode(); - let mut span = self.end_group(); + self.end_group(); + self.skip_white(); + if self.peek().is_some() { + last_was_chain = true; + let item = self.parse_bracket_call(true); + let span = item.span; + let t = vec![item.map(|f| SyntaxNode::Call(f))]; + args.push(SpannedEntry::val(Spanned::new(Expr::Tree(t), span))); + } - if self.check(Token::LeftBracket) { + let end = if !last_was_chain { + self.tokens.pop_mode(); + self.end_group().end + } else { + args.last() + .expect("last_was_chain set, call expected") + .1.val.span.end + }; + + let start = if chained { after_bracket } else { before_bracket }; + let mut span = Span::new(start, end); + + + if self.check(Token::LeftBracket) && !last_was_chain { self.start_group(Delimiter::Bracket); self.tokens.push_mode(TokenMode::Body); @@ -358,7 +386,7 @@ impl Parser<'_> { // This is a bracketed function call. Token::LeftBracket => { - let call = self.parse_bracket_call(); + let call = self.parse_bracket_call(false); let tree = vec![call.map(|c| SyntaxNode::Call(c))]; Spanned::new(Expr::Tree(tree), span) } @@ -415,12 +443,18 @@ impl Parser<'_> { impl<'s> Parser<'s> { fn start_group(&mut self, delimiter: Delimiter) { let start = self.pos(); - self.assert(delimiter.start()); + if let Some(start_token) = delimiter.start() { + self.assert(start_token); + } self.delimiters.push((start, delimiter.end())); } fn end_group(&mut self) -> Span { - assert_eq!(self.peek(), None, "unfinished group"); + if self.delimiters.last() + .expect("group was not started").1 != Token::FunctionLink { + assert_eq!(self.peek(), None, "unfinished group"); + } + let (start, end_token) = self.delimiters.pop() .expect("group was not started"); @@ -431,10 +465,12 @@ impl<'s> Parser<'s> { } _ => { let end = self.pos(); - error!( - @self.feedback, Span::at(end), - "expected {}", end_token.name(), - ); + if end_token != Token::FunctionLink { + error!( + @self.feedback, Span::at(end), + "expected {}", end_token.name(), + ); + } Span::new(start, end) } } @@ -471,6 +507,7 @@ impl<'s> Parser<'s> { } } + /// Checks if the next token is of some kind fn check(&mut self, token: Token<'_>) -> bool { self.peekv() == Some(token) } @@ -517,21 +554,24 @@ enum Delimiter { Paren, Bracket, Brace, + Subheader, } impl Delimiter { fn is_delimiter(token: Token<'_>) -> bool { matches!( token, - Token::RightParen | Token::RightBracket | Token::RightBrace + Token::RightParen | Token::RightBracket + | Token::RightBrace | Token::FunctionLink ) } - fn start(self) -> Token<'static> { + fn start(self) -> Option> { match self { - Self::Paren => Token::LeftParen, - Self::Bracket => Token::LeftBracket, - Self::Brace => Token::LeftBrace, + Self::Paren => Some(Token::LeftParen), + Self::Bracket => Some(Token::LeftBracket), + Self::Brace => Some(Token::LeftBrace), + Self::Subheader => None, } } @@ -540,6 +580,7 @@ impl Delimiter { Self::Paren => Token::RightParen, Self::Bracket => Token::RightBracket, Self::Brace => Token::RightBrace, + Self::Subheader => Token::FunctionLink, } } } @@ -837,6 +878,18 @@ mod tests { e!("[ 🌎]" => s(0,3, 0,4, "expected function name, found invalid token")); } + #[test] + fn test_parse_subgroups() { + // 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")]))])]); + + // Errors for unclosed / empty predecessor groups + e!("[hi: (5.0, 2.1 >> you]" => s(0, 15, 0, 15, "expected closing paren")); + e!("[>> abc]" => s(0, 1, 0, 1, "expected function name")); + } + #[test] fn test_parse_colon_starting_func_args() { // Just colon without args. diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index 50eea4556..d30620759 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -34,6 +34,8 @@ pub enum Token<'s> { LeftBrace, /// A right brace in a function header: `}`. RightBrace, + /// A double forward chevron in a function header: `>>`. + FunctionLink, /// A colon in a function header: `:`. Colon, @@ -108,6 +110,7 @@ impl<'s> Token<'s> { RightParen => "closing paren", LeftBrace => "opening brace", RightBrace => "closing brace", + FunctionLink => "function chain operator", Colon => "colon", Comma => "comma", Equals => "equals sign", @@ -218,6 +221,8 @@ impl<'s> Iterator for Tokens<'s> { ':' if self.mode == Header => Colon, ',' if self.mode == Header => Comma, '=' if self.mode == Header => Equals, + '>' if self.mode == Header && self.peek() == Some('>') => + self.read_function_chain(), // Expression operators. '+' if self.mode == Header => Plus, @@ -308,6 +313,12 @@ impl<'s> Tokens<'s> { }, true, 0, -2).0) } + fn read_function_chain(&mut self) -> Token<'s> { + let second = self.eat(); + debug_assert!(second == Some('>')); + FunctionLink + } + fn read_whitespace(&mut self, start: Pos) -> Token<'s> { self.read_string_until(|n| !n.is_whitespace(), false, 0, 0); let end = self.pos(); @@ -490,6 +501,7 @@ mod tests { LeftBracket as L, RightBracket as R, LeftParen as LP, RightParen as RP, LeftBrace as LB, RightBrace as RB, + FunctionLink, Ident as Id, Bool, Number as Num, @@ -576,8 +588,10 @@ mod tests { t!(Header, "120%" => Num(1.2)); t!(Header, "12e4%" => Num(1200.0)); t!(Header, "__main__" => Id("__main__")); + t!(Header, ">main" => Invalid(">main")); t!(Header, ".func.box" => Id(".func.box")); t!(Header, "arg, _b, _1" => Id("arg"), Comma, S(0), Id("_b"), Comma, S(0), Id("_1")); + t!(Header, "f: arg >> g" => Id("f"), Colon, S(0), Id("arg"), S(0), FunctionLink, S(0), Id("g")); t!(Header, "12_pt, 12pt" => Invalid("12_pt"), Comma, S(0), Len(Length::pt(12.0))); t!(Header, "1e5in" => Len(Length::inches(100000.0))); t!(Header, "2.3cm" => Len(Length::cm(2.3)));