summaryrefslogtreecommitdiff
path: root/support/texlab/src/syntax/bibtex
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/syntax/bibtex')
-rw-r--r--support/texlab/src/syntax/bibtex/ast.rs577
-rw-r--r--support/texlab/src/syntax/bibtex/finder.rs110
-rw-r--r--support/texlab/src/syntax/bibtex/lexer.rs176
-rw-r--r--support/texlab/src/syntax/bibtex/mod.rs75
-rw-r--r--support/texlab/src/syntax/bibtex/parser.rs219
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
+ }
+ }
+}