From 3fbbccfc50a33a1b671d24cdc669f35ac64b5f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20F=C3=B6rster?= Date: Thu, 18 Apr 2019 14:40:10 +0200 Subject: [PATCH] Simplify BibTeX parser --- src/syntax/bibtex/ast.rs | 164 ++++++++++++++------ src/syntax/bibtex/lexer.rs | 99 ++++++++++-- src/syntax/bibtex/mod.rs | 295 +----------------------------------- src/syntax/bibtex/parser.rs | 25 ++- 4 files changed, 215 insertions(+), 368 deletions(-) diff --git a/src/syntax/bibtex/ast.rs b/src/syntax/bibtex/ast.rs index 07a5aed2..424f1c9c 100644 --- a/src/syntax/bibtex/ast.rs +++ b/src/syntax/bibtex/ast.rs @@ -1,6 +1,6 @@ +use crate::range; use crate::syntax::text::{Span, SyntaxNode}; use lsp_types::Range; -use std::rc::Rc; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum BibtexTokenKind { @@ -52,43 +52,34 @@ impl BibtexRoot { } } -pub trait BibtexVisitor { - fn visit_comment(&mut self, comment: Rc) -> T; - - fn visit_preamble(&mut self, preamble: Rc) -> T; - - fn visit_string(&mut self, string: Rc) -> T; - - fn visit_entry(&mut self, entry: Rc) -> T; - - fn visit_field(&mut self, field: Rc) -> T; - - fn visit_word(&mut self, word: Rc) -> T; - - fn visit_command(&mut self, command: Rc) -> T; - - fn visit_quoted_content(&mut self, content: Rc) -> T; - - fn visit_braced_content(&mut self, content: Rc) -> T; - - fn visit_concat(&mut self, concat: Rc) -> T; +impl SyntaxNode for BibtexRoot { + fn range(&self) -> Range { + if self.children.is_empty() { + range::create(0, 0, 0, 0) + } else { + Range::new( + self.children[0].start(), + self.children[self.children.len() - 1].end(), + ) + } + } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum BibtexDeclaration { - Comment(Rc), - Preamble(Rc), - String(Rc), - Entry(Rc), + Comment(BibtexComment), + Preamble(BibtexPreamble), + String(BibtexString), + Entry(BibtexEntry), } impl BibtexDeclaration { - pub fn accept(&self, visitor: &mut BibtexVisitor) -> T { + pub fn accept<'a>(&'a self, visitor: &mut BibtexVisitor<'a>) { match self { - BibtexDeclaration::Comment(comment) => visitor.visit_comment(comment.clone()), - BibtexDeclaration::Preamble(preamble) => visitor.visit_preamble(preamble.clone()), - BibtexDeclaration::String(string) => visitor.visit_string(string.clone()), - BibtexDeclaration::Entry(entry) => visitor.visit_entry(entry.clone()), + BibtexDeclaration::Comment(comment) => visitor.visit_comment(comment), + BibtexDeclaration::Preamble(preamble) => visitor.visit_preamble(preamble), + BibtexDeclaration::String(string) => visitor.visit_string(string), + BibtexDeclaration::Entry(entry) => visitor.visit_entry(entry), } } } @@ -225,7 +216,7 @@ pub struct BibtexEntry { pub left: Option, pub key: Option, pub comma: Option, - pub fields: Vec>, + pub fields: Vec, pub right: Option, } @@ -235,7 +226,7 @@ impl BibtexEntry { left: Option, key: Option, comma: Option, - fields: Vec>, + fields: Vec, right: Option, ) -> Self { let end = if let Some(ref right) = right { @@ -314,21 +305,21 @@ impl SyntaxNode for BibtexField { #[derive(Debug, PartialEq, Eq, Clone)] pub enum BibtexContent { - Word(Rc), - Command(Rc), - QuotedContent(Rc), - BracedContent(Rc), - Concat(Rc), + Word(BibtexWord), + Command(BibtexCommand), + QuotedContent(BibtexQuotedContent), + BracedContent(BibtexBracedContent), + Concat(Box), } impl BibtexContent { - pub fn accept(&self, visitor: &mut BibtexVisitor) -> T { + pub fn accept<'a>(&'a self, visitor: &mut BibtexVisitor<'a>) { match self { - BibtexContent::Word(word) => visitor.visit_word(word.clone()), - BibtexContent::Command(command) => visitor.visit_command(command.clone()), - BibtexContent::QuotedContent(content) => visitor.visit_quoted_content(content.clone()), - BibtexContent::BracedContent(content) => visitor.visit_braced_content(content.clone()), - BibtexContent::Concat(concat) => visitor.visit_concat(concat.clone()), + BibtexContent::Word(word) => visitor.visit_word(word), + BibtexContent::Command(command) => visitor.visit_command(command), + BibtexContent::QuotedContent(content) => visitor.visit_quoted_content(content), + BibtexContent::BracedContent(content) => visitor.visit_braced_content(content), + BibtexContent::Concat(concat) => visitor.visit_concat(concat), } } } @@ -336,11 +327,11 @@ impl BibtexContent { impl SyntaxNode for BibtexContent { fn range(&self) -> Range { match self { - BibtexContent::Word(word) => word.range, - BibtexContent::Command(command) => command.range, - BibtexContent::QuotedContent(content) => content.range, - BibtexContent::BracedContent(content) => content.range, - BibtexContent::Concat(concat) => concat.range, + BibtexContent::Word(word) => word.range(), + BibtexContent::Command(command) => command.range(), + BibtexContent::QuotedContent(content) => content.range(), + BibtexContent::BracedContent(content) => content.range(), + BibtexContent::Concat(concat) => concat.range(), } } } @@ -491,3 +482,80 @@ impl SyntaxNode for BibtexConcat { self.range } } + +pub trait BibtexVisitor<'a> { + fn visit_root(&mut self, root: &'a BibtexRoot); + + fn visit_comment(&mut self, comment: &'a BibtexComment); + + fn visit_preamble(&mut self, preamble: &'a BibtexPreamble); + + fn visit_string(&mut self, string: &'a BibtexString); + + fn visit_entry(&mut self, entry: &'a BibtexEntry); + + fn visit_field(&mut self, field: &'a BibtexField); + + fn visit_word(&mut self, word: &'a BibtexWord); + + fn visit_command(&mut self, command: &'a BibtexCommand); + + fn visit_quoted_content(&mut self, content: &'a BibtexQuotedContent); + + fn visit_braced_content(&mut self, content: &'a BibtexBracedContent); + + fn visit_concat(&mut self, concat: &'a BibtexConcat); +} + +pub struct BibtexWalker; + +impl BibtexWalker { + fn walk_root<'a>(visitor: &mut BibtexVisitor<'a>, root: &'a BibtexRoot) { + for declaration in &root.children { + declaration.accept(visitor); + } + } + + fn walk_preamble<'a>(visitor: &mut BibtexVisitor<'a>, preamble: &'a BibtexPreamble) { + if let Some(ref content) = preamble.content { + content.accept(visitor); + } + } + + fn walk_string<'a>(visitor: &mut BibtexVisitor<'a>, string: &'a BibtexString) { + if let Some(ref value) = string.value { + value.accept(visitor); + } + } + + fn walk_entry<'a>(visitor: &mut BibtexVisitor<'a>, entry: &'a BibtexEntry) { + for field in &entry.fields { + visitor.visit_field(field); + } + } + + fn walk_field<'a>(visitor: &mut BibtexVisitor<'a>, field: &'a BibtexField) { + if let Some(ref content) = field.content { + content.accept(visitor); + } + } + + fn walk_quoted_content<'a>(visitor: &mut BibtexVisitor<'a>, content: &'a BibtexQuotedContent) { + for child in &content.children { + child.accept(visitor); + } + } + + fn walk_braced_content<'a>(visitor: &mut BibtexVisitor<'a>, content: &'a BibtexBracedContent) { + for child in &content.children { + child.accept(visitor); + } + } + + fn walk_concat<'a>(visitor: &mut BibtexVisitor<'a>, concat: &'a BibtexConcat) { + concat.left.accept(visitor); + if let Some(ref right) = concat.right { + right.accept(visitor); + } + } +} diff --git a/src/syntax/bibtex/lexer.rs b/src/syntax/bibtex/lexer.rs index 8e8524a2..a606dff4 100644 --- a/src/syntax/bibtex/lexer.rs +++ b/src/syntax/bibtex/lexer.rs @@ -5,20 +5,13 @@ pub struct BibtexLexer<'a> { stream: CharStream<'a>, } -impl<'a> From> for BibtexLexer<'a> { - fn from(stream: CharStream<'a>) -> Self { - BibtexLexer { stream } - } -} - -impl<'a> From<&'a str> for BibtexLexer<'a> { - fn from(text: &'a str) -> Self { - let stream = CharStream::new(text); - BibtexLexer::from(stream) - } -} - impl<'a> BibtexLexer<'a> { + pub fn new(text: &'a str) -> Self { + BibtexLexer { + stream: CharStream::new(text), + } + } + fn kind(&mut self) -> BibtexToken { fn is_type_char(c: char) -> bool { c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' @@ -30,7 +23,7 @@ impl<'a> BibtexLexer<'a> { self.stream.next(); } let span = self.stream.end_span(); - let kind = match span.text.as_ref() { + let kind = match span.text.to_lowercase().as_ref() { "@preamble" => BibtexTokenKind::PreambleKind, "@string" => BibtexTokenKind::StringKind, _ => BibtexTokenKind::EntryKind, @@ -103,3 +96,81 @@ impl<'a> Iterator for BibtexLexer<'a> { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::syntax::text::Span; + use lsp_types::{Position, Range}; + + fn verify<'a>( + lexer: &mut BibtexLexer<'a>, + line: u64, + character: u64, + text: &str, + kind: BibtexTokenKind, + ) { + let start = Position::new(line, character); + let end = Position::new(line, character + text.chars().count() as u64); + let range = Range::new(start, end); + let span = Span::new(range, text.to_owned()); + let token = BibtexToken::new(span, kind); + assert_eq!(Some(token), lexer.next()); + } + + #[test] + fn test_word() { + let mut lexer = BibtexLexer::new("foo bar baz"); + verify(&mut lexer, 0, 0, "foo", BibtexTokenKind::Word); + verify(&mut lexer, 0, 4, "bar", BibtexTokenKind::Word); + verify(&mut lexer, 0, 8, "baz", BibtexTokenKind::Word); + assert_eq!(None, lexer.next()); + } + + #[test] + fn test_command() { + let mut lexer = BibtexLexer::new("\\foo\\bar@baz"); + verify(&mut lexer, 0, 0, "\\foo", BibtexTokenKind::Command); + verify(&mut lexer, 0, 4, "\\bar@baz", BibtexTokenKind::Command); + assert_eq!(None, lexer.next()); + } + + #[test] + fn test_escape_sequence() { + let mut lexer = BibtexLexer::new("\\foo*\n\\%\\**"); + verify(&mut lexer, 0, 0, "\\foo*", BibtexTokenKind::Command); + verify(&mut lexer, 1, 0, "\\%", BibtexTokenKind::Command); + verify(&mut lexer, 1, 2, "\\*", BibtexTokenKind::Command); + verify(&mut lexer, 1, 4, "*", BibtexTokenKind::Word); + assert_eq!(None, lexer.next()); + } + + #[test] + fn test_delimiter() { + let mut lexer = BibtexLexer::new("{}()\""); + verify(&mut lexer, 0, 0, "{", BibtexTokenKind::BeginBrace); + verify(&mut lexer, 0, 1, "}", BibtexTokenKind::EndBrace); + verify(&mut lexer, 0, 2, "(", BibtexTokenKind::BeginParen); + verify(&mut lexer, 0, 3, ")", BibtexTokenKind::EndParen); + verify(&mut lexer, 0, 4, "\"", BibtexTokenKind::Quote); + assert_eq!(None, lexer.next()); + } + + #[test] + fn test_kind() { + let mut lexer = BibtexLexer::new("@pReAmBlE\n@article\n@string"); + verify(&mut lexer, 0, 0, "@pReAmBlE", BibtexTokenKind::PreambleKind); + verify(&mut lexer, 1, 0, "@article", BibtexTokenKind::EntryKind); + verify(&mut lexer, 2, 0, "@string", BibtexTokenKind::StringKind); + assert_eq!(None, lexer.next()); + } + + #[test] + fn test_operator() { + let mut lexer = BibtexLexer::new("=,#"); + verify(&mut lexer, 0, 0, "=", BibtexTokenKind::Assign); + verify(&mut lexer, 0, 1, ",", BibtexTokenKind::Comma); + verify(&mut lexer, 0, 2, "#", BibtexTokenKind::Concat); + assert_eq!(None, lexer.next()); + } +} diff --git a/src/syntax/bibtex/mod.rs b/src/syntax/bibtex/mod.rs index 58bcfd97..1cef7714 100644 --- a/src/syntax/bibtex/mod.rs +++ b/src/syntax/bibtex/mod.rs @@ -2,312 +2,25 @@ pub mod ast; pub mod lexer; pub mod parser; -use crate::range; -use crate::syntax::bibtex::ast::*; +use crate::syntax::bibtex::ast::BibtexRoot; use crate::syntax::bibtex::lexer::BibtexLexer; use crate::syntax::bibtex::parser::BibtexParser; -use crate::syntax::text::SyntaxNode; -use lsp_types::{Position, Range}; -use std::rc::Rc; - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum BibtexNodeKind { - Comment, - Preamble, - String, - Entry, - Field, - Word, - Command, - QuotedContent, - BracedContent, - Concat, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum BibtexNode { - Declaration(BibtexDeclaration), - Field(Rc), - Content(BibtexContent), -} - -impl BibtexNode { - fn kind(&self) -> BibtexNodeKind { - match self { - BibtexNode::Declaration(declaration) => match declaration { - BibtexDeclaration::Comment(_) => BibtexNodeKind::Comment, - BibtexDeclaration::Preamble(_) => BibtexNodeKind::Preamble, - BibtexDeclaration::String(_) => BibtexNodeKind::String, - BibtexDeclaration::Entry(_) => BibtexNodeKind::Entry, - }, - BibtexNode::Field(_) => BibtexNodeKind::Field, - BibtexNode::Content(content) => match content { - BibtexContent::Word(_) => BibtexNodeKind::Word, - BibtexContent::Command(_) => BibtexNodeKind::Command, - BibtexContent::QuotedContent(_) => BibtexNodeKind::QuotedContent, - BibtexContent::BracedContent(_) => BibtexNodeKind::BracedContent, - BibtexContent::Concat(_) => BibtexNodeKind::Concat, - }, - } - } -} - -impl SyntaxNode for BibtexNode { - fn range(&self) -> Range { - match self { - BibtexNode::Declaration(declaration) => declaration.range(), - BibtexNode::Field(field) => field.range, - BibtexNode::Content(content) => content.range(), - } - } -} - -struct BibtexFinder { - pub position: Option, - pub results: Vec, -} - -impl BibtexFinder { - pub fn new(position: Option) -> Self { - BibtexFinder { - position, - results: Vec::new(), - } - } - - fn check_range(&self, node: &BibtexNode) -> bool { - if let Some(position) = self.position { - range::contains(node.range(), position) - } else { - true - } - } -} - -impl BibtexVisitor<()> for BibtexFinder { - fn visit_comment(&mut self, comment: Rc) { - let node = BibtexNode::Declaration(BibtexDeclaration::Comment(Rc::clone(&comment))); - if self.check_range(&node) { - self.results.push(node); - } - } - - fn visit_preamble(&mut self, preamble: Rc) { - let node = BibtexNode::Declaration(BibtexDeclaration::Preamble(Rc::clone(&preamble))); - if self.check_range(&node) { - self.results.push(node); - if let Some(ref content) = preamble.content { - content.accept(self); - } - } - } - - fn visit_string(&mut self, string: Rc) { - let node = BibtexNode::Declaration(BibtexDeclaration::String(Rc::clone(&string))); - if self.check_range(&node) { - self.results.push(node); - if let Some(ref content) = string.value { - content.accept(self); - } - } - } - - fn visit_entry(&mut self, entry: Rc) { - let node = BibtexNode::Declaration(BibtexDeclaration::Entry(Rc::clone(&entry))); - if self.check_range(&node) { - self.results.push(node); - for field in &entry.fields { - self.visit_field(Rc::clone(&field)); - } - } - } - - fn visit_field(&mut self, field: Rc) { - let node = BibtexNode::Field(Rc::clone(&field)); - if self.check_range(&node) { - self.results.push(node); - if let Some(ref content) = field.content { - content.accept(self); - } - } - } - - fn visit_word(&mut self, word: Rc) { - let node = BibtexNode::Content(BibtexContent::Word(Rc::clone(&word))); - if self.check_range(&node) { - self.results.push(node); - } - } - - fn visit_command(&mut self, command: Rc) { - let node = BibtexNode::Content(BibtexContent::Command(Rc::clone(&command))); - if self.check_range(&node) { - self.results.push(node); - } - } - - fn visit_quoted_content(&mut self, content: Rc) { - let node = BibtexNode::Content(BibtexContent::QuotedContent(Rc::clone(&content))); - if self.check_range(&node) { - self.results.push(node); - for child in &content.children { - child.accept(self); - } - } - } - - fn visit_braced_content(&mut self, content: Rc) { - let node = BibtexNode::Content(BibtexContent::BracedContent(Rc::clone(&content))); - if self.check_range(&node) { - self.results.push(node); - for child in &content.children { - child.accept(self); - } - } - } - - fn visit_concat(&mut self, concat: Rc) { - let node = BibtexNode::Content(BibtexContent::Concat(Rc::clone(&concat))); - if self.check_range(&node) { - self.results.push(node); - concat.left.accept(self); - if let Some(ref right) = concat.right { - right.accept(self); - } - } - } -} pub struct BibtexSyntaxTree { pub root: BibtexRoot, - pub descendants: Vec, } impl From for BibtexSyntaxTree { fn from(root: BibtexRoot) -> Self { - let mut finder = BibtexFinder::new(None); - for child in &root.children { - child.accept(&mut finder); - } - BibtexSyntaxTree { - root, - descendants: finder.results, - } + BibtexSyntaxTree { root } } } impl From<&str> for BibtexSyntaxTree { fn from(text: &str) -> Self { - let tokens = BibtexLexer::from(text); - let mut parser = BibtexParser::new(tokens); + let lexer = BibtexLexer::new(text); + let mut parser = BibtexParser::new(lexer); let root = parser.root(); BibtexSyntaxTree::from(root) } } - -impl BibtexSyntaxTree { - fn find(&self, position: Position) -> Vec { - let mut finder = BibtexFinder::new(Some(position)); - for child in &self.root.children { - child.accept(&mut finder); - } - finder.results - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn verify(text: &str, expected: Vec) { - let actual: Vec = BibtexSyntaxTree::from(text) - .descendants - .iter() - .map(|node| node.kind()) - .collect(); - assert_eq!(expected, actual); - } - - #[test] - fn test_empty() { - verify("", Vec::new()); - } - - #[test] - fn test_preamble() { - verify("@preamble", vec![BibtexNodeKind::Preamble]); - verify("@preamble{", vec![BibtexNodeKind::Preamble]); - verify( - "@preamble{\"foo\"", - vec![ - BibtexNodeKind::Preamble, - BibtexNodeKind::QuotedContent, - BibtexNodeKind::Word, - ], - ); - verify( - "@preamble{\"foo\"}", - vec![ - BibtexNodeKind::Preamble, - BibtexNodeKind::QuotedContent, - BibtexNodeKind::Word, - ], - ); - } - - #[test] - fn test_string() { - verify("@string", vec![BibtexNodeKind::String]); - verify("@string{", vec![BibtexNodeKind::String]); - verify("@string{key", vec![BibtexNodeKind::String]); - verify( - "@string{key=value", - vec![BibtexNodeKind::String, BibtexNodeKind::Word], - ); - verify( - "@string{key=value}", - vec![BibtexNodeKind::String, BibtexNodeKind::Word], - ); - } - - #[test] - fn test_entry() { - verify("@article", vec![BibtexNodeKind::Entry]); - verify("@article{", vec![BibtexNodeKind::Entry]); - verify("@article{key", vec![BibtexNodeKind::Entry]); - verify("@article{key,", vec![BibtexNodeKind::Entry]); - verify( - "@article{key, foo = bar}", - vec![ - BibtexNodeKind::Entry, - BibtexNodeKind::Field, - BibtexNodeKind::Word, - ], - ); - } - - #[test] - fn test_content() { - verify( - "@article{key, foo = {bar baz \\qux}}", - vec![ - BibtexNodeKind::Entry, - BibtexNodeKind::Field, - BibtexNodeKind::BracedContent, - BibtexNodeKind::Word, - BibtexNodeKind::Word, - BibtexNodeKind::Command, - ], - ); - verify( - "@article{key, foo = bar # baz}", - vec![ - BibtexNodeKind::Entry, - BibtexNodeKind::Field, - BibtexNodeKind::Concat, - BibtexNodeKind::Word, - BibtexNodeKind::Word, - ], - ); - } -} diff --git a/src/syntax/bibtex/parser.rs b/src/syntax/bibtex/parser.rs index 431f850c..80564b30 100644 --- a/src/syntax/bibtex/parser.rs +++ b/src/syntax/bibtex/parser.rs @@ -1,6 +1,5 @@ use crate::syntax::bibtex::ast::*; use std::iter::Peekable; -use std::rc::Rc; pub struct BibtexParser> { tokens: Peekable, @@ -18,18 +17,18 @@ impl> BibtexParser { while let Some(ref token) = self.tokens.peek() { match token.kind { BibtexTokenKind::PreambleKind => { - children.push(BibtexDeclaration::Preamble(Rc::new(self.preamble()))); + children.push(BibtexDeclaration::Preamble(self.preamble())); } BibtexTokenKind::StringKind => { - children.push(BibtexDeclaration::String(Rc::new(self.string()))); + children.push(BibtexDeclaration::String(self.string())); } BibtexTokenKind::EntryKind => { - children.push(BibtexDeclaration::Entry(Rc::new(self.entry()))); + children.push(BibtexDeclaration::Entry(self.entry())); } _ => { let token = self.tokens.next().unwrap(); let comment = BibtexComment::new(token); - children.push(BibtexDeclaration::Comment(Rc::new(comment))); + children.push(BibtexDeclaration::Comment(comment)); } } } @@ -100,7 +99,7 @@ impl> BibtexParser { let mut fields = Vec::new(); while self.next_of_kind(BibtexTokenKind::Word) { - fields.push(Rc::new(self.field())); + fields.push(self.field()); } let right = self.expect2(BibtexTokenKind::EndBrace, BibtexTokenKind::EndParen); @@ -134,8 +133,8 @@ impl> BibtexParser { | BibtexTokenKind::Assign | BibtexTokenKind::Comma | BibtexTokenKind::BeginParen - | BibtexTokenKind::EndParen => BibtexContent::Word(Rc::new(BibtexWord::new(token))), - BibtexTokenKind::Command => BibtexContent::Command(Rc::new(BibtexCommand::new(token))), + | BibtexTokenKind::EndParen => BibtexContent::Word(BibtexWord::new(token)), + BibtexTokenKind::Command => BibtexContent::Command(BibtexCommand::new(token)), BibtexTokenKind::Quote => { let mut children = Vec::new(); while self.can_match_content() { @@ -145,9 +144,7 @@ impl> BibtexParser { children.push(self.content()); } let right = self.expect1(BibtexTokenKind::Quote); - BibtexContent::QuotedContent(Rc::new(BibtexQuotedContent::new( - token, children, right, - ))) + BibtexContent::QuotedContent(BibtexQuotedContent::new(token, children, right)) } BibtexTokenKind::BeginBrace => { let mut children = Vec::new(); @@ -155,9 +152,7 @@ impl> BibtexParser { children.push(self.content()); } let right = self.expect1(BibtexTokenKind::EndBrace); - BibtexContent::BracedContent(Rc::new(BibtexBracedContent::new( - token, children, right, - ))) + BibtexContent::BracedContent(BibtexBracedContent::new(token, children, right)) } _ => unreachable!(), }; @@ -167,7 +162,7 @@ impl> BibtexParser { } else { None }; - BibtexContent::Concat(Rc::new(BibtexConcat::new(left, operator, right))) + BibtexContent::Concat(Box::new(BibtexConcat::new(left, operator, right))) } else { left }