Simplify BibTeX parser

This commit is contained in:
Patrick Förster 2019-04-18 14:40:10 +02:00
parent afce772da8
commit 3fbbccfc50
4 changed files with 215 additions and 368 deletions

View file

@ -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<T> {
fn visit_comment(&mut self, comment: Rc<BibtexComment>) -> T;
fn visit_preamble(&mut self, preamble: Rc<BibtexPreamble>) -> T;
fn visit_string(&mut self, string: Rc<BibtexString>) -> T;
fn visit_entry(&mut self, entry: Rc<BibtexEntry>) -> T;
fn visit_field(&mut self, field: Rc<BibtexField>) -> T;
fn visit_word(&mut self, word: Rc<BibtexWord>) -> T;
fn visit_command(&mut self, command: Rc<BibtexCommand>) -> T;
fn visit_quoted_content(&mut self, content: Rc<BibtexQuotedContent>) -> T;
fn visit_braced_content(&mut self, content: Rc<BibtexBracedContent>) -> T;
fn visit_concat(&mut self, concat: Rc<BibtexConcat>) -> 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<BibtexComment>),
Preamble(Rc<BibtexPreamble>),
String(Rc<BibtexString>),
Entry(Rc<BibtexEntry>),
Comment(BibtexComment),
Preamble(BibtexPreamble),
String(BibtexString),
Entry(BibtexEntry),
}
impl BibtexDeclaration {
pub fn accept<T>(&self, visitor: &mut BibtexVisitor<T>) -> 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<BibtexToken>,
pub key: Option<BibtexToken>,
pub comma: Option<BibtexToken>,
pub fields: Vec<Rc<BibtexField>>,
pub fields: Vec<BibtexField>,
pub right: Option<BibtexToken>,
}
@ -235,7 +226,7 @@ impl BibtexEntry {
left: Option<BibtexToken>,
key: Option<BibtexToken>,
comma: Option<BibtexToken>,
fields: Vec<Rc<BibtexField>>,
fields: Vec<BibtexField>,
right: Option<BibtexToken>,
) -> 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<BibtexWord>),
Command(Rc<BibtexCommand>),
QuotedContent(Rc<BibtexQuotedContent>),
BracedContent(Rc<BibtexBracedContent>),
Concat(Rc<BibtexConcat>),
Word(BibtexWord),
Command(BibtexCommand),
QuotedContent(BibtexQuotedContent),
BracedContent(BibtexBracedContent),
Concat(Box<BibtexConcat>),
}
impl BibtexContent {
pub fn accept<T>(&self, visitor: &mut BibtexVisitor<T>) -> 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);
}
}
}

View file

@ -5,20 +5,13 @@ pub struct BibtexLexer<'a> {
stream: CharStream<'a>,
}
impl<'a> From<CharStream<'a>> 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());
}
}

View file

@ -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<BibtexField>),
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<Position>,
pub results: Vec<BibtexNode>,
}
impl BibtexFinder {
pub fn new(position: Option<Position>) -> 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<BibtexComment>) {
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<BibtexPreamble>) {
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<BibtexString>) {
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<BibtexEntry>) {
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<BibtexField>) {
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<BibtexWord>) {
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<BibtexCommand>) {
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<BibtexQuotedContent>) {
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<BibtexBracedContent>) {
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<BibtexConcat>) {
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<BibtexNode>,
}
impl From<BibtexRoot> 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<BibtexNode> {
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<BibtexNodeKind>) {
let actual: Vec<BibtexNodeKind> = 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,
],
);
}
}

View file

@ -1,6 +1,5 @@
use crate::syntax::bibtex::ast::*;
use std::iter::Peekable;
use std::rc::Rc;
pub struct BibtexParser<I: Iterator<Item = BibtexToken>> {
tokens: Peekable<I>,
@ -18,18 +17,18 @@ impl<I: Iterator<Item = BibtexToken>> BibtexParser<I> {
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<I: Iterator<Item = BibtexToken>> BibtexParser<I> {
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<I: Iterator<Item = BibtexToken>> BibtexParser<I> {
| 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<I: Iterator<Item = BibtexToken>> BibtexParser<I> {
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<I: Iterator<Item = BibtexToken>> BibtexParser<I> {
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<I: Iterator<Item = BibtexToken>> BibtexParser<I> {
} else {
None
};
BibtexContent::Concat(Rc::new(BibtexConcat::new(left, operator, right)))
BibtexContent::Concat(Box::new(BibtexConcat::new(left, operator, right)))
} else {
left
}