diff options
Diffstat (limited to 'support/texlab/src/syntax/bibtex')
-rw-r--r-- | support/texlab/src/syntax/bibtex/ast.rs | 577 | ||||
-rw-r--r-- | support/texlab/src/syntax/bibtex/finder.rs | 110 | ||||
-rw-r--r-- | support/texlab/src/syntax/bibtex/lexer.rs | 176 | ||||
-rw-r--r-- | support/texlab/src/syntax/bibtex/mod.rs | 75 | ||||
-rw-r--r-- | support/texlab/src/syntax/bibtex/parser.rs | 219 |
5 files changed, 1157 insertions, 0 deletions
diff --git a/support/texlab/src/syntax/bibtex/ast.rs b/support/texlab/src/syntax/bibtex/ast.rs new file mode 100644 index 0000000000..09f2ee5b88 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/ast.rs @@ -0,0 +1,577 @@ +use crate::range::RangeExt; +use crate::syntax::text::{Span, SyntaxNode}; +use lsp_types::Range; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum BibtexTokenKind { + PreambleKind, + StringKind, + EntryKind, + Word, + Command, + Assign, + Comma, + Concat, + Quote, + BeginBrace, + EndBrace, + BeginParen, + EndParen, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct BibtexToken { + pub span: Span, + pub kind: BibtexTokenKind, +} + +impl BibtexToken { + pub fn new(span: Span, kind: BibtexTokenKind) -> Self { + BibtexToken { span, kind } + } + + pub fn text(&self) -> &str { + &self.span.text + } +} + +impl SyntaxNode for BibtexToken { + fn range(&self) -> Range { + self.span.range + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct BibtexRoot { + pub children: Vec<BibtexDeclaration>, +} + +impl BibtexRoot { + pub fn new(children: Vec<BibtexDeclaration>) -> Self { + BibtexRoot { children } + } +} + +impl SyntaxNode for BibtexRoot { + fn range(&self) -> Range { + if self.children.is_empty() { + Range::new_simple(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(Box<BibtexComment>), + Preamble(Box<BibtexPreamble>), + String(Box<BibtexString>), + Entry(Box<BibtexEntry>), +} + +impl BibtexDeclaration { + pub fn accept<'a, T: BibtexVisitor<'a>>(&'a self, visitor: &mut T) { + match self { + 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), + } + } +} + +impl SyntaxNode for BibtexDeclaration { + fn range(&self) -> Range { + match self { + BibtexDeclaration::Comment(comment) => comment.range, + BibtexDeclaration::Preamble(preamble) => preamble.range, + BibtexDeclaration::String(string) => string.range, + BibtexDeclaration::Entry(entry) => entry.range, + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct BibtexComment { + pub range: Range, + pub token: BibtexToken, +} + +impl BibtexComment { + pub fn new(token: BibtexToken) -> Self { + BibtexComment { + range: token.range(), + token, + } + } +} + +impl SyntaxNode for BibtexComment { + fn range(&self) -> Range { + self.range + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct BibtexPreamble { + pub range: Range, + pub ty: BibtexToken, + pub left: Option<BibtexToken>, + pub content: Option<BibtexContent>, + pub right: Option<BibtexToken>, +} + +impl BibtexPreamble { + pub fn new( + ty: BibtexToken, + left: Option<BibtexToken>, + content: Option<BibtexContent>, + right: Option<BibtexToken>, + ) -> Self { + let end = if let Some(ref right) = right { + right.end() + } else if let Some(ref content) = content { + content.end() + } else if let Some(ref left) = left { + left.end() + } else { + ty.end() + }; + BibtexPreamble { + range: Range::new(ty.start(), end), + ty, + left, + content, + right, + } + } +} + +impl SyntaxNode for BibtexPreamble { + fn range(&self) -> Range { + self.range + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct BibtexString { + pub range: Range, + pub ty: BibtexToken, + pub left: Option<BibtexToken>, + pub name: Option<BibtexToken>, + pub assign: Option<BibtexToken>, + pub value: Option<BibtexContent>, + pub right: Option<BibtexToken>, +} + +impl BibtexString { + pub fn new( + ty: BibtexToken, + left: Option<BibtexToken>, + name: Option<BibtexToken>, + assign: Option<BibtexToken>, + value: Option<BibtexContent>, + right: Option<BibtexToken>, + ) -> Self { + let end = if let Some(ref right) = right { + right.end() + } else if let Some(ref value) = value { + value.end() + } else if let Some(ref assign) = assign { + assign.end() + } else if let Some(ref name) = name { + name.end() + } else if let Some(ref left) = left { + left.end() + } else { + ty.end() + }; + + BibtexString { + range: Range::new(ty.start(), end), + ty, + left, + name, + assign, + value, + right, + } + } +} + +impl SyntaxNode for BibtexString { + fn range(&self) -> Range { + self.range + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct BibtexEntry { + pub range: Range, + pub ty: BibtexToken, + pub left: Option<BibtexToken>, + pub key: Option<BibtexToken>, + pub comma: Option<BibtexToken>, + pub fields: Vec<BibtexField>, + pub right: Option<BibtexToken>, +} + +impl BibtexEntry { + pub fn new( + ty: BibtexToken, + left: Option<BibtexToken>, + key: Option<BibtexToken>, + comma: Option<BibtexToken>, + fields: Vec<BibtexField>, + right: Option<BibtexToken>, + ) -> Self { + let end = if let Some(ref right) = right { + right.end() + } else if !fields.is_empty() { + fields[fields.len() - 1].range.end + } else if let Some(ref comma) = comma { + comma.end() + } else if let Some(ref key) = key { + key.end() + } else if let Some(ref left) = left { + left.end() + } else { + ty.end() + }; + + BibtexEntry { + range: Range::new(ty.start(), end), + ty, + left, + key, + comma, + fields, + right, + } + } + + pub fn is_comment(&self) -> bool { + self.ty.text().to_lowercase() == "@comment" + } + + pub fn find_field(&self, name: &str) -> Option<&BibtexField> { + self.fields + .iter() + .find(|field| field.name.text().to_lowercase() == name) + } +} + +impl SyntaxNode for BibtexEntry { + fn range(&self) -> Range { + self.range + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct BibtexField { + pub range: Range, + pub name: BibtexToken, + pub assign: Option<BibtexToken>, + pub content: Option<BibtexContent>, + pub comma: Option<BibtexToken>, +} + +impl BibtexField { + pub fn new( + name: BibtexToken, + assign: Option<BibtexToken>, + content: Option<BibtexContent>, + comma: Option<BibtexToken>, + ) -> Self { + let end = if let Some(ref comma) = comma { + comma.end() + } else if let Some(ref content) = content { + content.end() + } else if let Some(ref assign) = assign { + assign.end() + } else { + name.end() + }; + + BibtexField { + range: Range::new(name.start(), end), + name, + assign, + content, + comma, + } + } +} + +impl SyntaxNode for BibtexField { + fn range(&self) -> Range { + self.range + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum BibtexContent { + Word(BibtexWord), + Command(BibtexCommand), + QuotedContent(BibtexQuotedContent), + BracedContent(BibtexBracedContent), + Concat(Box<BibtexConcat>), +} + +impl BibtexContent { + pub fn accept<'a, T: BibtexVisitor<'a>>(&'a self, visitor: &mut T) { + match self { + 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), + } + } +} + +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(), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct BibtexWord { + pub range: Range, + pub token: BibtexToken, +} + +impl BibtexWord { + pub fn new(token: BibtexToken) -> Self { + BibtexWord { + range: token.range(), + token, + } + } +} + +impl SyntaxNode for BibtexWord { + fn range(&self) -> Range { + self.range + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct BibtexCommand { + pub range: Range, + pub token: BibtexToken, +} + +impl BibtexCommand { + pub fn new(token: BibtexToken) -> Self { + BibtexCommand { + range: token.range(), + token, + } + } +} + +impl SyntaxNode for BibtexCommand { + fn range(&self) -> Range { + self.range + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct BibtexQuotedContent { + pub range: Range, + pub left: BibtexToken, + pub children: Vec<BibtexContent>, + pub right: Option<BibtexToken>, +} + +impl BibtexQuotedContent { + pub fn new( + left: BibtexToken, + children: Vec<BibtexContent>, + right: Option<BibtexToken>, + ) -> Self { + let end = if let Some(ref right) = right { + right.end() + } else if !children.is_empty() { + children[children.len() - 1].end() + } else { + left.end() + }; + + BibtexQuotedContent { + range: Range::new(left.start(), end), + left, + children, + right, + } + } +} + +impl SyntaxNode for BibtexQuotedContent { + fn range(&self) -> Range { + self.range + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct BibtexBracedContent { + pub range: Range, + pub left: BibtexToken, + pub children: Vec<BibtexContent>, + pub right: Option<BibtexToken>, +} + +impl BibtexBracedContent { + pub fn new( + left: BibtexToken, + children: Vec<BibtexContent>, + right: Option<BibtexToken>, + ) -> Self { + let end = if let Some(ref right) = right { + right.end() + } else if !children.is_empty() { + children[children.len() - 1].end() + } else { + left.end() + }; + + BibtexBracedContent { + range: Range::new(left.start(), end), + left, + children, + right, + } + } +} + +impl SyntaxNode for BibtexBracedContent { + fn range(&self) -> Range { + self.range + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct BibtexConcat { + pub range: Range, + pub left: BibtexContent, + pub operator: BibtexToken, + pub right: Option<BibtexContent>, +} + +impl BibtexConcat { + pub fn new(left: BibtexContent, operator: BibtexToken, right: Option<BibtexContent>) -> Self { + let end = if let Some(ref right) = right { + right.end() + } else { + operator.end() + }; + + BibtexConcat { + range: Range::new(left.start(), end), + left, + operator, + right, + } + } +} + +impl SyntaxNode for BibtexConcat { + fn range(&self) -> Range { + 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 { + pub fn walk_root<'a, T: BibtexVisitor<'a>>(visitor: &mut T, root: &'a BibtexRoot) { + for declaration in &root.children { + declaration.accept(visitor); + } + } + + pub fn walk_preamble<'a, T: BibtexVisitor<'a>>(visitor: &mut T, preamble: &'a BibtexPreamble) { + if let Some(ref content) = preamble.content { + content.accept(visitor); + } + } + + pub fn walk_string<'a, T: BibtexVisitor<'a>>(visitor: &mut T, string: &'a BibtexString) { + if let Some(ref value) = string.value { + value.accept(visitor); + } + } + + pub fn walk_entry<'a, T: BibtexVisitor<'a>>(visitor: &mut T, entry: &'a BibtexEntry) { + for field in &entry.fields { + visitor.visit_field(field); + } + } + + pub fn walk_field<'a, T: BibtexVisitor<'a>>(visitor: &mut T, field: &'a BibtexField) { + if let Some(ref content) = field.content { + content.accept(visitor); + } + } + + pub fn walk_quoted_content<'a, T: BibtexVisitor<'a>>( + visitor: &mut T, + content: &'a BibtexQuotedContent, + ) { + for child in &content.children { + child.accept(visitor); + } + } + + pub fn walk_braced_content<'a, T: BibtexVisitor<'a>>( + visitor: &mut T, + content: &'a BibtexBracedContent, + ) { + for child in &content.children { + child.accept(visitor); + } + } + + pub fn walk_concat<'a, T: BibtexVisitor<'a>>(visitor: &mut T, concat: &'a BibtexConcat) { + concat.left.accept(visitor); + if let Some(ref right) = concat.right { + right.accept(visitor); + } + } +} diff --git a/support/texlab/src/syntax/bibtex/finder.rs b/support/texlab/src/syntax/bibtex/finder.rs new file mode 100644 index 0000000000..acc46c6f45 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/finder.rs @@ -0,0 +1,110 @@ +use super::ast::*; +use crate::range::RangeExt; +use crate::syntax::text::SyntaxNode; +use lsp_types::Position; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum BibtexNode<'a> { + Root(&'a BibtexRoot), + Preamble(&'a BibtexPreamble), + String(&'a BibtexString), + Entry(&'a BibtexEntry), + Comment(&'a BibtexComment), + Field(&'a BibtexField), + Word(&'a BibtexWord), + Command(&'a BibtexCommand), + QuotedContent(&'a BibtexQuotedContent), + BracedContent(&'a BibtexBracedContent), + Concat(&'a BibtexConcat), +} + +#[derive(Debug)] +pub struct BibtexFinder<'a> { + pub position: Position, + pub results: Vec<BibtexNode<'a>>, +} + +impl<'a> BibtexFinder<'a> { + pub fn new(position: Position) -> Self { + BibtexFinder { + position, + results: Vec::new(), + } + } +} + +impl<'a> BibtexVisitor<'a> for BibtexFinder<'a> { + fn visit_root(&mut self, root: &'a BibtexRoot) { + if root.range().contains(self.position) { + self.results.push(BibtexNode::Root(root)); + BibtexWalker::walk_root(self, root); + } + } + + fn visit_comment(&mut self, comment: &'a BibtexComment) { + if comment.range.contains(self.position) { + self.results.push(BibtexNode::Comment(comment)); + } + } + + fn visit_preamble(&mut self, preamble: &'a BibtexPreamble) { + if preamble.range.contains(self.position) { + self.results.push(BibtexNode::Preamble(preamble)); + BibtexWalker::walk_preamble(self, preamble); + } + } + + fn visit_string(&mut self, string: &'a BibtexString) { + if string.range.contains(self.position) { + self.results.push(BibtexNode::String(string)); + BibtexWalker::walk_string(self, string); + } + } + + fn visit_entry(&mut self, entry: &'a BibtexEntry) { + if entry.range.contains(self.position) { + self.results.push(BibtexNode::Entry(entry)); + BibtexWalker::walk_entry(self, entry); + } + } + + fn visit_field(&mut self, field: &'a BibtexField) { + if field.range.contains(self.position) { + self.results.push(BibtexNode::Field(field)); + BibtexWalker::walk_field(self, field); + } + } + + fn visit_word(&mut self, word: &'a BibtexWord) { + if word.range.contains(self.position) { + self.results.push(BibtexNode::Word(word)); + } + } + + fn visit_command(&mut self, command: &'a BibtexCommand) { + if command.range.contains(self.position) { + self.results.push(BibtexNode::Command(command)); + } + } + + fn visit_quoted_content(&mut self, content: &'a BibtexQuotedContent) { + if content.range.contains(self.position) { + self.results.push(BibtexNode::QuotedContent(content)); + BibtexWalker::walk_quoted_content(self, content); + } + } + + fn visit_braced_content(&mut self, content: &'a BibtexBracedContent) { + if content.range.contains(self.position) { + self.results.push(BibtexNode::BracedContent(content)); + BibtexWalker::walk_braced_content(self, content); + } + } + + fn visit_concat(&mut self, concat: &'a BibtexConcat) { + if concat.range.contains(self.position) { + self.results.push(BibtexNode::Concat(concat)); + BibtexWalker::walk_concat(self, concat); + } + } +} diff --git a/support/texlab/src/syntax/bibtex/lexer.rs b/support/texlab/src/syntax/bibtex/lexer.rs new file mode 100644 index 0000000000..8278ef2631 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/lexer.rs @@ -0,0 +1,176 @@ +use super::ast::{BibtexToken, BibtexTokenKind}; +use crate::syntax::text::CharStream; + +pub struct BibtexLexer<'a> { + stream: CharStream<'a>, +} + +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' + } + + self.stream.start_span(); + self.stream.next().unwrap(); + while self.stream.satifies(|c| is_type_char(*c)) { + self.stream.next(); + } + let span = self.stream.end_span(); + let kind = match span.text.to_lowercase().as_ref() { + "@preamble" => BibtexTokenKind::PreambleKind, + "@string" => BibtexTokenKind::StringKind, + _ => BibtexTokenKind::EntryKind, + }; + BibtexToken::new(span, kind) + } + + fn single_character(&mut self, kind: BibtexTokenKind) -> BibtexToken { + self.stream.start_span(); + self.stream.next(); + let span = self.stream.end_span(); + BibtexToken::new(span, kind) + } + + fn command(&mut self) -> BibtexToken { + let span = self.stream.command(); + BibtexToken::new(span, BibtexTokenKind::Command) + } + + fn word(&mut self) -> BibtexToken { + fn is_word_char(c: char) -> bool { + !c.is_whitespace() + && c != '@' + && c != '=' + && c != ',' + && c != '#' + && c != '"' + && c != '{' + && c != '}' + && c != '(' + && c != ')' + } + + self.stream.start_span(); + while self.stream.satifies(|c| is_word_char(*c)) { + self.stream.next(); + } + let span = self.stream.end_span(); + BibtexToken::new(span, BibtexTokenKind::Word) + } +} + +impl<'a> Iterator for BibtexLexer<'a> { + type Item = BibtexToken; + + fn next(&mut self) -> Option<BibtexToken> { + loop { + match self.stream.peek() { + Some('@') => return Some(self.kind()), + Some('=') => return Some(self.single_character(BibtexTokenKind::Assign)), + Some(',') => return Some(self.single_character(BibtexTokenKind::Comma)), + Some('#') => return Some(self.single_character(BibtexTokenKind::Concat)), + Some('"') => return Some(self.single_character(BibtexTokenKind::Quote)), + Some('{') => return Some(self.single_character(BibtexTokenKind::BeginBrace)), + Some('}') => return Some(self.single_character(BibtexTokenKind::EndBrace)), + Some('(') => return Some(self.single_character(BibtexTokenKind::BeginParen)), + Some(')') => return Some(self.single_character(BibtexTokenKind::EndParen)), + Some('\\') => return Some(self.command()), + Some(c) => { + if c.is_whitespace() { + self.stream.next(); + } else { + return Some(self.word()); + } + } + None => { + return None; + } + } + } + } +} + +#[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/support/texlab/src/syntax/bibtex/mod.rs b/support/texlab/src/syntax/bibtex/mod.rs new file mode 100644 index 0000000000..9910390fe7 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/mod.rs @@ -0,0 +1,75 @@ +mod ast; +mod finder; +mod lexer; +mod parser; + +use self::lexer::BibtexLexer; +use self::parser::BibtexParser; + +pub use self::ast::*; +pub use self::finder::*; +use lsp_types::Position; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct BibtexSyntaxTree { + pub root: BibtexRoot, +} + +impl BibtexSyntaxTree { + pub fn entries(&self) -> Vec<&BibtexEntry> { + let mut entries: Vec<&BibtexEntry> = Vec::new(); + for declaration in &self.root.children { + if let BibtexDeclaration::Entry(entry) = declaration { + entries.push(&entry); + } + } + entries + } + + pub fn strings(&self) -> Vec<&BibtexString> { + let mut strings: Vec<&BibtexString> = Vec::new(); + for declaration in &self.root.children { + if let BibtexDeclaration::String(string) = declaration { + strings.push(&string); + } + } + strings + } + + pub fn find(&self, position: Position) -> Vec<BibtexNode> { + let mut finder = BibtexFinder::new(position); + finder.visit_root(&self.root); + finder.results + } + + pub fn find_entry(&self, key: &str) -> Option<&BibtexEntry> { + self.entries() + .into_iter() + .find(|entry| entry.key.as_ref().map(BibtexToken::text) == Some(key)) + } + + pub fn resolve_crossref(&self, entry: &BibtexEntry) -> Option<&BibtexEntry> { + if let Some(field) = entry.find_field("crossref") { + if let Some(BibtexContent::BracedContent(content)) = &field.content { + if let Some(BibtexContent::Word(name)) = content.children.get(0) { + return self.find_entry(name.token.text()); + } + } + } + None + } +} + +impl From<BibtexRoot> for BibtexSyntaxTree { + fn from(root: BibtexRoot) -> Self { + BibtexSyntaxTree { root } + } +} + +impl From<&str> for BibtexSyntaxTree { + fn from(text: &str) -> Self { + let lexer = BibtexLexer::new(text); + let mut parser = BibtexParser::new(lexer); + parser.root().into() + } +} diff --git a/support/texlab/src/syntax/bibtex/parser.rs b/support/texlab/src/syntax/bibtex/parser.rs new file mode 100644 index 0000000000..ffc33da545 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/parser.rs @@ -0,0 +1,219 @@ +use super::ast::*; +use std::iter::Peekable; + +pub struct BibtexParser<I: Iterator<Item = BibtexToken>> { + tokens: Peekable<I>, +} + +impl<I: Iterator<Item = BibtexToken>> BibtexParser<I> { + pub fn new(tokens: I) -> Self { + BibtexParser { + tokens: tokens.peekable(), + } + } + + pub fn root(&mut self) -> BibtexRoot { + let mut children = Vec::new(); + while let Some(ref token) = self.tokens.peek() { + match token.kind { + BibtexTokenKind::PreambleKind => { + let preamble = Box::new(self.preamble()); + children.push(BibtexDeclaration::Preamble(preamble)); + } + BibtexTokenKind::StringKind => { + let string = Box::new(self.string()); + children.push(BibtexDeclaration::String(string)); + } + BibtexTokenKind::EntryKind => { + let entry = Box::new(self.entry()); + children.push(BibtexDeclaration::Entry(entry)); + } + _ => { + let comment = BibtexComment::new(self.tokens.next().unwrap()); + children.push(BibtexDeclaration::Comment(Box::new(comment))); + } + } + } + BibtexRoot::new(children) + } + + fn preamble(&mut self) -> BibtexPreamble { + let ty = self.tokens.next().unwrap(); + + let left = self.expect2(BibtexTokenKind::BeginBrace, BibtexTokenKind::BeginParen); + if left.is_none() { + return BibtexPreamble::new(ty, None, None, None); + } + + if !self.can_match_content() { + return BibtexPreamble::new(ty, left, None, None); + } + let content = self.content(); + + let right = self.expect2(BibtexTokenKind::EndBrace, BibtexTokenKind::EndParen); + BibtexPreamble::new(ty, left, Some(content), right) + } + + fn string(&mut self) -> BibtexString { + let ty = self.tokens.next().unwrap(); + + let left = self.expect2(BibtexTokenKind::BeginBrace, BibtexTokenKind::BeginParen); + if left.is_none() { + return BibtexString::new(ty, None, None, None, None, None); + } + + let name = self.expect1(BibtexTokenKind::Word); + if name.is_none() { + return BibtexString::new(ty, left, None, None, None, None); + } + + let assign = self.expect1(BibtexTokenKind::Assign); + if assign.is_none() { + return BibtexString::new(ty, left, name, None, None, None); + } + + if !self.can_match_content() { + return BibtexString::new(ty, left, name, assign, None, None); + } + let value = self.content(); + + let right = self.expect2(BibtexTokenKind::EndBrace, BibtexTokenKind::EndParen); + BibtexString::new(ty, left, name, assign, Some(value), right) + } + + fn entry(&mut self) -> BibtexEntry { + let ty = self.tokens.next().unwrap(); + + let left = self.expect2(BibtexTokenKind::BeginBrace, BibtexTokenKind::BeginParen); + if left.is_none() { + return BibtexEntry::new(ty, None, None, None, Vec::new(), None); + } + + let name = self.expect1(BibtexTokenKind::Word); + if name.is_none() { + return BibtexEntry::new(ty, left, None, None, Vec::new(), None); + } + + let comma = self.expect1(BibtexTokenKind::Comma); + if comma.is_none() { + return BibtexEntry::new(ty, left, name, None, Vec::new(), None); + } + + let mut fields = Vec::new(); + while self.next_of_kind(BibtexTokenKind::Word) { + fields.push(self.field()); + } + + let right = self.expect2(BibtexTokenKind::EndBrace, BibtexTokenKind::EndParen); + BibtexEntry::new(ty, left, name, comma, fields, right) + } + + fn field(&mut self) -> BibtexField { + let name = self.tokens.next().unwrap(); + + let assign = self.expect1(BibtexTokenKind::Assign); + if assign.is_none() { + return BibtexField::new(name, None, None, None); + } + + if !self.can_match_content() { + return BibtexField::new(name, assign, None, None); + } + let content = self.content(); + + let comma = self.expect1(BibtexTokenKind::Comma); + BibtexField::new(name, assign, Some(content), comma) + } + + fn content(&mut self) -> BibtexContent { + let token = self.tokens.next().unwrap(); + let left = match token.kind { + BibtexTokenKind::PreambleKind + | BibtexTokenKind::StringKind + | BibtexTokenKind::EntryKind + | BibtexTokenKind::Word + | BibtexTokenKind::Assign + | BibtexTokenKind::Comma + | BibtexTokenKind::BeginParen + | 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() { + if self.next_of_kind(BibtexTokenKind::Quote) { + break; + } + children.push(self.content()); + } + let right = self.expect1(BibtexTokenKind::Quote); + BibtexContent::QuotedContent(BibtexQuotedContent::new(token, children, right)) + } + BibtexTokenKind::BeginBrace => { + let mut children = Vec::new(); + while self.can_match_content() { + children.push(self.content()); + } + let right = self.expect1(BibtexTokenKind::EndBrace); + BibtexContent::BracedContent(BibtexBracedContent::new(token, children, right)) + } + _ => unreachable!(), + }; + if let Some(operator) = self.expect1(BibtexTokenKind::Concat) { + let right = if self.can_match_content() { + Some(self.content()) + } else { + None + }; + BibtexContent::Concat(Box::new(BibtexConcat::new(left, operator, right))) + } else { + left + } + } + + fn can_match_content(&mut self) -> bool { + if let Some(ref token) = self.tokens.peek() { + match token.kind { + BibtexTokenKind::PreambleKind + | BibtexTokenKind::StringKind + | BibtexTokenKind::EntryKind + | BibtexTokenKind::Word + | BibtexTokenKind::Command + | BibtexTokenKind::Assign + | BibtexTokenKind::Comma + | BibtexTokenKind::Quote + | BibtexTokenKind::BeginBrace + | BibtexTokenKind::BeginParen + | BibtexTokenKind::EndParen => true, + BibtexTokenKind::Concat | BibtexTokenKind::EndBrace => false, + } + } else { + false + } + } + + fn expect1(&mut self, kind: BibtexTokenKind) -> Option<BibtexToken> { + if let Some(ref token) = self.tokens.peek() { + if token.kind == kind { + return self.tokens.next(); + } + } + None + } + + fn expect2(&mut self, kind1: BibtexTokenKind, kind2: BibtexTokenKind) -> Option<BibtexToken> { + if let Some(ref token) = self.tokens.peek() { + if token.kind == kind1 || token.kind == kind2 { + return self.tokens.next(); + } + } + None + } + + fn next_of_kind(&mut self, kind: BibtexTokenKind) -> bool { + if let Some(token) = self.tokens.peek() { + token.kind == kind + } else { + false + } + } +} |