diff options
Diffstat (limited to 'support/texlab/src/syntax/bibtex')
25 files changed, 1121 insertions, 1656 deletions
diff --git a/support/texlab/src/syntax/bibtex/ast.rs b/support/texlab/src/syntax/bibtex/ast.rs deleted file mode 100644 index 77ee98e26d..0000000000 --- a/support/texlab/src/syntax/bibtex/ast.rs +++ /dev/null @@ -1,431 +0,0 @@ -use crate::{ - protocol::{Position, Range, RangeExt}, - syntax::{Span, SyntaxNode}, -}; -use itertools::Itertools; -use petgraph::graph::{Graph, NodeIndex}; -use serde::{Deserialize, Serialize}; -use std::fmt; - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -pub enum TokenKind { - PreambleKind, - StringKind, - EntryKind, - Word, - Command, - Assign, - Comma, - Concat, - Quote, - BeginBrace, - EndBrace, - BeginParen, - EndParen, -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Token { - pub span: Span, - pub kind: TokenKind, -} - -impl SyntaxNode for Token { - fn range(&self) -> Range { - self.span.range() - } -} - -impl Token { - pub fn new(span: Span, kind: TokenKind) -> Self { - Self { span, kind } - } - - pub fn text(&self) -> &str { - &self.span.text - } -} - -#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -pub struct Root { - pub range: Range, -} - -impl SyntaxNode for Root { - fn range(&self) -> Range { - self.range - } -} - -impl fmt::Debug for Root { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Root") - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Comment { - pub token: Token, -} - -impl SyntaxNode for Comment { - fn range(&self) -> Range { - self.token.range() - } -} - -impl fmt::Debug for Comment { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Comment({})", self.token.text()) - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Preamble { - pub range: Range, - pub ty: Token, - pub left: Option<Token>, - pub right: Option<Token>, -} - -impl SyntaxNode for Preamble { - fn range(&self) -> Range { - self.range - } -} - -impl fmt::Debug for Preamble { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Preamble") - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct String { - pub range: Range, - pub ty: Token, - pub left: Option<Token>, - pub name: Option<Token>, - pub assign: Option<Token>, - pub right: Option<Token>, -} - -impl SyntaxNode for String { - fn range(&self) -> Range { - self.range - } -} - -impl fmt::Debug for String { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "String({:?})", self.name.as_ref().map(Token::text)) - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Entry { - pub range: Range, - pub ty: Token, - pub left: Option<Token>, - pub key: Option<Token>, - pub comma: Option<Token>, - pub right: Option<Token>, -} - -impl SyntaxNode for Entry { - fn range(&self) -> Range { - self.range - } -} - -impl fmt::Debug for Entry { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Entry({:?})", self.key.as_ref().map(Token::text)) - } -} - -impl Entry { - pub fn is_comment(&self) -> bool { - self.ty.text().to_lowercase() == "@comment" - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Field { - pub range: Range, - pub name: Token, - pub assign: Option<Token>, - pub comma: Option<Token>, -} - -impl SyntaxNode for Field { - fn range(&self) -> Range { - self.range - } -} - -impl fmt::Debug for Field { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Field({})", self.name.text()) - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Word { - pub token: Token, -} - -impl SyntaxNode for Word { - fn range(&self) -> Range { - self.token.range() - } -} - -impl fmt::Debug for Word { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Word({})", self.token.text()) - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Command { - pub token: Token, -} - -impl SyntaxNode for Command { - fn range(&self) -> Range { - self.token.range() - } -} - -impl fmt::Debug for Command { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Command({})", self.token.text()) - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct QuotedContent { - pub range: Range, - pub left: Token, - pub right: Option<Token>, -} - -impl SyntaxNode for QuotedContent { - fn range(&self) -> Range { - self.range - } -} - -impl fmt::Debug for QuotedContent { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "QuotedContent") - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct BracedContent { - pub range: Range, - pub left: Token, - pub right: Option<Token>, -} - -impl SyntaxNode for BracedContent { - fn range(&self) -> Range { - self.range - } -} - -impl fmt::Debug for BracedContent { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "BracedContent") - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Concat { - pub range: Range, - pub operator: Token, -} - -impl SyntaxNode for Concat { - fn range(&self) -> Range { - self.range - } -} - -impl fmt::Debug for Concat { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Concat") - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub enum Node { - Root(Root), - Comment(Comment), - Preamble(Box<Preamble>), - String(Box<String>), - Entry(Box<Entry>), - Field(Box<Field>), - Word(Word), - Command(Command), - QuotedContent(QuotedContent), - BracedContent(BracedContent), - Concat(Concat), -} - -impl SyntaxNode for Node { - fn range(&self) -> Range { - match self { - Self::Root(root) => root.range(), - Self::Comment(comment) => comment.range(), - Self::Preamble(preamble) => preamble.range(), - Self::String(string) => string.range(), - Self::Entry(entry) => entry.range(), - Self::Field(field) => field.range(), - Self::Word(word) => word.range(), - Self::Command(cmd) => cmd.range(), - Self::QuotedContent(content) => content.range(), - Self::BracedContent(content) => content.range(), - Self::Concat(concat) => concat.range(), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Tree { - pub graph: Graph<Node, ()>, - pub root: NodeIndex, -} - -impl Tree { - pub fn children(&self, parent: NodeIndex) -> impl Iterator<Item = NodeIndex> { - self.graph - .neighbors(parent) - .sorted_by_key(|child| self.graph[*child].start()) - } - - pub fn has_children(&self, parent: NodeIndex) -> bool { - self.children(parent).next().is_some() - } - - pub fn walk<'a, V: Visitor<'a>>(&'a self, visitor: &mut V, parent: NodeIndex) { - for child in self.children(parent) { - visitor.visit(self, child); - } - } - - pub fn find(&self, pos: Position) -> Vec<NodeIndex> { - let mut finder = Finder::new(pos); - finder.visit(self, self.root); - finder.results - } - - pub fn as_preamble(&self, node: NodeIndex) -> Option<&Preamble> { - if let Node::Preamble(preamble) = &self.graph[node] { - Some(preamble) - } else { - None - } - } - - pub fn as_string(&self, node: NodeIndex) -> Option<&String> { - if let Node::String(string) = &self.graph[node] { - Some(string) - } else { - None - } - } - - pub fn as_entry(&self, node: NodeIndex) -> Option<&Entry> { - if let Node::Entry(entry) = &self.graph[node] { - Some(entry) - } else { - None - } - } - - pub fn as_field(&self, node: NodeIndex) -> Option<&Field> { - if let Node::Field(field) = &self.graph[node] { - Some(field) - } else { - None - } - } - - pub fn as_command(&self, node: NodeIndex) -> Option<&Command> { - if let Node::Command(cmd) = &self.graph[node] { - Some(cmd) - } else { - None - } - } - - pub fn as_word(&self, node: NodeIndex) -> Option<&Word> { - if let Node::Word(word) = &self.graph[node] { - Some(word) - } else { - None - } - } - - pub fn entry_by_key(&self, key: &str) -> Option<NodeIndex> { - for node in self.children(self.root) { - if let Some(entry) = self.as_entry(node) { - if entry.key.as_ref().map(Token::text) == Some(key) { - return Some(node); - } - } - } - None - } - - pub fn field_by_name(&self, parent: NodeIndex, name: &str) -> Option<NodeIndex> { - let name = name.to_lowercase(); - self.as_entry(parent)?; - for node in self.children(parent) { - if let Some(field) = self.as_field(node) { - if field.name.text() == name { - return Some(node); - } - } - } - None - } - - pub fn crossref(&self, entry: NodeIndex) -> Option<NodeIndex> { - let field = self.field_by_name(entry, "crossref")?; - let content = self.children(field).next()?; - let key = self.as_word(self.children(content).next()?)?; - self.entry_by_key(key.token.text()) - } -} - -pub trait Visitor<'a> { - fn visit(&mut self, tree: &'a Tree, node: NodeIndex); -} - -#[derive(Debug)] -struct Finder { - position: Position, - results: Vec<NodeIndex>, -} - -impl Finder { - fn new(position: Position) -> Self { - Self { - position, - results: Vec::new(), - } - } -} - -impl<'a> Visitor<'a> for Finder { - fn visit(&mut self, tree: &'a Tree, node: NodeIndex) { - if tree.graph[node].range().contains(self.position) { - self.results.push(node); - tree.walk(self, node); - } - } -} diff --git a/support/texlab/src/syntax/bibtex/cst.rs b/support/texlab/src/syntax/bibtex/cst.rs new file mode 100644 index 0000000000..7f824681dc --- /dev/null +++ b/support/texlab/src/syntax/bibtex/cst.rs @@ -0,0 +1,212 @@ +use cstree::TextRange; + +use crate::syntax::CstNode; + +use super::{Language, SyntaxKind::*, SyntaxNode, SyntaxToken}; + +macro_rules! cst_node { + ($name:ident, $($kind:pat),+) => { + #[derive(Clone)] + #[repr(transparent)] + pub struct $name<'a>(&'a SyntaxNode); + + impl<'a> CstNode<'a> for $name<'a> { + type Lang = Language; + + fn cast(node: &'a cstree::ResolvedNode<Self::Lang>) -> Option<Self> + where + Self: Sized, + { + match node.kind() { + $($kind => Some(Self(node)),)+ + _ => None, + } + } + + fn syntax(&self) -> &'a cstree::ResolvedNode<Self::Lang> { + &self.0 + } + + fn small_range(&self) -> TextRange { + let full_range = self.syntax().text_range(); + let start = full_range.start(); + let mut token = self.syntax().last_token(); + while let Some(current) = token { + if !matches!(current.kind(), WHITESPACE | JUNK) { + return TextRange::new(start, current.text_range().end()); + } + token = current.prev_token(); + } + TextRange::new(start, start) + } + } + }; +} + +pub trait HasCurly<'a>: CstNode<'a, Lang = Language> { + fn left_curly(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == L_CURLY.into()) + } + + fn right_curly(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == R_CURLY.into()) + } +} + +pub trait HasQuotes<'a>: CstNode<'a, Lang = Language> { + fn left_quote(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == QUOTE.into()) + } + + fn right_quote(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .filter(|node| node.kind() == QUOTE.into()) + .nth(1) + } +} + +pub trait HasDelimiters<'a>: CstNode<'a, Lang = Language> { + fn left_delimiter(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| matches!(node.kind(), L_CURLY | L_PAREN)) + } + + fn right_delimiter(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| matches!(node.kind(), R_CURLY | R_PAREN)) + } +} + +pub trait HasType<'a>: CstNode<'a, Lang = Language> { + fn ty(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| { + matches!( + node.kind(), + PREAMBLE_TYPE | STRING_TYPE | COMMENT_TYPE | ENTRY_TYPE + ) + }) + } +} + +cst_node!(Root, ROOT); + +cst_node!(Junk, JUNK); + +cst_node!(Comment, COMMENT); + +impl<'a> HasType<'a> for Comment<'a> {} + +cst_node!(Preamble, PREAMBLE); + +impl<'a> HasType<'a> for Preamble<'a> {} + +impl<'a> HasDelimiters<'a> for Preamble<'a> {} + +impl<'a> Preamble<'a> { + pub fn value(&self) -> Option<Value<'a>> { + self.syntax().children().find_map(Value::cast) + } +} + +cst_node!(String, STRING); + +impl<'a> HasType<'a> for String<'a> {} + +impl<'a> HasDelimiters<'a> for String<'a> {} + +impl<'a> String<'a> { + pub fn name(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == WORD) + } + + pub fn equality_sign(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == EQUALITY_SIGN) + } + + pub fn value(&self) -> Option<Value<'a>> { + self.syntax().children().find_map(Value::cast) + } +} + +cst_node!(Entry, ENTRY); + +impl<'a> HasType<'a> for Entry<'a> {} + +impl<'a> HasDelimiters<'a> for Entry<'a> {} + +impl<'a> Entry<'a> { + pub fn key(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == WORD) + } + + pub fn fields(&self) -> impl Iterator<Item = Field<'a>> { + self.syntax().children().filter_map(Field::cast) + } +} + +cst_node!(Field, FIELD); + +impl<'a> Field<'a> { + pub fn name(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == WORD) + } + + pub fn equality_sign(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == EQUALITY_SIGN) + } + + pub fn value(&self) -> Option<Value<'a>> { + self.syntax().children().find_map(Value::cast) + } +} + +cst_node!(Value, VALUE); + +impl<'a> Value<'a> { + pub fn tokens(&self) -> impl Iterator<Item = Token<'a>> { + self.syntax().children().filter_map(Token::cast) + } +} + +cst_node!(Token, TOKEN); + +cst_node!(BraceGroup, BRACE_GROUP); + +impl<'a> HasCurly<'a> for BraceGroup<'a> {} + +cst_node!(QuoteGroup, QUOTE_GROUP); + +impl<'a> HasQuotes<'a> for QuoteGroup<'a> {} diff --git a/support/texlab/src/syntax/bibtex/formatter.rs b/support/texlab/src/syntax/bibtex/formatter.rs deleted file mode 100644 index d4965e7cd4..0000000000 --- a/support/texlab/src/syntax/bibtex/formatter.rs +++ /dev/null @@ -1,329 +0,0 @@ -use super::ast::*; -use crate::{protocol::BibtexFormattingOptions, syntax::text::SyntaxNode}; -use petgraph::graph::NodeIndex; -use std::{i32, string::String as StdString}; - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct FormattingParams<'a> { - pub tab_size: usize, - pub insert_spaces: bool, - pub options: &'a BibtexFormattingOptions, -} - -impl<'a> FormattingParams<'a> { - fn line_length(self) -> i32 { - let line_length = self.options.line_length.unwrap_or(120); - if line_length <= 0 { - i32::MAX - } else { - line_length - } - } - - fn indent(self) -> StdString { - if self.insert_spaces { - let mut buffer = StdString::new(); - for _ in 0..self.tab_size { - buffer.push(' '); - } - buffer - } else { - "\t".into() - } - } -} - -#[derive(Debug, Clone)] -struct Formatter<'a> { - params: FormattingParams<'a>, - indent: StdString, - output: StdString, - align: Vec<usize>, -} - -impl<'a> Formatter<'a> { - fn new(params: FormattingParams<'a>) -> Self { - Self { - params, - indent: params.indent(), - output: StdString::new(), - align: Vec::new(), - } - } - - fn visit_token_lowercase(&mut self, token: &Token) { - self.output.push_str(token.text().to_lowercase().as_ref()); - } - - fn should_insert_space(previous: &Token, current: &Token) -> bool { - previous.start().line != current.start().line - || previous.end().character < current.start().character - } -} - -impl<'a, 'b> Visitor<'b> for Formatter<'a> { - fn visit(&mut self, tree: &'b Tree, node: NodeIndex) { - match &tree.graph[node] { - Node::Root(_) => tree.walk(self, node), - Node::Comment(comment) => self.output.push_str(comment.token.text()), - Node::Preamble(preamble) => { - self.visit_token_lowercase(&preamble.ty); - self.output.push('{'); - if tree.has_children(node) { - self.align.push(self.output.chars().count()); - tree.walk(self, node); - self.output.push('}'); - } - } - Node::String(string) => { - self.visit_token_lowercase(&string.ty); - self.output.push('{'); - if let Some(name) = &string.name { - self.output.push_str(name.text()); - self.output.push_str(" = "); - if tree.has_children(node) { - self.align.push(self.output.chars().count()); - tree.walk(self, node); - self.output.push('}'); - } - } - } - Node::Entry(entry) => { - self.visit_token_lowercase(&entry.ty); - self.output.push('{'); - if let Some(key) = &entry.key { - self.output.push_str(key.text()); - self.output.push(','); - self.output.push('\n'); - tree.walk(self, node); - self.output.push('}'); - } - } - Node::Field(field) => { - self.output.push_str(&self.indent); - self.visit_token_lowercase(&field.name); - self.output.push_str(" = "); - if tree.has_children(node) { - let count = field.name.text().chars().count(); - self.align.push(self.params.tab_size as usize + count + 3); - tree.walk(self, node); - self.output.push(','); - self.output.push('\n'); - } - } - Node::Word(_) - | Node::Command(_) - | Node::BracedContent(_) - | Node::QuotedContent(_) - | Node::Concat(_) => { - let mut analyzer = ContentAnalyzer::default(); - analyzer.visit(tree, node); - let tokens = analyzer.tokens; - self.output.push_str(tokens[0].text()); - - let align = self.align.pop().unwrap_or_default(); - let mut length = align + tokens[0].text().chars().count(); - for i in 1..tokens.len() { - let previous = tokens[i - 1]; - let current = tokens[i]; - let current_length = current.text().chars().count(); - - let insert_space = Self::should_insert_space(previous, current); - let space_length = if insert_space { 1 } else { 0 }; - - if length + current_length + space_length > self.params.line_length() as usize { - self.output.push('\n'); - self.output.push_str(self.indent.as_ref()); - for _ in 0..=align - self.params.tab_size { - self.output.push(' '); - } - length = align; - } else if insert_space { - self.output.push(' '); - length += 1; - } - self.output.push_str(current.text()); - length += current_length; - } - } - } - } -} - -#[derive(Debug, Default)] -struct ContentAnalyzer<'a> { - tokens: Vec<&'a Token>, -} - -impl<'a> Visitor<'a> for ContentAnalyzer<'a> { - fn visit(&mut self, tree: &'a Tree, node: NodeIndex) { - match &tree.graph[node] { - Node::Root(_) - | Node::Comment(_) - | Node::Preamble(_) - | Node::String(_) - | Node::Entry(_) - | Node::Field(_) => tree.walk(self, node), - Node::Word(word) => self.tokens.push(&word.token), - Node::Command(cmd) => self.tokens.push(&cmd.token), - Node::QuotedContent(content) => { - self.tokens.push(&content.left); - tree.walk(self, node); - if let Some(right) = &content.right { - self.tokens.push(right); - } - } - Node::BracedContent(content) => { - self.tokens.push(&content.left); - tree.walk(self, node); - if let Some(right) = &content.right { - self.tokens.push(right); - } - } - Node::Concat(concat) => { - let mut children = tree.children(node); - let left = children.next().unwrap(); - self.visit(tree, left); - self.tokens.push(&concat.operator); - children.for_each(|right| self.visit(tree, right)); - } - } - } -} - -pub fn format(tree: &Tree, node: NodeIndex, params: FormattingParams) -> StdString { - let mut formatter = Formatter::new(params); - formatter.visit(tree, node); - formatter.output -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::syntax::bibtex; - use indoc::indoc; - - fn verify(source: &str, expected: &str, line_length: i32) { - let tree = bibtex::open(source); - let options = BibtexFormattingOptions { - line_length: Some(line_length), - formatter: None, - }; - - let mut children = tree.children(tree.root); - let declaration = children.next().unwrap(); - assert_eq!(children.next(), None); - - let actual = format( - &tree, - declaration, - FormattingParams { - tab_size: 4, - insert_spaces: true, - options: &options, - }, - ); - assert_eq!(actual, expected); - } - - #[test] - fn wrap_long_lines() { - let source = - "@article{foo, bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit.},}"; - let expected = indoc!( - " - @article{foo, - bar = {Lorem ipsum dolor - sit amet, - consectetur - adipiscing elit.}, - }" - ); - verify(source, expected, 30); - } - - #[test] - fn line_length_zero() { - let source = - "@article{foo, bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit.},}"; - let expected = indoc!( - " - @article{foo, - bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit.}, - }" - ); - verify(source, expected, 0); - } - - #[test] - fn trailing_commas() { - let source = "@article{foo, bar = baz}"; - let expected = indoc!( - " - @article{foo, - bar = baz, - }" - ); - verify(source, expected, 30); - } - - #[test] - fn insert_braces() { - let source = "@article{foo, bar = baz,"; - let expected = indoc!( - " - @article{foo, - bar = baz, - }" - ); - verify(source, expected, 30); - } - - #[test] - fn commands() { - let source = "@article{foo, bar = \"\\baz\",}"; - let expected = indoc!( - "@article{foo, - bar = \"\\baz\", - }" - ); - verify(source, expected, 30); - } - - #[test] - fn concatenation() { - let source = "@article{foo, bar = \"baz\" # \"qux\"}"; - let expected = indoc!( - " - @article{foo, - bar = \"baz\" # \"qux\", - }" - ); - verify(source, expected, 30); - } - - #[test] - fn parentheses() { - let source = "@article(foo,)"; - let expected = indoc!( - " - @article{foo, - }" - ); - verify(source, expected, 30); - } - - #[test] - fn string() { - let source = "@string{foo=\"bar\"}"; - let expected = "@string{foo = \"bar\"}"; - verify(source, expected, 30); - } - - #[test] - fn preamble() { - let source = "@preamble{\n\"foo bar baz\"}"; - let expected = "@preamble{\"foo bar baz\"}"; - verify(source, expected, 30); - } -} diff --git a/support/texlab/src/syntax/bibtex/kind.rs b/support/texlab/src/syntax/bibtex/kind.rs new file mode 100644 index 0000000000..fcb26b69bd --- /dev/null +++ b/support/texlab/src/syntax/bibtex/kind.rs @@ -0,0 +1,51 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)] +#[allow(non_camel_case_types)] +#[repr(u16)] +pub enum SyntaxKind { + ERROR = 0, + MISSING, + + WHITESPACE, + PREAMBLE_TYPE, + STRING_TYPE, + COMMENT_TYPE, + ENTRY_TYPE, + WORD, + L_CURLY, + R_CURLY, + L_PAREN, + R_PAREN, + COMMA, + HASH, + QUOTE, + EQUALITY_SIGN, + COMMAND_NAME, + + JUNK, + PREAMBLE, + STRING, + COMMENT, + ENTRY, + FIELD, + VALUE, + TOKEN, + BRACE_GROUP, + QUOTE_GROUP, + ROOT, +} + +impl SyntaxKind { + pub fn is_type(&self) -> bool { + use SyntaxKind::*; + matches!( + self, + PREAMBLE_TYPE | STRING_TYPE | COMMENT_TYPE | ENTRY_TYPE + ) + } +} + +impl From<SyntaxKind> for cstree::SyntaxKind { + fn from(kind: SyntaxKind) -> Self { + Self(kind as u16) + } +} diff --git a/support/texlab/src/syntax/bibtex/lexer.rs b/support/texlab/src/syntax/bibtex/lexer.rs index e089b15864..dff111d645 100644 --- a/support/texlab/src/syntax/bibtex/lexer.rs +++ b/support/texlab/src/syntax/bibtex/lexer.rs @@ -1,173 +1,82 @@ -use super::ast::{Token, TokenKind}; -use crate::syntax::text::CharStream; +use logos::Logos; -#[derive(Debug)] -pub struct Lexer<'a> { - stream: CharStream<'a>, -} +use super::kind::SyntaxKind; -impl<'a> Lexer<'a> { - pub fn new(text: &'a str) -> Self { - Self { - stream: CharStream::new(text), - } - } +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord, Logos)] +#[allow(non_camel_case_types)] +#[repr(u16)] +enum LogosToken { + #[regex(r"\s+")] + WHITESPACE = 2, - fn kind(&mut self) -> Token { - fn is_type_char(c: char) -> bool { - c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' - } + #[regex(r"@[Pp][Rr][Ee][Aa][Mm][Bb][Ll][Ee]")] + PREAMBLE_TYPE, - 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" => TokenKind::PreambleKind, - "@string" => TokenKind::StringKind, - _ => TokenKind::EntryKind, - }; - Token::new(span, kind) - } + #[regex(r"@[Ss][Tt][Rr][Ii][Nn][Gg]")] + STRING_TYPE, - fn single_character(&mut self, kind: TokenKind) -> Token { - self.stream.start_span(); - self.stream.next(); - let span = self.stream.end_span(); - Token::new(span, kind) - } + #[regex(r"@[Cc][Oo][Mm][Mm][Ee][Nn][Tt]")] + COMMENT_TYPE, - fn command(&mut self) -> Token { - let span = self.stream.command(); - Token::new(span, TokenKind::Command) - } + #[regex(r"@[!\$\&\*\+\-\./:;<>\?@\[\]\\\^_`\|\~a-zA-Z][!\$\&\*\+\-\./:;<>\?@\[\]\\\^_`\|\~a-zA-Z0-9]*|@")] + ENTRY_TYPE, - fn word(&mut self) -> Token { - fn is_word_char(c: char) -> bool { - !c.is_whitespace() - && c != '@' - && c != '=' - && c != ',' - && c != '#' - && c != '"' - && c != '{' - && c != '}' - && c != '(' - && c != ')' - } + #[regex(r#"[^\s\{\}\(\),#"=\\]+"#)] + #[error] + WORD, - self.stream.start_span(); - while self.stream.satifies(|c| is_word_char(*c)) { - self.stream.next(); - } - let span = self.stream.end_span(); - Token::new(span, TokenKind::Word) - } -} + #[token("{")] + L_CURLY, -impl<'a> Iterator for Lexer<'a> { - type Item = Token; - - fn next(&mut self) -> Option<Token> { - loop { - match self.stream.peek() { - Some('@') => return Some(self.kind()), - Some('=') => return Some(self.single_character(TokenKind::Assign)), - Some(',') => return Some(self.single_character(TokenKind::Comma)), - Some('#') => return Some(self.single_character(TokenKind::Concat)), - Some('"') => return Some(self.single_character(TokenKind::Quote)), - Some('{') => return Some(self.single_character(TokenKind::BeginBrace)), - Some('}') => return Some(self.single_character(TokenKind::EndBrace)), - Some('(') => return Some(self.single_character(TokenKind::BeginParen)), - Some(')') => return Some(self.single_character(TokenKind::EndParen)), - Some('\\') => return Some(self.command()), - Some(c) => { - if c.is_whitespace() { - self.stream.next(); - } else { - return Some(self.word()); - } - } - None => { - return None; - } - } - } - } -} + #[token("}")] + R_CURLY, -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - protocol::{Position, Range}, - syntax::text::Span, - }; - - fn verify<'a>(lexer: &mut Lexer<'a>, line: u64, character: u64, text: &str, kind: TokenKind) { - 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 = Token::new(span, kind); - assert_eq!(Some(token), lexer.next()); - } + #[token("(")] + L_PAREN, - #[test] - fn word() { - let mut lexer = Lexer::new("foo bar baz"); - verify(&mut lexer, 0, 0, "foo", TokenKind::Word); - verify(&mut lexer, 0, 4, "bar", TokenKind::Word); - verify(&mut lexer, 0, 8, "baz", TokenKind::Word); - assert_eq!(None, lexer.next()); - } + #[token(")")] + R_PAREN, - #[test] - fn command() { - let mut lexer = Lexer::new("\\foo\\bar@baz"); - verify(&mut lexer, 0, 0, "\\foo", TokenKind::Command); - verify(&mut lexer, 0, 4, "\\bar@baz", TokenKind::Command); - assert_eq!(None, lexer.next()); - } + #[token(",")] + COMMA, - #[test] - fn escape_sequence() { - let mut lexer = Lexer::new("\\foo*\n\\%\\**"); - verify(&mut lexer, 0, 0, "\\foo*", TokenKind::Command); - verify(&mut lexer, 1, 0, "\\%", TokenKind::Command); - verify(&mut lexer, 1, 2, "\\*", TokenKind::Command); - verify(&mut lexer, 1, 4, "*", TokenKind::Word); - assert_eq!(None, lexer.next()); - } + #[token("#")] + HASH, + + #[token("\"")] + QUOTE, + + #[token("=")] + EQUALITY_SIGN, - #[test] - fn delimiter() { - let mut lexer = Lexer::new("{}()\""); - verify(&mut lexer, 0, 0, "{", TokenKind::BeginBrace); - verify(&mut lexer, 0, 1, "}", TokenKind::EndBrace); - verify(&mut lexer, 0, 2, "(", TokenKind::BeginParen); - verify(&mut lexer, 0, 3, ")", TokenKind::EndParen); - verify(&mut lexer, 0, 4, "\"", TokenKind::Quote); - assert_eq!(None, lexer.next()); + #[regex(r"\\([^\r\n]|[@a-zA-Z:_]+\*?)?")] + COMMAND_NAME, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Lexer<'a> { + tokens: Vec<(SyntaxKind, &'a str)>, +} + +impl<'a> Lexer<'a> { + pub fn new(text: &'a str) -> Self { + let mut tokens = Vec::new(); + let mut lexer = LogosToken::lexer(text); + while let Some(kind) = lexer.next() { + tokens.push(( + unsafe { std::mem::transmute::<LogosToken, SyntaxKind>(kind) }, + lexer.slice(), + )); + } + tokens.reverse(); + Self { tokens } } - #[test] - fn kind() { - let mut lexer = Lexer::new("@pReAmBlE\n@article\n@string"); - verify(&mut lexer, 0, 0, "@pReAmBlE", TokenKind::PreambleKind); - verify(&mut lexer, 1, 0, "@article", TokenKind::EntryKind); - verify(&mut lexer, 2, 0, "@string", TokenKind::StringKind); - assert_eq!(None, lexer.next()); + pub fn peek(&self) -> Option<SyntaxKind> { + self.tokens.last().map(|(kind, _)| *kind) } - #[test] - fn operator() { - let mut lexer = Lexer::new("=,#"); - verify(&mut lexer, 0, 0, "=", TokenKind::Assign); - verify(&mut lexer, 0, 1, ",", TokenKind::Comma); - verify(&mut lexer, 0, 2, "#", TokenKind::Concat); - assert_eq!(None, lexer.next()); + pub fn consume(&mut self) -> Option<(SyntaxKind, &'a str)> { + self.tokens.pop() } } diff --git a/support/texlab/src/syntax/bibtex/mod.rs b/support/texlab/src/syntax/bibtex/mod.rs deleted file mode 100644 index aa7242c1f7..0000000000 --- a/support/texlab/src/syntax/bibtex/mod.rs +++ /dev/null @@ -1,396 +0,0 @@ -mod ast; -mod formatter; -mod lexer; -mod parser; - -pub use self::{ast::*, formatter::*}; - -use self::{lexer::Lexer, parser::Parser}; - -pub fn open(text: &str) -> Tree { - let lexer = Lexer::new(text); - let parser = Parser::new(lexer); - parser.parse() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - protocol::{Range, RangeExt}, - syntax::text::SyntaxNode, - }; - use petgraph::graph::NodeIndex; - - #[derive(Debug, Default)] - struct TreeTraversal { - nodes: Vec<NodeIndex>, - } - - impl<'a> Visitor<'a> for TreeTraversal { - fn visit(&mut self, tree: &Tree, node: NodeIndex) { - self.nodes.push(node); - tree.walk(self, node); - } - } - - mod range { - use super::*; - use indoc::indoc; - - fn verify(expected_ranges: Vec<Range>, text: &str) { - let tree = open(text.trim()); - - let mut traversal = TreeTraversal::default(); - traversal.visit(&tree, tree.root); - let actual_ranges: Vec<_> = traversal - .nodes - .into_iter() - .map(|node| tree.graph[node].range()) - .collect(); - - assert_eq!(actual_ranges, expected_ranges); - } - - #[test] - fn empty_document() { - verify(vec![Range::new_simple(0, 0, 0, 0)], ""); - } - - #[test] - fn comment() { - verify( - vec![Range::new_simple(0, 0, 0, 3), Range::new_simple(0, 0, 0, 3)], - "foo", - ); - } - - #[test] - fn preamble_no_left() { - verify( - vec![Range::new_simple(0, 0, 0, 9), Range::new_simple(0, 0, 0, 9)], - "@preamble", - ); - } - - #[test] - fn preamble_no_content() { - verify( - vec![ - Range::new_simple(0, 0, 0, 10), - Range::new_simple(0, 0, 0, 10), - ], - "@preamble{", - ); - } - - #[test] - fn preamble_no_right() { - verify( - vec![ - Range::new_simple(0, 0, 0, 15), - Range::new_simple(0, 0, 0, 15), - Range::new_simple(0, 10, 0, 15), - Range::new_simple(0, 11, 0, 14), - ], - r#"@preamble{"foo""#, - ); - } - - #[test] - fn preamble_complete() { - verify( - vec![ - Range::new_simple(0, 0, 0, 16), - Range::new_simple(0, 0, 0, 16), - Range::new_simple(0, 10, 0, 15), - Range::new_simple(0, 11, 0, 14), - ], - r#"@preamble{"foo"}"#, - ); - } - - #[test] - fn string_no_left() { - verify( - vec![Range::new_simple(0, 0, 0, 7), Range::new_simple(0, 0, 0, 7)], - r#"@string"#, - ); - } - - #[test] - fn string_no_name() { - verify( - vec![Range::new_simple(0, 0, 0, 8), Range::new_simple(0, 0, 0, 8)], - r#"@string{"#, - ); - } - - #[test] - fn string_no_assign() { - verify( - vec![ - Range::new_simple(0, 0, 0, 11), - Range::new_simple(0, 0, 0, 11), - ], - r#"@string{foo"#, - ); - } - - #[test] - fn string_no_value() { - verify( - vec![ - Range::new_simple(0, 0, 0, 13), - Range::new_simple(0, 0, 0, 13), - ], - r#"@string{foo ="#, - ); - } - - #[test] - fn string_no_right() { - verify( - vec![ - Range::new_simple(0, 0, 0, 19), - Range::new_simple(0, 0, 0, 19), - Range::new_simple(0, 14, 0, 19), - Range::new_simple(0, 15, 0, 18), - ], - r#"@string{foo = "bar""#, - ); - } - - #[test] - fn string_complete() { - verify( - vec![ - Range::new_simple(0, 0, 0, 20), - Range::new_simple(0, 0, 0, 20), - Range::new_simple(0, 14, 0, 19), - Range::new_simple(0, 15, 0, 18), - ], - r#"@string{foo = "bar"}"#, - ); - } - - #[test] - fn entry_no_left() { - verify( - vec![Range::new_simple(0, 0, 0, 8), Range::new_simple(0, 0, 0, 8)], - r#"@article"#, - ); - } - - #[test] - fn entry_no_key() { - verify( - vec![Range::new_simple(0, 0, 0, 9), Range::new_simple(0, 0, 0, 9)], - r#"@article{"#, - ); - } - - #[test] - fn entry_no_comma() { - verify( - vec![ - Range::new_simple(0, 0, 0, 12), - Range::new_simple(0, 0, 0, 12), - ], - r#"@article{foo"#, - ); - } - - #[test] - fn entry_no_right() { - verify( - vec![ - Range::new_simple(0, 0, 0, 13), - Range::new_simple(0, 0, 0, 13), - ], - r#"@article{foo,"#, - ); - } - - #[test] - fn entry_parentheses() { - verify( - vec![ - Range::new_simple(0, 0, 0, 14), - Range::new_simple(0, 0, 0, 14), - ], - r#"@article(foo,)"#, - ); - } - - #[test] - fn field_no_assign() { - verify( - vec![ - Range::new_simple(0, 0, 1, 10), - Range::new_simple(0, 0, 1, 10), - Range::new_simple(1, 4, 1, 10), - ], - indoc!( - r#" - @article{foo, - author - "# - ), - ); - } - - #[test] - fn field_no_value() { - verify( - vec![ - Range::new_simple(0, 0, 1, 12), - Range::new_simple(0, 0, 1, 12), - Range::new_simple(1, 4, 1, 12), - ], - indoc!( - r#" - @article{foo, - author = - "# - ), - ); - } - - #[test] - fn field_no_comma() { - verify( - vec![ - Range::new_simple(0, 0, 1, 16), - Range::new_simple(0, 0, 1, 16), - Range::new_simple(1, 4, 1, 16), - Range::new_simple(1, 13, 1, 16), - ], - indoc!( - r#" - @article{foo, - author = bar - "# - ), - ); - } - - #[test] - fn field_complete() { - verify( - vec![ - Range::new_simple(0, 0, 2, 1), - Range::new_simple(0, 0, 2, 1), - Range::new_simple(1, 4, 1, 17), - Range::new_simple(1, 13, 1, 16), - ], - indoc!( - r#" - @article{foo, - author = bar, - } - "# - ), - ); - } - - #[test] - fn entry_two_fields() { - verify( - vec![ - Range::new_simple(0, 0, 3, 1), - Range::new_simple(0, 0, 3, 1), - Range::new_simple(1, 4, 1, 17), - Range::new_simple(1, 13, 1, 16), - Range::new_simple(2, 4, 2, 16), - Range::new_simple(2, 12, 2, 15), - ], - indoc!( - r#" - @article{foo, - author = bar, - title = baz, - } - "# - ), - ); - } - - #[test] - fn quoted_content_no_children() { - verify( - vec![ - Range::new_simple(0, 0, 0, 12), - Range::new_simple(0, 0, 0, 12), - Range::new_simple(0, 10, 0, 11), - ], - r#"@preamble{"}"#, - ); - } - - #[test] - fn quoted_content_no_right() { - verify( - vec![ - Range::new_simple(0, 0, 0, 16), - Range::new_simple(0, 0, 0, 16), - Range::new_simple(0, 10, 0, 15), - Range::new_simple(0, 11, 0, 15), - ], - r#"@preamble{"word}"#, - ); - } - - #[test] - fn braced_content_no_children() { - verify( - vec![ - Range::new_simple(0, 0, 0, 11), - Range::new_simple(0, 0, 0, 11), - Range::new_simple(0, 10, 0, 11), - ], - r#"@preamble{{"#, - ); - } - - #[test] - fn braced_content_no_right() { - verify( - vec![ - Range::new_simple(0, 0, 0, 15), - Range::new_simple(0, 0, 0, 15), - Range::new_simple(0, 10, 0, 15), - Range::new_simple(0, 11, 0, 15), - ], - r#"@preamble{{word"#, - ); - } - - #[test] - fn concat_no_right() { - verify( - vec![ - Range::new_simple(0, 0, 0, 16), - Range::new_simple(0, 0, 0, 16), - Range::new_simple(0, 10, 0, 15), - Range::new_simple(0, 10, 0, 13), - ], - r#"@preamble{foo #}"#, - ); - } - - #[test] - fn concat_complete() { - verify( - vec![ - Range::new_simple(0, 0, 0, 20), - Range::new_simple(0, 0, 0, 20), - Range::new_simple(0, 10, 0, 19), - Range::new_simple(0, 10, 0, 13), - Range::new_simple(0, 16, 0, 19), - ], - r#"@preamble{foo # bar}"#, - ); - } - } -} diff --git a/support/texlab/src/syntax/bibtex/parser.rs b/support/texlab/src/syntax/bibtex/parser.rs index b23903aeb3..ffdb1c94a0 100644 --- a/support/texlab/src/syntax/bibtex/parser.rs +++ b/support/texlab/src/syntax/bibtex/parser.rs @@ -1,401 +1,413 @@ -use super::ast::*; -use crate::{ - protocol::{Range, RangeExt}, - syntax::text::SyntaxNode, +use cstree::GreenNodeBuilder; + +use super::{ + lexer::Lexer, + SyntaxKind::{self, *}, + SyntaxNode, }; -use petgraph::graph::{Graph, NodeIndex}; -use std::iter::Peekable; -pub struct Parser<I: Iterator<Item = Token>> { - graph: Graph<Node, ()>, - tokens: Peekable<I>, +#[derive(Debug, Clone)] +pub struct Parse { + pub root: SyntaxNode, +} + +struct Parser<'a> { + lexer: Lexer<'a>, + builder: GreenNodeBuilder<'static, 'static>, } -impl<I: Iterator<Item = Token>> Parser<I> { - pub fn new(tokens: I) -> Self { +impl<'a> Parser<'a> { + pub fn new(lexer: Lexer<'a>) -> Self { Self { - tokens: tokens.peekable(), - graph: Graph::new(), + lexer, + builder: GreenNodeBuilder::new(), } } - pub fn parse(mut self) -> Tree { - let mut children = Vec::new(); + fn peek(&self) -> Option<SyntaxKind> { + self.lexer.peek() + } - while let Some(ref token) = self.tokens.peek() { - match token.kind { - TokenKind::PreambleKind => children.push(self.preamble()), - TokenKind::StringKind => children.push(self.string()), - TokenKind::EntryKind => children.push(self.entry()), - _ => children.push(self.comment()), - } - } + fn eat(&mut self) { + let (kind, text) = self.lexer.consume().unwrap(); + self.builder.token(kind.into(), text); + } - let range = if children.is_empty() { - Range::new_simple(0, 0, 0, 0) + fn expect(&mut self, kind: SyntaxKind) { + if self.peek() == Some(kind) { + self.eat(); + self.trivia(); } else { - let start = self.graph[children[0]].start(); - let end = self.graph[children[children.len() - 1]].end(); - Range::new(start, end) - }; + self.builder.token(MISSING.into(), ""); + } + } - let root = self.graph.add_node(Node::Root(Root { range })); - self.connect(root, &children); - Tree { - graph: self.graph, - root, + pub fn parse(mut self) -> Parse { + self.builder.start_node(ROOT.into()); + while let Some(kind) = self.peek() { + match kind { + PREAMBLE_TYPE => self.preamble(), + STRING_TYPE => self.string(), + COMMENT_TYPE => self.comment(), + ENTRY_TYPE => self.entry(), + _ => self.junk(), + } + } + self.builder.finish_node(); + let (green, resolver) = self.builder.finish(); + Parse { + root: SyntaxNode::new_root_with_resolver(green, resolver.unwrap()), } } - fn preamble(&mut self) -> NodeIndex { - let ty = self.tokens.next().unwrap(); + fn trivia(&mut self) { + while self.peek() == Some(WHITESPACE) { + self.eat(); + } + } - let left = self.expect2(TokenKind::BeginBrace, TokenKind::BeginParen); - if left.is_none() { - return self.graph.add_node(Node::Preamble(Box::new(Preamble { - range: ty.range(), - ty, - left: None, - right: None, - }))); + fn junk(&mut self) { + self.builder.start_node(JUNK.into()); + while self + .lexer + .peek() + .filter(|&kind| { + !matches!( + kind, + PREAMBLE_TYPE | STRING_TYPE | COMMENT_TYPE | ENTRY_TYPE, + ) + }) + .is_some() + { + self.eat(); } + self.builder.finish_node(); + } - if !self.can_match_content() { - return self.graph.add_node(Node::Preamble(Box::new(Preamble { - range: Range::new(ty.start(), left.as_ref().unwrap().end()), - ty, - left, - right: None, - }))); + fn left_delimiter_or_missing(&mut self) { + if self + .lexer + .peek() + .filter(|&kind| matches!(kind, L_CURLY | L_PAREN)) + .is_some() + { + self.eat(); + self.trivia(); + } else { + self.builder.token(MISSING.into(), ""); } + } - let content = self.content(); - - let right = self.expect2(TokenKind::EndBrace, TokenKind::EndParen); - let end = right - .as_ref() - .map(Token::end) - .unwrap_or_else(|| self.graph[content].end()); - - let parent = self.graph.add_node(Node::Preamble(Box::new(Preamble { - range: Range::new(ty.start(), end), - ty, - left, - right, - }))); - self.graph.add_edge(parent, content, ()); - parent - } - - fn string(&mut self) -> NodeIndex { - let ty = self.tokens.next().unwrap(); - - let left = self.expect2(TokenKind::BeginBrace, TokenKind::BeginParen); - if left.is_none() { - return self.graph.add_node(Node::String(Box::new(String { - range: ty.range(), - ty, - left: None, - name: None, - assign: None, - right: None, - }))); + fn right_delimiter_or_missing(&mut self) { + if self + .lexer + .peek() + .filter(|&kind| matches!(kind, R_CURLY | R_PAREN)) + .is_some() + { + self.eat(); + self.trivia(); + } else { + self.builder.token(MISSING.into(), ""); } + } - let name = self.expect1(TokenKind::Word); - if name.is_none() { - return self.graph.add_node(Node::String(Box::new(String { - range: Range::new(ty.start(), left.as_ref().unwrap().end()), - ty, - left, - name: None, - assign: None, - right: None, - }))); + fn value_or_missing(&mut self) { + if self + .lexer + .peek() + .filter(|&kind| matches!(kind, L_CURLY | QUOTE | WORD)) + .is_some() + { + self.value(); + } else { + self.builder.token(MISSING.into(), ""); } + } + + fn preamble(&mut self) { + self.builder.start_node(PREAMBLE.into()); + self.eat(); + self.trivia(); + self.left_delimiter_or_missing(); + self.value_or_missing(); + self.right_delimiter_or_missing(); + self.builder.finish_node(); + } - let assign = self.expect1(TokenKind::Assign); - if assign.is_none() { - return self.graph.add_node(Node::String(Box::new(String { - range: Range::new(ty.start(), name.as_ref().unwrap().end()), - ty, - left, - name, - assign: None, - right: None, - }))); + fn string(&mut self) { + self.builder.start_node(STRING.into()); + self.eat(); + self.trivia(); + self.left_delimiter_or_missing(); + + if self.peek() != Some(WORD) { + self.builder.token(MISSING.into(), ""); + self.builder.finish_node(); + return; } + self.eat(); + self.trivia(); + self.expect(EQUALITY_SIGN); + + self.value_or_missing(); + + self.right_delimiter_or_missing(); + self.builder.finish_node(); + } + + fn comment(&mut self) { + self.builder.start_node(COMMENT.into()); + self.eat(); + self.builder.finish_node(); + } - if !self.can_match_content() { - return self.graph.add_node(Node::String(Box::new(String { - range: Range::new(ty.start(), assign.as_ref().unwrap().end()), - ty, - left, - name, - assign, - right: None, - }))); + fn entry(&mut self) { + self.builder.start_node(ENTRY.into()); + self.eat(); + self.trivia(); + + self.left_delimiter_or_missing(); + + if self.peek() != Some(WORD) { + self.builder.token(MISSING.into(), ""); + self.builder.finish_node(); + return; } - let value = self.content(); - - let right = self.expect2(TokenKind::EndBrace, TokenKind::EndParen); - let end = right - .as_ref() - .map(Token::end) - .unwrap_or_else(|| self.graph[value].end()); - - let parent = self.graph.add_node(Node::String(Box::new(String { - range: Range::new(ty.start(), end), - ty, - left, - name, - assign, - right, - }))); - self.graph.add_edge(parent, value, ()); - parent - } - - fn entry(&mut self) -> NodeIndex { - let ty = self.tokens.next().unwrap(); - - let left = self.expect2(TokenKind::BeginBrace, TokenKind::BeginParen); - if left.is_none() { - return self.graph.add_node(Node::Entry(Box::new(Entry { - range: ty.range(), - ty, - left: None, - key: None, - comma: None, - right: None, - }))); + self.eat(); + + while let Some(kind) = self.peek() { + match kind { + WHITESPACE => self.eat(), + WORD => self.field(), + COMMA => self.eat(), + _ => break, + }; } - let key = self.expect1(TokenKind::Word); - if key.is_none() { - return self.graph.add_node(Node::Entry(Box::new(Entry { - range: Range::new(ty.start(), left.as_ref().unwrap().end()), - ty, - left, - key: None, - comma: None, - right: None, - }))); - } + self.right_delimiter_or_missing(); - let comma = self.expect1(TokenKind::Comma); - if comma.is_none() { - return self.graph.add_node(Node::Entry(Box::new(Entry { - range: Range::new(ty.start(), key.as_ref().unwrap().end()), - ty, - left, - key, - comma: None, - right: None, - }))); - } + self.builder.finish_node(); + } - let mut fields = Vec::new(); - while self.next_of_kind(TokenKind::Word) { - fields.push(self.field()); - } + fn field(&mut self) { + self.builder.start_node(FIELD.into()); + self.eat(); + self.trivia(); - let right = self.expect2(TokenKind::EndBrace, TokenKind::EndParen); - - let end = right - .as_ref() - .map(Token::end) - .or_else(|| fields.last().map(|field| self.graph[*field].end())) - .unwrap_or_else(|| comma.as_ref().unwrap().end()); - let parent = self.graph.add_node(Node::Entry(Box::new(Entry { - range: Range::new(ty.start(), end), - ty, - left, - key, - comma, - right, - }))); - self.connect(parent, &fields); - parent - } - - fn comment(&mut self) -> NodeIndex { - let token = self.tokens.next().unwrap(); - self.graph.add_node(Node::Comment(Comment { token })) - } - - fn field(&mut self) -> NodeIndex { - let name = self.tokens.next().unwrap(); - - let assign = self.expect1(TokenKind::Assign); - if assign.is_none() { - return self.graph.add_node(Node::Field(Box::new(Field { - range: name.range(), - name, - assign: None, - comma: None, - }))); + if self.peek() == Some(EQUALITY_SIGN) { + self.eat(); + self.trivia(); + } else { + self.builder.token(MISSING.into(), ""); } - if !self.can_match_content() { - return self.graph.add_node(Node::Field(Box::new(Field { - range: Range::new(name.start(), assign.as_ref().unwrap().end()), - name, - assign, - comma: None, - }))); + if self + .lexer + .peek() + .filter(|&kind| matches!(kind, L_CURLY | QUOTE | WORD)) + .is_some() + { + self.value(); + } else { + self.builder.token(MISSING.into(), ""); } - let content = self.content(); - - let comma = self.expect1(TokenKind::Comma); - - let end = comma - .as_ref() - .map(Token::end) - .unwrap_or_else(|| self.graph[content].end()); - let parent = self.graph.add_node(Node::Field(Box::new(Field { - range: Range::new(name.start(), end), - name, - assign, - comma, - }))); - self.graph.add_edge(parent, content, ()); - parent - } - - fn content(&mut self) -> NodeIndex { - let token = self.tokens.next().unwrap(); - let left = match token.kind { - TokenKind::PreambleKind - | TokenKind::StringKind - | TokenKind::EntryKind - | TokenKind::Word - | TokenKind::Assign - | TokenKind::Comma - | TokenKind::BeginParen - | TokenKind::EndParen => self.graph.add_node(Node::Word(Word { token })), - TokenKind::Command => self.graph.add_node(Node::Command(Command { token })), - TokenKind::Quote => { - let mut children = Vec::new(); - while self.can_match_content() { - if self.next_of_kind(TokenKind::Quote) { - break; - } - children.push(self.content()); - } - let right = self.expect1(TokenKind::Quote); - - let end = right - .as_ref() - .map(Token::end) - .or_else(|| children.last().map(|child| self.graph[*child].end())) - .unwrap_or_else(|| token.end()); - let parent = self.graph.add_node(Node::QuotedContent(QuotedContent { - range: Range::new(token.start(), end), - left: token, - right, - })); - self.connect(parent, &children); - parent - } - TokenKind::BeginBrace => { - let mut children = Vec::new(); - while self.can_match_content() { - children.push(self.content()); - } - let right = self.expect1(TokenKind::EndBrace); - - let end = right - .as_ref() - .map(Token::end) - .or_else(|| children.last().map(|child| self.graph[*child].end())) - .unwrap_or_else(|| token.end()); - let parent = self.graph.add_node(Node::BracedContent(BracedContent { - range: Range::new(token.start(), end), - left: token, - right, - })); - self.connect(parent, &children); - parent + self.builder.finish_node(); + } + + fn value(&mut self) { + self.builder.start_node(VALUE.into()); + self.token(); + while let Some(kind) = self.peek() { + match kind { + WHITESPACE => self.eat(), + L_CURLY | QUOTE | WORD => self.token(), + HASH => self.eat(), + _ => break, } + } + self.builder.finish_node(); + } + + fn token(&mut self) { + self.builder.start_node(TOKEN.into()); + match self.peek().unwrap() { + L_CURLY => self.brace_group(), + QUOTE => self.quote_group(), + WORD => self.eat(), _ => unreachable!(), }; + self.builder.finish_node(); + } - match self.expect1(TokenKind::Concat) { - Some(operator) => { - if self.can_match_content() { - let right = self.content(); - let parent = self.graph.add_node(Node::Concat(Concat { - range: Range::new(self.graph[left].start(), self.graph[right].end()), - operator, - })); - self.graph.add_edge(parent, left, ()); - self.graph.add_edge(parent, right, ()); - parent - } else { - let parent = self.graph.add_node(Node::Concat(Concat { - range: Range::new(self.graph[left].start(), operator.end()), - operator, - })); - self.graph.add_edge(parent, left, ()); - parent - } - } - None => left, + fn brace_group(&mut self) { + self.builder.start_node(BRACE_GROUP.into()); + self.eat(); + + while let Some(kind) = self.peek() { + match kind { + WHITESPACE => self.eat(), + PREAMBLE_TYPE => break, + STRING_TYPE => break, + COMMENT_TYPE => break, + ENTRY_TYPE => break, + WORD => self.eat(), + L_CURLY => self.brace_group(), + R_CURLY => break, + L_PAREN => self.eat(), + R_PAREN => self.eat(), + COMMA => self.eat(), + HASH => self.eat(), + QUOTE => self.eat(), + EQUALITY_SIGN => self.eat(), + COMMAND_NAME => self.eat(), + _ => unreachable!(), + }; } + + self.expect(R_CURLY); + + self.builder.finish_node(); } - fn connect(&mut self, parent: NodeIndex, children: &[NodeIndex]) { - for child in children { - self.graph.add_edge(parent, *child, ()); + fn quote_group(&mut self) { + self.builder.start_node(QUOTE_GROUP.into()); + self.eat(); + + while let Some(kind) = self.peek() { + match kind { + WHITESPACE => self.eat(), + PREAMBLE_TYPE => break, + STRING_TYPE => break, + COMMENT_TYPE => break, + ENTRY_TYPE => break, + WORD => self.eat(), + L_CURLY => self.brace_group(), + R_CURLY => break, + L_PAREN => self.eat(), + R_PAREN => self.eat(), + COMMA => self.eat(), + HASH => self.eat(), + QUOTE => break, + EQUALITY_SIGN => self.eat(), + COMMAND_NAME => self.eat(), + _ => unreachable!(), + }; } + + self.expect(QUOTE); + self.builder.finish_node(); } +} - fn can_match_content(&mut self) -> bool { - if let Some(ref token) = self.tokens.peek() { - match token.kind { - TokenKind::PreambleKind - | TokenKind::StringKind - | TokenKind::EntryKind - | TokenKind::Word - | TokenKind::Command - | TokenKind::Assign - | TokenKind::Comma - | TokenKind::Quote - | TokenKind::BeginBrace - | TokenKind::BeginParen - | TokenKind::EndParen => true, - TokenKind::Concat | TokenKind::EndBrace => false, - } - } else { - false - } +pub fn parse(text: &str) -> Parse { + Parser::new(Lexer::new(text)).parse() +} + +#[cfg(test)] +mod tests { + use insta::assert_debug_snapshot; + + use super::*; + + fn setup(text: &str) -> SyntaxNode { + parse(&text.trim().replace("\r", "")).root } - fn expect1(&mut self, kind: TokenKind) -> Option<Token> { - if let Some(ref token) = self.tokens.peek() { - if token.kind == kind { - return self.tokens.next(); - } - } - None + #[test] + fn test_empty() { + assert_debug_snapshot!(setup(r#""#)); } - fn expect2(&mut self, kind1: TokenKind, kind2: TokenKind) -> Option<Token> { - if let Some(ref token) = self.tokens.peek() { - if token.kind == kind1 || token.kind == kind2 { - return self.tokens.next(); - } - } - None + #[test] + fn test_junk() { + assert_debug_snapshot!(setup(r#"Hello World!"#)); } - fn next_of_kind(&mut self, kind: TokenKind) -> bool { - if let Some(token) = self.tokens.peek() { - token.kind == kind - } else { - false - } + #[test] + fn test_preamble_complete() { + assert_debug_snapshot!(setup(r#"@preamble{ "Hello World" }"#)); + } + + #[test] + fn test_preamble_missing_end() { + assert_debug_snapshot!(setup(r#"@preamble{ "Hello World" "#)); + } + + #[test] + fn test_preamble_casing() { + assert_debug_snapshot!(setup(r#"@preAmbLe{ "Hello World" }"#)); + } + + #[test] + fn test_string_complete() { + assert_debug_snapshot!(setup(r#"@string{foo = {Hello World}}"#)); + } + + #[test] + fn test_string_incomplete() { + assert_debug_snapshot!(setup(r#"@string{foo = {Hello World}"#)); + } + + #[test] + fn test_string_concatenation() { + assert_debug_snapshot!(setup( + r#"@string{foo = {Hello World}} @string{bar = foo # "!"}"# + )); + } + + #[test] + fn test_string_casing() { + assert_debug_snapshot!(setup(r#"@STRING{foo = "Hello World"}"#)); + } + + #[test] + fn test_string_missing_quote() { + assert_debug_snapshot!(setup(r#"@STRING{foo = "Hello World}"#)); + } + + #[test] + fn test_entry_no_fields() { + assert_debug_snapshot!(setup(r#"@article{foo,}"#)); + } + + #[test] + fn test_entry_no_fields_missing_comma() { + assert_debug_snapshot!(setup(r#"@article{foo}"#)); + } + + #[test] + fn test_entry_one_field() { + assert_debug_snapshot!(setup(r#"@article{foo, author = {Foo Bar}}"#)); + } + + #[test] + fn test_entry_one_field_number_key() { + assert_debug_snapshot!(setup(r#"@article{foo2021, author = {Foo Bar}}"#)); + } + + #[test] + fn test_entry_one_field_trailing_comma() { + assert_debug_snapshot!(setup(r#"@article{foo, author = {Foo Bar},}"#)); + } + + #[test] + fn test_entry_two_fields() { + assert_debug_snapshot!(setup( + r#"@article{foo, author = {Foo Bar}, title = {Hello World}}"# + )); + } + + #[test] + fn test_entry_two_fields_incomplete() { + assert_debug_snapshot!(setup(r#"@article{foo, author = {Foo Bar}, t}"#)); + } + + #[test] + fn test_entry_complete_parens() { + assert_debug_snapshot!(setup( + r#"@article(foo, author = {Foo Bar}, title = {Hello})"# + )); } } diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__empty.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__empty.snap new file mode 100644 index 0000000000..55e1954d2f --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__empty.snap @@ -0,0 +1,7 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"\"#)" + +--- +ROOT@0..0 + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_complete_parens.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_complete_parens.snap new file mode 100644 index 0000000000..cfd469f161 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_complete_parens.snap @@ -0,0 +1,40 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@article(foo, author = {Foo Bar}, title = {Hello})\"#)" + +--- +ROOT@0..50 + ENTRY@0..50 + ENTRY_TYPE@0..8 "@article" + L_PAREN@8..9 "(" + WORD@9..12 "foo" + COMMA@12..13 "," + WHITESPACE@13..14 " " + FIELD@14..32 + WORD@14..20 "author" + WHITESPACE@20..21 " " + EQUALITY_SIGN@21..22 "=" + WHITESPACE@22..23 " " + VALUE@23..32 + TOKEN@23..32 + BRACE_GROUP@23..32 + L_CURLY@23..24 "{" + WORD@24..27 "Foo" + WHITESPACE@27..28 " " + WORD@28..31 "Bar" + R_CURLY@31..32 "}" + COMMA@32..33 "," + WHITESPACE@33..34 " " + FIELD@34..49 + WORD@34..39 "title" + WHITESPACE@39..40 " " + EQUALITY_SIGN@40..41 "=" + WHITESPACE@41..42 " " + VALUE@42..49 + TOKEN@42..49 + BRACE_GROUP@42..49 + L_CURLY@42..43 "{" + WORD@43..48 "Hello" + R_CURLY@48..49 "}" + R_PAREN@49..50 ")" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_no_fields.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_no_fields.snap new file mode 100644 index 0000000000..a98df7e382 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_no_fields.snap @@ -0,0 +1,13 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@article{foo,}\"#)" + +--- +ROOT@0..14 + ENTRY@0..14 + ENTRY_TYPE@0..8 "@article" + L_CURLY@8..9 "{" + WORD@9..12 "foo" + COMMA@12..13 "," + R_CURLY@13..14 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_no_fields_missing_comma.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_no_fields_missing_comma.snap new file mode 100644 index 0000000000..b83037aae0 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_no_fields_missing_comma.snap @@ -0,0 +1,12 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@article{foo}\"#)" + +--- +ROOT@0..13 + ENTRY@0..13 + ENTRY_TYPE@0..8 "@article" + L_CURLY@8..9 "{" + WORD@9..12 "foo" + R_CURLY@12..13 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field.snap new file mode 100644 index 0000000000..704cbbbd47 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field.snap @@ -0,0 +1,27 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@article{foo, author = {Foo Bar}}\"#)" + +--- +ROOT@0..33 + ENTRY@0..33 + ENTRY_TYPE@0..8 "@article" + L_CURLY@8..9 "{" + WORD@9..12 "foo" + COMMA@12..13 "," + WHITESPACE@13..14 " " + FIELD@14..32 + WORD@14..20 "author" + WHITESPACE@20..21 " " + EQUALITY_SIGN@21..22 "=" + WHITESPACE@22..23 " " + VALUE@23..32 + TOKEN@23..32 + BRACE_GROUP@23..32 + L_CURLY@23..24 "{" + WORD@24..27 "Foo" + WHITESPACE@27..28 " " + WORD@28..31 "Bar" + R_CURLY@31..32 "}" + R_CURLY@32..33 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field_number_key.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field_number_key.snap new file mode 100644 index 0000000000..8f00c5ee1c --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field_number_key.snap @@ -0,0 +1,27 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@article{foo2021, author = {Foo Bar}}\"#)" + +--- +ROOT@0..37 + ENTRY@0..37 + ENTRY_TYPE@0..8 "@article" + L_CURLY@8..9 "{" + WORD@9..16 "foo2021" + COMMA@16..17 "," + WHITESPACE@17..18 " " + FIELD@18..36 + WORD@18..24 "author" + WHITESPACE@24..25 " " + EQUALITY_SIGN@25..26 "=" + WHITESPACE@26..27 " " + VALUE@27..36 + TOKEN@27..36 + BRACE_GROUP@27..36 + L_CURLY@27..28 "{" + WORD@28..31 "Foo" + WHITESPACE@31..32 " " + WORD@32..35 "Bar" + R_CURLY@35..36 "}" + R_CURLY@36..37 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field_trailing_comma.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field_trailing_comma.snap new file mode 100644 index 0000000000..af8b1593df --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field_trailing_comma.snap @@ -0,0 +1,28 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@article{foo, author = {Foo Bar},}\"#)" + +--- +ROOT@0..34 + ENTRY@0..34 + ENTRY_TYPE@0..8 "@article" + L_CURLY@8..9 "{" + WORD@9..12 "foo" + COMMA@12..13 "," + WHITESPACE@13..14 " " + FIELD@14..32 + WORD@14..20 "author" + WHITESPACE@20..21 " " + EQUALITY_SIGN@21..22 "=" + WHITESPACE@22..23 " " + VALUE@23..32 + TOKEN@23..32 + BRACE_GROUP@23..32 + L_CURLY@23..24 "{" + WORD@24..27 "Foo" + WHITESPACE@27..28 " " + WORD@28..31 "Bar" + R_CURLY@31..32 "}" + COMMA@32..33 "," + R_CURLY@33..34 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_two_fields.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_two_fields.snap new file mode 100644 index 0000000000..f47b3b6917 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_two_fields.snap @@ -0,0 +1,42 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@article{foo, author = {Foo Bar}, title = {Hello World}}\"#)" + +--- +ROOT@0..56 + ENTRY@0..56 + ENTRY_TYPE@0..8 "@article" + L_CURLY@8..9 "{" + WORD@9..12 "foo" + COMMA@12..13 "," + WHITESPACE@13..14 " " + FIELD@14..32 + WORD@14..20 "author" + WHITESPACE@20..21 " " + EQUALITY_SIGN@21..22 "=" + WHITESPACE@22..23 " " + VALUE@23..32 + TOKEN@23..32 + BRACE_GROUP@23..32 + L_CURLY@23..24 "{" + WORD@24..27 "Foo" + WHITESPACE@27..28 " " + WORD@28..31 "Bar" + R_CURLY@31..32 "}" + COMMA@32..33 "," + WHITESPACE@33..34 " " + FIELD@34..55 + WORD@34..39 "title" + WHITESPACE@39..40 " " + EQUALITY_SIGN@40..41 "=" + WHITESPACE@41..42 " " + VALUE@42..55 + TOKEN@42..55 + BRACE_GROUP@42..55 + L_CURLY@42..43 "{" + WORD@43..48 "Hello" + WHITESPACE@48..49 " " + WORD@49..54 "World" + R_CURLY@54..55 "}" + R_CURLY@55..56 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_two_fields_incomplete.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_two_fields_incomplete.snap new file mode 100644 index 0000000000..34a9019582 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_two_fields_incomplete.snap @@ -0,0 +1,33 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@article{foo, author = {Foo Bar}, t}\"#)" + +--- +ROOT@0..36 + ENTRY@0..36 + ENTRY_TYPE@0..8 "@article" + L_CURLY@8..9 "{" + WORD@9..12 "foo" + COMMA@12..13 "," + WHITESPACE@13..14 " " + FIELD@14..32 + WORD@14..20 "author" + WHITESPACE@20..21 " " + EQUALITY_SIGN@21..22 "=" + WHITESPACE@22..23 " " + VALUE@23..32 + TOKEN@23..32 + BRACE_GROUP@23..32 + L_CURLY@23..24 "{" + WORD@24..27 "Foo" + WHITESPACE@27..28 " " + WORD@28..31 "Bar" + R_CURLY@31..32 "}" + COMMA@32..33 "," + WHITESPACE@33..34 " " + FIELD@34..35 + WORD@34..35 "t" + MISSING@35..35 "" + MISSING@35..35 "" + R_CURLY@35..36 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__junk.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__junk.snap new file mode 100644 index 0000000000..e77313cabf --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__junk.snap @@ -0,0 +1,11 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"Hello World!\"#)" + +--- +ROOT@0..12 + JUNK@0..12 + WORD@0..5 "Hello" + WHITESPACE@5..6 " " + WORD@6..12 "World!" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_casing.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_casing.snap new file mode 100644 index 0000000000..f4669b8a01 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_casing.snap @@ -0,0 +1,21 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@preAmbLe{ \"Hello World\" }\"#)" + +--- +ROOT@0..26 + PREAMBLE@0..26 + PREAMBLE_TYPE@0..9 "@preAmbLe" + L_CURLY@9..10 "{" + WHITESPACE@10..11 " " + VALUE@11..25 + TOKEN@11..25 + QUOTE_GROUP@11..25 + QUOTE@11..12 "\"" + WORD@12..17 "Hello" + WHITESPACE@17..18 " " + WORD@18..23 "World" + QUOTE@23..24 "\"" + WHITESPACE@24..25 " " + R_CURLY@25..26 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_complete.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_complete.snap new file mode 100644 index 0000000000..d1977f2e76 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_complete.snap @@ -0,0 +1,21 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@preamble{ \"Hello World\" }\"#)" + +--- +ROOT@0..26 + PREAMBLE@0..26 + PREAMBLE_TYPE@0..9 "@preamble" + L_CURLY@9..10 "{" + WHITESPACE@10..11 " " + VALUE@11..25 + TOKEN@11..25 + QUOTE_GROUP@11..25 + QUOTE@11..12 "\"" + WORD@12..17 "Hello" + WHITESPACE@17..18 " " + WORD@18..23 "World" + QUOTE@23..24 "\"" + WHITESPACE@24..25 " " + R_CURLY@25..26 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_missing_end.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_missing_end.snap new file mode 100644 index 0000000000..4ba447a2ae --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_missing_end.snap @@ -0,0 +1,20 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@preamble{ \"Hello World\" \"#)" + +--- +ROOT@0..24 + PREAMBLE@0..24 + PREAMBLE_TYPE@0..9 "@preamble" + L_CURLY@9..10 "{" + WHITESPACE@10..11 " " + VALUE@11..24 + TOKEN@11..24 + QUOTE_GROUP@11..24 + QUOTE@11..12 "\"" + WORD@12..17 "Hello" + WHITESPACE@17..18 " " + WORD@18..23 "World" + QUOTE@23..24 "\"" + MISSING@24..24 "" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_casing.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_casing.snap new file mode 100644 index 0000000000..1c8b1dbea4 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_casing.snap @@ -0,0 +1,23 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@STRING{foo = \"Hello World\"}\"#)" + +--- +ROOT@0..28 + STRING@0..28 + STRING_TYPE@0..7 "@STRING" + L_CURLY@7..8 "{" + WORD@8..11 "foo" + WHITESPACE@11..12 " " + EQUALITY_SIGN@12..13 "=" + WHITESPACE@13..14 " " + VALUE@14..27 + TOKEN@14..27 + QUOTE_GROUP@14..27 + QUOTE@14..15 "\"" + WORD@15..20 "Hello" + WHITESPACE@20..21 " " + WORD@21..26 "World" + QUOTE@26..27 "\"" + R_CURLY@27..28 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_complete.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_complete.snap new file mode 100644 index 0000000000..506452894d --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_complete.snap @@ -0,0 +1,23 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@string{foo = {Hello World}}\"#)" + +--- +ROOT@0..28 + STRING@0..28 + STRING_TYPE@0..7 "@string" + L_CURLY@7..8 "{" + WORD@8..11 "foo" + WHITESPACE@11..12 " " + EQUALITY_SIGN@12..13 "=" + WHITESPACE@13..14 " " + VALUE@14..27 + TOKEN@14..27 + BRACE_GROUP@14..27 + L_CURLY@14..15 "{" + WORD@15..20 "Hello" + WHITESPACE@20..21 " " + WORD@21..26 "World" + R_CURLY@26..27 "}" + R_CURLY@27..28 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_concatenation.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_concatenation.snap new file mode 100644 index 0000000000..7f457d6964 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_concatenation.snap @@ -0,0 +1,43 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@string{foo = {Hello World}} @string{bar = foo # \"!\"}\"#)" + +--- +ROOT@0..53 + STRING@0..29 + STRING_TYPE@0..7 "@string" + L_CURLY@7..8 "{" + WORD@8..11 "foo" + WHITESPACE@11..12 " " + EQUALITY_SIGN@12..13 "=" + WHITESPACE@13..14 " " + VALUE@14..27 + TOKEN@14..27 + BRACE_GROUP@14..27 + L_CURLY@14..15 "{" + WORD@15..20 "Hello" + WHITESPACE@20..21 " " + WORD@21..26 "World" + R_CURLY@26..27 "}" + R_CURLY@27..28 "}" + WHITESPACE@28..29 " " + STRING@29..53 + STRING_TYPE@29..36 "@string" + L_CURLY@36..37 "{" + WORD@37..40 "bar" + WHITESPACE@40..41 " " + EQUALITY_SIGN@41..42 "=" + WHITESPACE@42..43 " " + VALUE@43..52 + TOKEN@43..46 + WORD@43..46 "foo" + WHITESPACE@46..47 " " + HASH@47..48 "#" + WHITESPACE@48..49 " " + TOKEN@49..52 + QUOTE_GROUP@49..52 + QUOTE@49..50 "\"" + WORD@50..51 "!" + QUOTE@51..52 "\"" + R_CURLY@52..53 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_incomplete.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_incomplete.snap new file mode 100644 index 0000000000..685d02a179 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_incomplete.snap @@ -0,0 +1,23 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@string{foo = {Hello World}\"#)" + +--- +ROOT@0..27 + STRING@0..27 + STRING_TYPE@0..7 "@string" + L_CURLY@7..8 "{" + WORD@8..11 "foo" + WHITESPACE@11..12 " " + EQUALITY_SIGN@12..13 "=" + WHITESPACE@13..14 " " + VALUE@14..27 + TOKEN@14..27 + BRACE_GROUP@14..27 + L_CURLY@14..15 "{" + WORD@15..20 "Hello" + WHITESPACE@20..21 " " + WORD@21..26 "World" + R_CURLY@26..27 "}" + MISSING@27..27 "" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_missing_quote.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_missing_quote.snap new file mode 100644 index 0000000000..8eeefa9bf9 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_missing_quote.snap @@ -0,0 +1,23 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@STRING{foo = \"Hello World}\"#)" + +--- +ROOT@0..27 + STRING@0..27 + STRING_TYPE@0..7 "@STRING" + L_CURLY@7..8 "{" + WORD@8..11 "foo" + WHITESPACE@11..12 " " + EQUALITY_SIGN@12..13 "=" + WHITESPACE@13..14 " " + VALUE@14..26 + TOKEN@14..26 + QUOTE_GROUP@14..26 + QUOTE@14..15 "\"" + WORD@15..20 "Hello" + WHITESPACE@20..21 " " + WORD@21..26 "World" + MISSING@26..26 "" + R_CURLY@26..27 "}" + |