summaryrefslogtreecommitdiff
path: root/support/texlab/src/syntax
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/syntax')
-rw-r--r--support/texlab/src/syntax/bibtex.rs34
-rw-r--r--support/texlab/src/syntax/bibtex/ast.rs431
-rw-r--r--support/texlab/src/syntax/bibtex/cst.rs212
-rw-r--r--support/texlab/src/syntax/bibtex/formatter.rs329
-rw-r--r--support/texlab/src/syntax/bibtex/kind.rs51
-rw-r--r--support/texlab/src/syntax/bibtex/lexer.rs215
-rw-r--r--support/texlab/src/syntax/bibtex/mod.rs396
-rw-r--r--support/texlab/src/syntax/bibtex/parser.rs706
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__empty.snap7
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_complete_parens.snap40
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_no_fields.snap13
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_no_fields_missing_comma.snap12
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field.snap27
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field_number_key.snap27
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field_trailing_comma.snap28
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_two_fields.snap42
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_two_fields_incomplete.snap33
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__junk.snap11
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_casing.snap21
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_complete.snap21
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_missing_end.snap20
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_casing.snap23
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_complete.snap23
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_concatenation.snap43
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_incomplete.snap23
-rw-r--r--support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_missing_quote.snap23
-rw-r--r--support/texlab/src/syntax/build_log.rs1161
-rw-r--r--support/texlab/src/syntax/generic_ast.rs51
-rw-r--r--support/texlab/src/syntax/lang_data.rs231
-rw-r--r--support/texlab/src/syntax/latex.rs36
-rw-r--r--support/texlab/src/syntax/latex/analysis.rs943
-rw-r--r--support/texlab/src/syntax/latex/analysis/command.rs12
-rw-r--r--support/texlab/src/syntax/latex/analysis/distro_file.rs71
-rw-r--r--support/texlab/src/syntax/latex/analysis/environment.rs11
-rw-r--r--support/texlab/src/syntax/latex/analysis/explicit_link.rs72
-rw-r--r--support/texlab/src/syntax/latex/analysis/implicit_link.rs41
-rw-r--r--support/texlab/src/syntax/latex/analysis/label_name.rs66
-rw-r--r--support/texlab/src/syntax/latex/analysis/label_number.rs22
-rw-r--r--support/texlab/src/syntax/latex/analysis/theorem.rs23
-rw-r--r--support/texlab/src/syntax/latex/analysis/types.rs73
-rw-r--r--support/texlab/src/syntax/latex/ast.rs414
-rw-r--r--support/texlab/src/syntax/latex/cst.rs639
-rw-r--r--support/texlab/src/syntax/latex/kind.rs180
-rw-r--r--support/texlab/src/syntax/latex/lexer.rs353
-rw-r--r--support/texlab/src/syntax/latex/mod.rs686
-rw-r--r--support/texlab/src/syntax/latex/parser.rs1809
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_parameter.snap47
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_star.snap23
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__comment.snap35
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__delimiters.snap79
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__empty.snap6
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__escape_sequence.snap19
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__formula.snap55
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__invalid_parameter.snap11
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acronym_declaration.snap49
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_options.snap46
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_simple.snap28
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_options.snap29
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_simple.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_options.snap30
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_simple.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__bibtex_include_simple.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_missing_end.snap15
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_simple.snap15
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption.snap26
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_error.snap26
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_figure.snap28
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal.snap21
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal_error.snap21
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_empty.snap13
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_missing_brace.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_multiple_keys.snap17
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote.snap19
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote_postnote.snap24
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_redundant_comma.snap16
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_simple.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_star.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_empty.snap13
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_options.snap37
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_simple.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_definition_simple.snap27
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_reference_simple.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error1.snap29
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error2.snap22
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error3.snap19
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error4.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_simple.snap32
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_argc.snap19
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl.snap15
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl_error.snap15
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_simple.snap24
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__empty.snap8
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_definition.snap38
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested.snap37
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested_missing_braces.snap42
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_simple.snap27
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation.snap17
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_label_reference_simple.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_begin.snap30
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_end.snap31
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__escaped_brackets.snap19
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_args.snap20
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_empty.snap10
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_escape.snap10
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_definition_simple.snap33
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_options.snap32
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_simple.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_options.snap25
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_simple.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__hello_world.snap12
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_incomplete.snap15
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_simple.snap18
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_options.snap25
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_simple.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline.snap26
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline_double_dollar.snap26
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_definition_simple.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_number.snap23
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_multiple.snap17
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_error.snap18
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_incomplete.snap15
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_simple.snap18
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_simple.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_include_simple.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_input_simple.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_no_impl.snap15
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_simple.snap19
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_empty.snap13
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_multiple.snap17
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_options.snap37
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_simple.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter.snap9
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter_error.snap9
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__pgf_library_import_simple.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_enum_item.snap45
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_invalid_nesting.snap23
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_nested.snap57
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_siblings.snap29
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_options.snap25
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_simple.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_full.snap27
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_counter.snap19
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description.snap19
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description_and_counter.snap23
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_only_name.snap15
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__tikz_library_import_simple.snap14
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_braces.snap13
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets.snap13
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets_with_group.snap16
-rw-r--r--support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__verbatim_include_simple.snap14
-rw-r--r--support/texlab/src/syntax/latexindent.rs21
-rw-r--r--support/texlab/src/syntax/lsp_kind.rs93
-rw-r--r--support/texlab/src/syntax/mod.rs14
-rw-r--r--support/texlab/src/syntax/text.rs260
154 files changed, 7770 insertions, 4616 deletions
diff --git a/support/texlab/src/syntax/bibtex.rs b/support/texlab/src/syntax/bibtex.rs
new file mode 100644
index 0000000000..c6e8bb3760
--- /dev/null
+++ b/support/texlab/src/syntax/bibtex.rs
@@ -0,0 +1,34 @@
+mod cst;
+mod kind;
+mod lexer;
+mod parser;
+
+pub use self::{
+ cst::*,
+ kind::SyntaxKind::{self, *},
+ parser::{parse, Parse},
+};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Language {}
+
+impl cstree::Language for Language {
+ type Kind = SyntaxKind;
+
+ fn kind_from_raw(raw: cstree::SyntaxKind) -> Self::Kind {
+ assert!(raw.0 <= ROOT as u16);
+ unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
+ }
+
+ fn kind_to_raw(kind: Self::Kind) -> cstree::SyntaxKind {
+ kind.into()
+ }
+}
+
+pub type SyntaxNode = cstree::ResolvedNode<Language>;
+
+pub type SyntaxToken = cstree::ResolvedToken<Language>;
+
+pub type SyntaxElement = cstree::ResolvedElement<Language>;
+
+pub type SyntaxElementRef<'a> = cstree::ResolvedElementRef<'a, Language>;
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 "}"
+
diff --git a/support/texlab/src/syntax/build_log.rs b/support/texlab/src/syntax/build_log.rs
new file mode 100644
index 0000000000..65a5938262
--- /dev/null
+++ b/support/texlab/src/syntax/build_log.rs
@@ -0,0 +1,1161 @@
+use std::{cmp::Ordering, path::PathBuf};
+
+use once_cell::sync::Lazy;
+use regex::{Match, Regex};
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
+pub enum BuildErrorLevel {
+ Error,
+ Warning,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Hash)]
+pub struct BuildError {
+ pub relative_path: PathBuf,
+ pub level: BuildErrorLevel,
+ pub message: String,
+ pub line: Option<u32>,
+}
+
+const MAX_LINE_LENGTH: usize = 79;
+
+pub static PACKAGE_MESSAGE_REGEX: Lazy<Regex> =
+ Lazy::new(|| Regex::new("^\\([a-zA-Z_\\-]+\\)\\s*(?P<msg>.*)$").unwrap());
+
+pub static FILE_REGEX: Lazy<Regex> =
+ Lazy::new(|| Regex::new("\\((?P<file>[^\r\n()]+\\.(tex|sty|cls))").unwrap());
+
+pub static TEX_ERROR_REGEX: Lazy<Regex> = Lazy::new(|| {
+ Regex::new("(?m)^! ((?P<msg1>(.|\r|\n)*?)\r?\nl\\.(?P<line>\\d+)|(?P<msg2>[^\r\n]*))").unwrap()
+});
+
+pub static WARNING_REGEX: Lazy<Regex> =
+ Lazy::new(|| Regex::new("(LaTeX|Package [a-zA-Z_\\-]+) Warning: (?P<msg>[^\r\n]*)").unwrap());
+
+pub static BAD_BOX_REGEX: Lazy<Regex> = Lazy::new(|| {
+ Regex::new("(?P<msg>(Ov|Und)erfull \\\\[hv]box[^\r\n]*lines? (?P<line>\\d+)[^\r\n]*)").unwrap()
+});
+
+#[derive(Debug, PartialEq, Eq, Clone, Hash)]
+pub struct Parse {
+ pub errors: Vec<BuildError>,
+}
+
+pub fn parse(log: &str) -> Parse {
+ let log = prepare_log(log);
+ let mut ranges: Vec<FileRange> = FILE_REGEX
+ .find_iter(&log)
+ .map(|result| FileRange::create(&log, result))
+ .collect();
+ ranges.sort();
+
+ let tex_errors = extract_matches(&log, &ranges, &TEX_ERROR_REGEX, BuildErrorLevel::Error);
+ let warnings = extract_matches(&log, &ranges, &WARNING_REGEX, BuildErrorLevel::Warning);
+ let bad_boxes = extract_matches(&log, &ranges, &BAD_BOX_REGEX, BuildErrorLevel::Warning);
+
+ Parse {
+ errors: vec![tex_errors, warnings, bad_boxes].concat(),
+ }
+}
+
+fn extract_matches(
+ log: &str,
+ ranges: &[FileRange],
+ regex: &Regex,
+ level: BuildErrorLevel,
+) -> Vec<BuildError> {
+ let mut errors = Vec::new();
+ for result in regex.find_iter(&log) {
+ let captures = regex.captures(&log[result.start()..result.end()]).unwrap();
+ let message = captures
+ .name("msg")
+ .or_else(|| captures.name("msg1"))
+ .or_else(|| captures.name("msg2"))
+ .unwrap()
+ .as_str()
+ .lines()
+ .next()
+ .unwrap_or_default()
+ .to_owned();
+
+ if let Some(range) = ranges.iter().find(|range| range.contains(result.start())) {
+ let line = captures
+ .name("line")
+ .map(|result| u32::from_str_radix(result.as_str(), 10).unwrap() - 1);
+
+ errors.push(BuildError {
+ relative_path: range.path.clone(),
+ level,
+ message,
+ line,
+ });
+ }
+ }
+ errors
+}
+
+fn prepare_log(log: &str) -> String {
+ let mut old_lines = log.lines();
+ let mut new_lines: Vec<String> = Vec::new();
+ while let Some(line) = old_lines.next() {
+ if PACKAGE_MESSAGE_REGEX.is_match(&line) {
+ let captures = PACKAGE_MESSAGE_REGEX.captures(&line).unwrap();
+ if let Some(last_line) = new_lines.last_mut() {
+ last_line.push(' ');
+ last_line.push_str(captures.name("msg").unwrap().as_str());
+ }
+ } else if line.ends_with("...") {
+ let mut new_line = line[line.len() - 3..].to_owned();
+ if let Some(old_line) = old_lines.next() {
+ new_line.push_str(&old_line);
+ }
+ new_lines.push(new_line);
+ } else if line.chars().count() == MAX_LINE_LENGTH {
+ let mut new_line = String::new();
+ new_line.push_str(line);
+ if let Some(old_line) = old_lines.next() {
+ new_line.push_str(old_line);
+ }
+ new_lines.push(new_line);
+ } else {
+ new_lines.push(line.to_owned());
+ }
+ }
+ new_lines.join("\n")
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+struct FileRange {
+ pub path: PathBuf,
+ pub start: usize,
+ pub end: usize,
+}
+
+impl FileRange {
+ fn create(log: &str, result: Match) -> Self {
+ let mut balance = 1;
+ let mut end = result.start() + 1;
+ let chars = (&log[result.start() + 1..]).chars();
+ for c in chars {
+ if balance <= 0 {
+ break;
+ }
+
+ if c == '(' {
+ balance += 1;
+ } else if c == ')' {
+ balance -= 1;
+ }
+ end += c.len_utf8();
+ }
+
+ let captures = FILE_REGEX.captures(result.as_str()).unwrap();
+ let path = PathBuf::from(captures.name("file").unwrap().as_str());
+ Self {
+ path,
+ start: result.start(),
+ end,
+ }
+ }
+
+ fn len(&self) -> usize {
+ self.end - self.start + 1
+ }
+
+ fn contains(&self, index: usize) -> bool {
+ index >= self.start && index <= self.end
+ }
+}
+
+impl Ord for FileRange {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.len().cmp(&other.len())
+ }
+}
+
+impl PartialOrd for FileRange {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use indoc::indoc;
+
+ #[test]
+ fn test_parse_001() {
+ let log = indoc! {
+ r#"
+ This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:50
+ entering extended mode
+ restricted \write18 enabled.
+ %&-line parsing enabled.
+ **./parent.tex
+ (./parent.tex
+ LaTeX2e <2017-04-15>
+ Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
+ (/TexLive/texmf-dist/tex/latex/base/article.cls
+ Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
+ (/TexLive/texmf-dist/tex/latex/base/size10.clo
+ File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
+ )
+ \c@part=\count79
+ \c@section=\count80
+ \c@subsection=\count81
+ \c@subsubsection=\count82
+ \c@paragraph=\count83
+ \c@subparagraph=\count84
+ \c@figure=\count85
+ \c@table=\count86
+ \abovecaptionskip=\skip41
+ \belowcaptionskip=\skip42
+ \bibindent=\dimen102
+ )
+ (/TexLive/texmf-dist/tex/latex/multirow/bigstrut.sty
+ Package: bigstrut 2016/11/25 v2.2 Provide larger struts in tabulars
+ \bigstrutjot=\dimen103
+ )
+ (/TexLive/texmf-dist/tex/latex/multirow/multirow.sty
+ Package: multirow 2016/11/25 v2.2 Span multiple rows of a table
+ \multirow@colwidth=\skip43
+ \multirow@cntb=\count87
+ \multirow@dima=\skip44
+ ) (./parent.aux)
+ \openout1 = `parent.aux'.
+
+ LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 6.
+ LaTeX Font Info: ... okay on input line 6.
+ LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 6.
+ LaTeX Font Info: ... okay on input line 6.
+ LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 6.
+ LaTeX Font Info: ... okay on input line 6.
+ LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 6.
+ LaTeX Font Info: ... okay on input line 6.
+ LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 6.
+ LaTeX Font Info: ... okay on input line 6.
+ LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 6.
+ LaTeX Font Info: ... okay on input line 6.
+
+ Overfull \hbox (200.00162pt too wide) in paragraph at lines 8--9
+ []\OT1/cmr/m/n/10 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ []
+
+ LaTeX Font Info: External font `cmex10' loaded for size
+ (Font) <7> on input line 11.
+ LaTeX Font Info: External font `cmex10' loaded for size
+ (Font) <5> on input line 11.
+
+ Overfull \vbox (3.19998pt too high) detected at line 23
+ []
+
+ [1
+
+ {/TexLive/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (./parent.aux) )
+ Here is how much of TeX's memory you used:
+ 265 strings out of 492995
+ 3121 string characters out of 6138727
+ 55074 words of memory out of 5000000
+ 3896 multiletter control sequences out of 15000+600000
+ 3640 words of font info for 14 fonts, out of 8000000 for 9000
+ 1141 hyphenation exceptions out of 8191
+ 23i,20n,20p,124b,282s stack positions out of 5000i,500n,10000p,200000b,80000s
+ </
+ TexLive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb></TexLive/texmf-d
+ ist/fonts/type1/public/amsfonts/cm/cmr7.pfb>
+ Output written on parent.pdf (1 page, 17505 bytes).
+ PDF statistics:
+ 16 PDF objects out of 1000 (max. 8388607)
+ 10 compressed objects within 1 object stream
+ 0 named destinations out of 1000 (max. 500000)
+ 1 words of extra memory for PDF output out of 10000 (max. 10000000)"#,
+ };
+
+ assert_eq!(
+ parse(&log).errors,
+ vec![
+ BuildError {
+ relative_path: "./parent.tex".into(),
+ level: BuildErrorLevel::Warning,
+ message: "Overfull \\hbox (200.00162pt too wide) in paragraph at lines 8--9"
+ .into(),
+ line: Some(7),
+ },
+ BuildError {
+ relative_path: "./parent.tex".into(),
+ level: BuildErrorLevel::Warning,
+ message: "Overfull \\vbox (3.19998pt too high) detected at line 23".into(),
+ line: Some(22),
+ }
+ ]
+ );
+ }
+
+ #[test]
+ fn test_parse_002() {
+ let log = indoc! {
+ r#"
+ This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:52
+ entering extended mode
+ restricted \write18 enabled.
+ %&-line parsing enabled.
+ **./parent.tex
+ (./parent.tex
+ LaTeX2e <2017-04-15>
+ Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
+ (/TexLive/texmf-dist/tex/latex/base/article.cls
+ Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
+ (/TexLive/texmf-dist/tex/latex/base/size10.clo
+ File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
+ )
+ \c@part=\count79
+ \c@section=\count80
+ \c@subsection=\count81
+ \c@subsubsection=\count82
+ \c@paragraph=\count83
+ \c@subparagraph=\count84
+ \c@figure=\count85
+ \c@table=\count86
+ \abovecaptionskip=\skip41
+ \belowcaptionskip=\skip42
+ \bibindent=\dimen102
+ ) (./parent.aux (./child.aux))
+ \openout1 = `parent.aux'.
+
+ LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 4.
+ LaTeX Font Info: ... okay on input line 4.
+ LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 4.
+ LaTeX Font Info: ... okay on input line 4.
+ LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 4.
+ LaTeX Font Info: ... okay on input line 4.
+ LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 4.
+ LaTeX Font Info: ... okay on input line 4.
+ LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 4.
+ LaTeX Font Info: ... okay on input line 4.
+ LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 4.
+ LaTeX Font Info: ... okay on input line 4.
+ (./child.tex
+ ! Undefined control sequence.
+ l.1 \foo
+
+ The control sequence at the end of the top line
+ of your error message was never \def'ed. If you have
+ misspelled it (e.g., `\hobx'), type `I' and the correct
+ spelling (e.g., `I\hbox'). Otherwise just continue,
+ and I'll forget about whatever was undefined.
+
+ ) (./parent.aux) )
+ Here is how much of TeX's memory you used:
+ 205 strings out of 492995
+ 2149 string characters out of 6138727
+ 54074 words of memory out of 5000000
+ 3841 multiletter control sequences out of 15000+600000
+ 3640 words of font info for 14 fonts, out of 8000000 for 9000
+ 1141 hyphenation exceptions out of 8191
+ 23i,1n,17p,116b,36s stack positions out of 5000i,500n,10000p,200000b,80000s
+
+ No pages of output.
+ PDF statistics:
+ 0 PDF objects out of 1000 (max. 8388607)
+ 0 named destinations out of 1000 (max. 500000)
+ 1 words of extra memory for PDF output out of 10000 (max. 10000000)"#,
+ };
+
+ assert_eq!(
+ parse(&log).errors,
+ vec![BuildError {
+ relative_path: "./child.tex".into(),
+ level: BuildErrorLevel::Error,
+ message: "Undefined control sequence.".into(),
+ line: Some(0)
+ }]
+ );
+ }
+
+ #[test]
+ fn test_parse_003() {
+ let log = indoc! {
+ r#"
+ This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:51
+ entering extended mode
+ restricted \write18 enabled.
+ %&-line parsing enabled.
+ **./parent.tex
+ (./parent.tex
+ LaTeX2e <2017-04-15>
+ Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
+ (/TexLive/texmf-dist/tex/latex/base/article.cls
+ Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
+ (/TexLive/texmf-dist/tex/latex/base/size10.clo
+ File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
+ )
+ \c@part=\count79
+ \c@section=\count80
+ \c@subsection=\count81
+ \c@subsubsection=\count82
+ \c@paragraph=\count83
+ \c@subparagraph=\count84
+ \c@figure=\count85
+ \c@table=\count86
+ \abovecaptionskip=\skip41
+ \belowcaptionskip=\skip42
+ \bibindent=\dimen102
+ ) (./parent.aux)
+ \openout1 = `parent.aux'.
+
+ LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 4.
+ LaTeX Font Info: ... okay on input line 4.
+ LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 4.
+ LaTeX Font Info: ... okay on input line 4.
+ LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 4.
+ LaTeX Font Info: ... okay on input line 4.
+ LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 4.
+ LaTeX Font Info: ... okay on input line 4.
+ LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 4.
+ LaTeX Font Info: ... okay on input line 4.
+ LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 4.
+ LaTeX Font Info: ... okay on input line 4.
+
+ LaTeX Warning: Citation `foo' on page 1 undefined on input line 6.
+
+ [1
+
+ {/TexLive/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (./parent.aux)
+
+ LaTeX Warning: There were undefined references.
+
+ )
+ Here is how much of TeX's memory you used:
+ 204 strings out of 492995
+ 2142 string characters out of 6138727
+ 54074 words of memory out of 5000000
+ 3842 multiletter control sequences out of 15000+600000
+ 3948 words of font info for 15 fonts, out of 8000000 for 9000
+ 1141 hyphenation exceptions out of 8191
+ 23i,4n,21p,116b,107s stack positions out of 5000i,500n,10000p,200000b,80000s
+ </TexLive/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb></TexLive/
+ texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb>
+ Output written on parent.pdf (1 page, 17339 bytes).
+ PDF statistics:
+ 16 PDF objects out of 1000 (max. 8388607)
+ 10 compressed objects within 1 object stream
+ 0 named destinations out of 1000 (max. 500000)
+ 1 words of extra memory for PDF output out of 10000 (max. 10000000)"#,
+ };
+
+ assert_eq!(
+ parse(&log).errors,
+ vec![
+ BuildError {
+ relative_path: "./parent.tex".into(),
+ level: BuildErrorLevel::Warning,
+ message: "Citation `foo' on page 1 undefined on input line 6.".into(),
+ line: None
+ },
+ BuildError {
+ relative_path: "./parent.tex".into(),
+ level: BuildErrorLevel::Warning,
+ message: "There were undefined references.".into(),
+ line: None,
+ }
+ ]
+ );
+ }
+
+ #[test]
+ fn test_parse_004() {
+ let log = indoc! {
+ r#"
+ This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:40
+ entering extended mode
+ restricted \write18 enabled.
+ %&-line parsing enabled.
+ **./parent.tex
+ (./parent.tex
+ LaTeX2e <2017-04-15>
+ Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
+ (/TexLive/texmf-dist/tex/latex/base/article.cls
+ Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
+ (/TexLive/texmf-dist/tex/latex/base/size10.clo
+ File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
+ )
+ \c@part=\count79
+ \c@section=\count80
+ \c@subsection=\count81
+ \c@subsubsection=\count82
+ \c@paragraph=\count83
+ \c@subparagraph=\count84
+ \c@figure=\count85
+ \c@table=\count86
+ \abovecaptionskip=\skip41
+ \belowcaptionskip=\skip42
+ \bibindent=\dimen102
+ )
+ (/TexLive/texmf-dist/tex/generic/babel/babel.sty
+ Package: babel 2017/05/19 3.10 The Babel package
+
+ ! Package babel Error: Unknown option `foo'. Either you misspelled it
+ (babel) or the language definition file foo.ldf was not found.
+
+ See the babel package documentation for explanation.
+ Type H <return> for immediate help.
+ ...
+
+ l.393 \ProcessOptions*
+
+ Valid options are: shorthands=, KeepShorthandsActive,
+ activeacute, activegrave, noconfigs, safe=, main=, math=
+ headfoot=, strings=, config=, hyphenmap=, or a language name.
+
+ ! Package babel Error: You haven't specified a language option.
+
+ See the babel package documentation for explanation.
+ Type H <return> for immediate help.
+ ...
+
+ l.426 ...ry to proceed from here, type x to quit.}
+
+ You need to specify a language, either as a global option
+ or as an optional argument to the \usepackage command;
+ You shouldn't try to proceed from here, type x to quit.
+
+ ) (./parent.aux)
+ \openout1 = `parent.aux'.
+
+ LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 5.
+ LaTeX Font Info: ... okay on input line 5.
+ LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 5.
+ LaTeX Font Info: ... okay on input line 5.
+ LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 5.
+ LaTeX Font Info: ... okay on input line 5.
+ LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 5.
+ LaTeX Font Info: ... okay on input line 5.
+ LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 5.
+ LaTeX Font Info: ... okay on input line 5.
+ LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 5.
+ LaTeX Font Info: ... okay on input line 5.
+ (./parent.aux) )
+ Here is how much of TeX's memory you used:
+ 304 strings out of 492995
+ 3421 string characters out of 6138727
+ 56035 words of memory out of 5000000
+ 3938 multiletter control sequences out of 15000+600000
+ 3640 words of font info for 14 fonts, out of 8000000 for 9000
+ 1141 hyphenation exceptions out of 8191
+ 23i,1n,17p,116b,36s stack positions out of 5000i,500n,10000p,200000b,80000s
+
+ No pages of output.
+ PDF statistics:
+ 0 PDF objects out of 1000 (max. 8388607)
+ 0 named destinations out of 1000 (max. 500000)
+ 1 words of extra memory for PDF output out of 10000 (max. 10000000)"#,
+ };
+
+ assert_eq!(
+ parse(&log).errors,
+ vec![
+ BuildError {
+ relative_path: "/TexLive/texmf-dist/tex/generic/babel/babel.sty".into(),
+ level: BuildErrorLevel::Error,
+ message: "Package babel Error: Unknown option `foo'. Either you misspelled it or the language definition file foo.ldf was not found.".into(),
+ line: Some(392)
+ },
+ BuildError {
+ relative_path: "/TexLive/texmf-dist/tex/generic/babel/babel.sty".into(),
+ level: BuildErrorLevel::Error,
+ message: "Package babel Error: You haven't specified a language option.".into(),
+ line: Some(425)
+ }
+ ]
+ );
+ }
+
+ #[test]
+ fn test_parse_005() {
+ let log = indoc! {
+ r#"
+ This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:45
+ entering extended mode
+ restricted \write18 enabled.
+ %&-line parsing enabled.
+ **./parent.tex
+ (./parent.tex
+ LaTeX2e <2017-04-15>
+ Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
+ (/TexLive/texmf-dist/tex/latex/base/article.cls
+ Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
+ (/TexLive/texmf-dist/tex/latex/base/size10.clo
+ File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
+ )
+ \c@part=\count79
+ \c@section=\count80
+ \c@subsection=\count81
+ \c@subsubsection=\count82
+ \c@paragraph=\count83
+ \c@subparagraph=\count84
+ \c@figure=\count85
+ \c@table=\count86
+ \abovecaptionskip=\skip41
+ \belowcaptionskip=\skip42
+ \bibindent=\dimen102
+ )
+ (/TexLive/texmf-dist/tex/generic/babel/babel.sty
+ Package: babel 2017/05/19 3.10 The Babel package
+
+ (/TexLive/texmf-dist/tex/generic/babel-spanish/spanish.ldf
+ Language: spanish.ldf 2016/03/03 v5.0p Spanish support from the babel system
+
+ (/TexLive/texmf-dist/tex/generic/babel/babel.def
+ File: babel.def 2017/05/19 3.10 Babel common definitions
+ \babel@savecnt=\count87
+ \U@D=\dimen103
+ )
+ \es@quottoks=\toks14
+ \es@quotdepth=\count88
+ Package babel Info: Making " an active character on input line 561.
+ Package babel Info: Making . an active character on input line 662.
+ Package babel Info: Making < an active character on input line 707.
+ Package babel Info: Making > an active character on input line 707.
+ ))
+ (/TexLive/texmf-dist/tex/latex/biblatex/biblatex.sty
+ Package: biblatex 2016/12/05 v3.7 programmable bibliographies (PK/JW/AB)
+
+ (/TexLive/texmf-dist/tex/generic/oberdiek/pdftexcmds.sty
+ Package: pdftexcmds 2017/03/19 v0.25 Utility functions of pdfTeX for LuaTeX (HO
+ )
+
+ (/TexLive/texmf-dist/tex/generic/oberdiek/infwarerr.sty
+ Package: infwarerr 2016/05/16 v1.4 Providing info/warning/error messages (HO)
+ )
+ (/TexLive/texmf-dist/tex/generic/oberdiek/ifluatex.sty
+ Package: ifluatex 2016/05/16 v1.4 Provides the ifluatex switch (HO)
+ Package ifluatex Info: LuaTeX not detected.
+ )
+ (/TexLive/texmf-dist/tex/generic/oberdiek/ltxcmds.sty
+ Package: ltxcmds 2016/05/16 v1.23 LaTeX kernel commands for general use (HO)
+ )
+ (/TexLive/texmf-dist/tex/generic/oberdiek/ifpdf.sty
+ Package: ifpdf 2017/03/15 v3.2 Provides the ifpdf switch
+ )
+ Package pdftexcmds Info: LuaTeX not detected.
+ Package pdftexcmds Info: \pdf@primitive is available.
+ Package pdftexcmds Info: \pdf@ifprimitive is available.
+ Package pdftexcmds Info: \pdfdraftmode found.
+ )
+ (/TexLive/texmf-dist/tex/latex/etoolbox/etoolbox.sty
+ Package: etoolbox 2017/01/02 v2.4 e-TeX tools for LaTeX (JAW)
+ \etb@tempcnta=\count89
+ )
+ (/TexLive/texmf-dist/tex/latex/graphics/keyval.sty
+ Package: keyval 2014/10/28 v1.15 key=value parser (DPC)
+ \KV@toks@=\toks15
+ )
+ (/TexLive/texmf-dist/tex/latex/oberdiek/kvoptions.sty
+ Package: kvoptions 2016/05/16 v3.12 Key value format for package options (HO)
+
+ (/TexLive/texmf-dist/tex/generic/oberdiek/kvsetkeys.sty
+ Package: kvsetkeys 2016/05/16 v1.17 Key value parser (HO)
+
+ (/TexLive/texmf-dist/tex/generic/oberdiek/etexcmds.sty
+ Package: etexcmds 2016/05/16 v1.6 Avoid name clashes with e-TeX commands (HO)
+ Package etexcmds Info: Could not find \expanded.
+ (etexcmds) That can mean that you are not using pdfTeX 1.50 or
+ (etexcmds) that some package has redefined \expanded.
+ (etexcmds) In the latter case, load this package earlier.
+ )))
+ (/TexLive/texmf-dist/tex/latex/logreq/logreq.sty
+ Package: logreq 2010/08/04 v1.0 xml request logger
+ \lrq@indent=\count90
+
+ (/TexLive/texmf-dist/tex/latex/logreq/logreq.def
+ File: logreq.def 2010/08/04 v1.0 logreq spec v1.0
+ ))
+ (/TexLive/texmf-dist/tex/latex/base/ifthen.sty
+ Package: ifthen 2014/09/29 v1.1c Standard LaTeX ifthen package (DPC)
+ )
+ (/TexLive/texmf-dist/tex/latex/url/url.sty
+ \Urlmuskip=\muskip10
+ Package: url 2013/09/16 ver 3.4 Verb mode for urls, etc.
+ )
+ (/TexLive/texmf-dist/tex/generic/xstring/xstring.sty
+ (/TexLive/texmf-dist/tex/generic/xstring/xstring.tex
+ \@xs@message=\write3
+ \integerpart=\count91
+ \decimalpart=\count92
+ )
+ Package: xstring 2013/10/13 v1.7c String manipulations (C Tellechea)
+ )
+ \c@tabx@nest=\count93
+ \c@listtotal=\count94
+ \c@listcount=\count95
+ \c@liststart=\count96
+ \c@liststop=\count97
+ \c@citecount=\count98
+ \c@citetotal=\count99
+ \c@multicitecount=\count100
+ \c@multicitetotal=\count101
+ \c@instcount=\count102
+ \c@maxnames=\count103
+ \c@minnames=\count104
+ \c@maxitems=\count105
+ \c@minitems=\count106
+ \c@citecounter=\count107
+ \c@savedcitecounter=\count108
+ \c@uniquelist=\count109
+ \c@uniquename=\count110
+ \c@refsection=\count111
+ \c@refsegment=\count112
+ \c@maxextratitle=\count113
+ \c@maxextratitleyear=\count114
+ \c@maxextrayear=\count115
+ \c@maxextraalpha=\count116
+ \c@abbrvpenalty=\count117
+ \c@highnamepenalty=\count118
+ \c@lownamepenalty=\count119
+ \c@maxparens=\count120
+ \c@parenlevel=\count121
+ \blx@tempcnta=\count122
+ \blx@tempcntb=\count123
+ \blx@tempcntc=\count124
+ \blx@maxsection=\count125
+ \blx@maxsegment@0=\count126
+ \blx@notetype=\count127
+ \blx@parenlevel@text=\count128
+ \blx@parenlevel@foot=\count129
+ \blx@sectionciteorder@0=\count130
+ \labelnumberwidth=\skip43
+ \labelalphawidth=\skip44
+ \biblabelsep=\skip45
+ \bibitemsep=\skip46
+ \bibnamesep=\skip47
+ \bibinitsep=\skip48
+ \bibparsep=\skip49
+ \bibhang=\skip50
+ \blx@bcfin=\read1
+ \blx@bcfout=\write4
+ \c@mincomprange=\count131
+ \c@maxcomprange=\count132
+ \c@mincompwidth=\count133
+ Package biblatex Info: Trying to load biblatex default data model...
+ Package biblatex Info: ... file 'blx-dm.def' found.
+
+ (/TexLive/texmf-dist/tex/latex/biblatex/blx-dm.def)
+ Package biblatex Info: Trying to load biblatex style data model...
+ Package biblatex Info: ... file 'ieee.dbx' not found.
+ Package biblatex Info: Trying to load biblatex custom data model...
+ Package biblatex Info: ... file 'biblatex-dm.cfg' not found.
+ \c@afterword=\count134
+ \c@savedafterword=\count135
+ \c@annotator=\count136
+ \c@savedannotator=\count137
+ \c@author=\count138
+ \c@savedauthor=\count139
+ \c@bookauthor=\count140
+ \c@savedbookauthor=\count141
+ \c@commentator=\count142
+ \c@savedcommentator=\count143
+ \c@editor=\count144
+ \c@savededitor=\count145
+ \c@editora=\count146
+ \c@savededitora=\count147
+ \c@editorb=\count148
+ \c@savededitorb=\count149
+ \c@editorc=\count150
+ \c@savededitorc=\count151
+ \c@foreword=\count152
+ \c@savedforeword=\count153
+ \c@holder=\count154
+ \c@savedholder=\count155
+ \c@introduction=\count156
+ \c@savedintroduction=\count157
+ \c@namea=\count158
+ \c@savednamea=\count159
+ \c@nameb=\count160
+ \c@savednameb=\count161
+ \c@namec=\count162
+ \c@savednamec=\count163
+ \c@translator=\count164
+ \c@savedtranslator=\count165
+ \c@shortauthor=\count166
+ \c@savedshortauthor=\count167
+ \c@shorteditor=\count168
+ \c@savedshorteditor=\count169
+ \c@labelname=\count170
+ \c@savedlabelname=\count171
+ \c@institution=\count172
+ \c@savedinstitution=\count173
+ \c@lista=\count174
+ \c@savedlista=\count175
+ \c@listb=\count176
+ \c@savedlistb=\count177
+ \c@listc=\count178
+ \c@savedlistc=\count179
+ \c@listd=\count180
+ \c@savedlistd=\count181
+ \c@liste=\count182
+ \c@savedliste=\count183
+ \c@listf=\count184
+ \c@savedlistf=\count185
+ \c@location=\count186
+ \c@savedlocation=\count187
+ \c@organization=\count188
+ \c@savedorganization=\count189
+ \c@origlocation=\count190
+ \c@savedoriglocation=\count191
+ \c@origpublisher=\count192
+ \c@savedorigpublisher=\count193
+ \c@publisher=\count194
+ \c@savedpublisher=\count195
+ \c@language=\count196
+ \c@savedlanguage=\count197
+ \c@pageref=\count198
+ \c@savedpageref=\count199
+ \shorthandwidth=\skip51
+ \shortjournalwidth=\skip52
+ \shortserieswidth=\skip53
+ \shorttitlewidth=\skip54
+ \shortauthorwidth=\skip55
+ \shorteditorwidth=\skip56
+ Package biblatex Info: Trying to load compatibility code...
+ Package biblatex Info: ... file 'blx-compat.def' found.
+
+ (/TexLive/texmf-dist/tex/latex/biblatex/blx-compat.def
+ File: blx-compat.def 2016/12/05 v3.7 biblatex compatibility (PK/JW/AB)
+ )
+ Package biblatex Info: Trying to load generic definitions...
+ Package biblatex Info: ... file 'biblatex.def' found.
+
+ (/TexLive/texmf-dist/tex/latex/biblatex/biblatex.def
+ File: biblatex.def 2016/12/05 v3.7 biblatex compatibility (PK/JW/AB)
+ \c@textcitecount=\count266
+ \c@textcitetotal=\count267
+ \c@textcitemaxnames=\count268
+ \c@biburlnumpenalty=\count269
+ \c@biburlucpenalty=\count270
+ \c@biburllcpenalty=\count271
+ \c@smartand=\count272
+ )
+ Package biblatex Info: Trying to load bibliography style 'ieee'...
+ Package biblatex Info: ... file 'ieee.bbx' found.
+
+ (/TexLive/texmf-dist/tex/latex/biblatex-ieee/ieee.bbx
+ File: ieee.bbx 2017/03/27 v1.2d biblatex bibliography style
+ Package biblatex Info: Trying to load bibliography style 'numeric-comp'...
+ Package biblatex Info: ... file 'numeric-comp.bbx' found.
+
+ (/TexLive/texmf-dist/tex/latex/biblatex/bbx/numeric-comp.bbx
+ File: numeric-comp.bbx 2016/12/05 v3.7 biblatex bibliography style (PK/JW/AB)
+ Package biblatex Info: Trying to load bibliography style 'numeric'...
+ Package biblatex Info: ... file 'numeric.bbx' found.
+
+ (/TexLive/texmf-dist/tex/latex/biblatex/bbx/numeric.bbx
+ File: numeric.bbx 2016/12/05 v3.7 biblatex bibliography style (PK/JW/AB)
+ Package biblatex Info: Trying to load bibliography style 'standard'...
+ Package biblatex Info: ... file 'standard.bbx' found.
+
+ (/TexLive/texmf-dist/tex/latex/biblatex/bbx/standard.bbx
+ File: standard.bbx 2016/12/05 v3.7 biblatex bibliography style (PK/JW/AB)
+ \c@bbx:relatedcount=\count273
+ \c@bbx:relatedtotal=\count274
+ ))))
+ Package biblatex Info: Trying to load citation style 'ieee'...
+ Package biblatex Info: ... file 'ieee.cbx' found.
+
+ (/TexLive/texmf-dist/tex/latex/biblatex-ieee/ieee.cbx
+ File: ieee.cbx 2017/03/27 v1.2d biblatex citation style
+ Package biblatex Info: Trying to load citation style 'numeric-comp'...
+ Package biblatex Info: ... file 'numeric-comp.cbx' found.
+
+ (/TexLive/texmf-dist/tex/latex/biblatex/cbx/numeric-comp.cbx
+ File: numeric-comp.cbx 2016/12/05 v3.7 biblatex citation style (PK/JW/AB)
+ \c@cbx@tempcnta=\count275
+ \c@cbx@tempcntb=\count276
+ Package biblatex Info: Redefining '\cite'.
+ Package biblatex Info: Redefining '\parencite'.
+ Package biblatex Info: Redefining '\footcite'.
+ Package biblatex Info: Redefining '\footcitetext'.
+ Package biblatex Info: Redefining '\smartcite'.
+ Package biblatex Info: Redefining '\supercite'.
+ Package biblatex Info: Redefining '\textcite'.
+ Package biblatex Info: Redefining '\textcites'.
+ Package biblatex Info: Redefining '\cites'.
+ Package biblatex Info: Redefining '\parencites'.
+ Package biblatex Info: Redefining '\smartcites'.
+ )
+ Package biblatex Info: Redefining '\cite'.
+ Package biblatex Info: Redefining '\cites'.
+ )
+ Package biblatex Info: Trying to load configuration file...
+ Package biblatex Info: ... file 'biblatex.cfg' found.
+
+ (/TexLive/texmf-dist/tex/latex/biblatex/biblatex.cfg
+ File: biblatex.cfg
+ ))
+ Package biblatex Info: Trying to load language 'spanish'...
+ Package biblatex Info: ... file 'spanish.lbx' found.
+
+ (/TexLive/texmf-dist/tex/latex/biblatex/lbx/spanish.lbx
+ File: spanish.lbx 2016/12/05 v3.7 biblatex localization (PK/JW/AB)
+ )
+
+ Package biblatex Warning: 'babel/polyglossia' detected but 'csquotes' missing.
+ (biblatex) Loading 'csquotes' recommended.
+
+ \@quotelevel=\count277
+ \@quotereset=\count278
+ (./parent.aux)
+ \openout1 = `parent.aux'.
+
+ LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 6.
+ LaTeX Font Info: ... okay on input line 6.
+ LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 6.
+ LaTeX Font Info: ... okay on input line 6.
+ LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 6.
+ LaTeX Font Info: ... okay on input line 6.
+ LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 6.
+ LaTeX Font Info: ... okay on input line 6.
+ LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 6.
+ LaTeX Font Info: ... okay on input line 6.
+ LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 6.
+ LaTeX Font Info: ... okay on input line 6.
+ LaTeX Info: Redefining \sptext on input line 6.
+ LaTeX Info: Redefining \. on input line 6.
+ LaTeX Info: Redefining \% on input line 6.
+ Package biblatex Info: No input encoding detected.
+ (biblatex) Assuming 'ascii'.
+ Package biblatex Info: Automatic encoding selection.
+ (biblatex) Assuming data encoding 'ascii'.
+ \openout4 = `parent.bcf'.
+
+ Package biblatex Info: Trying to load bibliographic data...
+ Package biblatex Info: ... file 'parent.bbl' not found.
+
+ No file parent.bbl.
+ Package biblatex Info: Reference section=0 on input line 6.
+ Package biblatex Info: Reference segment=0 on input line 6.
+ (./parent.aux)
+
+ LaTeX Warning: There were undefined references.
+
+ Package biblatex Warning: Please (re)run Biber on the file:
+ (biblatex) parent
+ (biblatex) and rerun LaTeX afterwards.
+
+ Package logreq Info: Writing requests to 'parent.run.xml'.
+ \openout1 = `parent.run.xml'.
+
+ )
+ Here is how much of TeX's memory you used:
+ 7717 strings out of 492995
+ 133301 string characters out of 6138727
+ 557258 words of memory out of 5000000
+ 11248 multiletter control sequences out of 15000+600000
+ 3640 words of font info for 14 fonts, out of 8000000 for 9000
+ 1141 hyphenation exceptions out of 8191
+ 35i,1n,30p,856b,700s stack positions out of 5000i,500n,10000p,200000b,80000s
+
+ No pages of output.
+ PDF statistics:
+ 0 PDF objects out of 1000 (max. 8388607)
+ 0 named destinations out of 1000 (max. 500000)
+ 1 words of extra memory for PDF output out of 10000 (max. 10000000)"#,
+ };
+
+ assert_eq!(
+ parse(&log).errors,
+ vec![
+ BuildError {
+ relative_path: "./parent.tex".into(),
+ level: BuildErrorLevel::Warning,
+ message: "'babel/polyglossia' detected but 'csquotes' missing. Loading 'csquotes' recommended.".into(),
+ line: None
+ },
+ BuildError {
+ relative_path: "./parent.tex".into(),
+ level: BuildErrorLevel::Warning,
+ message: "There were undefined references.".into(),
+ line: None,
+ },
+ BuildError {
+ relative_path: "./parent.tex".into(),
+ level: BuildErrorLevel::Warning,
+ message: "Please (re)run Biber on the file: parent and rerun LaTeX afterwards.".into(),
+ line: None
+ }
+ ]
+ );
+ }
+
+ #[test]
+ fn test_parse_006() {
+ let log = indoc! {
+ r#"
+ This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:33
+ entering extended mode
+ restricted \write18 enabled.
+ %&-line parsing enabled.
+ **./parent.tex
+ (./parent.tex
+ LaTeX2e <2017-04-15>
+ Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
+ (/TexLive/texmf-dist/tex/latex/base/article.cls
+ Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
+ (/TexLive/texmf-dist/tex/latex/base/size10.clo
+ File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option)
+ )
+ \c@part=\count79
+ \c@section=\count80
+ \c@subsection=\count81
+ \c@subsubsection=\count82
+ \c@paragraph=\count83
+ \c@subparagraph=\count84
+ \c@figure=\count85
+ \c@table=\count86
+ \abovecaptionskip=\skip41
+ \belowcaptionskip=\skip42
+ \bibindent=\dimen102
+ ) (./parent.aux
+ (./child.tex.aux))
+ \openout1 = `parent.aux'.
+
+ LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 3.
+ LaTeX Font Info: ... okay on input line 3.
+ LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 3.
+ LaTeX Font Info: ... okay on input line 3.
+ LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 3.
+ LaTeX Font Info: ... okay on input line 3.
+ LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 3.
+ LaTeX Font Info: ... okay on input line 3.
+ LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 3.
+ LaTeX Font Info: ... okay on input line 3.
+ LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 3.
+ LaTeX Font Info: ... okay on input line 3.
+ \openout2 = `child.aux'.
+
+ (./child.tex)
+ ! Undefined control sequence.
+ l.7 \foo
+
+ The control sequence at the end of the top line
+ of your error message was never \def'ed. If you have
+ misspelled it (e.g., `\hobx'), type `I' and the correct
+ spelling (e.g., `I\hbox'). Otherwise just continue,
+ and I'll forget about whatever was undefined.
+
+ ! Missing $ inserted.
+ <inserted text>
+ $
+ l.8 \bar
+
+ I've inserted a begin-math/end-math symbol since I think
+ you left one out. Proceed, with fingers crossed.
+
+ LaTeX Font Info: External font `cmex10' loaded for size
+ (Font) <7> on input line 8.
+ LaTeX Font Info: External font `cmex10' loaded for size
+ (Font) <5> on input line 8.
+ ! Undefined control sequence.
+ l.9 \baz
+
+ The control sequence at the end of the top line
+ of your error message was never \def'ed. If you have
+ misspelled it (e.g., `\hobx'), type `I' and the correct
+ spelling (e.g., `I\hbox'). Otherwise just continue,
+ and I'll forget about whatever was undefined.
+
+ ! Missing { inserted.
+ <to be read again>
+ \par
+ l.10
+
+ A left brace was mandatory here, so I've put one in.
+ You might want to delete and/or insert some corrections
+ so that I will find a matching right brace soon.
+ (If you're confused by all this, try typing `I}' now.)
+
+ ! Missing $ inserted.
+ <inserted text>
+ $
+ l.10
+
+ I've inserted a begin-math/end-math symbol since I think
+ you left one out. Proceed, with fingers crossed.
+
+ ! Missing } inserted.
+ <inserted text>
+ }
+ l.10
+
+ I've inserted something that you may have forgotten.
+ (See the <inserted text> above.)
+ With luck, this will get me unwedged. But if you
+ really didn't forget anything, try typing `2' now; then
+ my insertion and my current dilemma will both disappear.
+
+ [1
+
+ {/TexLive/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (./parent.aux
+ (./child.aux)) )
+ Here is how much of TeX's memory you used:
+ 212 strings out of 492995
+ 2238 string characters out of 6138727
+ 54074 words of memory out of 5000000
+ 3843 multiletter control sequences out of 15000+600000
+ 3640 words of font info for 14 fonts, out of 8000000 for 9000
+ 1141 hyphenation exceptions out of 8191
+ 23i,4n,17p,116b,107s stack positions out of 5000i,500n,10000p,200000b,80000s
+ </TexLive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb
+ >
+ Output written on parent.pdf (1 page, 8329 bytes).
+ PDF statistics:
+ 12 PDF objects out of 1000 (max. 8388607)
+ 7 compressed objects within 1 object stream
+ 0 named destinations out of 1000 (max. 500000)
+ 1 words of extra memory for PDF output out of 10000 (max. 10000000)"#,
+ };
+
+ assert_eq!(
+ parse(&log).errors,
+ vec![
+ BuildError {
+ relative_path: "./parent.tex".into(),
+ level: BuildErrorLevel::Error,
+ message: "Undefined control sequence.".into(),
+ line: Some(6)
+ },
+ BuildError {
+ relative_path: "./parent.tex".into(),
+ level: BuildErrorLevel::Error,
+ message: "Missing $ inserted.".into(),
+ line: Some(7)
+ },
+ BuildError {
+ relative_path: "./parent.tex".into(),
+ level: BuildErrorLevel::Error,
+ message: "Undefined control sequence.".into(),
+ line: Some(8)
+ },
+ BuildError {
+ relative_path: "./parent.tex".into(),
+ level: BuildErrorLevel::Error,
+ message: "Missing { inserted.".into(),
+ line: Some(9)
+ },
+ BuildError {
+ relative_path: "./parent.tex".into(),
+ level: BuildErrorLevel::Error,
+ message: "Missing $ inserted.".into(),
+ line: Some(9)
+ },
+ BuildError {
+ relative_path: "./parent.tex".into(),
+ level: BuildErrorLevel::Error,
+ message: "Missing } inserted.".into(),
+ line: Some(9)
+ },
+ ]
+ );
+ }
+}
diff --git a/support/texlab/src/syntax/generic_ast.rs b/support/texlab/src/syntax/generic_ast.rs
deleted file mode 100644
index 3311460c0a..0000000000
--- a/support/texlab/src/syntax/generic_ast.rs
+++ /dev/null
@@ -1,51 +0,0 @@
-use serde::{Deserialize, Serialize};
-use std::ops::Index;
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
-pub struct AstNodeIndex(usize);
-
-#[derive(Debug, PartialEq, Eq, Clone, Default)]
-pub struct Ast<T> {
- nodes: Vec<T>,
- edges: Vec<Vec<AstNodeIndex>>,
-}
-
-impl<T> Ast<T> {
- pub fn new() -> Self {
- Self {
- nodes: Vec::new(),
- edges: Vec::new(),
- }
- }
-
- pub fn nodes(&self) -> Vec<AstNodeIndex> {
- let mut nodes = Vec::new();
- for i in 0..self.nodes.len() {
- nodes.push(AstNodeIndex(i));
- }
- nodes
- }
-
- pub fn add_node(&mut self, value: T) -> AstNodeIndex {
- let node = AstNodeIndex(self.nodes.len());
- self.nodes.push(value);
- self.edges.push(Vec::new());
- node
- }
-
- pub fn add_edge(&mut self, parent: AstNodeIndex, child: AstNodeIndex) {
- self.edges[parent.0].push(child);
- }
-
- pub fn children<'a>(&'a self, parent: AstNodeIndex) -> impl Iterator<Item = AstNodeIndex> + 'a {
- self.edges[parent.0].iter().map(|child| *child)
- }
-}
-
-impl<T> Index<AstNodeIndex> for Ast<T> {
- type Output = T;
-
- fn index(&self, index: AstNodeIndex) -> &Self::Output {
- &self.nodes[index.0]
- }
-}
diff --git a/support/texlab/src/syntax/lang_data.rs b/support/texlab/src/syntax/lang_data.rs
deleted file mode 100644
index 4ea7cc44da..0000000000
--- a/support/texlab/src/syntax/lang_data.rs
+++ /dev/null
@@ -1,231 +0,0 @@
-use once_cell::sync::Lazy;
-use serde::{Deserialize, Serialize};
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct LatexEnvironmentCommand {
- pub name: String,
- pub index: usize,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct LatexCitationCommand {
- pub name: String,
- pub index: usize,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub enum LatexLabelReferenceSource {
- Everything,
- Math,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub enum LatexLabelKind {
- Definition,
- Reference(LatexLabelReferenceSource),
-}
-
-impl LatexLabelKind {
- pub fn is_reference(self) -> bool {
- match self {
- LatexLabelKind::Definition => false,
- LatexLabelKind::Reference(_) => true,
- }
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct LatexLabelCommand {
- pub name: String,
- pub index: usize,
- pub kind: LatexLabelKind,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct LatexSectionCommand {
- pub name: String,
- pub index: usize,
- pub level: i32,
- pub prefix: String,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub enum LatexIncludeKind {
- Package,
- Class,
- Latex,
- Bibliography,
- Image,
- Svg,
- Pdf,
- Everything,
-}
-
-impl LatexIncludeKind {
- pub fn extensions(self) -> Option<&'static [&'static str]> {
- match self {
- LatexIncludeKind::Package => Some(&["sty"]),
- LatexIncludeKind::Class => Some(&["cls"]),
- LatexIncludeKind::Latex => Some(&["tex"]),
- LatexIncludeKind::Bibliography => Some(&["bib"]),
- LatexIncludeKind::Image => Some(&["pdf", "png", "jpg", "jpeg", "bmp"]),
- LatexIncludeKind::Svg => Some(&["svg"]),
- LatexIncludeKind::Pdf => Some(&["pdf"]),
- LatexIncludeKind::Everything => None,
- }
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct LatexIncludeCommand {
- pub name: String,
- pub index: usize,
- pub kind: LatexIncludeKind,
- pub include_extension: bool,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct LatexCommandDefinitionCommand {
- pub name: String,
- pub definition_index: usize,
- pub arg_count_index: usize,
- pub implementation_index: usize,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct LatexMathOperatorCommand {
- pub name: String,
- pub definition_index: usize,
- pub implementation_index: usize,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct LatexTheoremDefinitionCommand {
- pub name: String,
- pub index: usize,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct LatexColorCommand {
- pub name: String,
- pub index: usize,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct LatexColorModelCommand {
- pub name: String,
- pub index: usize,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub enum LatexGlossaryEntryKind {
- General,
- Acronym,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct LatexGlossaryEntryDefinitionCommand {
- pub name: String,
- pub label_index: usize,
- pub kind: LatexGlossaryEntryKind,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct LatexGlossaryEntryReferenceCommand {
- pub name: String,
- pub index: usize,
- pub kind: LatexGlossaryEntryKind,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub enum BibtexEntryTypeCategory {
- Misc,
- String,
- Article,
- Book,
- Collection,
- Part,
- Thesis,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct BibtexEntryTypeDoc {
- pub name: String,
- pub category: BibtexEntryTypeCategory,
- pub documentation: Option<String>,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct BibtexFieldDoc {
- pub name: String,
- pub documentation: String,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct LanguageData {
- pub environment_commands: Vec<LatexEnvironmentCommand>,
- pub citation_commands: Vec<LatexCitationCommand>,
- pub label_commands: Vec<LatexLabelCommand>,
- pub section_commands: Vec<LatexSectionCommand>,
- pub include_commands: Vec<LatexIncludeCommand>,
- pub command_definition_commands: Vec<LatexCommandDefinitionCommand>,
- pub math_operator_commands: Vec<LatexMathOperatorCommand>,
- pub theorem_definition_commands: Vec<LatexTheoremDefinitionCommand>,
- pub colors: Vec<String>,
- pub color_commands: Vec<LatexColorCommand>,
- pub color_model_commands: Vec<LatexColorModelCommand>,
- pub glossary_entry_definition_commands: Vec<LatexGlossaryEntryDefinitionCommand>,
- pub glossary_entry_reference_commands: Vec<LatexGlossaryEntryReferenceCommand>,
- pub entry_types: Vec<BibtexEntryTypeDoc>,
- pub fields: Vec<BibtexFieldDoc>,
- pub pgf_libraries: Vec<String>,
- pub tikz_libraries: Vec<String>,
- pub math_environments: Vec<String>,
- pub enum_environments: Vec<String>,
-}
-
-impl LanguageData {
- pub fn find_entry_type(&self, name: &str) -> Option<&BibtexEntryTypeDoc> {
- let name = name.to_lowercase();
- self.entry_types
- .iter()
- .find(|ty| ty.name.to_lowercase() == name)
- }
-
- pub fn entry_type_documentation(&self, name: &str) -> Option<&str> {
- self.find_entry_type(name)
- .and_then(|ty| ty.documentation.as_ref().map(AsRef::as_ref))
- }
-
- pub fn field_documentation(&self, name: &str) -> Option<&str> {
- self.fields
- .iter()
- .find(|field| field.name.to_lowercase() == name.to_lowercase())
- .map(|field| field.documentation.as_ref())
- }
-}
-
-pub static LANGUAGE_DATA: Lazy<LanguageData> = Lazy::new(|| {
- const JSON: &str = include_str!("../../data/lang_data.json");
- serde_json::from_str(JSON).expect("Failed to deserialize language.json")
-});
diff --git a/support/texlab/src/syntax/latex.rs b/support/texlab/src/syntax/latex.rs
new file mode 100644
index 0000000000..7839c1795b
--- /dev/null
+++ b/support/texlab/src/syntax/latex.rs
@@ -0,0 +1,36 @@
+mod analysis;
+mod cst;
+mod kind;
+mod lexer;
+mod parser;
+
+pub use self::{
+ analysis::*,
+ cst::*,
+ kind::SyntaxKind::{self, *},
+ parser::{parse, Parse},
+};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Language {}
+
+impl cstree::Language for Language {
+ type Kind = SyntaxKind;
+
+ fn kind_from_raw(raw: cstree::SyntaxKind) -> Self::Kind {
+ assert!(raw.0 <= ROOT as u16);
+ unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
+ }
+
+ fn kind_to_raw(kind: Self::Kind) -> cstree::SyntaxKind {
+ kind.into()
+ }
+}
+
+pub type SyntaxNode = cstree::ResolvedNode<Language>;
+
+pub type SyntaxToken = cstree::ResolvedToken<Language>;
+
+pub type SyntaxElement = cstree::ResolvedElement<Language>;
+
+pub type SyntaxElementRef<'a> = cstree::ResolvedElementRef<'a, Language>;
diff --git a/support/texlab/src/syntax/latex/analysis.rs b/support/texlab/src/syntax/latex/analysis.rs
index af8b756f31..063d8e95f3 100644
--- a/support/texlab/src/syntax/latex/analysis.rs
+++ b/support/texlab/src/syntax/latex/analysis.rs
@@ -1,913 +1,36 @@
-use crate::{
- protocol::{Options, Position, Range, RangeExt, Uri},
- syntax::{generic_ast::AstNodeIndex, lang_data::*, latex::ast::*, text::SyntaxNode},
- tex::Resolver,
+mod command;
+mod distro_file;
+mod environment;
+mod explicit_link;
+mod implicit_link;
+mod label_name;
+mod label_number;
+mod theorem;
+mod types;
+
+use crate::syntax::latex;
+
+pub use self::types::*;
+use self::{
+ command::analyze_command,
+ environment::analyze_begin,
+ explicit_link::{analyze_import, analyze_include},
+ implicit_link::analyze_implicit_links,
+ label_name::analyze_label_name,
+ label_number::analyze_label_number,
+ theorem::analyze_theorem_definition,
};
-use itertools::{iproduct, Itertools};
-use serde::{Deserialize, Serialize};
-use std::{borrow::Cow, ops::Deref, path::Path};
-#[derive(Debug, Clone)]
-pub struct SymbolTableParams<'a> {
- pub tree: Tree,
- pub uri: &'a Uri,
- pub resolver: &'a Resolver,
- pub options: &'a Options,
- pub current_dir: &'a Path,
-}
-
-#[derive(Debug, Clone)]
-pub struct SymbolTable {
- pub(crate) tree: Tree,
- pub commands: Vec<AstNodeIndex>,
- pub environments: Vec<Environment>,
- pub is_standalone: bool,
- pub includes: Vec<Include>,
- pub imports: Vec<Import>,
- pub components: Vec<String>,
- pub citations: Vec<Citation>,
- pub command_definitions: Vec<CommandDefinition>,
- pub glossary_entries: Vec<GlossaryEntry>,
- pub equations: Vec<Equation>,
- pub inlines: Vec<Inline>,
- pub math_operators: Vec<MathOperator>,
- pub theorem_definitions: Vec<TheoremDefinition>,
- pub sections: Vec<Section>,
- pub labels: Vec<Label>,
- pub label_numberings: Vec<LabelNumbering>,
- pub captions: Vec<Caption>,
- pub items: Vec<Item>,
-}
-
-impl SymbolTable {
- pub fn analyze(params: SymbolTableParams) -> Self {
- let SymbolTableParams {
- tree,
- uri,
- resolver,
- options,
- current_dir,
- } = params;
-
- let commands: Vec<_> = tree.commands().collect();
- let ctx = SymbolContext {
- tree: &tree,
- commands: &commands,
- uri,
- resolver,
- options,
- current_dir,
- };
-
- let mut environments = None;
- let mut includes = None;
- let mut imports = None;
- let mut citations = None;
- let mut command_definitions = None;
- let mut glossary_entries = None;
- let mut equations = None;
- let mut inlines = None;
- let mut math_operators = None;
- let mut theorem_definitions = None;
- let mut sections = None;
- let mut labels = None;
- let mut label_numberings = None;
- let mut captions = None;
- let mut items = None;
-
- rayon::scope(|s| {
- s.spawn(|_| environments = Some(Environment::parse(ctx)));
- s.spawn(|_| includes = Some(Include::parse(ctx)));
- s.spawn(|_| imports = Some(Import::parse(ctx)));
- s.spawn(|_| citations = Some(Citation::parse(ctx)));
- s.spawn(|_| command_definitions = Some(CommandDefinition::parse(ctx)));
- s.spawn(|_| glossary_entries = Some(GlossaryEntry::parse(ctx)));
- s.spawn(|_| equations = Some(Equation::parse(ctx)));
- s.spawn(|_| inlines = Some(Inline::parse(ctx)));
- s.spawn(|_| math_operators = Some(MathOperator::parse(ctx)));
- s.spawn(|_| theorem_definitions = Some(TheoremDefinition::parse(ctx)));
- s.spawn(|_| sections = Some(Section::parse(ctx)));
- s.spawn(|_| labels = Some(Label::parse(ctx)));
- s.spawn(|_| label_numberings = Some(LabelNumbering::parse(ctx)));
- s.spawn(|_| captions = Some(Caption::parse(ctx)));
- s.spawn(|_| items = Some(Item::parse(ctx)));
- });
-
- let is_standalone = environments
- .as_ref()
- .unwrap()
- .iter()
- .any(|env| env.is_root(&tree));
-
- let components = includes
- .as_ref()
- .unwrap()
- .iter()
- .flat_map(|include| include.components(&tree))
- .collect();
-
- Self {
- tree,
- commands,
- environments: environments.unwrap(),
- is_standalone,
- includes: includes.unwrap(),
- imports: imports.unwrap(),
- components,
- citations: citations.unwrap(),
- command_definitions: command_definitions.unwrap(),
- glossary_entries: glossary_entries.unwrap(),
- equations: equations.unwrap(),
- inlines: inlines.unwrap(),
- math_operators: math_operators.unwrap(),
- theorem_definitions: theorem_definitions.unwrap(),
- sections: sections.unwrap(),
- labels: labels.unwrap(),
- label_numberings: label_numberings.unwrap(),
- captions: captions.unwrap(),
- items: items.unwrap(),
- }
- }
-
- pub fn is_direct_child(&self, env: Environment, pos: Position) -> bool {
- env.range(&self.tree).contains(pos)
- && !self
- .environments
- .iter()
- .filter(|e| e.left.parent != env.left.parent)
- .filter(|e| env.range(&self.tree).contains(e.range(&self.tree).start))
- .any(|e| e.range(&self.tree).contains(pos))
- }
-
- pub fn is_enum_item(&self, enumeration: Environment, item: Item) -> bool {
- let item_range = self.tree[item.parent].range();
- enumeration.range(&self.tree).contains(item_range.start)
- && !self
- .environments
- .iter()
- .filter(|env| env.left.parent != enumeration.left.parent)
- .filter(|env| env.left.is_enum(&self.tree))
- .filter(|env| {
- enumeration
- .range(&self.tree)
- .contains(env.range(&self.tree).start)
- })
- .any(|env| env.range(&self.tree).contains(item_range.start))
- }
-
- pub fn find_label_by_range(&self, range: Range) -> Option<&Label> {
- self.labels
- .iter()
- .filter(|label| label.kind == LatexLabelKind::Definition)
- .filter(|label| label.names(&self).len() == 1)
- .find(|label| range.contains(self[label.parent].range().start))
- }
-
- pub fn find_label_by_environment(&self, env: Environment) -> Option<&Label> {
- self.labels
- .iter()
- .filter(|label| label.kind == LatexLabelKind::Definition)
- .filter(|label| label.names(&self.tree).len() == 1)
- .find(|label| self.is_direct_child(env, self.tree[label.parent].start()))
- }
-}
-
-impl Deref for SymbolTable {
- type Target = Tree;
-
- fn deref(&self) -> &Self::Target {
- &self.tree
- }
-}
-
-#[derive(Debug, Clone, Copy)]
-pub struct SymbolContext<'a> {
- tree: &'a Tree,
- commands: &'a [AstNodeIndex],
- uri: &'a Uri,
- resolver: &'a Resolver,
- options: &'a Options,
- current_dir: &'a Path,
-}
-
-#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
-pub struct EnvironmentDelimiter {
- pub parent: AstNodeIndex,
-}
-
-impl EnvironmentDelimiter {
- pub fn name(self, tree: &Tree) -> Option<&Token> {
- tree.extract_word(self.parent, GroupKind::Group, 0)
- }
-
- pub fn is_math(self, tree: &Tree) -> bool {
- self.is_special(tree, LANGUAGE_DATA.math_environments.iter())
- }
-
- pub fn is_enum(self, tree: &Tree) -> bool {
- self.is_special(tree, LANGUAGE_DATA.enum_environments.iter())
- }
-
- fn is_special<'a, I: Iterator<Item = &'a String>>(self, tree: &Tree, mut values: I) -> bool {
- match self.name(tree) {
- Some(name) => values.any(|env| env == name.text()),
- None => false,
- }
- }
-}
-
-#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
-pub struct Environment {
- pub left: EnvironmentDelimiter,
- pub right: EnvironmentDelimiter,
-}
-
-impl Environment {
- pub fn is_root(self, tree: &Tree) -> bool {
- self.left
- .name(tree)
- .iter()
- .chain(self.right.name(tree).iter())
- .any(|name| name.text() == "document")
- }
-
- pub fn range(self, tree: &Tree) -> Range {
- let start = tree[self.left.parent].start();
- let end = tree[self.right.parent].end();
- Range::new(start, end)
- }
-
- fn parse(ctx: SymbolContext) -> Vec<Self> {
- let mut stack = Vec::new();
- let mut envs = Vec::new();
- for parent in ctx.commands {
- if let Some((delim, delim_cmd)) = Self::parse_delimiter(ctx.tree, *parent) {
- if delim_cmd.name.text() == "\\begin" {
- stack.push(delim);
- } else if let Some(left) = stack.pop() {
- envs.push(Self { left, right: delim });
- }
- }
- }
- envs
- }
-
- fn parse_delimiter(
- tree: &Tree,
- parent: AstNodeIndex,
- ) -> Option<(EnvironmentDelimiter, &Command)> {
- let cmd = tree.as_command(parent)?;
- if cmd.name.text() != "\\begin" && cmd.name.text() != "\\end" {
- return None;
- }
-
- let group = tree.extract_group(parent, GroupKind::Group, 0)?;
- if tree.extract_word(parent, GroupKind::Group, 0).is_some()
- || tree.children(group).next().is_none()
- || tree.as_group(group)?.right.is_none()
- {
- Some((EnvironmentDelimiter { parent }, cmd))
- } else {
- None
- }
- }
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct Include {
- pub parent: AstNodeIndex,
- pub arg_index: usize,
- pub kind: LatexIncludeKind,
- pub all_targets: Vec<Vec<Uri>>,
- pub include_extension: bool,
-}
-
-impl Include {
- pub fn paths<'a>(&self, tree: &'a Tree) -> Vec<&'a Token> {
- tree.extract_comma_separated_words(self.parent, GroupKind::Group, self.arg_index)
- .unwrap()
- }
-
- pub fn components<'a>(&self, tree: &'a Tree) -> impl Iterator<Item = String> + 'a {
- let kind = self.kind;
- self.paths(tree)
- .into_iter()
- .filter_map(move |path| match kind {
- LatexIncludeKind::Package => Some(format!("{}.sty", path.text())),
- LatexIncludeKind::Class => Some(format!("{}.cls", path.text())),
- LatexIncludeKind::Latex
- | LatexIncludeKind::Bibliography
- | LatexIncludeKind::Image
- | LatexIncludeKind::Svg
- | LatexIncludeKind::Pdf
- | LatexIncludeKind::Everything => None,
- })
- }
-
- fn parse(ctx: SymbolContext) -> Vec<Self> {
- iproduct!(ctx.commands, LANGUAGE_DATA.include_commands.iter())
- .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc))
- .collect()
- }
-
- fn parse_single(
- ctx: SymbolContext,
- parent: AstNodeIndex,
- desc: &LatexIncludeCommand,
- ) -> Option<Self> {
- let cmd = ctx.tree.as_command(parent)?;
- if cmd.name.text() != desc.name {
- return None;
- }
-
- let mut all_targets = Vec::new();
- let paths = ctx
- .tree
- .extract_comma_separated_words(parent, GroupKind::Group, desc.index)?;
- for path in paths {
- let mut targets = Vec::new();
- let base_url = base_url(ctx)?;
- targets.push(base_url.join(path.text()).ok()?.into());
-
- if let Some(extensions) = desc.kind.extensions() {
- for extension in extensions {
- let path = format!("{}.{}", path.text(), extension);
- targets.push(base_url.join(&path).ok()?.into());
- }
- }
-
- if let Some(target) = Self::resolve_distro_file(ctx, desc, path.text()) {
- targets.push(target);
- }
- all_targets.push(targets);
- }
-
- let include = Self {
- parent,
- arg_index: desc.index,
- kind: desc.kind,
- all_targets,
- include_extension: desc.include_extension,
- };
- Some(include)
- }
-
- fn resolve_distro_file(
- ctx: SymbolContext,
- desc: &LatexIncludeCommand,
- name: &str,
- ) -> Option<Uri> {
- let mut path = ctx.resolver.files_by_name.get(name);
- if let Some(extensions) = desc.kind.extensions() {
- for extension in extensions {
- path = path.or_else(|| {
- let full_name = format!("{}.{}", name, extension);
- ctx.resolver.files_by_name.get(&full_name)
- });
- }
- }
- path.and_then(|p| Uri::from_file_path(p).ok())
- }
-}
-
-fn base_url(ctx: SymbolContext) -> Option<Uri> {
- if let Some(root_directory) = ctx
- .options
- .latex
- .as_ref()
- .and_then(|opts| opts.root_directory.as_ref())
- {
- let file_name = ctx.uri.path_segments()?.last()?;
- let path = ctx.current_dir.join(root_directory).join(file_name);
- Uri::from_file_path(path).ok()
- } else {
- Some(ctx.uri.clone())
- }
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct Import {
- pub parent: AstNodeIndex,
- pub targets: Vec<Uri>,
-}
-
-impl Import {
- pub fn dir<'a>(&self, tree: &'a Tree) -> &'a Token {
- tree.extract_word(self.parent, GroupKind::Group, 0).unwrap()
- }
-
- pub fn file<'a>(&self, tree: &'a Tree) -> &'a Token {
- tree.extract_word(self.parent, GroupKind::Group, 1).unwrap()
- }
-
- fn parse(ctx: SymbolContext) -> Vec<Self> {
- ctx.commands
- .iter()
- .filter_map(|parent| Self::parse_single(ctx, *parent))
- .collect()
- }
-
- fn parse_single(ctx: SymbolContext, parent: AstNodeIndex) -> Option<Self> {
- let cmd = ctx.tree.as_command(parent)?;
- if cmd.name.text() != "\\import" && cmd.name.text() != "\\subimport" {
- return None;
- }
-
- let dir = ctx.tree.extract_word(parent, GroupKind::Group, 0)?;
- let file = ctx.tree.extract_word(parent, GroupKind::Group, 1)?;
-
- let mut targets = Vec::new();
- let base_url = base_url(ctx)?.join(dir.text()).ok()?;
- targets.push(base_url.join(file.text()).ok()?.into());
- targets.push(base_url.join(&format!("{}.tex", file.text())).ok()?.into());
- Some(Self { parent, targets })
- }
-}
-
-#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
-pub struct Citation {
- parent: AstNodeIndex,
- arg_index: usize,
-}
-
-impl Citation {
- pub fn keys(self, tree: &Tree) -> Vec<&Token> {
- tree.extract_comma_separated_words(self.parent, GroupKind::Group, self.arg_index)
- .unwrap()
- }
-
- fn parse(ctx: SymbolContext) -> Vec<Self> {
- iproduct!(ctx.commands, LANGUAGE_DATA.citation_commands.iter())
- .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc))
- .collect()
- }
-
- fn parse_single(
- ctx: SymbolContext,
- parent: AstNodeIndex,
- desc: &LatexCitationCommand,
- ) -> Option<Self> {
- let cmd = ctx.tree.as_command(parent)?;
- if cmd.name.text() != desc.name {
- return None;
- }
-
- ctx.tree
- .extract_comma_separated_words(parent, GroupKind::Group, desc.index)?;
-
- Some(Self {
- parent,
- arg_index: desc.index,
- })
- }
-}
-
-#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
-pub struct CommandDefinition {
- pub parent: AstNodeIndex,
- pub definition: AstNodeIndex,
- pub definition_index: usize,
- pub implementation: AstNodeIndex,
- pub implementation_index: usize,
- pub arg_count_index: usize,
-}
-
-impl CommandDefinition {
- pub fn definition_name(self, tree: &Tree) -> &str {
- tree.as_command(self.definition).unwrap().name.text()
- }
-
- fn parse(ctx: SymbolContext) -> Vec<Self> {
- let def = LANGUAGE_DATA.command_definition_commands.iter();
- iproduct!(ctx.commands, def)
- .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc))
- .collect()
- }
-
- fn parse_single(
- ctx: SymbolContext,
- parent: AstNodeIndex,
- desc: &LatexCommandDefinitionCommand,
- ) -> Option<Self> {
- let cmd = ctx.tree.as_command(parent)?;
- if cmd.name.text() != desc.name {
- return None;
- }
-
- let group_kind = GroupKind::Group;
- let implementation =
- ctx.tree
- .extract_group(parent, group_kind, desc.implementation_index)?;
-
- let def_group = ctx
- .tree
- .extract_group(parent, group_kind, desc.definition_index)?;
-
- let mut def_children = ctx.tree.children(def_group);
- let definition = def_children.next()?;
- ctx.tree.as_command(definition)?;
- Some(Self {
- parent,
- definition,
- definition_index: desc.definition_index,
- implementation,
- implementation_index: desc.implementation_index,
- arg_count_index: desc.arg_count_index,
- })
- }
-}
-
-#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
-pub struct GlossaryEntry {
- pub parent: AstNodeIndex,
- pub label_index: usize,
- pub kind: LatexGlossaryEntryKind,
-}
-
-impl GlossaryEntry {
- pub fn label(self, tree: &Tree) -> &Token {
- tree.extract_word(self.parent, GroupKind::Group, self.label_index)
- .unwrap()
- }
-
- fn parse(ctx: SymbolContext) -> Vec<Self> {
- let entry = LANGUAGE_DATA.glossary_entry_definition_commands.iter();
- iproduct!(ctx.commands, entry)
- .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc))
- .collect()
- }
-
- fn parse_single(
- ctx: SymbolContext,
- parent: AstNodeIndex,
- desc: &LatexGlossaryEntryDefinitionCommand,
- ) -> Option<Self> {
- let cmd = ctx.tree.as_command(parent)?;
- if cmd.name.text() != desc.name {
- return None;
- }
-
- ctx.tree
- .extract_word(parent, GroupKind::Group, desc.label_index)?;
-
- Some(Self {
- parent,
- label_index: desc.label_index,
- kind: desc.kind,
- })
- }
-}
-
-#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
-pub struct Equation {
- pub left: AstNodeIndex,
- pub right: AstNodeIndex,
-}
-
-impl Equation {
- pub fn range(self, tree: &Tree) -> Range {
- let start = tree[self.left].start();
- let end = tree[self.right].end();
- Range::new(start, end)
- }
-
- fn parse(ctx: SymbolContext) -> Vec<Self> {
- let mut equations = Vec::new();
- let mut left = None;
- for node in ctx.commands {
- let cmd = ctx.tree.as_command(*node).unwrap();
- let name = cmd.name.text();
- if name == "\\[" || name == "\\(" {
- left = Some(node);
- } else if name == "\\]" || name == "\\)" {
- if let Some(begin) = left {
- equations.push(Self {
- left: *begin,
- right: *node,
- });
- left = None;
- }
- }
- }
- equations
- }
-}
-
-#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
-pub struct Inline {
- pub left: AstNodeIndex,
- pub right: AstNodeIndex,
-}
-
-impl Inline {
- pub fn range(self, tree: &Tree) -> Range {
- let start = tree[self.left].start();
- let end = tree[self.right].end();
- Range::new(start, end)
- }
-
- fn parse(ctx: SymbolContext) -> Vec<Self> {
- let mut inlines = Vec::new();
- let mut left = None;
- for node in ctx
- .tree
- .nodes()
- .into_iter()
- .filter(|node| ctx.tree.as_math(*node).is_some())
- .sorted_by_key(|node| ctx.tree[*node].start())
- {
- if let Some(l) = left {
- inlines.push(Inline {
- left: l,
- right: node,
- });
- left = None;
- } else {
- left = Some(node);
- }
- }
- inlines
- }
-}
-
-#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
-pub struct MathOperator {
- pub parent: AstNodeIndex,
- pub definition: AstNodeIndex,
- pub definition_index: usize,
- pub implementation: AstNodeIndex,
- pub implementation_index: usize,
-}
-
-impl MathOperator {
- pub fn definition_name(self, tree: &Tree) -> &str {
- tree.as_command(self.definition).unwrap().name.text()
- }
-
- fn parse(ctx: SymbolContext) -> Vec<Self> {
- iproduct!(ctx.commands, LANGUAGE_DATA.math_operator_commands.iter())
- .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc))
- .collect()
- }
-
- fn parse_single(
- ctx: SymbolContext,
- parent: AstNodeIndex,
- desc: &LatexMathOperatorCommand,
- ) -> Option<Self> {
- let cmd = ctx.tree.as_command(parent)?;
- if cmd.name.text() != desc.name {
- return None;
- }
-
- let group_kind = GroupKind::Group;
- let def_group = ctx
- .tree
- .extract_group(parent, group_kind, desc.definition_index)?;
- let implementation =
- ctx.tree
- .extract_group(parent, group_kind, desc.implementation_index)?;
-
- let mut def_children = ctx.tree.children(def_group);
- let definition = def_children.next()?;
- Some(Self {
- parent,
- definition,
- definition_index: desc.definition_index,
- implementation,
- implementation_index: desc.implementation_index,
- })
- }
-}
-
-#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
-pub struct TheoremDefinition {
- pub parent: AstNodeIndex,
- pub arg_index: usize,
-}
-
-impl TheoremDefinition {
- pub fn name(self, tree: &Tree) -> &Token {
- tree.extract_word(self.parent, GroupKind::Group, self.arg_index)
- .unwrap()
- }
-
- fn parse(ctx: SymbolContext) -> Vec<Self> {
- let thm = LANGUAGE_DATA.theorem_definition_commands.iter();
- iproduct!(ctx.commands, thm)
- .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc))
- .collect()
- }
-
- fn parse_single(
- ctx: SymbolContext,
- parent: AstNodeIndex,
- desc: &LatexTheoremDefinitionCommand,
- ) -> Option<Self> {
- let cmd = ctx.tree.as_command(parent)?;
- if cmd.name.text() != desc.name {
- return None;
- }
-
- let group_kind = GroupKind::Group;
- ctx.tree.extract_word(parent, group_kind, desc.index)?;
-
- Some(Self {
- parent,
- arg_index: desc.index,
- })
- }
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct Section {
- pub parent: AstNodeIndex,
- pub arg_index: usize,
- pub level: i32,
- pub prefix: Cow<'static, str>,
-}
-
-impl Section {
- pub fn print(&self, tree: &Tree) -> Option<String> {
- tree.print_group_content(self.parent, GroupKind::Group, self.arg_index)
- }
-
- fn parse(ctx: SymbolContext) -> Vec<Self> {
- iproduct!(ctx.commands, LANGUAGE_DATA.section_commands.iter())
- .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc))
- .collect()
- }
-
- fn parse_single(
- ctx: SymbolContext,
- parent: AstNodeIndex,
- desc: &'static LatexSectionCommand,
- ) -> Option<Self> {
- let cmd = ctx.tree.as_command(parent)?;
- if cmd.name.text() != desc.name {
- return None;
- }
-
- let group_kind = GroupKind::Group;
- ctx.tree.extract_group(parent, group_kind, desc.index)?;
-
- Some(Self {
- parent,
- arg_index: desc.index,
- level: desc.level,
- prefix: Cow::from(&desc.prefix),
- })
- }
-}
-
-#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
-pub struct Label {
- pub parent: AstNodeIndex,
- pub arg_index: usize,
- pub kind: LatexLabelKind,
-}
-
-impl Label {
- pub fn names(self, tree: &Tree) -> Vec<&Token> {
- tree.extract_comma_separated_words(self.parent, GroupKind::Group, self.arg_index)
- .unwrap()
- }
-
- fn parse(ctx: SymbolContext) -> Vec<Self> {
- iproduct!(ctx.commands, LANGUAGE_DATA.label_commands.iter())
- .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc))
- .collect()
- }
-
- fn parse_single(
- ctx: SymbolContext,
- parent: AstNodeIndex,
- desc: &LatexLabelCommand,
- ) -> Option<Self> {
- let cmd = ctx.tree.as_command(parent)?;
- if cmd.name.text() != desc.name {
- return None;
- }
-
- ctx.tree
- .extract_comma_separated_words(parent, GroupKind::Group, desc.index)?;
-
- Some(Self {
- parent,
- arg_index: desc.index,
- kind: desc.kind,
- })
- }
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct LabelNumbering {
- pub parent: AstNodeIndex,
- pub number: String,
-}
-
-impl LabelNumbering {
- pub fn name<'a>(&self, tree: &'a Tree) -> &'a Token {
- tree.extract_word(self.parent, GroupKind::Group, 0).unwrap()
- }
-
- fn parse(ctx: SymbolContext) -> Vec<Self> {
- ctx.commands
- .iter()
- .filter_map(|parent| Self::parse_single(ctx, *parent))
- .collect()
- }
-
- fn parse_single(ctx: SymbolContext, parent: AstNodeIndex) -> Option<Self> {
- let cmd = ctx.tree.as_command(parent)?;
- if cmd.name.text() != "\\newlabel" {
- return None;
- }
-
- ctx.tree.extract_word(parent, GroupKind::Group, 0)?;
-
- let arg = ctx.tree.extract_group(parent, GroupKind::Group, 1)?;
- let mut analyzer = FirstText::default();
- analyzer.visit(ctx.tree, arg);
- Some(Self {
- parent,
- number: analyzer.text?,
- })
- }
-}
-
-#[derive(Debug, Default)]
-struct FirstText {
- text: Option<String>,
-}
-
-impl Visitor for FirstText {
- fn visit(&mut self, tree: &Tree, node: AstNodeIndex) {
- if let Some(text) = tree.as_text(node) {
- self.text = Some(text.words.iter().map(Token::text).join(" "));
- }
-
- if self.text.is_none() {
- tree.walk(self, node);
- }
- }
-}
-
-#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
-pub struct Caption {
- pub parent: AstNodeIndex,
- pub arg_index: usize,
-}
-
-impl Caption {
- pub fn print(self, tree: &Tree) -> Option<String> {
- tree.print_group_content(self.parent, GroupKind::Group, self.arg_index)
- }
-
- fn parse(ctx: SymbolContext) -> Vec<Self> {
- ctx.commands
- .iter()
- .flat_map(|parent| Self::parse_single(ctx, *parent))
- .collect()
- }
-
- fn parse_single(ctx: SymbolContext, parent: AstNodeIndex) -> Option<Self> {
- let cmd = ctx.tree.as_command(parent)?;
- if cmd.name.text() != "\\caption" {
- return None;
- }
-
- ctx.tree.extract_group(parent, GroupKind::Group, 0)?;
- Some(Self {
- parent,
- arg_index: 0,
- })
- }
-}
-
-#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
-pub struct Item {
- pub parent: AstNodeIndex,
-}
-
-impl Item {
- pub fn name(self, tree: &Tree) -> Option<String> {
- tree.print_group_content(self.parent, GroupKind::Options, 0)
- }
-
- fn parse(ctx: SymbolContext) -> Vec<Self> {
- ctx.commands
- .iter()
- .filter_map(|parent| Self::parse_single(ctx, *parent))
- .collect()
- }
-
- fn parse_single(ctx: SymbolContext, parent: AstNodeIndex) -> Option<Self> {
- let cmd = ctx.tree.as_command(parent)?;
- if cmd.name.text() != "\\item" {
- return None;
- }
-
- Some(Self { parent })
- }
+pub fn analyze(context: &mut LatexAnalyzerContext, root: &latex::SyntaxNode) {
+ analyze_implicit_links(context);
+ for node in root.descendants() {
+ analyze_command(context, node)
+ .or_else(|| analyze_begin(context, node))
+ .or_else(|| analyze_include(context, node))
+ .or_else(|| analyze_import(context, node))
+ .or_else(|| analyze_label_name(context, node))
+ .or_else(|| analyze_label_number(context, node))
+ .or_else(|| analyze_theorem_definition(context, node));
+ }
+ context.extras.has_document_environment = context.extras.environment_names.contains("document");
}
diff --git a/support/texlab/src/syntax/latex/analysis/command.rs b/support/texlab/src/syntax/latex/analysis/command.rs
new file mode 100644
index 0000000000..9dea4d84ee
--- /dev/null
+++ b/support/texlab/src/syntax/latex/analysis/command.rs
@@ -0,0 +1,12 @@
+use crate::syntax::{latex, CstNode};
+
+use super::LatexAnalyzerContext;
+
+pub fn analyze_command(context: &mut LatexAnalyzerContext, node: &latex::SyntaxNode) -> Option<()> {
+ let command = latex::GenericCommand::cast(node)?;
+ context
+ .extras
+ .command_names
+ .insert(command.name()?.text().into());
+ Some(())
+}
diff --git a/support/texlab/src/syntax/latex/analysis/distro_file.rs b/support/texlab/src/syntax/latex/analysis/distro_file.rs
new file mode 100644
index 0000000000..4c747ce760
--- /dev/null
+++ b/support/texlab/src/syntax/latex/analysis/distro_file.rs
@@ -0,0 +1,71 @@
+use crate::{distro::Resolver, Uri};
+
+pub fn resolve_distro_file(resolver: &Resolver, stem: &str, extensions: &[&str]) -> Option<Uri> {
+ let mut document = resolver.files_by_name.get(stem);
+ for extension in extensions {
+ document = document.or_else(|| {
+ let full_name = format!("{}.{}", stem, extension);
+ resolver.files_by_name.get(full_name.as_str())
+ });
+ }
+ document.and_then(|path| Uri::from_file_path(path).ok())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ #[cfg(windows)]
+ fn test() {
+ let mut resolver = Resolver::default();
+ resolver
+ .files_by_name
+ .insert("foo.tex".into(), "C:/distro/foo.tex".into());
+ resolver
+ .files_by_name
+ .insert("foo.sty".into(), "C:/distro/foo.sty".into());
+ resolver
+ .files_by_name
+ .insert("bar.tex".into(), "C:/distro/bar.tex".into());
+
+ assert_eq!(
+ resolve_distro_file(&resolver, "foo", &["tex"]),
+ Some(Uri::from_file_path("C:/distro/foo.tex").unwrap())
+ );
+
+ assert_eq!(
+ resolve_distro_file(&resolver, "foo", &["sty"]),
+ Some(Uri::from_file_path("C:/distro/foo.sty").unwrap())
+ );
+
+ assert_eq!(resolve_distro_file(&resolver, "foo", &["cls"]), None);
+ }
+
+ #[test]
+ #[cfg(unix)]
+ fn test() {
+ let mut resolver = Resolver::default();
+ resolver
+ .files_by_name
+ .insert("foo.tex".into(), "/distro/foo.tex".into());
+ resolver
+ .files_by_name
+ .insert("foo.sty".into(), "/distro/foo.sty".into());
+ resolver
+ .files_by_name
+ .insert("bar.tex".into(), "/distro/bar.tex".into());
+
+ assert_eq!(
+ resolve_distro_file(&resolver, "foo", &["tex"]),
+ Some(Uri::from_file_path("/distro/foo.tex").unwrap())
+ );
+
+ assert_eq!(
+ resolve_distro_file(&resolver, "foo", &["sty"]),
+ Some(Uri::from_file_path("/distro/foo.sty").unwrap())
+ );
+
+ assert_eq!(resolve_distro_file(&resolver, "foo", &["cls"]), None);
+ }
+}
diff --git a/support/texlab/src/syntax/latex/analysis/environment.rs b/support/texlab/src/syntax/latex/analysis/environment.rs
new file mode 100644
index 0000000000..3b83b40b2f
--- /dev/null
+++ b/support/texlab/src/syntax/latex/analysis/environment.rs
@@ -0,0 +1,11 @@
+use crate::syntax::{latex, CstNode};
+
+use super::LatexAnalyzerContext;
+
+pub fn analyze_begin(context: &mut LatexAnalyzerContext, node: &latex::SyntaxNode) -> Option<()> {
+ let begin = latex::Begin::cast(node)?;
+ let name = begin.name()?.word()?.text();
+ let extras = &mut context.extras;
+ extras.environment_names.insert(name.into());
+ Some(())
+}
diff --git a/support/texlab/src/syntax/latex/analysis/explicit_link.rs b/support/texlab/src/syntax/latex/analysis/explicit_link.rs
new file mode 100644
index 0000000000..363a576aaa
--- /dev/null
+++ b/support/texlab/src/syntax/latex/analysis/explicit_link.rs
@@ -0,0 +1,72 @@
+use std::sync::Arc;
+
+use crate::syntax::{latex, CstNode};
+
+use super::{
+ distro_file::resolve_distro_file, ExplicitLink, ExplicitLinkKind, LatexAnalyzerContext,
+};
+
+pub fn analyze_include(context: &mut LatexAnalyzerContext, node: &latex::SyntaxNode) -> Option<()> {
+ let include = latex::Include::cast(node)?;
+ let kind = match include.syntax().kind() {
+ latex::LATEX_INCLUDE => ExplicitLinkKind::Latex,
+ latex::BIBLATEX_INCLUDE | latex::BIBTEX_INCLUDE => ExplicitLinkKind::Bibtex,
+ latex::PACKAGE_INCLUDE => ExplicitLinkKind::Package,
+ latex::CLASS_INCLUDE => ExplicitLinkKind::Class,
+ _ => return None,
+ };
+
+ let extensions = match kind {
+ ExplicitLinkKind::Latex => &["tex"],
+ ExplicitLinkKind::Bibtex => &["bib"],
+ ExplicitLinkKind::Package => &["sty"],
+ ExplicitLinkKind::Class => &["cls"],
+ };
+
+ for path in include.path_list()?.words() {
+ let stem = path.text();
+ let mut targets = vec![Arc::new(context.base_uri.join(stem).ok()?.into())];
+ for extension in extensions {
+ let path = format!("{}.{}", stem, extension);
+ targets.push(Arc::new(context.base_uri.join(&path).ok()?.into()));
+ }
+
+ resolve_distro_file(&context.inner.resolver.lock().unwrap(), stem, extensions)
+ .into_iter()
+ .for_each(|target| targets.push(Arc::new(target)));
+
+ context.extras.explicit_links.push(ExplicitLink {
+ kind,
+ stem: stem.into(),
+ stem_range: path.text_range(),
+ targets,
+ });
+ }
+
+ Some(())
+}
+
+pub fn analyze_import(context: &mut LatexAnalyzerContext, node: &latex::SyntaxNode) -> Option<()> {
+ let import = latex::Import::cast(node)?;
+
+ let mut targets = Vec::new();
+ let directory = context
+ .base_uri
+ .join(import.directory()?.word()?.text())
+ .ok()?;
+
+ let file = import.file()?.word()?;
+ let stem = file.text();
+ targets.push(Arc::new(directory.join(stem).ok()?.into()));
+ targets.push(Arc::new(
+ directory.join(&format!("{}.tex", stem)).ok()?.into(),
+ ));
+
+ context.extras.explicit_links.push(ExplicitLink {
+ stem: stem.into(),
+ stem_range: file.text_range(),
+ targets,
+ kind: ExplicitLinkKind::Latex,
+ });
+ Some(())
+}
diff --git a/support/texlab/src/syntax/latex/analysis/implicit_link.rs b/support/texlab/src/syntax/latex/analysis/implicit_link.rs
new file mode 100644
index 0000000000..74055b3e31
--- /dev/null
+++ b/support/texlab/src/syntax/latex/analysis/implicit_link.rs
@@ -0,0 +1,41 @@
+use std::sync::Arc;
+
+use crate::Uri;
+
+use super::LatexAnalyzerContext;
+
+pub fn analyze_implicit_links(context: &mut LatexAnalyzerContext) {
+ context.extras.implicit_links.aux = find_by_extension(context, "aux").unwrap_or_default();
+ context.extras.implicit_links.log = find_by_extension(context, "log").unwrap_or_default();
+ context.extras.implicit_links.pdf = find_by_extension(context, "pdf").unwrap_or_default();
+}
+
+fn find_by_extension(context: &LatexAnalyzerContext, extension: &str) -> Option<Vec<Arc<Uri>>> {
+ let mut targets: Vec<Arc<Uri>> = Vec::new();
+ targets.push(Arc::new(context.document_uri.with_extension(extension)?));
+ if context.document_uri.scheme() == "file" {
+ let file_path = context.document_uri.to_file_path().ok()?;
+ let file_stem = file_path.file_stem()?;
+ let aux_name = format!("{}.{}", file_stem.to_str()?, extension);
+
+ let options = context.inner.options.read().unwrap();
+ if let Some(root_dir) = options.root_directory.as_ref() {
+ let path = context
+ .inner
+ .current_directory
+ .join(root_dir)
+ .join(&aux_name);
+ targets.push(Arc::new(Uri::from_file_path(path).ok()?));
+ }
+
+ if let Some(build_dir) = options.aux_directory.as_ref() {
+ let path = context
+ .inner
+ .current_directory
+ .join(build_dir)
+ .join(&aux_name);
+ targets.push(Arc::new(Uri::from_file_path(path).ok()?));
+ }
+ }
+ Some(targets)
+}
diff --git a/support/texlab/src/syntax/latex/analysis/label_name.rs b/support/texlab/src/syntax/latex/analysis/label_name.rs
new file mode 100644
index 0000000000..d0410b953c
--- /dev/null
+++ b/support/texlab/src/syntax/latex/analysis/label_name.rs
@@ -0,0 +1,66 @@
+use latex::LabelReferenceRange;
+
+use crate::syntax::{latex, CstNode};
+
+use super::{LabelName, LatexAnalyzerContext};
+
+pub fn analyze_label_name(
+ context: &mut LatexAnalyzerContext,
+ node: &latex::SyntaxNode,
+) -> Option<()> {
+ analyze_label_definition_name(context, node)
+ .or_else(|| analyze_label_reference_name(context, node))
+ .or_else(|| analyze_label_reference_range_name(context, node))
+}
+
+fn analyze_label_definition_name(
+ context: &mut LatexAnalyzerContext,
+ node: &latex::SyntaxNode,
+) -> Option<()> {
+ let label = latex::LabelDefinition::cast(node)?;
+ let name = label.name()?.word()?;
+ context.extras.label_names.push(LabelName {
+ text: name.text().into(),
+ range: name.text_range(),
+ is_definition: true,
+ });
+ Some(())
+}
+
+fn analyze_label_reference_name(
+ context: &mut LatexAnalyzerContext,
+ node: &latex::SyntaxNode,
+) -> Option<()> {
+ let label = latex::LabelReference::cast(node)?;
+ for name in label.name_list()?.words() {
+ context.extras.label_names.push(LabelName {
+ text: name.text().into(),
+ range: name.text_range(),
+ is_definition: false,
+ });
+ }
+ Some(())
+}
+
+fn analyze_label_reference_range_name(
+ context: &mut LatexAnalyzerContext,
+ node: &latex::SyntaxNode,
+) -> Option<()> {
+ let label = LabelReferenceRange::cast(node)?;
+ if let Some(name1) = label.from().and_then(|name| name.word()) {
+ context.extras.label_names.push(LabelName {
+ text: name1.text().into(),
+ range: name1.text_range(),
+ is_definition: false,
+ });
+ }
+
+ if let Some(name2) = label.to().and_then(|name| name.word()) {
+ context.extras.label_names.push(LabelName {
+ text: name2.text().into(),
+ range: name2.text_range(),
+ is_definition: false,
+ });
+ }
+ Some(())
+}
diff --git a/support/texlab/src/syntax/latex/analysis/label_number.rs b/support/texlab/src/syntax/latex/analysis/label_number.rs
new file mode 100644
index 0000000000..de8d0dc1e1
--- /dev/null
+++ b/support/texlab/src/syntax/latex/analysis/label_number.rs
@@ -0,0 +1,22 @@
+use crate::syntax::{latex, CstNode};
+
+use super::LatexAnalyzerContext;
+
+pub fn analyze_label_number(
+ context: &mut LatexAnalyzerContext,
+ node: &latex::SyntaxNode,
+) -> Option<()> {
+ let number = latex::LabelNumber::cast(node)?;
+ let name = number.name()?.word()?.text().into();
+ let text = number
+ .text()?
+ .syntax()
+ .descendants_with_tokens()
+ .filter_map(|element| element.into_node())
+ .find(|node| node.kind() == latex::TEXT || node.kind() == latex::MIXED_GROUP)?
+ .text()
+ .to_string();
+
+ context.extras.label_numbers_by_name.insert(name, text);
+ Some(())
+}
diff --git a/support/texlab/src/syntax/latex/analysis/theorem.rs b/support/texlab/src/syntax/latex/analysis/theorem.rs
new file mode 100644
index 0000000000..0c365f0ef5
--- /dev/null
+++ b/support/texlab/src/syntax/latex/analysis/theorem.rs
@@ -0,0 +1,23 @@
+use crate::syntax::{
+ latex::{self, HasCurly},
+ CstNode,
+};
+
+use super::{LatexAnalyzerContext, TheoremEnvironment};
+
+pub fn analyze_theorem_definition(
+ context: &mut LatexAnalyzerContext,
+ node: &latex::SyntaxNode,
+) -> Option<()> {
+ let theorem = latex::TheoremDefinition::cast(node)?;
+ let name = theorem.name()?.word()?.text().into();
+ let description = theorem.description()?;
+ let description = description.content_text()?;
+
+ context
+ .extras
+ .theorem_environments
+ .push(TheoremEnvironment { name, description });
+
+ Some(())
+}
diff --git a/support/texlab/src/syntax/latex/analysis/types.rs b/support/texlab/src/syntax/latex/analysis/types.rs
new file mode 100644
index 0000000000..f089721397
--- /dev/null
+++ b/support/texlab/src/syntax/latex/analysis/types.rs
@@ -0,0 +1,73 @@
+use std::sync::Arc;
+
+use cstree::TextRange;
+use rustc_hash::{FxHashMap, FxHashSet};
+use smol_str::SmolStr;
+
+use crate::{ServerContext, Uri};
+
+#[derive(Debug)]
+pub struct LatexAnalyzerContext {
+ pub inner: Arc<ServerContext>,
+ pub document_uri: Arc<Uri>,
+ pub base_uri: Arc<Uri>,
+ pub extras: Extras,
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct Extras {
+ pub implicit_links: ImplicitLinks,
+ pub explicit_links: Vec<ExplicitLink>,
+ pub has_document_environment: bool,
+ pub command_names: FxHashSet<SmolStr>,
+ pub environment_names: FxHashSet<SmolStr>,
+ pub label_names: Vec<LabelName>,
+ pub label_numbers_by_name: FxHashMap<SmolStr, String>,
+ pub theorem_environments: Vec<TheoremEnvironment>,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Default, Hash)]
+pub struct ImplicitLinks {
+ pub aux: Vec<Arc<Uri>>,
+ pub log: Vec<Arc<Uri>>,
+ pub pdf: Vec<Arc<Uri>>,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
+pub enum ExplicitLinkKind {
+ Package,
+ Class,
+ Latex,
+ Bibtex,
+}
+
+#[derive(Debug, Clone)]
+pub struct ExplicitLink {
+ pub stem: SmolStr,
+ pub stem_range: TextRange,
+ pub targets: Vec<Arc<Uri>>,
+ pub kind: ExplicitLinkKind,
+}
+
+impl ExplicitLink {
+ pub fn as_component_name(&self) -> Option<String> {
+ match self.kind {
+ ExplicitLinkKind::Package => Some(format!("{}.sty", self.stem)),
+ ExplicitLinkKind::Class => Some(format!("{}.cls", self.stem)),
+ ExplicitLinkKind::Latex | ExplicitLinkKind::Bibtex => None,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Default, Hash)]
+pub struct TheoremEnvironment {
+ pub name: SmolStr,
+ pub description: String,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Default, Hash)]
+pub struct LabelName {
+ pub text: SmolStr,
+ pub range: TextRange,
+ pub is_definition: bool,
+}
diff --git a/support/texlab/src/syntax/latex/ast.rs b/support/texlab/src/syntax/latex/ast.rs
deleted file mode 100644
index ab7c6863e1..0000000000
--- a/support/texlab/src/syntax/latex/ast.rs
+++ /dev/null
@@ -1,414 +0,0 @@
-use crate::{
- protocol::{Position, Range, RangeExt},
- syntax::{
- generic_ast::{Ast, AstNodeIndex},
- text::{Span, SyntaxNode},
- },
-};
-use serde::{Deserialize, Serialize};
-use std::ops::Deref;
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
-pub enum TokenKind {
- Word,
- Command,
- Math,
- Comma,
- BeginGroup,
- EndGroup,
- BeginOptions,
- EndOptions,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-pub struct Token {
- pub span: Span,
- pub kind: TokenKind,
-}
-
-impl Token {
- pub fn new(span: Span, kind: TokenKind) -> Self {
- Self { span, kind }
- }
-
- pub fn text(&self) -> &str {
- &self.span.text
- }
-}
-
-impl SyntaxNode for Token {
- fn range(&self) -> Range {
- self.span.range()
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
-pub struct Root {
- pub range: Range,
-}
-
-impl SyntaxNode for Root {
- fn range(&self) -> Range {
- self.range
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
-pub enum GroupKind {
- Group,
- Options,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-pub struct Group {
- pub range: Range,
- pub left: Token,
- pub right: Option<Token>,
- pub kind: GroupKind,
-}
-
-impl SyntaxNode for Group {
- fn range(&self) -> Range {
- self.range
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-pub struct Command {
- pub range: Range,
- pub name: Token,
-}
-
-impl Command {
- pub fn short_name_range(&self) -> Range {
- Range::new_simple(
- self.name.start().line,
- self.name.start().character + 1,
- self.name.end().line,
- self.name.end().character,
- )
- }
-}
-
-impl SyntaxNode for Command {
- fn range(&self) -> Range {
- self.range
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-pub struct Text {
- pub range: Range,
- pub words: Vec<Token>,
-}
-
-impl SyntaxNode for Text {
- fn range(&self) -> Range {
- self.range
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-pub struct Comma {
- pub range: Range,
- pub token: Token,
-}
-
-impl SyntaxNode for Comma {
- fn range(&self) -> Range {
- self.range
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-pub struct Math {
- pub range: Range,
- pub token: Token,
-}
-
-impl SyntaxNode for Math {
- fn range(&self) -> Range {
- self.range
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-pub enum Node {
- Root(Root),
- Group(Group),
- Command(Command),
- Text(Text),
- Comma(Comma),
- Math(Math),
-}
-
-impl SyntaxNode for Node {
- fn range(&self) -> Range {
- match self {
- Self::Root(root) => root.range(),
- Self::Group(group) => group.range(),
- Self::Command(cmd) => cmd.range(),
- Self::Text(text) => text.range(),
- Self::Comma(comma) => comma.range(),
- Self::Math(math) => math.range(),
- }
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct Tree {
- pub inner: Ast<Node>,
- pub root: AstNodeIndex,
-}
-
-impl Deref for Tree {
- type Target = Ast<Node>;
-
- fn deref(&self) -> &Self::Target {
- &self.inner
- }
-}
-
-impl Tree {
- pub fn walk<V: Visitor>(&self, visitor: &mut V, parent: AstNodeIndex) {
- for child in self.children(parent) {
- visitor.visit(self, child);
- }
- }
-
- pub fn find(&self, pos: Position) -> Vec<AstNodeIndex> {
- let mut finder = Finder::new(pos);
- finder.visit(self, self.root);
- finder.results
- }
-
- pub fn find_command_by_short_name_range(&self, pos: Position) -> Option<AstNodeIndex> {
- self.find(pos).into_iter().find(|node| {
- self.as_command(*node)
- .filter(|cmd| {
- cmd.name.range().contains(pos) && cmd.name.start().character != pos.character
- })
- .is_some()
- })
- }
-
- pub fn print(&self, node: AstNodeIndex) -> String {
- let start_position = self[node].start();
- let mut printer = Printer::new(start_position);
- printer.visit(self, node);
- printer.output
- }
-
- pub fn commands<'a>(&'a self) -> impl Iterator<Item = AstNodeIndex> + 'a {
- self.inner
- .nodes()
- .into_iter()
- .filter(move |node| self.as_command(*node).is_some())
- }
-
- pub fn as_group(&self, node: AstNodeIndex) -> Option<&Group> {
- if let Node::Group(group) = &self[node] {
- Some(group)
- } else {
- None
- }
- }
-
- pub fn as_command(&self, node: AstNodeIndex) -> Option<&Command> {
- if let Node::Command(cmd) = &self[node] {
- Some(cmd)
- } else {
- None
- }
- }
-
- pub fn as_text(&self, node: AstNodeIndex) -> Option<&Text> {
- if let Node::Text(text) = &self[node] {
- Some(text)
- } else {
- None
- }
- }
-
- pub fn as_math(&self, node: AstNodeIndex) -> Option<&Math> {
- if let Node::Math(math) = &self[node] {
- Some(math)
- } else {
- None
- }
- }
-
- pub fn extract_group(
- &self,
- parent: AstNodeIndex,
- group_kind: GroupKind,
- index: usize,
- ) -> Option<AstNodeIndex> {
- self.children(parent)
- .filter(|child| {
- self.as_group(*child)
- .filter(|group| group.kind == group_kind)
- .is_some()
- })
- .nth(index)
- }
-
- pub fn extract_text(
- &self,
- parent: AstNodeIndex,
- group_kind: GroupKind,
- index: usize,
- ) -> Option<&Text> {
- let group = self.extract_group(parent, group_kind, index)?;
- let mut contents = self.children(group);
- let text = self.as_text(contents.next()?);
- if contents.next().is_none() {
- text
- } else {
- None
- }
- }
-
- pub fn extract_word(
- &self,
- parent: AstNodeIndex,
- group_kind: GroupKind,
- index: usize,
- ) -> Option<&Token> {
- let text = self.extract_text(parent, group_kind, index)?;
- if text.words.len() == 1 {
- Some(&text.words[0])
- } else {
- None
- }
- }
-
- pub fn extract_comma_separated_words(
- &self,
- parent: AstNodeIndex,
- group_kind: GroupKind,
- index: usize,
- ) -> Option<Vec<&Token>> {
- let group = self.extract_group(parent, group_kind, index)?;
- let mut words = Vec::new();
- for child in self.children(group) {
- match &self[child] {
- Node::Root(_) | Node::Group(_) | Node::Command(_) | Node::Math(_) => return None,
- Node::Text(text) => {
- for word in &text.words {
- words.push(word);
- }
- }
- Node::Comma(_) => (),
- }
- }
- Some(words)
- }
-
- pub fn print_group_content(
- &self,
- parent: AstNodeIndex,
- group_kind: GroupKind,
- index: usize,
- ) -> Option<String> {
- let arg = self.extract_group(parent, group_kind, index)?;
- let text = self.print(arg);
- self.as_group(arg)?.right.as_ref()?;
- Some(text[1..text.len() - 1].trim().into())
- }
-}
-
-pub trait Visitor {
- fn visit(&mut self, tree: &Tree, node: AstNodeIndex);
-}
-
-#[derive(Debug)]
-struct Finder {
- position: Position,
- results: Vec<AstNodeIndex>,
-}
-
-impl Finder {
- fn new(position: Position) -> Self {
- Self {
- position,
- results: Vec::new(),
- }
- }
-}
-
-impl Visitor for Finder {
- fn visit(&mut self, tree: &Tree, node: AstNodeIndex) {
- if tree[node].range().contains(self.position) {
- self.results.push(node);
- tree.walk(self, node);
- }
- }
-}
-
-#[derive(Debug)]
-struct Printer {
- output: String,
- position: Position,
-}
-
-impl Printer {
- fn new(start_position: Position) -> Self {
- Self {
- output: String::new(),
- position: start_position,
- }
- }
-
- fn synchronize(&mut self, position: Position) {
- while self.position.line < position.line {
- self.output.push('\n');
- self.position.line += 1;
- self.position.character = 0;
- }
-
- while self.position.character < position.character {
- self.output.push(' ');
- self.position.character += 1;
- }
-
- assert_eq!(self.position, position);
- }
-
- fn print_token(&mut self, token: &Token) {
- self.synchronize(token.start());
- self.output.push_str(token.text());
- self.position.character += token.end().character - token.start().character;
- self.synchronize(token.end());
- }
-}
-
-impl Visitor for Printer {
- fn visit(&mut self, tree: &Tree, node: AstNodeIndex) {
- match &tree[node] {
- Node::Root(_) => tree.walk(self, node),
- Node::Group(group) => {
- self.print_token(&group.left);
- tree.walk(self, node);
- if let Some(right) = &group.right {
- self.print_token(right);
- }
- }
- Node::Command(cmd) => {
- self.print_token(&cmd.name);
- tree.walk(self, node);
- }
- Node::Text(text) => {
- for word in &text.words {
- self.print_token(word);
- }
- }
- Node::Comma(comma) => {
- self.print_token(&comma.token);
- }
- Node::Math(math) => {
- self.print_token(&math.token);
- }
- }
- }
-}
diff --git a/support/texlab/src/syntax/latex/cst.rs b/support/texlab/src/syntax/latex/cst.rs
new file mode 100644
index 0000000000..0b839b706d
--- /dev/null
+++ b/support/texlab/src/syntax/latex/cst.rs
@@ -0,0 +1,639 @@
+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 | COMMENT) {
+ return TextRange::new(start, current.text_range().end());
+ }
+ token = current.prev_token();
+ }
+ TextRange::new(start, start)
+ }
+ }
+ };
+}
+
+cst_node!(Text, TEXT);
+
+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)
+ }
+
+ fn right_curly(&self) -> Option<&'a SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(|node| node.into_token())
+ .find(|node| node.kind() == R_CURLY)
+ }
+
+ fn content_text(&self) -> Option<String> {
+ self.left_curly()?;
+ self.right_curly()?;
+ let mut text = String::new();
+ for child in self
+ .syntax()
+ .descendants_with_tokens()
+ .filter_map(|child| child.into_token())
+ .filter(|token| !matches!(token.kind(), COMMENT))
+ {
+ text.push_str(child.text());
+ }
+ let text = text.trim_end();
+ let text = text[1..text.len() - 1].trim().to_string();
+
+ Some(text)
+ }
+}
+
+cst_node!(CurlyGroup, CURLY_GROUP);
+
+impl<'a> HasCurly<'a> for CurlyGroup<'a> {}
+
+impl<'a> CurlyGroup<'a> {}
+
+pub trait HasBrack<'a>: CstNode<'a, Lang = Language> {
+ fn left_brack(&self) -> Option<&'a SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(|node| node.into_token())
+ .find(|node| node.kind() == L_BRACK)
+ }
+
+ fn right_brack(&self) -> Option<&'a SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(|node| node.into_token())
+ .find(|node| node.kind() == R_BRACK)
+ }
+
+ fn content_text(&self) -> Option<String> {
+ self.left_brack()?;
+ self.right_brack()?;
+ let mut text = String::new();
+ for child in self
+ .syntax()
+ .descendants_with_tokens()
+ .filter_map(|child| child.into_token())
+ .filter(|token| !matches!(token.kind(), COMMENT))
+ {
+ text.push_str(child.text());
+ }
+ let text = text.trim_end();
+ let text = text[1..text.len() - 1].trim().to_string();
+
+ Some(text)
+ }
+}
+
+cst_node!(BrackGroup, BRACK_GROUP);
+
+impl<'a> BrackGroup<'a> {}
+
+impl<'a> HasBrack<'a> for BrackGroup<'a> {}
+
+pub trait HasParen<'a>: CstNode<'a, Lang = Language> {
+ fn left_paren(&self) -> Option<&'a SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(|node| node.into_token())
+ .find(|node| node.kind() == L_PAREN)
+ }
+
+ fn right_paren(&self) -> Option<&'a SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(|node| node.into_token())
+ .find(|node| node.kind() == R_PAREN)
+ }
+}
+
+cst_node!(ParenGroup, PAREN_GROUP);
+
+impl<'a> HasParen<'a> for ParenGroup<'a> {}
+
+cst_node!(MixedGroup, MIXED_GROUP);
+
+impl<'a> MixedGroup<'a> {
+ pub fn left_delim(&self) -> Option<&'a SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(|node| node.into_token())
+ .find(|node| matches!(node.kind().into(), L_BRACK | L_PAREN))
+ }
+
+ pub fn right_delim(&self) -> Option<&'a SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(|node| node.into_token())
+ .find(|node| matches!(node.kind().into(), R_BRACK | R_PAREN))
+ }
+}
+
+cst_node!(CurlyGroupWord, CURLY_GROUP_WORD);
+
+impl<'a> HasCurly<'a> for CurlyGroupWord<'a> {}
+
+impl<'a> CurlyGroupWord<'a> {
+ pub fn word(&self) -> Option<&'a SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(|node| node.into_token())
+ .find(|node| node.kind() == WORD)
+ }
+}
+
+cst_node!(BrackGroupWord, BRACK_GROUP_WORD);
+
+impl<'a> HasBrack<'a> for BrackGroupWord<'a> {}
+
+impl<'a> BrackGroupWord<'a> {
+ pub fn word(&self) -> Option<&'a SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(|node| node.into_token())
+ .find(|node| node.kind() == WORD)
+ }
+}
+
+cst_node!(CurlyGroupWordList, CURLY_GROUP_WORD_LIST);
+
+impl<'a> HasCurly<'a> for CurlyGroupWordList<'a> {}
+
+impl<'a> CurlyGroupWordList<'a> {
+ pub fn words(&self) -> impl Iterator<Item = &'a SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(|node| node.into_token())
+ .filter(|node| node.kind() == WORD)
+ }
+}
+
+cst_node!(CurlyGroupCommand, CURLY_GROUP_COMMAND);
+
+impl<'a> HasCurly<'a> for CurlyGroupCommand<'a> {}
+
+impl<'a> CurlyGroupCommand<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(|node| node.into_token())
+ .find(|node| node.kind() == GENERIC_COMMAND_NAME)
+ }
+}
+
+cst_node!(Key, KEY);
+
+impl<'a> Key<'a> {
+ pub fn words(&self) -> impl Iterator<Item = &'a SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(|node| node.into_token())
+ .filter(|node| node.kind() == WORD)
+ }
+}
+
+cst_node!(Value, VALUE);
+
+cst_node!(KeyValuePair, KEY_VALUE_PAIR);
+
+impl<'a> KeyValuePair<'a> {
+ pub fn key(&self) -> Option<Key<'a>> {
+ self.syntax().children().find_map(Key::cast)
+ }
+
+ pub fn value(&self) -> Option<Value<'a>> {
+ self.syntax().children().find_map(Value::cast)
+ }
+}
+
+cst_node!(KeyValueBody, KEY_VALUE_BODY);
+
+impl<'a> KeyValueBody<'a> {
+ pub fn pairs(&self) -> impl Iterator<Item = KeyValuePair<'a>> {
+ self.syntax().children().filter_map(KeyValuePair::cast)
+ }
+}
+
+pub trait HasKeyValueBody<'a>: CstNode<'a, Lang = Language> {
+ fn body(&self) -> Option<KeyValueBody<'a>> {
+ self.syntax().children().find_map(KeyValueBody::cast)
+ }
+}
+
+cst_node!(CurlyGroupKeyValue, CURLY_GROUP_KEY_VALUE);
+
+impl<'a> HasCurly<'a> for CurlyGroupKeyValue<'a> {}
+
+impl<'a> HasKeyValueBody<'a> for CurlyGroupKeyValue<'a> {}
+
+cst_node!(BrackGroupKeyValue, BRACK_GROUP_KEY_VALUE);
+
+impl<'a> HasBrack<'a> for BrackGroupKeyValue<'a> {}
+
+impl<'a> HasKeyValueBody<'a> for BrackGroupKeyValue<'a> {}
+
+cst_node!(Formula, FORMULA);
+
+cst_node!(GenericCommand, GENERIC_COMMAND);
+
+impl<'a> GenericCommand<'a> {
+ pub fn name(&self) -> Option<&'a SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(|node| node.into_token())
+ .find(|node| node.kind() == GENERIC_COMMAND_NAME)
+ }
+}
+
+cst_node!(Equation, EQUATION);
+
+cst_node!(Begin, BEGIN);
+
+impl<'a> Begin<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn name(&self) -> Option<CurlyGroupWord<'a>> {
+ self.syntax().children().find_map(CurlyGroupWord::cast)
+ }
+
+ pub fn options(&self) -> Option<BrackGroup<'a>> {
+ self.syntax().children().find_map(BrackGroup::cast)
+ }
+}
+
+cst_node!(End, END);
+
+impl<'a> End<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn name(&self) -> Option<CurlyGroupWord<'a>> {
+ self.syntax().children().find_map(CurlyGroupWord::cast)
+ }
+}
+
+cst_node!(Environment, ENVIRONMENT);
+
+impl<'a> Environment<'a> {
+ pub fn begin(&self) -> Option<Begin<'a>> {
+ self.syntax().children().find_map(Begin::cast)
+ }
+
+ pub fn end(&self) -> Option<End<'a>> {
+ self.syntax().children().find_map(End::cast)
+ }
+}
+
+cst_node!(
+ Section,
+ PART,
+ CHAPTER,
+ SECTION,
+ SUBSECTION,
+ SUBSUBSECTION,
+ PARAGRAPH,
+ SUBPARAGRAPH
+);
+
+impl<'a> Section<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn name(&self) -> Option<CurlyGroup<'a>> {
+ self.syntax().children().find_map(CurlyGroup::cast)
+ }
+}
+
+cst_node!(EnumItem, ENUM_ITEM);
+
+impl<'a> EnumItem<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn label(&self) -> Option<BrackGroup<'a>> {
+ self.syntax().children().find_map(BrackGroup::cast)
+ }
+}
+
+cst_node!(Caption, CAPTION);
+
+impl<'a> Caption<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn short(&self) -> Option<BrackGroup<'a>> {
+ self.syntax().children().find_map(BrackGroup::cast)
+ }
+
+ pub fn long(&self) -> Option<CurlyGroup<'a>> {
+ self.syntax().children().find_map(CurlyGroup::cast)
+ }
+}
+
+cst_node!(Citation, CITATION);
+
+impl<'a> Citation<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn prenote(&self) -> Option<BrackGroup<'a>> {
+ self.syntax().children().find_map(BrackGroup::cast)
+ }
+
+ pub fn postnote(&self) -> Option<BrackGroup<'a>> {
+ self.syntax().children().filter_map(BrackGroup::cast).nth(1)
+ }
+
+ pub fn key_list(&self) -> Option<CurlyGroupWordList<'a>> {
+ self.syntax().children().find_map(CurlyGroupWordList::cast)
+ }
+}
+
+cst_node!(
+ Include,
+ PACKAGE_INCLUDE,
+ CLASS_INCLUDE,
+ LATEX_INCLUDE,
+ BIBLATEX_INCLUDE,
+ BIBTEX_INCLUDE,
+ GRAPHICS_INCLUDE,
+ SVG_INCLUDE,
+ INKSCAPE_INCLUDE,
+ VERBATIM_INCLUDE
+);
+
+impl<'a> Include<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn path_list(&self) -> Option<CurlyGroupWordList<'a>> {
+ self.syntax().children().find_map(CurlyGroupWordList::cast)
+ }
+}
+
+cst_node!(Import, IMPORT);
+
+impl<'a> Import<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn directory(&self) -> Option<CurlyGroupWord<'a>> {
+ self.syntax().children().find_map(CurlyGroupWord::cast)
+ }
+
+ pub fn file(&self) -> Option<CurlyGroupWord<'a>> {
+ self.syntax()
+ .children()
+ .filter_map(CurlyGroupWord::cast)
+ .nth(1)
+ }
+}
+
+cst_node!(LabelDefinition, LABEL_DEFINITION);
+
+impl<'a> LabelDefinition<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn name(&self) -> Option<CurlyGroupWord<'a>> {
+ self.syntax().children().find_map(CurlyGroupWord::cast)
+ }
+}
+
+cst_node!(LabelReference, LABEL_REFERENCE);
+
+impl<'a> LabelReference<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn name_list(&self) -> Option<CurlyGroupWordList<'a>> {
+ self.syntax().children().find_map(CurlyGroupWordList::cast)
+ }
+}
+
+cst_node!(LabelReferenceRange, LABEL_REFERENCE_RANGE);
+
+impl<'a> LabelReferenceRange<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn from(&self) -> Option<CurlyGroupWord<'a>> {
+ self.syntax().children().find_map(CurlyGroupWord::cast)
+ }
+
+ pub fn to(&self) -> Option<CurlyGroupWord<'a>> {
+ self.syntax()
+ .children()
+ .filter_map(CurlyGroupWord::cast)
+ .nth(1)
+ }
+}
+
+cst_node!(LabelNumber, LABEL_NUMBER);
+
+impl<'a> LabelNumber<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn name(&self) -> Option<CurlyGroupWord<'a>> {
+ self.syntax().children().find_map(CurlyGroupWord::cast)
+ }
+
+ pub fn text(&self) -> Option<CurlyGroup<'a>> {
+ self.syntax().children().find_map(CurlyGroup::cast)
+ }
+}
+
+cst_node!(TheoremDefinition, THEOREM_DEFINITION);
+
+impl<'a> TheoremDefinition<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn name(&self) -> Option<CurlyGroupWord<'a>> {
+ self.syntax().children().find_map(CurlyGroupWord::cast)
+ }
+
+ pub fn description(&self) -> Option<CurlyGroup<'a>> {
+ self.syntax().children().find_map(CurlyGroup::cast)
+ }
+}
+
+cst_node!(CommandDefinition, COMMAND_DEFINITION, MATH_OPERATOR);
+
+impl<'a> CommandDefinition<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn name(&self) -> Option<CurlyGroupCommand<'a>> {
+ self.syntax().children().find_map(CurlyGroupCommand::cast)
+ }
+
+ pub fn implementation(&self) -> Option<CurlyGroup<'a>> {
+ self.syntax().children().find_map(CurlyGroup::cast)
+ }
+}
+
+cst_node!(AcronymReference, ACRONYM_REFERENCE);
+
+impl<'a> AcronymReference<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+}
+
+cst_node!(AcronymDefinition, ACRONYM_DEFINITION);
+
+impl<'a> AcronymDefinition<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn name(&self) -> Option<CurlyGroupWord<'a>> {
+ self.syntax().children().find_map(CurlyGroupWord::cast)
+ }
+}
+
+cst_node!(AcronymDeclaration, ACRONYM_DECLARATION);
+
+impl<'a> AcronymDeclaration<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn name(&self) -> Option<CurlyGroupWord<'a>> {
+ self.syntax().children().find_map(CurlyGroupWord::cast)
+ }
+}
+
+cst_node!(ColorDefinition, COLOR_DEFINITION);
+
+impl<'a> ColorDefinition<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn name(&self) -> Option<CurlyGroupWord<'a>> {
+ self.syntax().children().find_map(CurlyGroupWord::cast)
+ }
+
+ pub fn model(&self) -> Option<CurlyGroupWord<'a>> {
+ self.syntax()
+ .children()
+ .filter_map(CurlyGroupWord::cast)
+ .nth(1)
+ }
+
+ pub fn spec(&self) -> Option<CurlyGroup<'a>> {
+ self.syntax().children().find_map(CurlyGroup::cast)
+ }
+}
+
+cst_node!(ColorSetDefinition, COLOR_SET_DEFINITION);
+
+impl<'a> ColorSetDefinition<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn model_list(&self) -> Option<CurlyGroupWordList<'a>> {
+ self.syntax().children().find_map(CurlyGroupWordList::cast)
+ }
+}
+
+cst_node!(ColorReference, COLOR_REFERENCE);
+
+impl<'a> ColorReference<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn name(&self) -> Option<CurlyGroupWord<'a>> {
+ self.syntax().children().find_map(CurlyGroupWord::cast)
+ }
+}
+
+cst_node!(GlossaryEntryReference, GLOSSARY_ENTRY_REFERENCE);
+
+impl<'a> GlossaryEntryReference<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn name(&self) -> Option<CurlyGroupWord<'a>> {
+ self.syntax().children().find_map(CurlyGroupWord::cast)
+ }
+}
+
+cst_node!(GlossaryEntryDefinition, GLOSSARY_ENTRY_DEFINITION);
+
+impl<'a> GlossaryEntryDefinition<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn name(&self) -> Option<CurlyGroupWord<'a>> {
+ self.syntax().children().find_map(CurlyGroupWord::cast)
+ }
+}
+
+cst_node!(TikzLibraryImport, TIKZ_LIBRARY_IMPORT);
+
+impl<'a> TikzLibraryImport<'a> {
+ pub fn command(&self) -> Option<&'a SyntaxToken> {
+ self.syntax().first_token()
+ }
+
+ pub fn name_list(&self) -> Option<CurlyGroupWordList<'a>> {
+ self.syntax().children().find_map(CurlyGroupWordList::cast)
+ }
+}
diff --git a/support/texlab/src/syntax/latex/kind.rs b/support/texlab/src/syntax/latex/kind.rs
new file mode 100644
index 0000000000..fcab393380
--- /dev/null
+++ b/support/texlab/src/syntax/latex/kind.rs
@@ -0,0 +1,180 @@
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
+#[allow(non_camel_case_types)]
+#[repr(u16)]
+pub enum SyntaxKind {
+ ERROR = 0,
+ MISSING,
+
+ WHITESPACE,
+ COMMENT,
+ L_CURLY,
+ R_CURLY,
+ L_BRACK,
+ R_BRACK,
+ L_PAREN,
+ R_PAREN,
+ PARAMETER,
+ COMMA,
+ EQUALITY_SIGN,
+ WORD,
+ DOLLAR,
+ GENERIC_COMMAND_NAME,
+ BEGIN_ENVIRONMENT_NAME,
+ END_ENVIRONMENT_NAME,
+ BEGIN_EQUATION_NAME,
+ END_EQUATION_NAME,
+ PART_NAME,
+ CHAPTER_NAME,
+ SECTION_NAME,
+ SUBSECTION_NAME,
+ SUBSUBSECTION_NAME,
+ PARAGRAPH_NAME,
+ SUBPARAGRAPH_NAME,
+ ENUM_ITEM_NAME,
+ CAPTION_NAME,
+ CITATION_NAME,
+ PACKAGE_INCLUDE_NAME,
+ CLASS_INCLUDE_NAME,
+ LATEX_INCLUDE_NAME,
+ BIBLATEX_INCLUDE_NAME,
+ BIBTEX_INCLUDE_NAME,
+ GRAPHICS_INCLUDE_NAME,
+ SVG_INCLUDE_NAME,
+ INKSCAPE_INCLUDE_NAME,
+ VERBATIM_INCLUDE_NAME,
+ IMPORT_NAME,
+ LABEL_DEFINITION_NAME,
+ LABEL_REFERENCE_NAME,
+ LABEL_REFERENCE_RANGE_NAME,
+ LABEL_NUMBER_NAME,
+ COMMAND_DEFINITION_NAME,
+ MATH_OPERATOR_NAME,
+ GLOSSARY_ENTRY_DEFINITION_NAME,
+ GLOSSARY_ENTRY_REFERENCE_NAME,
+ ACRONYM_DEFINITION_NAME,
+ ACRONYM_DECLARATION_NAME,
+ ACRONYM_REFERENCE_NAME,
+ THEOREM_DEFINITION_NAME,
+ COLOR_REFERENCE_NAME,
+ COLOR_DEFINITION_NAME,
+ COLOR_SET_DEFINITION_NAME,
+ TIKZ_LIBRARY_IMPORT_NAME,
+ ENVIRONMENT_DEFINIITION_NAME,
+
+ PREAMBLE,
+ TEXT,
+ KEY,
+ VALUE,
+ KEY_VALUE_PAIR,
+ KEY_VALUE_BODY,
+ CURLY_GROUP,
+ CURLY_GROUP_WORD,
+ CURLY_GROUP_WORD_LIST,
+ CURLY_GROUP_COMMAND,
+ CURLY_GROUP_KEY_VALUE,
+ BRACK_GROUP,
+ BRACK_GROUP_WORD,
+ BRACK_GROUP_KEY_VALUE,
+ PAREN_GROUP,
+ MIXED_GROUP,
+ GENERIC_COMMAND,
+ ENVIRONMENT,
+ BEGIN,
+ END,
+ EQUATION,
+ PART,
+ CHAPTER,
+ SECTION,
+ SUBSECTION,
+ SUBSUBSECTION,
+ PARAGRAPH,
+ SUBPARAGRAPH,
+ ENUM_ITEM,
+ FORMULA,
+ CAPTION,
+ CITATION,
+ PACKAGE_INCLUDE,
+ CLASS_INCLUDE,
+ LATEX_INCLUDE,
+ BIBLATEX_INCLUDE,
+ BIBTEX_INCLUDE,
+ GRAPHICS_INCLUDE,
+ SVG_INCLUDE,
+ INKSCAPE_INCLUDE,
+ VERBATIM_INCLUDE,
+ IMPORT,
+ LABEL_DEFINITION,
+ LABEL_REFERENCE,
+ LABEL_REFERENCE_RANGE,
+ LABEL_NUMBER,
+ COMMAND_DEFINITION,
+ MATH_OPERATOR,
+ GLOSSARY_ENTRY_DEFINITION,
+ GLOSSARY_ENTRY_REFERENCE,
+ ACRONYM_DEFINITION,
+ ACRONYM_DECLARATION,
+ ACRONYM_REFERENCE,
+ THEOREM_DEFINITION,
+ COLOR_REFERENCE,
+ COLOR_DEFINITION,
+ COLOR_SET_DEFINITION,
+ TIKZ_LIBRARY_IMPORT,
+ ENVIRONMENT_DEFINITION,
+ ROOT,
+}
+
+impl SyntaxKind {
+ pub fn is_command_name(&self) -> bool {
+ use SyntaxKind::*;
+ matches!(
+ self,
+ GENERIC_COMMAND_NAME
+ | BEGIN_ENVIRONMENT_NAME
+ | END_ENVIRONMENT_NAME
+ | BEGIN_EQUATION_NAME
+ | END_EQUATION_NAME
+ | PART_NAME
+ | CHAPTER_NAME
+ | SECTION_NAME
+ | SUBSECTION_NAME
+ | SUBSUBSECTION_NAME
+ | PARAGRAPH_NAME
+ | SUBPARAGRAPH_NAME
+ | ENUM_ITEM_NAME
+ | CAPTION_NAME
+ | CITATION_NAME
+ | PACKAGE_INCLUDE_NAME
+ | CLASS_INCLUDE_NAME
+ | LATEX_INCLUDE_NAME
+ | BIBLATEX_INCLUDE_NAME
+ | BIBTEX_INCLUDE_NAME
+ | GRAPHICS_INCLUDE_NAME
+ | SVG_INCLUDE_NAME
+ | INKSCAPE_INCLUDE_NAME
+ | VERBATIM_INCLUDE_NAME
+ | IMPORT_NAME
+ | LABEL_DEFINITION_NAME
+ | LABEL_REFERENCE_NAME
+ | LABEL_REFERENCE_RANGE_NAME
+ | LABEL_NUMBER_NAME
+ | COMMAND_DEFINITION_NAME
+ | MATH_OPERATOR_NAME
+ | GLOSSARY_ENTRY_DEFINITION_NAME
+ | GLOSSARY_ENTRY_REFERENCE_NAME
+ | ACRONYM_DEFINITION_NAME
+ | ACRONYM_DECLARATION_NAME
+ | ACRONYM_REFERENCE_NAME
+ | THEOREM_DEFINITION_NAME
+ | COLOR_REFERENCE_NAME
+ | COLOR_DEFINITION_NAME
+ | COLOR_SET_DEFINITION_NAME
+ | TIKZ_LIBRARY_IMPORT_NAME
+ )
+ }
+}
+
+impl From<SyntaxKind> for cstree::SyntaxKind {
+ fn from(kind: SyntaxKind) -> Self {
+ Self(kind as u16)
+ }
+}
diff --git a/support/texlab/src/syntax/latex/lexer.rs b/support/texlab/src/syntax/latex/lexer.rs
index e14f1f6d85..7cd94ec897 100644
--- a/support/texlab/src/syntax/latex/lexer.rs
+++ b/support/texlab/src/syntax/latex/lexer.rs
@@ -1,176 +1,255 @@
-use super::ast::{Token, TokenKind};
-use crate::syntax::text::CharStream;
+use logos::Logos;
-#[derive(Debug)]
+use super::kind::SyntaxKind;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord, Logos)]
+#[allow(non_camel_case_types)]
+#[repr(u16)]
+enum Token {
+ #[regex(r"\s+")]
+ WHITESPACE = 2,
+
+ #[regex(r"%[^\r\n]*")]
+ COMMENT,
+
+ #[token("{")]
+ L_CURLY,
+
+ #[token("}")]
+ R_CURLY,
+
+ #[token("[")]
+ L_BRACK,
+
+ #[token("]")]
+ R_BRACK,
+
+ #[token("(")]
+ L_PAREN,
+
+ #[token(")")]
+ R_PAREN,
+
+ #[regex(r"#\d?")]
+ PARAMETER,
+
+ #[token(",")]
+ COMMA,
+
+ #[token("=")]
+ EQUALITY_SIGN,
+
+ #[regex(r"[^\s\\%\{\},\$\[\]\(\)=\#]+")]
+ #[error]
+ WORD,
+
+ #[regex(r"\$\$?")]
+ DOLLAR,
+
+ #[regex(r"\\([^\r\n]|[@a-zA-Z:_]+\*?)?")]
+ GENERIC_COMMAND_NAME,
+
+ #[regex(r"\\begin")]
+ BEGIN_ENVIRONMENT_NAME,
+
+ #[regex(r"\\end")]
+ END_ENVIRONMENT_NAME,
+
+ #[regex(r"\\\[")]
+ BEGIN_EQUATION_NAME,
+
+ #[regex(r"\\\]")]
+ END_EQUATION_NAME,
+
+ #[regex(r"\\part\*?")]
+ PART_NAME,
+
+ #[regex(r"\\chapter\*?")]
+ CHAPTER_NAME,
+
+ #[regex(r"\\section\*?")]
+ SECTION_NAME,
+
+ #[regex(r"\\subsection\*?")]
+ SUBSECTION_NAME,
+
+ #[regex(r"\\subsubsection\*?")]
+ SUBSUBSECTION_NAME,
+
+ #[regex(r"\\paragraph\*?")]
+ PARAGRAPH_NAME,
+
+ #[regex(r"\\subparagraph\*?")]
+ SUBPARAGRAPH_NAME,
+
+ #[regex(r"\\item")]
+ ENUM_ITEM_NAME,
+
+ #[regex(r"\\caption")]
+ CAPTION_NAME,
+
+ #[regex(r"\\cite|\\cite\*|\\Cite|\\nocite|\\citet|\\citep|\\citet\*|\\citep\*|\\citeauthor|\\citeauthor\*|\\Citeauthor|\\Citeauthor\*|\\citetitle|\\citetitle\*|\\citeyear|\\citeyear\*|\\citedate|\\citedate\*|\\citeurl|\\fullcite|\\citeyearpar|\\citealt|\\citealp|\\citetext|\\parencite|\\parencite\*|\\Parencite|\\footcite|\\footfullcite|\\footcitetext|\\textcite|\\Textcite|\\smartcite|\\Smartcite|\\supercite|\\autocite|\\Autocite|\\autocite\*|\\Autocite\*|\\volcite|\\Volcite|\\pvolcite|\\Pvolcite|\\fvolcite|\\ftvolcite|\\svolcite|\\Svolcite|\\tvolcite|\\Tvolcite|\\avolcite|\\Avolcite|\\notecite|\\notecite|\\pnotecite|\\Pnotecite|\\fnotecite|\\citeA|\\citeA\*")]
+ CITATION_NAME,
+
+ #[regex(r"\\usepackage|\\RequirePackage")]
+ PACKAGE_INCLUDE_NAME,
+
+ #[regex(r"\\documentclass")]
+ CLASS_INCLUDE_NAME,
+
+ #[regex(r"\\include|\\subfileinclude|\\input|\\subfile")]
+ LATEX_INCLUDE_NAME,
+
+ #[regex(r"\\addbibresource")]
+ BIBLATEX_INCLUDE_NAME,
+
+ #[regex(r"\\bibliography")]
+ BIBTEX_INCLUDE_NAME,
+
+ #[regex(r"\\includegraphics")]
+ GRAPHICS_INCLUDE_NAME,
+
+ #[regex(r"\\includesvg")]
+ SVG_INCLUDE_NAME,
+
+ #[regex(r"\\includeinkscape")]
+ INKSCAPE_INCLUDE_NAME,
+
+ #[regex(r"\\verbatiminput|\\VerbatimInput")]
+ VERBATIM_INCLUDE_NAME,
+
+ #[regex(r"\\import|\\subimport|\\inputfrom|\\subimportfrom|\\includefrom|\\subincludefrom")]
+ IMPORT_NAME,
+
+ #[regex(r"\\label")]
+ LABEL_DEFINITION_NAME,
+
+ #[regex(r"\\ref|\\vref|\\Vref|\\autoref|\\pageref|\\cref|\\Cref|\\cref*|\\Cref*|\\namecref|\\nameCref|\\lcnamecref|\\namecrefs|\\nameCrefs|\\lcnamecrefs|\\labelcref|\\labelcpageref|\\eqref")]
+ LABEL_REFERENCE_NAME,
+
+ #[regex(r"\\crefrange\*?|\\Crefrange\*?")]
+ LABEL_REFERENCE_RANGE_NAME,
+
+ #[regex(r"\\newlabel")]
+ LABEL_NUMBER_NAME,
+
+ #[regex(r"\\newcommand\*?|\\renewcommand|\\DeclareRobustCommand")]
+ COMMAND_DEFINITION_NAME,
+
+ #[regex(r"\\DeclareMathOperator\*?")]
+ MATH_OPERATOR_NAME,
+
+ #[regex(r"\\newglossaryentry")]
+ GLOSSARY_ENTRY_DEFINITION_NAME,
+
+ #[regex(r"\\gls|\\Gls|\\GLS|\\glspl|\\Glspl|\\GLSpl|\\glsdisp|\\glslink|\\glstext|\\Glstext|\\GLStext|\\glsfirst|\\Glsfirst|\\GLSfirst|\\glsplural|\\Glsplural|\\GLSplural|\\glsfirstplural|\\Glsfirstplural|\\GLSfirstplural|\\glsname|\\Glsname|\\GLSname|\\glssymbol|\\Glssymbol|\\glsdesc|\\Glsdesc|\\GLSdesc|\\glsuseri|\\Glsuseri|\\GLSuseri|\\glsuserii|\\Glsuserii|\\GLSuserii|\\glsuseriii|\\Glsuseriii|\\GLSuseriii|\\glsuseriv|\\Glsuseriv|\\GLSuseriv|\\glsuserv|\\Glsuserv|\\GLSuserv|\\glsuservi|\\Glsuservi|\\GLSuservi")]
+ GLOSSARY_ENTRY_REFERENCE_NAME,
+
+ #[regex(r"\\newacronym")]
+ ACRONYM_DEFINITION_NAME,
+
+ #[regex(r"\\DeclareAcronym")]
+ ACRONYM_DECLARATION_NAME,
+
+ #[regex(r"\\acrshort|\\Acrshort|\\ACRshort|\\acrshortpl|\\Acrshortpl|\\ACRshortpl|\\acrlong|\\Acrlong|\\ACRlong|\\acrlongpl|\\Acrlongpl|\\ACRlongpl|\\acrfull|\\Acrfull|\\ACRfull|\\acrfullpl|\\Acrfullpl|\\ACRfullpl|\\acs|\\Acs|\\acsp|\\Acsp|\\acl|\\Acl|\\aclp|\\Aclp|\\acf|\\Acf|\\acfp|\\Acfp|\\ac|\\Ac|\\acp|\\glsentrylong|\\Glsentrylong|\\glsentrylongpl|\\Glsentrylongpl|\\glsentryshort|\\Glsentryshort|\\glsentryshortpl|\\Glsentryshortpl|\\glsentryfullpl|\\Glsentryfullpl")]
+ ACRONYM_REFERENCE_NAME,
+
+ #[regex(r"\\newtheorem|\\declaretheorem")]
+ THEOREM_DEFINITION_NAME,
+
+ #[regex(r"\\color|\\colorbox|\\textcolor|\\pagecolor")]
+ COLOR_REFERENCE_NAME,
+
+ #[regex(r"\\definecolor")]
+ COLOR_DEFINITION_NAME,
+
+ #[regex(r"\\definecolorset")]
+ COLOR_SET_DEFINITION_NAME,
+
+ #[regex(r"\\usepgflibrary|\\usetikzlibrary")]
+ TIKZ_LIBRARY_IMPORT_NAME,
+
+ #[regex(r"\\newenvironment|\\newenvironment*")]
+ ENVIRONMENT_DEFINITION_NAME,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Lexer<'a> {
- stream: CharStream<'a>,
+ tokens: Vec<(SyntaxKind, &'a str)>,
}
impl<'a> Lexer<'a> {
pub fn new(text: &'a str) -> Self {
- Self {
- stream: CharStream::new(text),
+ let mut tokens = Vec::new();
+ let mut lexer = Token::lexer(text);
+ while let Some(kind) = lexer.next() {
+ tokens.push((
+ unsafe { std::mem::transmute::<Token, SyntaxKind>(kind) },
+ lexer.slice(),
+ ));
}
+ tokens.reverse();
+ Self { tokens }
}
- fn single_char(&mut self, kind: TokenKind) -> Token {
- self.stream.start_span();
- self.stream.next();
- let span = self.stream.end_span();
- Token::new(span, kind)
+ pub fn peek(&self) -> Option<SyntaxKind> {
+ self.tokens.last().map(|(kind, _)| *kind)
}
- fn math(&mut self) -> Token {
- self.stream.start_span();
- self.stream.next();
- if self.stream.satifies(|c| *c == '$') {
- self.stream.next();
- }
- let span = self.stream.end_span();
- Token::new(span, TokenKind::Math)
+ pub fn eat(&mut self) -> Option<(SyntaxKind, &'a str)> {
+ self.tokens.pop()
}
+}
- fn command(&mut self) -> Token {
- let span = self.stream.command();
- Token::new(span, TokenKind::Command)
- }
+#[cfg(test)]
+mod tests {
+ use insta::assert_debug_snapshot;
- fn word(&mut self) -> Token {
- self.stream.start_span();
- self.stream.next();
- while self.stream.satifies(|c| is_word_char(*c)) {
- self.stream.next();
- }
+ use super::*;
- let span = self.stream.end_span();
- Token::new(span, TokenKind::Word)
+ fn verify(text: &str) -> Vec<(SyntaxKind, &str)> {
+ let mut tokens = Lexer::new(text).tokens;
+ tokens.reverse();
+ tokens
}
-}
-impl<'a> Iterator for Lexer<'a> {
- type Item = Token;
-
- fn next(&mut self) -> Option<Token> {
- loop {
- match self.stream.peek() {
- Some('%') => {
- self.stream.skip_rest_of_line();
- }
- Some('{') => {
- return Some(self.single_char(TokenKind::BeginGroup));
- }
- Some('}') => {
- return Some(self.single_char(TokenKind::EndGroup));
- }
- Some('[') => {
- return Some(self.single_char(TokenKind::BeginOptions));
- }
- Some(']') => {
- return Some(self.single_char(TokenKind::EndOptions));
- }
- Some('$') => {
- return Some(self.math());
- }
- Some(',') => {
- return Some(self.single_char(TokenKind::Comma));
- }
- Some('\\') => {
- return Some(self.command());
- }
- Some(c) => {
- if c.is_whitespace() {
- self.stream.next();
- } else {
- return Some(self.word());
- }
- }
- None => {
- return None;
- }
- }
- }
+ #[test]
+ fn test_empty() {
+ assert_debug_snapshot!(verify(r#""#));
}
-}
-
-fn is_word_char(c: char) -> bool {
- !c.is_whitespace()
- && c != '%'
- && c != '{'
- && c != '}'
- && c != '['
- && c != ']'
- && c != '\\'
- && c != '$'
- && c != ','
-}
-#[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());
+ #[test]
+ fn test_delimiters() {
+ assert_debug_snapshot!(verify(r#"{foo} (bar) [baz, qux = foo-bar]"#));
}
#[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());
+ fn test_command_with_parameter() {
+ assert_debug_snapshot!(verify(r#"\newcommand{\id}[1]{#1}"#));
}
#[test]
- fn command() {
- let mut lexer = Lexer::new("\\foo\\bar@baz\n\\foo*");
- verify(&mut lexer, 0, 0, "\\foo", TokenKind::Command);
- verify(&mut lexer, 0, 4, "\\bar@baz", TokenKind::Command);
- verify(&mut lexer, 1, 0, "\\foo*", TokenKind::Command);
- assert_eq!(None, lexer.next());
+ fn test_command_with_star() {
+ assert_debug_snapshot!(verify(r#"\section*{Foo}"#));
}
#[test]
- fn escape_sequence() {
- let mut lexer = Lexer::new("\\%\\**");
- verify(&mut lexer, 0, 0, "\\%", TokenKind::Command);
- verify(&mut lexer, 0, 2, "\\*", TokenKind::Command);
- verify(&mut lexer, 0, 4, "*", TokenKind::Word);
- assert_eq!(None, lexer.next());
+ fn test_escape_sequence() {
+ assert_debug_snapshot!(verify(r#"\% hello"#));
}
#[test]
- fn group_delimiter() {
- let mut lexer = Lexer::new("{}[]");
- verify(&mut lexer, 0, 0, "{", TokenKind::BeginGroup);
- verify(&mut lexer, 0, 1, "}", TokenKind::EndGroup);
- verify(&mut lexer, 0, 2, "[", TokenKind::BeginOptions);
- verify(&mut lexer, 0, 3, "]", TokenKind::EndOptions);
- assert_eq!(None, lexer.next());
+ fn test_formula() {
+ assert_debug_snapshot!(verify(r#"$ f(x) = y $$"#));
}
#[test]
- fn math() {
- let mut lexer = Lexer::new("$$ $ $");
- verify(&mut lexer, 0, 0, "$$", TokenKind::Math);
- verify(&mut lexer, 0, 3, "$", TokenKind::Math);
- verify(&mut lexer, 0, 5, "$", TokenKind::Math);
- assert_eq!(None, lexer.next());
+ fn test_comment() {
+ assert_debug_snapshot!(verify("hello %world\r\ntest %test"));
}
#[test]
- fn line_comment() {
- let mut lexer = Lexer::new(" %foo \nfoo");
- verify(&mut lexer, 1, 0, "foo", TokenKind::Word);
- assert_eq!(None, lexer.next());
+ fn test_invalid_parameter() {
+ assert_debug_snapshot!(verify(r#"#"#))
}
}
diff --git a/support/texlab/src/syntax/latex/mod.rs b/support/texlab/src/syntax/latex/mod.rs
deleted file mode 100644
index cee83ab04e..0000000000
--- a/support/texlab/src/syntax/latex/mod.rs
+++ /dev/null
@@ -1,686 +0,0 @@
-mod analysis;
-mod ast;
-mod lexer;
-mod parser;
-
-pub use self::{analysis::*, ast::*};
-
-use self::{lexer::Lexer, parser::Parser};
-use crate::{
- protocol::{Options, Uri},
- tex::Resolver,
-};
-use std::path::Path;
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub struct OpenParams<'a> {
- pub text: &'a str,
- pub uri: &'a Uri,
- pub resolver: &'a Resolver,
- pub options: &'a Options,
- pub current_dir: &'a Path,
-}
-
-pub fn open(params: OpenParams) -> SymbolTable {
- let OpenParams {
- text,
- uri,
- resolver,
- options,
- current_dir,
- } = params;
-
- let lexer = Lexer::new(text);
- let parser = Parser::new(lexer);
- let tree = parser.parse();
-
- let params = SymbolTableParams {
- tree,
- uri,
- resolver,
- options,
- current_dir,
- };
- SymbolTable::analyze(params)
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::{
- protocol::{Options, Range, RangeExt, Uri},
- syntax::{generic_ast::AstNodeIndex, text::SyntaxNode},
- tex::Resolver,
- };
- use indoc::indoc;
- use std::env;
-
- fn open_simple(text: &str) -> SymbolTable {
- open(OpenParams {
- text: text.trim(),
- uri: &Uri::parse("http://www.foo.com/bar.tex").unwrap(),
- resolver: &Resolver::default(),
- options: &Options::default(),
- current_dir: &env::current_dir().unwrap(),
- })
- }
-
- #[derive(Debug, Default)]
- struct TreeTraversal {
- nodes: Vec<AstNodeIndex>,
- }
-
- impl Visitor for TreeTraversal {
- fn visit(&mut self, tree: &Tree, node: AstNodeIndex) {
- self.nodes.push(node);
- tree.walk(self, node);
- }
- }
-
- mod range {
- use super::*;
-
- fn verify(expected_ranges: Vec<Range>, text: &str) {
- let table = open_simple(text);
-
- let mut traversal = TreeTraversal::default();
- traversal.visit(&table.tree, table.tree.root);
- let actual_ranges: Vec<_> = traversal
- .nodes
- .into_iter()
- .map(|node| table[node].range())
- .collect();
- assert_eq!(actual_ranges, expected_ranges);
- }
-
- #[test]
- fn command() {
- verify(
- vec![
- Range::new_simple(0, 0, 2, 14),
- Range::new_simple(0, 0, 0, 23),
- Range::new_simple(0, 14, 0, 23),
- Range::new_simple(0, 15, 0, 22),
- Range::new_simple(1, 0, 1, 20),
- Range::new_simple(1, 11, 1, 20),
- Range::new_simple(1, 12, 1, 19),
- Range::new_simple(2, 0, 2, 14),
- Range::new_simple(2, 4, 2, 9),
- Range::new_simple(2, 5, 2, 8),
- Range::new_simple(2, 9, 2, 14),
- Range::new_simple(2, 10, 2, 13),
- ],
- indoc!(
- r#"
- \documentclass{article}
- \usepackage{amsmath}
- \foo[bar]{baz}
- "#
- ),
- );
- }
-
- #[test]
- fn text() {
- verify(
- vec![
- Range::new_simple(0, 0, 0, 11),
- Range::new_simple(0, 0, 0, 11),
- ],
- indoc!(
- r#"
- foo bar baz
- "#
- ),
- );
- }
-
- #[test]
- fn text_bracket() {
- verify(
- vec![Range::new_simple(0, 0, 0, 5), Range::new_simple(0, 0, 0, 5)],
- indoc!(
- r#"
- ]foo[
- "#
- ),
- );
- }
-
- #[test]
- fn group() {
- verify(
- vec![
- Range::new_simple(0, 0, 0, 15),
- Range::new_simple(0, 0, 0, 15),
- Range::new_simple(0, 2, 0, 5),
- Range::new_simple(0, 6, 0, 13),
- Range::new_simple(0, 8, 0, 11),
- ],
- indoc!(
- r#"
- { foo { bar } }
- "#
- ),
- );
- }
-
- #[test]
- fn group_incomplete() {
- verify(
- vec![Range::new_simple(0, 1, 0, 2), Range::new_simple(0, 1, 0, 2)],
- indoc!(
- r#"
- }{
- "#
- ),
- );
- }
-
- #[test]
- fn math() {
- verify(
- vec![
- Range::new_simple(0, 0, 0, 9),
- Range::new_simple(0, 0, 0, 1),
- Range::new_simple(0, 2, 0, 7),
- Range::new_simple(0, 8, 0, 9),
- ],
- indoc!(
- r#"
- $ x = 1 $
- "#
- ),
- );
- }
-
- #[test]
- fn comma() {
- verify(
- vec![
- Range::new_simple(0, 0, 0, 8),
- Range::new_simple(0, 0, 0, 3),
- Range::new_simple(0, 3, 0, 4),
- Range::new_simple(0, 5, 0, 8),
- ],
- indoc!(
- r#"
- foo, bar
- "#
- ),
- );
- }
- }
-
- mod command {
- use super::*;
-
- fn verify(expected_names: Vec<&str>, text: &str) {
- let table = open(OpenParams {
- text,
- uri: &Uri::parse("http://www.foo.com/bar.tex").unwrap(),
- resolver: &Resolver::default(),
- options: &Options::default(),
- current_dir: &env::current_dir().unwrap(),
- });
-
- let actual_names: Vec<_> = table
- .commands
- .iter()
- .map(|node| table.tree.as_command(*node).unwrap().name.text())
- .collect();
-
- assert_eq!(actual_names, expected_names);
- }
-
- #[test]
- fn basic() {
- verify(
- vec!["\\documentclass", "\\usepackage", "\\begin", "\\end"],
- indoc!(
- r#"
- \documentclass{article}
- \usepackage{amsmath}
- \begin{document}
- Hello World
- \end{document}
- "#
- ),
- );
- }
-
- #[test]
- fn star() {
- verify(
- vec!["\\section*", "\\subsection*"],
- indoc!(
- r#"
- \section*{Foo}
- \subsection**{Bar}
- "#
- ),
- );
- }
-
- #[test]
- fn at() {
- verify(vec!["\\foo@bar"], indoc!(r#"\foo@bar"#));
- }
-
- #[test]
- fn escape() {
- verify(vec!["\\%"], indoc!(r#"\%foo"#))
- }
- }
-
- mod environment {
- use super::*;
-
- fn verify(expected_names: Vec<(&str, &str)>, text: &str) {
- let table = open_simple(text);
- let actual_names: Vec<_> = table
- .environments
- .iter()
- .map(|env| {
- (
- env.left
- .name(&table.tree)
- .map(Token::text)
- .unwrap_or_default(),
- env.right
- .name(&table.tree)
- .map(Token::text)
- .unwrap_or_default(),
- )
- })
- .collect();
-
- assert_eq!(actual_names, expected_names);
- }
-
- #[test]
- fn nested() {
- verify(
- vec![("b", "b"), ("a", "a")],
- indoc!(
- r#"
- \begin{a}
- \begin{b}
- \end{b}
- \end{a}
- "#
- ),
- );
- }
-
- #[test]
- fn empty_name() {
- verify(
- vec![("a", ""), ("", "b")],
- indoc!(
- r#"
- \begin{a}
- \end{}
- \begin{}
- \end{b}
- "#
- ),
- );
- }
-
- #[test]
- fn incomplete() {
- verify(
- Vec::new(),
- indoc!(
- r#"
- \end{a}
- \begin{a}
- "#
- ),
- );
- }
-
- #[test]
- fn standalone_true() {
- let table = open_simple(r#"\begin{document}\end{document}"#);
- assert!(table.is_standalone);
- }
-
- #[test]
- fn standalone_false() {
- let table = open_simple(r#"\begin{doc}\end{doc}"#);
- assert!(!table.is_standalone);
- }
- }
-
- mod include {
- use super::*;
-
- fn verify(expected_targets: Vec<Vec<&str>>, resolver: Resolver, text: &str) {
- let table = open(OpenParams {
- text,
- uri: &Uri::parse("http://www.foo.com/dir1/dir2/foo.tex").unwrap(),
- resolver: &resolver,
- options: &Options::default(),
- current_dir: &env::current_dir().unwrap(),
- });
-
- assert_eq!(table.includes.len(), 1);
- let include = &table.includes[0];
- let actual_targets: Vec<Vec<&str>> = include
- .all_targets
- .iter()
- .map(|targets| targets.iter().map(|target| target.as_str()).collect())
- .collect();
-
- assert_eq!(actual_targets, expected_targets);
- }
-
- #[test]
- fn same_directory() {
- verify(
- vec![vec![
- "http://www.foo.com/dir1/dir2/bar",
- "http://www.foo.com/dir1/dir2/bar.tex",
- ]],
- Resolver::default(),
- indoc!(r#"\include{bar}"#),
- );
- }
-
- #[test]
- fn two_paths() {
- verify(
- vec![
- vec![
- "http://www.foo.com/dir1/dir2/bar.tex",
- "http://www.foo.com/dir1/dir2/bar.tex.tex",
- ],
- vec![
- "http://www.foo.com/dir1/dir2/baz.tex",
- "http://www.foo.com/dir1/dir2/baz.tex.tex",
- ],
- ],
- Resolver::default(),
- indoc!(r#"\input{bar.tex, ./baz.tex}"#),
- );
- }
-
- #[test]
- fn sub_directory() {
- verify(
- vec![vec![
- "http://www.foo.com/dir1/dir2/dir3/bar",
- "http://www.foo.com/dir1/dir2/dir3/bar.tex",
- ]],
- Resolver::default(),
- indoc!(r#"\include{dir3/bar}"#),
- );
- }
-
- #[test]
- fn parent_directory() {
- verify(
- vec![vec![
- "http://www.foo.com/dir1/bar",
- "http://www.foo.com/dir1/bar.tex",
- ]],
- Resolver::default(),
- indoc!(r#"\include{../bar}"#),
- );
- }
-
- #[test]
- fn distro_file() {
- let mut resolver = Resolver::default();
- let path = env::current_dir().unwrap().join("biblatex-examples.bib");
- resolver
- .files_by_name
- .insert("biblatex-examples.bib".into(), path.clone());
- verify(
- vec![vec![
- "http://www.foo.com/dir1/dir2/biblatex-examples.bib",
- "http://www.foo.com/dir1/dir2/biblatex-examples.bib.bib",
- Uri::from_file_path(&path).unwrap().as_str(),
- ]],
- resolver,
- indoc!(r#"\addbibresource{biblatex-examples.bib}"#),
- );
- }
-
- #[test]
- fn component() {
- let table = open(OpenParams {
- text: indoc!(
- r#"
- \documentclass{article}
- \usepackage{amsmath}
- \usepackage{geometry, lipsum}
- "#
- ),
- uri: &Uri::parse("http://www.foo.com/bar.tex").unwrap(),
- resolver: &Resolver::default(),
- options: &Options::default(),
- current_dir: &env::current_dir().unwrap(),
- });
- assert_eq!(
- table.components,
- vec!["article.cls", "amsmath.sty", "geometry.sty", "lipsum.sty"]
- );
- }
- }
-
- #[test]
- fn citation() {
- let table = open_simple(indoc!(
- r#"
- \cite{key1}
- \cite{key2, key3}
- \nocite{*}
- "#
- ));
-
- let expected_keys = vec![vec!["key1"], vec!["key2", "key3"], vec!["*"]];
-
- let actual_keys: Vec<Vec<&str>> = table
- .citations
- .iter()
- .map(|cit| cit.keys(&table.tree).into_iter().map(Token::text).collect())
- .collect();
-
- assert_eq!(actual_keys, expected_keys);
- }
-
- #[test]
- fn command_definition() {
- let table = open_simple(indoc!(
- r#"
- \newcommand{\foo}{Foo}
- \newcommand[2]{\bar}{Bar}
- \renewcommand{\baz}{Baz}
- \qux
- "#
- ));
-
- let expected_cmds = vec!["\\foo", "\\bar", "\\baz"];
-
- let actual_cmds: Vec<&str> = table
- .command_definitions
- .iter()
- .map(|def| def.definition_name(&table.tree))
- .collect();
-
- assert_eq!(actual_cmds, expected_cmds);
- }
-
- #[test]
- fn glossary_entry() {
- let table = open_simple(indoc!(
- r#"
- \newglossaryentry{foo}{...}
- \newacronym{bar}{...}
- "#
- ));
-
- let expected_entries = vec!["foo", "bar"];
-
- let actual_entries: Vec<&str> = table
- .glossary_entries
- .iter()
- .map(|entry| entry.label(&table.tree).text())
- .collect();
-
- assert_eq!(actual_entries, expected_entries);
- }
-
- #[test]
- fn equation() {
- let table = open_simple(indoc!(
- r#"
- \[
- e^{i \pi} + 1 = 0
- \]
- \] \[
- "#
- ));
-
- assert_eq!(table.equations.len(), 1);
- }
-
- #[test]
- fn inline() {
- let table = open_simple(indoc!(
- r#"
- $ x $
- $
- "#
- ));
-
- assert_eq!(table.inlines.len(), 1);
- }
-
- #[test]
- fn math_operator() {
- let table = open_simple(indoc!(
- r#"
- \DeclareMathOperator{\foo}{foo}
- "#
- ));
-
- assert_eq!(table.math_operators.len(), 1);
- assert_eq!(
- table.math_operators[0].definition_name(&table.tree),
- "\\foo"
- );
- }
-
- #[test]
- fn theorem_definition() {
- let table = open_simple(indoc!(
- r#"
- \newtheorem{lemma}{Lemma}
- "#
- ));
-
- assert_eq!(table.theorem_definitions.len(), 1);
- assert_eq!(
- table.theorem_definitions[0].name(&table.tree).text(),
- "lemma"
- );
- }
-
- #[test]
- fn section() {
- let table = open_simple(indoc!(
- r#"
- \section{Introduction to \LaTeX}
- \subsection*{Foo
- "#
- ));
- assert_eq!(table.sections.len(), 2);
- assert_eq!(
- table.sections[0].print(&table.tree).unwrap(),
- "Introduction to \\LaTeX"
- );
- assert_eq!(table.sections[1].print(&table.tree), None);
- }
-
- #[test]
- fn label() {
- let table = open_simple(indoc!(
- r#"
- \label{foo}
- \ref{bar, baz}
- "#
- ));
-
- let expected_names = vec![vec!["foo"], vec!["bar", "baz"]];
-
- let actual_names: Vec<Vec<&str>> = table
- .labels
- .iter()
- .map(|label| {
- label
- .names(&table.tree)
- .into_iter()
- .map(Token::text)
- .collect()
- })
- .collect();
-
- assert_eq!(actual_names, expected_names);
- }
-
- #[test]
- fn label_numbering() {
- let table = open_simple(indoc!(
- r#"
- \newlabel{foo}{{1}{1}}
- "#
- ));
-
- assert_eq!(table.label_numberings.len(), 1);
- assert_eq!(table.label_numberings[0].name(&table.tree).text(), "foo");
- assert_eq!(table.label_numberings[0].number, "1");
- }
-
- #[test]
- fn caption() {
- let table = open_simple(indoc!(
- r#"
- \caption{Foo \LaTeX Bar}
- "#
- ));
-
- assert_eq!(table.captions.len(), 1);
- assert_eq!(
- table.captions[0].print(&table.tree).unwrap(),
- "Foo \\LaTeX Bar"
- );
- }
-
- #[test]
- fn item_without_name() {
- let table = open_simple(indoc!(
- r#"
- \item
- "#
- ));
-
- assert_eq!(table.items.len(), 1);
- assert_eq!(table.items[0].name(&table.tree), None);
- }
-
- #[test]
- fn item_with_name() {
- let table = open_simple(indoc!(
- r#"
- \item[foo bar]
- "#
- ));
-
- assert_eq!(table.items.len(), 1);
- assert_eq!(table.items[0].name(&table.tree).unwrap(), "foo bar");
- }
-}
diff --git a/support/texlab/src/syntax/latex/parser.rs b/support/texlab/src/syntax/latex/parser.rs
index 4ace4fbcd6..4333235ba5 100644
--- a/support/texlab/src/syntax/latex/parser.rs
+++ b/support/texlab/src/syntax/latex/parser.rs
@@ -1,186 +1,1709 @@
-use super::ast::*;
-use crate::{
- protocol::{Range, RangeExt},
- syntax::{
- generic_ast::{Ast, AstNodeIndex},
- text::SyntaxNode,
- },
+use cstree::GreenNodeBuilder;
+
+use super::{
+ lexer::Lexer,
+ SyntaxKind::{self, *},
+ SyntaxNode,
};
-use std::iter::Peekable;
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-enum Scope {
- Root,
- Group,
- Options,
+#[derive(Clone)]
+pub struct Parse {
+ pub root: SyntaxNode,
+}
+
+#[derive(Debug, Clone, Copy)]
+struct ParserContext {
+ allow_environment: bool,
+ allow_comma: bool,
+}
+
+impl Default for ParserContext {
+ fn default() -> Self {
+ Self {
+ allow_environment: true,
+ allow_comma: true,
+ }
+ }
}
#[derive(Debug)]
-pub struct Parser<I: Iterator<Item = Token>> {
- tree: Ast<Node>,
- tokens: Peekable<I>,
+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(text: &'a str) -> Self {
Self {
- tree: Ast::new(),
- tokens: tokens.peekable(),
+ lexer: Lexer::new(text),
+ builder: GreenNodeBuilder::new(),
}
}
- pub fn parse(mut self) -> Tree {
- let children = self.content(Scope::Root);
+ fn eat(&mut self) {
+ let (kind, text) = self.lexer.eat().unwrap();
+ self.builder.token(kind.into(), text);
+ }
- let range = if children.is_empty() {
- Range::new_simple(0, 0, 0, 0)
+ fn peek(&self) -> Option<SyntaxKind> {
+ self.lexer.peek()
+ }
+
+ fn expect(&mut self, kind: SyntaxKind) {
+ if self.peek() == Some(kind) {
+ self.eat();
+ self.trivia();
} else {
- let start = self.tree[children[0]].start();
- let end = self.tree[children[children.len() - 1]].end();
- Range::new(start, end)
- };
+ self.builder.token(MISSING.into(), "");
+ }
+ }
- let root = self.tree.add_node(Node::Root(Root { range }));
- self.connect(root, &children);
- Tree {
- inner: self.tree,
- root,
+ fn expect2(&mut self, kind1: SyntaxKind, kind2: SyntaxKind) {
+ if self
+ .peek()
+ .filter(|&kind| kind == kind1 || kind == kind2)
+ .is_some()
+ {
+ self.eat();
+ self.trivia();
+ } else {
+ self.builder.token(MISSING.into(), "");
}
}
- fn content(&mut self, scope: Scope) -> Vec<AstNodeIndex> {
- let mut children = Vec::new();
- while let Some(ref token) = self.tokens.peek() {
- match token.kind {
- TokenKind::Word | TokenKind::BeginOptions => {
- children.push(self.text(scope));
- }
- TokenKind::Command => {
- children.push(self.command());
- }
- TokenKind::Comma => {
- children.push(self.comma());
- }
- TokenKind::Math => {
- children.push(self.math());
- }
- TokenKind::BeginGroup => {
- children.push(self.group(GroupKind::Group));
- }
- TokenKind::EndGroup => {
- if scope == Scope::Root {
- self.tokens.next();
- } else {
- return children;
- }
- }
- TokenKind::EndOptions => {
- if scope == Scope::Options {
- return children;
+ fn trivia(&mut self) {
+ while self
+ .peek()
+ .filter(|&kind| matches!(kind, WHITESPACE | COMMENT))
+ .is_some()
+ {
+ self.eat();
+ }
+ }
+
+ pub fn parse(mut self) -> Parse {
+ self.builder.start_node(ROOT.into());
+ self.preamble();
+ while self.peek().is_some() {
+ self.content(ParserContext::default());
+ }
+ self.builder.finish_node();
+ let (green_node, interner) = self.builder.finish();
+ Parse {
+ root: SyntaxNode::new_root_with_resolver(green_node, interner.unwrap()),
+ }
+ }
+
+ fn content(&mut self, context: ParserContext) {
+ match self.peek().unwrap() {
+ WHITESPACE | COMMENT => self.eat(),
+ L_CURLY if context.allow_environment => self.curly_group(),
+ L_CURLY => self.curly_group_without_environments(),
+ L_BRACK | L_PAREN => self.mixed_group(),
+ R_CURLY | R_BRACK | R_PAREN => {
+ self.builder.start_node(ERROR.into());
+ self.eat();
+ self.builder.finish_node();
+ }
+ PARAMETER => self.eat(),
+ WORD | COMMA => self.text(context),
+ EQUALITY_SIGN => self.eat(),
+ DOLLAR => self.formula(),
+ GENERIC_COMMAND_NAME => self.generic_command(),
+ BEGIN_ENVIRONMENT_NAME if context.allow_environment => self.environment(),
+ BEGIN_ENVIRONMENT_NAME => self.generic_command(),
+ END_ENVIRONMENT_NAME => self.generic_command(),
+ BEGIN_EQUATION_NAME => self.equation(),
+ END_EQUATION_NAME => self.generic_command(),
+ MISSING | ERROR => self.eat(),
+ PART_NAME => self.part(),
+ CHAPTER_NAME => self.chapter(),
+ SECTION_NAME => self.section(),
+ SUBSECTION_NAME => self.subsection(),
+ SUBSUBSECTION_NAME => self.subsubsection(),
+ PARAGRAPH_NAME => self.paragraph(),
+ SUBPARAGRAPH_NAME => self.subparagraph(),
+ ENUM_ITEM_NAME => self.enum_item(),
+ CAPTION_NAME => self.caption(),
+ CITATION_NAME => self.citation(),
+ PACKAGE_INCLUDE_NAME => self.package_include(),
+ CLASS_INCLUDE_NAME => self.class_include(),
+ LATEX_INCLUDE_NAME => self.latex_include(),
+ BIBLATEX_INCLUDE_NAME => self.biblatex_include(),
+ BIBTEX_INCLUDE_NAME => self.bibtex_include(),
+ GRAPHICS_INCLUDE_NAME => self.graphics_include(),
+ SVG_INCLUDE_NAME => self.svg_include(),
+ INKSCAPE_INCLUDE_NAME => self.inkscape_include(),
+ VERBATIM_INCLUDE_NAME => self.verbatim_include(),
+ IMPORT_NAME => self.import(),
+ LABEL_DEFINITION_NAME => self.label_definition(),
+ LABEL_REFERENCE_NAME => self.label_reference(),
+ LABEL_REFERENCE_RANGE_NAME => self.label_reference_range(),
+ LABEL_NUMBER_NAME => self.label_number(),
+ COMMAND_DEFINITION_NAME => self.command_definition(),
+ MATH_OPERATOR_NAME => self.math_operator(),
+ GLOSSARY_ENTRY_DEFINITION_NAME => self.glossary_entry_definition(),
+ GLOSSARY_ENTRY_REFERENCE_NAME => self.glossary_entry_reference(),
+ ACRONYM_DEFINITION_NAME => self.acronym_definition(),
+ ACRONYM_DECLARATION_NAME => self.acronym_declaration(),
+ ACRONYM_REFERENCE_NAME => self.acronym_reference(),
+ THEOREM_DEFINITION_NAME => self.theorem_definition(),
+ COLOR_REFERENCE_NAME => self.color_reference(),
+ COLOR_DEFINITION_NAME => self.color_definition(),
+ COLOR_SET_DEFINITION_NAME => self.color_set_definition(),
+ TIKZ_LIBRARY_IMPORT_NAME => self.tikz_library_import(),
+ ENVIRONMENT_DEFINIITION_NAME => self.environment_definition(),
+ _ => unreachable!(),
+ }
+ }
+
+ fn text(&mut self, context: ParserContext) {
+ self.builder.start_node(TEXT.into());
+ self.eat();
+ while self
+ .peek()
+ .filter(|&kind| {
+ matches!(kind, WHITESPACE | COMMENT | WORD | COMMA)
+ && (context.allow_comma || kind != COMMA)
+ })
+ .is_some()
+ {
+ self.eat();
+ }
+ self.builder.finish_node();
+ }
+
+ fn group_with_token(
+ &mut self,
+ node_kind: SyntaxKind,
+ content_kind: SyntaxKind,
+ right_kind: SyntaxKind,
+ ) {
+ self.builder.start_node(node_kind.into());
+ self.eat();
+ self.trivia();
+ self.expect(content_kind);
+ self.expect(right_kind);
+ self.builder.finish_node();
+ }
+
+ fn curly_group(&mut self) {
+ self.builder.start_node(CURLY_GROUP.into());
+ self.eat();
+ while self
+ .peek()
+ .filter(|&kind| !matches!(kind, R_CURLY | END_ENVIRONMENT_NAME))
+ .is_some()
+ {
+ self.content(ParserContext::default());
+ }
+ self.expect(R_CURLY);
+ self.builder.finish_node();
+ }
+
+ fn curly_group_without_environments(&mut self) {
+ self.builder.start_node(CURLY_GROUP.into());
+ self.eat();
+ while self
+ .peek()
+ .filter(|&kind| !matches!(kind, R_CURLY))
+ .is_some()
+ {
+ self.content(ParserContext {
+ allow_environment: false,
+ allow_comma: true,
+ });
+ }
+ self.expect(R_CURLY);
+ self.builder.finish_node();
+ }
+
+ fn curly_group_word(&mut self) {
+ self.group_with_token(CURLY_GROUP_WORD, WORD, R_CURLY);
+ }
+
+ fn curly_group_word_list(&mut self) {
+ self.builder.start_node(CURLY_GROUP_WORD_LIST.into());
+ self.eat();
+
+ while self
+ .peek()
+ .filter(|&kind| matches!(kind, WHITESPACE | COMMENT | WORD | COMMA))
+ .is_some()
+ {
+ self.eat();
+ }
+
+ self.expect(R_CURLY);
+ self.builder.finish_node();
+ }
+
+ fn curly_group_command(&mut self) {
+ self.group_with_token(CURLY_GROUP_COMMAND, GENERIC_COMMAND_NAME, R_CURLY);
+ }
+
+ fn brack_group(&mut self) {
+ self.builder.start_node(BRACK_GROUP.into());
+ self.eat();
+ while self
+ .peek()
+ .filter(|&kind| {
+ !matches!(
+ kind,
+ R_CURLY
+ | R_BRACK
+ | PART_NAME
+ | CHAPTER_NAME
+ | SECTION_NAME
+ | SUBSECTION_NAME
+ | PARAGRAPH_NAME
+ | SUBPARAGRAPH_NAME
+ | ENUM_ITEM_NAME
+ | END_ENVIRONMENT_NAME
+ )
+ })
+ .is_some()
+ {
+ self.content(ParserContext::default());
+ }
+ self.expect(R_BRACK);
+ self.builder.finish_node();
+ }
+
+ fn brack_group_word(&mut self) {
+ self.group_with_token(BRACK_GROUP_WORD, WORD, R_BRACK);
+ }
+
+ // fn paren_group(&mut self) {
+ // self.builder.start_node(PAREN_GROUP.into());
+ // self.eat();
+ // while self
+ // .peek()
+ // .filter(|&kind| {
+ // !matches!(
+ // kind,
+ // R_CURLY
+ // | R_BRACK
+ // | R_PAREN
+ // | PART_NAME
+ // | CHAPTER_NAME
+ // | SECTION_NAME
+ // | SUBSECTION_NAME
+ // | PARAGRAPH_NAME
+ // | SUBPARAGRAPH_NAME
+ // | ENUM_ITEM_NAME
+ // | END_ENVIRONMENT_NAME
+ // )
+ // })
+ // .is_some()
+ // {
+ // self.content();
+ // }
+ // self.expect(R_PAREN);
+ // self.builder.finish_node();
+ // }
+
+ fn mixed_group(&mut self) {
+ self.builder.start_node(MIXED_GROUP.into());
+ self.eat();
+ self.trivia();
+ while self
+ .peek()
+ .filter(|&kind| {
+ !matches!(
+ kind,
+ R_CURLY
+ | R_BRACK
+ | R_PAREN
+ | PART_NAME
+ | CHAPTER_NAME
+ | SECTION_NAME
+ | SUBSECTION_NAME
+ | PARAGRAPH_NAME
+ | SUBPARAGRAPH_NAME
+ | ENUM_ITEM_NAME
+ | END_ENVIRONMENT_NAME
+ )
+ })
+ .is_some()
+ {
+ self.content(ParserContext::default());
+ }
+ self.expect2(R_BRACK, R_PAREN);
+ self.builder.finish_node();
+ }
+
+ fn key(&mut self) {
+ self.builder.start_node(KEY.into());
+ self.eat();
+ while self
+ .peek()
+ .filter(|&kind| matches!(kind, WHITESPACE | COMMENT | WORD))
+ .is_some()
+ {
+ self.eat();
+ }
+ self.builder.finish_node();
+ }
+
+ fn value(&mut self) {
+ self.builder.start_node(VALUE.into());
+ self.content(ParserContext {
+ allow_environment: true,
+ allow_comma: false,
+ });
+ self.builder.finish_node();
+ }
+
+ fn key_value_pair(&mut self) {
+ self.builder.start_node(KEY_VALUE_PAIR.into());
+ self.key();
+ if self.peek() == Some(EQUALITY_SIGN) {
+ self.eat();
+ self.trivia();
+ if self
+ .peek()
+ .filter(|&kind| {
+ !matches!(
+ kind,
+ END_ENVIRONMENT_NAME | R_CURLY | R_BRACK | R_PAREN | COMMA
+ )
+ })
+ .is_some()
+ {
+ self.value();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn key_value_body(&mut self) {
+ self.builder.start_node(KEY_VALUE_BODY.into());
+ while let Some(kind) = self.peek() {
+ match kind {
+ WHITESPACE | COMMENT => self.eat(),
+ WORD => {
+ self.key_value_pair();
+ if self.peek() == Some(COMMA) {
+ self.eat();
} else {
- children.push(self.text(scope));
+ break;
}
}
+ _ => break,
}
}
- children
- }
-
- fn group(&mut self, kind: GroupKind) -> AstNodeIndex {
- let left = self.tokens.next().unwrap();
- let scope = match kind {
- GroupKind::Group => Scope::Group,
- GroupKind::Options => Scope::Options,
- };
-
- let children = self.content(scope);
- let right_kind = match kind {
- GroupKind::Group => TokenKind::EndGroup,
- GroupKind::Options => TokenKind::EndOptions,
- };
-
- let right = if self.next_of_kind(right_kind) {
- self.tokens.next()
- } else {
- None
- };
-
- let end = right
- .as_ref()
- .map(SyntaxNode::end)
- .or_else(|| children.last().map(|child| self.tree[*child].end()))
- .unwrap_or_else(|| left.end());
- let range = Range::new(left.start(), end);
-
- let node = self.tree.add_node(Node::Group(Group {
- range,
- left,
- kind,
- right,
- }));
- self.connect(node, &children);
- node
- }
-
- fn command(&mut self) -> AstNodeIndex {
- let name = self.tokens.next().unwrap();
- let mut children = Vec::new();
- while let Some(token) = self.tokens.peek() {
- match token.kind {
- TokenKind::BeginGroup => children.push(self.group(GroupKind::Group)),
- TokenKind::BeginOptions => children.push(self.group(GroupKind::Options)),
+ self.builder.finish_node();
+ }
+
+ fn group_key_value(&mut self, node_kind: SyntaxKind, right_kind: SyntaxKind) {
+ self.builder.start_node(node_kind.into());
+ self.eat();
+ self.trivia();
+ self.key_value_body();
+ self.expect(right_kind);
+ self.builder.finish_node();
+ }
+
+ fn curly_group_key_value(&mut self) {
+ self.group_key_value(CURLY_GROUP_KEY_VALUE, R_CURLY);
+ }
+
+ fn brack_group_key_value(&mut self) {
+ self.group_key_value(BRACK_GROUP_KEY_VALUE, R_BRACK);
+ }
+
+ fn formula(&mut self) {
+ self.builder.start_node(FORMULA.into());
+ self.eat();
+ self.trivia();
+ while self
+ .peek()
+ .filter(|&kind| !matches!(kind, R_CURLY | END_ENVIRONMENT_NAME | DOLLAR))
+ .is_some()
+ {
+ self.content(ParserContext::default());
+ }
+ self.expect(DOLLAR);
+ self.builder.finish_node();
+ }
+
+ fn generic_command(&mut self) {
+ self.builder.start_node(GENERIC_COMMAND.into());
+ self.eat();
+ while let Some(kind) = self.peek() {
+ match kind {
+ WHITESPACE | COMMENT => self.eat(),
+ L_CURLY => self.curly_group(),
+ L_BRACK | L_PAREN => self.mixed_group(),
_ => break,
}
}
+ self.builder.finish_node();
+ }
+
+ fn equation(&mut self) {
+ self.builder.start_node(EQUATION.into());
+ self.eat();
+ while self
+ .peek()
+ .filter(|&kind| !matches!(kind, END_ENVIRONMENT_NAME | R_CURLY | END_EQUATION_NAME))
+ .is_some()
+ {
+ self.content(ParserContext::default());
+ }
+ self.expect(END_EQUATION_NAME);
+ self.builder.finish_node();
+ }
- let end = children
- .last()
- .map(|child| self.tree[*child].end())
- .unwrap_or_else(|| name.end());
- let range = Range::new(name.start(), end);
+ fn begin(&mut self) {
+ self.builder.start_node(BEGIN.into());
+ self.eat();
+ self.trivia();
- let node = self.tree.add_node(Node::Command(Command { range, name }));
- self.connect(node, &children);
- node
+ if self.peek() == Some(L_CURLY) {
+ self.curly_group_word();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ if self.peek() == Some(L_BRACK) {
+ self.brack_group();
+ }
+ self.builder.finish_node();
}
- fn text(&mut self, scope: Scope) -> AstNodeIndex {
- let mut words = Vec::new();
- while let Some(ref token) = self.tokens.peek() {
- let kind = token.kind;
- let opts = kind == TokenKind::EndOptions && scope != Scope::Options;
- if kind == TokenKind::Word || kind == TokenKind::BeginOptions || opts {
- words.push(self.tokens.next().unwrap());
- } else {
- break;
- }
+ fn end(&mut self) {
+ self.builder.start_node(END.into());
+ self.eat();
+ self.trivia();
+
+ if self.peek() == Some(L_CURLY) {
+ self.curly_group_word();
+ } else {
+ self.builder.token(MISSING.into(), "");
}
- let range = Range::new(words[0].start(), words[words.len() - 1].end());
- self.tree.add_node(Node::Text(Text { range, words }))
+
+ self.builder.finish_node();
}
- fn comma(&mut self) -> AstNodeIndex {
- let token = self.tokens.next().unwrap();
- let range = token.range();
- self.tree.add_node(Node::Comma(Comma { range, token }))
+ fn environment(&mut self) {
+ self.builder.start_node(ENVIRONMENT.into());
+ self.begin();
+
+ while self
+ .peek()
+ .filter(|&kind| kind != END_ENVIRONMENT_NAME)
+ .is_some()
+ {
+ self.content(ParserContext::default());
+ }
+
+ if self.peek() == Some(END_ENVIRONMENT_NAME) {
+ self.end();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+ self.builder.finish_node();
}
- fn math(&mut self) -> AstNodeIndex {
- let token = self.tokens.next().unwrap();
- let range = token.range();
- self.tree.add_node(Node::Math(Math { range, token }))
+ fn preamble(&mut self) {
+ self.builder.start_node(PREAMBLE.into());
+ while self
+ .peek()
+ .filter(|&kind| kind != END_ENVIRONMENT_NAME)
+ .is_some()
+ {
+ self.content(ParserContext::default());
+ }
+ self.builder.finish_node();
}
- fn connect(&mut self, parent: AstNodeIndex, children: &[AstNodeIndex]) {
- for child in children {
- self.tree.add_edge(parent, *child);
+ fn part(&mut self) {
+ self.builder.start_node(PART.into());
+ self.eat();
+ self.trivia();
+
+ if self.peek() == Some(L_CURLY) {
+ self.curly_group();
+ } else {
+ self.builder.token(MISSING.into(), "");
}
+
+ while self
+ .peek()
+ .filter(|&kind| !matches!(kind, END_ENVIRONMENT_NAME | R_CURLY | PART_NAME))
+ .is_some()
+ {
+ self.content(ParserContext::default());
+ }
+ self.builder.finish_node();
}
- fn next_of_kind(&mut self, kind: TokenKind) -> bool {
- self.tokens
+ fn chapter(&mut self) {
+ self.builder.start_node(CHAPTER.into());
+ self.eat();
+ self.trivia();
+
+ if self.peek() == Some(L_CURLY) {
+ self.curly_group();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ while self
.peek()
- .filter(|token| token.kind == kind)
+ .filter(|&kind| {
+ !matches!(
+ kind,
+ END_ENVIRONMENT_NAME | R_CURLY | PART_NAME | CHAPTER_NAME
+ )
+ })
.is_some()
+ {
+ self.content(ParserContext::default());
+ }
+ self.builder.finish_node();
+ }
+
+ fn section(&mut self) {
+ self.builder.start_node(SECTION.into());
+ self.eat();
+ self.trivia();
+
+ if self.peek() == Some(L_CURLY) {
+ self.curly_group();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ while self
+ .peek()
+ .filter(|&kind| {
+ !matches!(
+ kind,
+ END_ENVIRONMENT_NAME | R_CURLY | PART_NAME | CHAPTER_NAME | SECTION_NAME
+ )
+ })
+ .is_some()
+ {
+ self.content(ParserContext::default());
+ }
+ self.builder.finish_node();
+ }
+
+ fn subsection(&mut self) {
+ self.builder.start_node(SUBSECTION.into());
+ self.eat();
+ self.trivia();
+
+ if self.peek() == Some(L_CURLY) {
+ self.curly_group();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ while self
+ .peek()
+ .filter(|&kind| {
+ !matches!(
+ kind,
+ END_ENVIRONMENT_NAME
+ | R_CURLY
+ | PART_NAME
+ | CHAPTER_NAME
+ | SECTION_NAME
+ | SUBSECTION_NAME
+ )
+ })
+ .is_some()
+ {
+ self.content(ParserContext::default());
+ }
+ self.builder.finish_node();
+ }
+
+ fn subsubsection(&mut self) {
+ self.builder.start_node(SUBSUBSECTION.into());
+ self.eat();
+ self.trivia();
+
+ if self.peek() == Some(L_CURLY) {
+ self.curly_group();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ while self
+ .peek()
+ .filter(|&kind| {
+ !matches!(
+ kind,
+ END_ENVIRONMENT_NAME
+ | R_CURLY
+ | PART_NAME
+ | CHAPTER_NAME
+ | SECTION_NAME
+ | SUBSECTION_NAME
+ | SUBSUBSECTION_NAME
+ )
+ })
+ .is_some()
+ {
+ self.content(ParserContext::default());
+ }
+ self.builder.finish_node();
+ }
+
+ fn paragraph(&mut self) {
+ self.builder.start_node(PARAGRAPH.into());
+ self.eat();
+ self.trivia();
+
+ if self.peek() == Some(L_CURLY) {
+ self.curly_group();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ while self
+ .peek()
+ .filter(|&kind| {
+ !matches!(
+ kind,
+ END_ENVIRONMENT_NAME
+ | R_CURLY
+ | PART_NAME
+ | CHAPTER_NAME
+ | SECTION_NAME
+ | SUBSECTION_NAME
+ | SUBSUBSECTION_NAME
+ | PARAGRAPH_NAME
+ )
+ })
+ .is_some()
+ {
+ self.content(ParserContext::default());
+ }
+ self.builder.finish_node();
+ }
+
+ fn subparagraph(&mut self) {
+ self.builder.start_node(SUBPARAGRAPH.into());
+ self.eat();
+ self.trivia();
+
+ if self.peek() == Some(L_CURLY) {
+ self.curly_group();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ while self
+ .peek()
+ .filter(|&kind| {
+ !matches!(
+ kind,
+ END_ENVIRONMENT_NAME
+ | R_CURLY
+ | PART_NAME
+ | CHAPTER_NAME
+ | SECTION_NAME
+ | SUBSECTION_NAME
+ | SUBSUBSECTION_NAME
+ | PARAGRAPH_NAME
+ | SUBPARAGRAPH_NAME
+ )
+ })
+ .is_some()
+ {
+ self.content(ParserContext::default());
+ }
+ self.builder.finish_node();
+ }
+
+ fn enum_item(&mut self) {
+ self.builder.start_node(ENUM_ITEM.into());
+ self.eat();
+ self.trivia();
+
+ if self.peek() == Some(L_BRACK) {
+ self.brack_group();
+ }
+
+ while self
+ .peek()
+ .filter(|&kind| {
+ !matches!(
+ kind,
+ END_ENVIRONMENT_NAME
+ | R_CURLY
+ | PART_NAME
+ | CHAPTER_NAME
+ | SECTION_NAME
+ | SUBSECTION_NAME
+ | SUBSUBSECTION_NAME
+ | PARAGRAPH_NAME
+ | SUBPARAGRAPH_NAME
+ | ENUM_ITEM_NAME
+ )
+ })
+ .is_some()
+ {
+ self.content(ParserContext::default());
+ }
+ self.builder.finish_node();
+ }
+
+ fn caption(&mut self) {
+ self.builder.start_node(CAPTION.into());
+ self.eat();
+ self.trivia();
+
+ if self.peek() == Some(L_BRACK) {
+ self.brack_group();
+ }
+
+ if self.peek() == Some(L_CURLY) {
+ self.curly_group();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn citation(&mut self) {
+ self.builder.start_node(CITATION.into());
+ self.eat();
+ self.trivia();
+ for _ in 0..2 {
+ if self.lexer.peek() == Some(L_BRACK) {
+ self.brack_group();
+ }
+ }
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word_list();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn generic_include(&mut self, kind: SyntaxKind, options: bool) {
+ self.builder.start_node(kind.into());
+ self.eat();
+ self.trivia();
+ if options && self.lexer.peek() == Some(L_BRACK) {
+ self.brack_group_key_value();
+ }
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word_list();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn package_include(&mut self) {
+ self.generic_include(PACKAGE_INCLUDE, true);
+ }
+
+ fn class_include(&mut self) {
+ self.generic_include(CLASS_INCLUDE, true);
+ }
+
+ fn latex_include(&mut self) {
+ self.generic_include(LATEX_INCLUDE, false);
+ }
+
+ fn biblatex_include(&mut self) {
+ self.generic_include(BIBLATEX_INCLUDE, true);
+ }
+
+ fn bibtex_include(&mut self) {
+ self.generic_include(BIBTEX_INCLUDE, false);
+ }
+
+ fn graphics_include(&mut self) {
+ self.generic_include(GRAPHICS_INCLUDE, true);
+ }
+
+ fn svg_include(&mut self) {
+ self.generic_include(SVG_INCLUDE, true);
+ }
+
+ fn inkscape_include(&mut self) {
+ self.generic_include(INKSCAPE_INCLUDE, true);
+ }
+
+ fn verbatim_include(&mut self) {
+ self.generic_include(VERBATIM_INCLUDE, false);
+ }
+
+ fn import(&mut self) {
+ self.builder.start_node(IMPORT.into());
+ self.eat();
+ self.trivia();
+
+ for _ in 0..2 {
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn label_definition(&mut self) {
+ self.builder.start_node(LABEL_DEFINITION.into());
+ self.eat();
+ self.trivia();
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+ self.builder.finish_node();
+ }
+
+ fn label_reference(&mut self) {
+ self.builder.start_node(LABEL_REFERENCE.into());
+ self.eat();
+ self.trivia();
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word_list();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+ self.builder.finish_node();
+ }
+
+ fn label_reference_range(&mut self) {
+ self.builder.start_node(LABEL_REFERENCE_RANGE.into());
+ self.eat();
+ self.trivia();
+
+ for _ in 0..2 {
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn label_number(&mut self) {
+ self.builder.start_node(LABEL_NUMBER.into());
+ self.eat();
+ self.trivia();
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group();
+ self.builder.token(MISSING.into(), "");
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn command_definition(&mut self) {
+ self.builder.start_node(COMMAND_DEFINITION.into());
+ self.eat();
+ self.trivia();
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_command();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ if self.lexer.peek() == Some(L_BRACK) {
+ self.brack_group_word();
+ }
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn math_operator(&mut self) {
+ self.builder.start_node(MATH_OPERATOR.into());
+ self.eat();
+ self.trivia();
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_command();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn glossary_entry_definition(&mut self) {
+ self.builder.start_node(GLOSSARY_ENTRY_DEFINITION.into());
+ self.eat();
+ self.trivia();
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_key_value();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn glossary_entry_reference(&mut self) {
+ self.builder.start_node(GLOSSARY_ENTRY_REFERENCE.into());
+ self.eat();
+ self.trivia();
+
+ if self.lexer.peek() == Some(L_BRACK) {
+ self.brack_group_key_value();
+ }
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn acronym_definition(&mut self) {
+ self.builder.start_node(ACRONYM_DEFINITION.into());
+ self.eat();
+ self.trivia();
+
+ if self.lexer.peek() == Some(L_BRACK) {
+ self.brack_group_key_value();
+ }
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ for _ in 0..2 {
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn acronym_declaration(&mut self) {
+ self.builder.start_node(ACRONYM_DECLARATION.into());
+ self.eat();
+ self.trivia();
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_key_value();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn acronym_reference(&mut self) {
+ self.builder.start_node(ACRONYM_REFERENCE.into());
+ self.eat();
+ self.trivia();
+
+ if self.lexer.peek() == Some(L_BRACK) {
+ self.brack_group_key_value();
+ }
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn theorem_definition(&mut self) {
+ self.builder.start_node(THEOREM_DEFINITION.into());
+ self.eat();
+ self.trivia();
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ if self.lexer.peek() == Some(L_BRACK) {
+ self.brack_group_word();
+ }
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ if self.lexer.peek() == Some(L_BRACK) {
+ self.brack_group_word();
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn color_reference(&mut self) {
+ self.builder.start_node(COLOR_REFERENCE.into());
+ self.eat();
+ self.trivia();
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn color_definition(&mut self) {
+ self.builder.start_node(COLOR_DEFINITION.into());
+ self.eat();
+ self.trivia();
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn color_set_definition(&mut self) {
+ self.builder.start_node(COLOR_SET_DEFINITION.into());
+ self.eat();
+ self.trivia();
+
+ if self.lexer.peek() == Some(L_BRACK) {
+ self.brack_group_word();
+ }
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word_list();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ for _ in 0..3 {
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn tikz_library_import(&mut self) {
+ self.builder.start_node(TIKZ_LIBRARY_IMPORT.into());
+ self.eat();
+ self.trivia();
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word_list();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ self.builder.finish_node();
+ }
+
+ fn environment_definition(&mut self) {
+ self.builder.start_node(ENVIRONMENT_DEFINITION.into());
+ self.eat();
+ self.trivia();
+
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_word();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+
+ if self.lexer.peek() == Some(L_BRACK) {
+ self.brack_group_word();
+ }
+
+ for _ in 0..2 {
+ if self.lexer.peek() == Some(L_CURLY) {
+ self.curly_group_without_environments();
+ } else {
+ self.builder.token(MISSING.into(), "");
+ }
+ }
+
+ self.builder.finish_node();
+ }
+}
+
+pub fn parse(text: &str) -> Parse {
+ Parser::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
+ }
+
+ #[test]
+ fn test_empty() {
+ assert_debug_snapshot!(setup(r#""#));
+ }
+
+ #[test]
+ fn test_hello_world() {
+ assert_debug_snapshot!(setup(r#"Hello World!"#));
+ }
+
+ #[test]
+ fn test_generic_command_empty() {
+ assert_debug_snapshot!(setup(r#"\foo"#));
+ }
+
+ #[test]
+ fn test_generic_command_escape() {
+ assert_debug_snapshot!(setup(r#"\#"#));
+ }
+
+ #[test]
+ fn test_generic_command_args() {
+ assert_debug_snapshot!(setup(r#"\foo{bar}[qux]"#));
+ }
+
+ #[test]
+ fn test_inline() {
+ assert_debug_snapshot!(setup(r#"$x \in [0, \infty)$"#));
+ }
+
+ #[test]
+ fn test_inline_double_dollar() {
+ assert_debug_snapshot!(setup(r#"$$x \in [0, \infty)$$"#));
+ }
+
+ #[test]
+ fn test_brace_group_simple() {
+ assert_debug_snapshot!(setup(r#"{hello world}"#));
+ }
+
+ #[test]
+ fn test_brace_group_missing_end() {
+ assert_debug_snapshot!(setup(r#"{hello world"#));
+ }
+
+ #[test]
+ fn test_unmatched_braces() {
+ assert_debug_snapshot!(setup(r#"}{"#));
+ }
+
+ #[test]
+ fn test_unmatched_brackets() {
+ assert_debug_snapshot!(setup(r#"]["#));
+ }
+
+ #[test]
+ fn test_unmatched_brackets_with_group() {
+ assert_debug_snapshot!(setup(r#"{][}"#));
+ }
+
+ #[test]
+ fn test_escaped_brackets() {
+ assert_debug_snapshot!(setup(r#"{[}{]}"#));
+ }
+
+ #[test]
+ fn test_parameter() {
+ assert_debug_snapshot!(setup(r#"#1"#));
+ }
+
+ #[test]
+ fn test_parameter_error() {
+ assert_debug_snapshot!(setup(r#"#"#));
+ }
+
+ #[test]
+ fn test_environment_simple() {
+ assert_debug_snapshot!(setup(r#"\begin{foo} Hello World \end{bar}"#));
+ }
+
+ #[test]
+ fn test_environment_nested() {
+ assert_debug_snapshot!(setup(r#"\begin{foo} \begin{qux} \end{baz} \end{bar}"#));
+ }
+
+ #[test]
+ fn test_environment_nested_missing_braces() {
+ assert_debug_snapshot!(setup(
+ r#"\begin{foo \begin{qux Hello World \end{baz} \end{bar"#
+ ));
+ }
+
+ #[test]
+ fn test_structure_siblings() {
+ assert_debug_snapshot!(setup(r#"\section{Foo} Foo \section{Bar} Bar"#));
+ }
+
+ #[test]
+ fn test_structure_nested() {
+ assert_debug_snapshot!(setup(
+ r#"\part{1}\chapter{2}\section{3}\subsection{4}\subsubsection{5}\paragraph{6}\subparagraph{7}"#
+ ));
+ }
+
+ #[test]
+ fn test_structure_enum_item() {
+ assert_debug_snapshot!(setup(
+ r#"\begin{enumerate} \item 1 \item[2] 2 \item 3 \end{enumerate}"#
+ ));
+ }
+
+ #[test]
+ fn test_structure_invalid_nesting() {
+ assert_debug_snapshot!(setup(r#"\section{Foo} \chapter{Bar}"#));
+ }
+
+ #[test]
+ fn test_equation() {
+ assert_debug_snapshot!(setup(r#"\[ foo bar \]"#));
+ }
+
+ #[test]
+ fn test_equation_missing_end() {
+ assert_debug_snapshot!(setup(r#"\begin{a} \[ foo bar \end{b}"#));
+ }
+
+ #[test]
+ fn test_equation_missing_begin() {
+ assert_debug_snapshot!(setup(r#"\begin{a} foo bar \] \end{b}"#));
+ }
+
+ #[test]
+ fn test_caption_minimal() {
+ assert_debug_snapshot!(setup(r#"\caption{Foo \Bar Baz}"#));
+ }
+
+ #[test]
+ fn test_caption_minimal_error() {
+ assert_debug_snapshot!(setup(r#"\caption{Foo \Bar Baz"#));
+ }
+
+ #[test]
+ fn test_caption() {
+ assert_debug_snapshot!(setup(r#"\caption[qux]{Foo \Bar Baz}"#));
+ }
+
+ #[test]
+ fn test_caption_error() {
+ assert_debug_snapshot!(setup(r#"\caption[qux]{Foo \Bar Baz"#));
+ }
+
+ #[test]
+ fn test_caption_figure() {
+ assert_debug_snapshot!(setup(r#"\begin{figure}\caption{Foo}\end{figure}"#));
+ }
+
+ #[test]
+ fn test_citation_empty() {
+ assert_debug_snapshot!(setup(r#"\cite{}"#));
+ }
+
+ #[test]
+ fn test_citation_simple() {
+ assert_debug_snapshot!(setup(r#"\cite{foo}"#));
+ }
+
+ #[test]
+ fn test_citation_multiple_keys() {
+ assert_debug_snapshot!(setup(r#"\cite{foo, bar}"#));
+ }
+
+ #[test]
+ fn test_citation_star() {
+ assert_debug_snapshot!(setup(r#"\nocite{*}"#));
+ }
+
+ #[test]
+ fn test_citation_prenote() {
+ assert_debug_snapshot!(setup(r#"\cite[foo]{bar}"#));
+ }
+
+ #[test]
+ fn test_citation_prenote_postnote() {
+ assert_debug_snapshot!(setup(r#"\cite[foo][bar]{baz}"#));
+ }
+
+ #[test]
+ fn test_citation_missing_brace() {
+ assert_debug_snapshot!(setup(r#"\cite{foo"#));
+ }
+
+ #[test]
+ fn test_citation_redundant_comma() {
+ assert_debug_snapshot!(setup(r#"\cite{,foo,}"#));
+ }
+
+ #[test]
+ fn test_package_include_empty() {
+ assert_debug_snapshot!(setup(r#"\usepackage{}"#));
+ }
+
+ #[test]
+ fn test_package_include_simple() {
+ assert_debug_snapshot!(setup(r#"\usepackage{amsmath}"#));
+ }
+
+ #[test]
+ fn test_package_include_multiple() {
+ assert_debug_snapshot!(setup(r#"\usepackage{amsmath, lipsum}"#));
+ }
+
+ #[test]
+ fn test_package_include_options() {
+ assert_debug_snapshot!(setup(r#"\usepackage[foo = bar, baz, qux]{amsmath}"#));
+ }
+
+ #[test]
+ fn test_class_include_empty() {
+ assert_debug_snapshot!(setup(r#"\documentclass{}"#));
+ }
+
+ #[test]
+ fn test_class_include_simple() {
+ assert_debug_snapshot!(setup(r#"\documentclass{article}"#));
+ }
+
+ #[test]
+ fn test_class_include_options() {
+ assert_debug_snapshot!(setup(r#"\documentclass[foo = bar, baz, qux]{article}"#));
+ }
+
+ #[test]
+ fn test_latex_include_simple() {
+ assert_debug_snapshot!(setup(r#"\include{foo/bar}"#));
+ }
+
+ #[test]
+ fn test_latex_input_simple() {
+ assert_debug_snapshot!(setup(r#"\input{foo/bar.tex}"#));
+ }
+
+ #[test]
+ fn test_biblatex_include_simple() {
+ assert_debug_snapshot!(setup(r#"\addbibresource{foo/bar.bib}"#));
+ }
+
+ #[test]
+ fn test_biblatex_include_options() {
+ assert_debug_snapshot!(setup(r#"\addbibresource[foo=bar, baz]{foo/bar.bib}"#));
+ }
+
+ #[test]
+ fn test_bibtex_include_simple() {
+ assert_debug_snapshot!(setup(r#"\bibliography{foo/bar}"#));
+ }
+
+ #[test]
+ fn test_graphics_include_simple() {
+ assert_debug_snapshot!(setup(r#"\includegraphics{foo/bar.pdf}"#));
+ }
+
+ #[test]
+ fn test_graphics_include_options() {
+ assert_debug_snapshot!(setup(r#"\includegraphics[scale=.5]{foo/bar.pdf}"#));
+ }
+
+ #[test]
+ fn test_svg_include_simple() {
+ assert_debug_snapshot!(setup(r#"\includesvg{foo/bar.svg}"#));
+ }
+
+ #[test]
+ fn test_svg_include_options() {
+ assert_debug_snapshot!(setup(r#"\includesvg[scale=.5]{foo/bar.svg}"#));
+ }
+
+ #[test]
+ fn test_inkscape_include_simple() {
+ assert_debug_snapshot!(setup(r#"\includesvg{foo/bar}"#));
+ }
+
+ #[test]
+ fn test_inkscape_include_options() {
+ assert_debug_snapshot!(setup(r#"\includesvg[scale=.5]{foo/bar}"#));
+ }
+
+ #[test]
+ fn test_verbatim_include_simple() {
+ assert_debug_snapshot!(setup(r#"\verbatiminput{foo/bar.txt}"#));
+ }
+
+ #[test]
+ fn test_import_simple() {
+ assert_debug_snapshot!(setup(r#"\import{foo}{bar}"#));
+ }
+
+ #[test]
+ fn test_import_incomplete() {
+ assert_debug_snapshot!(setup(r#"\import{foo"#));
+ }
+
+ #[test]
+ fn test_label_definition_simple() {
+ assert_debug_snapshot!(setup(r#"\label{foo}"#));
+ }
+
+ #[test]
+ fn test_label_reference_simple() {
+ assert_debug_snapshot!(setup(r#"\ref{foo}"#));
+ }
+
+ #[test]
+ fn test_label_reference_multiple() {
+ assert_debug_snapshot!(setup(r#"\ref{foo, bar}"#));
+ }
+
+ #[test]
+ fn test_equation_label_reference_simple() {
+ assert_debug_snapshot!(setup(r#"\eqref{foo}"#));
+ }
+
+ #[test]
+ fn test_label_reference_range_simple() {
+ assert_debug_snapshot!(setup(r#"\crefrange{foo}{bar}"#));
+ }
+
+ #[test]
+ fn test_label_reference_range_incomplete() {
+ assert_debug_snapshot!(setup(r#"\crefrange{foo}"#));
+ }
+
+ #[test]
+ fn test_label_reference_range_error() {
+ assert_debug_snapshot!(setup(r#"\crefrange{foo{bar}"#));
+ }
+
+ #[test]
+ fn test_label_number() {
+ assert_debug_snapshot!(setup(r#"\newlabel{foo}{{1.1}}"#));
+ }
+
+ #[test]
+ fn test_command_definition_simple() {
+ assert_debug_snapshot!(setup(r#"\newcommand[1]{\id}{#1}"#));
+ }
+
+ #[test]
+ fn test_command_definition_no_argc() {
+ assert_debug_snapshot!(setup(r#"\newcommand{\foo}{foo}"#));
+ }
+
+ #[test]
+ fn test_command_definition_no_impl() {
+ assert_debug_snapshot!(setup(r#"\newcommand{\foo}"#));
+ }
+
+ #[test]
+ fn test_command_definition_no_impl_error() {
+ assert_debug_snapshot!(setup(r#"\newcommand{\foo"#));
+ }
+
+ #[test]
+ fn test_math_operator_simple() {
+ assert_debug_snapshot!(setup(r#"\DeclareMathOperator{\foo}{foo}"#));
+ }
+
+ #[test]
+ fn test_math_operator_no_impl() {
+ assert_debug_snapshot!(setup(r#"\DeclareMathOperator{\foo}"#));
+ }
+
+ #[test]
+ fn test_glossary_entry_definition_simple() {
+ assert_debug_snapshot!(setup(r#"\newglossaryentry{foo}{bar = baz, qux,}"#));
+ }
+
+ #[test]
+ fn test_glossary_entry_reference_simple() {
+ assert_debug_snapshot!(setup(r#"\gls{foo}"#));
+ }
+
+ #[test]
+ fn test_glossary_entry_reference_options() {
+ assert_debug_snapshot!(setup(r#"\gls[foo = bar, qux]{baz}"#));
+ }
+
+ #[test]
+ fn test_acroynm_definition_simple() {
+ assert_debug_snapshot!(setup(r#"\newacronym{fpsLabel}{FPS}{Frame per Second}"#));
+ }
+
+ #[test]
+ fn test_acroynm_definition_options() {
+ assert_debug_snapshot!(setup(
+ r#"\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}"#
+ ));
+ }
+
+ #[test]
+ fn test_acroynm_reference_simple() {
+ assert_debug_snapshot!(setup(r#"\acrshort{fpsLabel}"#));
+ }
+
+ #[test]
+ fn test_acroynm_reference_options() {
+ assert_debug_snapshot!(setup(r#"\acrshort[foo=bar,baz]{fpsLabel}"#));
+ }
+
+ #[test]
+ fn test_theorem_definition_only_name() {
+ assert_debug_snapshot!(setup(r#"\newtheorem{foo}"#));
+ }
+
+ #[test]
+ fn test_theorem_definition_name_with_description() {
+ assert_debug_snapshot!(setup(r#"\newtheorem{foo}{Foo}"#));
+ }
+
+ #[test]
+ fn test_theorem_definition_name_with_description_and_counter() {
+ assert_debug_snapshot!(setup(r#"\newtheorem{foo}[bar]{Foo}"#));
+ }
+
+ #[test]
+ fn test_theorem_definition_name_with_counter() {
+ assert_debug_snapshot!(setup(r#"\newtheorem{foo}[bar]"#));
+ }
+
+ #[test]
+ fn test_theorem_definition_full() {
+ assert_debug_snapshot!(setup(r#"\newtheorem{foo}[bar]{Foo}[baz]"#));
+ }
+
+ #[test]
+ fn test_color_reference_simple() {
+ assert_debug_snapshot!(setup(r#"\color{black}"#));
+ }
+
+ #[test]
+ fn test_color_definition_simple() {
+ assert_debug_snapshot!(setup(r#"\definecolor{foo}{rgb}{255,168,0}"#));
+ }
+
+ #[test]
+ fn test_color_set_definition_simple() {
+ assert_debug_snapshot!(setup(r#"\definecolorset[ty]{rgb,HTML}{foo}{bar}{baz}"#));
+ }
+
+ #[test]
+ fn test_color_set_definition_error1() {
+ assert_debug_snapshot!(setup(r#"\definecolorset[ty]{rgb,HTML}{foo}{bar}"#));
+ }
+
+ #[test]
+ fn test_color_set_definition_error2() {
+ assert_debug_snapshot!(setup(r#"\definecolorset{rgb,HTML}{foo}"#));
+ }
+
+ #[test]
+ fn test_color_set_definition_error3() {
+ assert_debug_snapshot!(setup(r#"\definecolorset{rgb,HTML}"#));
+ }
+
+ #[test]
+ fn test_color_set_definition_error4() {
+ assert_debug_snapshot!(setup(r#"\definecolorset"#));
+ }
+
+ #[test]
+ fn test_pgf_library_import_simple() {
+ assert_debug_snapshot!(setup(r#"\usepgflibrary{foo}"#));
+ }
+
+ #[test]
+ fn test_tikz_library_import_simple() {
+ assert_debug_snapshot!(setup(r#"\usetikzlibrary{foo}"#));
+ }
+
+ #[test]
+ fn test_environment_definition() {
+ assert_debug_snapshot!(setup(r#"\newenvironment{bar}[1]{\begin{foo}}{\end{foo}}"#));
+ }
+
+ #[test]
+ fn test_acronym_declaration() {
+ assert_debug_snapshot!(setup(
+ r#"\DeclareAcronym{eg}{short = e.g,long = for example,tag = abbrev}"#
+ ));
}
}
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_parameter.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_parameter.snap
new file mode 100644
index 0000000000..9cff735e99
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_parameter.snap
@@ -0,0 +1,47 @@
+---
+source: src/syntax/latex/lexer.rs
+expression: "verify(r#\"\\newcommand{\\id}[1]{#1}\"#)"
+
+---
+[
+ (
+ COMMAND_DEFINITION_NAME,
+ "\\newcommand",
+ ),
+ (
+ L_CURLY,
+ "{",
+ ),
+ (
+ GENERIC_COMMAND_NAME,
+ "\\id",
+ ),
+ (
+ R_CURLY,
+ "}",
+ ),
+ (
+ L_BRACK,
+ "[",
+ ),
+ (
+ WORD,
+ "1",
+ ),
+ (
+ R_BRACK,
+ "]",
+ ),
+ (
+ L_CURLY,
+ "{",
+ ),
+ (
+ PARAMETER,
+ "#1",
+ ),
+ (
+ R_CURLY,
+ "}",
+ ),
+]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_star.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_star.snap
new file mode 100644
index 0000000000..41310edaa1
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_star.snap
@@ -0,0 +1,23 @@
+---
+source: src/syntax/latex/lexer.rs
+expression: "verify(r#\"\\section*{Foo}\"#)"
+
+---
+[
+ (
+ SECTION_NAME,
+ "\\section*",
+ ),
+ (
+ L_CURLY,
+ "{",
+ ),
+ (
+ WORD,
+ "Foo",
+ ),
+ (
+ R_CURLY,
+ "}",
+ ),
+]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__comment.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__comment.snap
new file mode 100644
index 0000000000..2186c957c5
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__comment.snap
@@ -0,0 +1,35 @@
+---
+source: src/syntax/latex/lexer.rs
+expression: "verify(\"hello %world\\r\\ntest %test\")"
+
+---
+[
+ (
+ WORD,
+ "hello",
+ ),
+ (
+ WHITESPACE,
+ " ",
+ ),
+ (
+ COMMENT,
+ "%world",
+ ),
+ (
+ WHITESPACE,
+ "\r\n",
+ ),
+ (
+ WORD,
+ "test",
+ ),
+ (
+ WHITESPACE,
+ " ",
+ ),
+ (
+ COMMENT,
+ "%test",
+ ),
+]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__delimiters.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__delimiters.snap
new file mode 100644
index 0000000000..36bec0a276
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__delimiters.snap
@@ -0,0 +1,79 @@
+---
+source: src/syntax/latex/lexer.rs
+expression: "verify(r#\"{foo} (bar) [baz, qux = foo-bar]\"#)"
+
+---
+[
+ (
+ L_CURLY,
+ "{",
+ ),
+ (
+ WORD,
+ "foo",
+ ),
+ (
+ R_CURLY,
+ "}",
+ ),
+ (
+ WHITESPACE,
+ " ",
+ ),
+ (
+ L_PAREN,
+ "(",
+ ),
+ (
+ WORD,
+ "bar",
+ ),
+ (
+ R_PAREN,
+ ")",
+ ),
+ (
+ WHITESPACE,
+ " ",
+ ),
+ (
+ L_BRACK,
+ "[",
+ ),
+ (
+ WORD,
+ "baz",
+ ),
+ (
+ COMMA,
+ ",",
+ ),
+ (
+ WHITESPACE,
+ " ",
+ ),
+ (
+ WORD,
+ "qux",
+ ),
+ (
+ WHITESPACE,
+ " ",
+ ),
+ (
+ EQUALITY_SIGN,
+ "=",
+ ),
+ (
+ WHITESPACE,
+ " ",
+ ),
+ (
+ WORD,
+ "foo-bar",
+ ),
+ (
+ R_BRACK,
+ "]",
+ ),
+]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__empty.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__empty.snap
new file mode 100644
index 0000000000..61735b5087
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__empty.snap
@@ -0,0 +1,6 @@
+---
+source: src/syntax/latex/lexer.rs
+expression: "verify(r#\"\"#)"
+
+---
+[]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__escape_sequence.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__escape_sequence.snap
new file mode 100644
index 0000000000..4dbc1bb8f5
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__escape_sequence.snap
@@ -0,0 +1,19 @@
+---
+source: src/syntax/latex/lexer.rs
+expression: "verify(r#\"\\% hello\"#)"
+
+---
+[
+ (
+ GENERIC_COMMAND_NAME,
+ "\\%",
+ ),
+ (
+ WHITESPACE,
+ " ",
+ ),
+ (
+ WORD,
+ "hello",
+ ),
+]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__formula.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__formula.snap
new file mode 100644
index 0000000000..3fb7079384
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__formula.snap
@@ -0,0 +1,55 @@
+---
+source: src/syntax/latex/lexer.rs
+expression: "verify(r#\"$ f(x) = y $$\"#)"
+
+---
+[
+ (
+ DOLLAR,
+ "$",
+ ),
+ (
+ WHITESPACE,
+ " ",
+ ),
+ (
+ WORD,
+ "f",
+ ),
+ (
+ L_PAREN,
+ "(",
+ ),
+ (
+ WORD,
+ "x",
+ ),
+ (
+ R_PAREN,
+ ")",
+ ),
+ (
+ WHITESPACE,
+ " ",
+ ),
+ (
+ EQUALITY_SIGN,
+ "=",
+ ),
+ (
+ WHITESPACE,
+ " ",
+ ),
+ (
+ WORD,
+ "y",
+ ),
+ (
+ WHITESPACE,
+ " ",
+ ),
+ (
+ DOLLAR,
+ "$$",
+ ),
+]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__invalid_parameter.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__invalid_parameter.snap
new file mode 100644
index 0000000000..adadfcf0ef
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__invalid_parameter.snap
@@ -0,0 +1,11 @@
+---
+source: src/syntax/latex/lexer.rs
+expression: "verify(r#\"#\"#)"
+
+---
+[
+ (
+ PARAMETER,
+ "#",
+ ),
+]
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acronym_declaration.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acronym_declaration.snap
new file mode 100644
index 0000000000..dfd05750d3
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acronym_declaration.snap
@@ -0,0 +1,49 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\DeclareAcronym{eg}{short = e.g,long = for example,tag = abbrev}\"#)"
+
+---
+ROOT@0..64
+ PREAMBLE@0..64
+ ACRONYM_DECLARATION@0..64
+ ACRONYM_DECLARATION_NAME@0..15 "\\DeclareAcronym"
+ CURLY_GROUP_WORD@15..19
+ L_CURLY@15..16 "{"
+ WORD@16..18 "eg"
+ R_CURLY@18..19 "}"
+ CURLY_GROUP_KEY_VALUE@19..64
+ L_CURLY@19..20 "{"
+ KEY_VALUE_BODY@20..63
+ KEY_VALUE_PAIR@20..31
+ KEY@20..26
+ WORD@20..25 "short"
+ WHITESPACE@25..26 " "
+ EQUALITY_SIGN@26..27 "="
+ WHITESPACE@27..28 " "
+ VALUE@28..31
+ TEXT@28..31
+ WORD@28..31 "e.g"
+ COMMA@31..32 ","
+ KEY_VALUE_PAIR@32..50
+ KEY@32..37
+ WORD@32..36 "long"
+ WHITESPACE@36..37 " "
+ EQUALITY_SIGN@37..38 "="
+ WHITESPACE@38..39 " "
+ VALUE@39..50
+ TEXT@39..50
+ WORD@39..42 "for"
+ WHITESPACE@42..43 " "
+ WORD@43..50 "example"
+ COMMA@50..51 ","
+ KEY_VALUE_PAIR@51..63
+ KEY@51..55
+ WORD@51..54 "tag"
+ WHITESPACE@54..55 " "
+ EQUALITY_SIGN@55..56 "="
+ WHITESPACE@56..57 " "
+ VALUE@57..63
+ TEXT@57..63
+ WORD@57..63 "abbrev"
+ R_CURLY@63..64 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_options.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_options.snap
new file mode 100644
index 0000000000..1838e80f9a
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_options.snap
@@ -0,0 +1,46 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}\"#)"
+
+---
+ROOT@0..76
+ PREAMBLE@0..76
+ ACRONYM_DEFINITION@0..76
+ ACRONYM_DEFINITION_NAME@0..11 "\\newacronym"
+ BRACK_GROUP_KEY_VALUE@11..43
+ L_BRACK@11..12 "["
+ KEY_VALUE_BODY@12..42
+ KEY_VALUE_PAIR@12..42
+ KEY@12..22
+ WORD@12..22 "longplural"
+ EQUALITY_SIGN@22..23 "="
+ VALUE@23..42
+ CURLY_GROUP@23..42
+ L_CURLY@23..24 "{"
+ TEXT@24..41
+ WORD@24..30 "Frames"
+ WHITESPACE@30..31 " "
+ WORD@31..34 "per"
+ WHITESPACE@34..35 " "
+ WORD@35..41 "Second"
+ R_CURLY@41..42 "}"
+ R_BRACK@42..43 "]"
+ CURLY_GROUP_WORD@43..53
+ L_CURLY@43..44 "{"
+ WORD@44..52 "fpsLabel"
+ R_CURLY@52..53 "}"
+ CURLY_GROUP@53..58
+ L_CURLY@53..54 "{"
+ TEXT@54..57
+ WORD@54..57 "FPS"
+ R_CURLY@57..58 "}"
+ CURLY_GROUP@58..76
+ L_CURLY@58..59 "{"
+ TEXT@59..75
+ WORD@59..64 "Frame"
+ WHITESPACE@64..65 " "
+ WORD@65..68 "per"
+ WHITESPACE@68..69 " "
+ WORD@69..75 "Second"
+ R_CURLY@75..76 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_simple.snap
new file mode 100644
index 0000000000..1d32bed249
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_simple.snap
@@ -0,0 +1,28 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\newacronym{fpsLabel}{FPS}{Frame per Second}\"#)"
+
+---
+ROOT@0..44
+ PREAMBLE@0..44
+ ACRONYM_DEFINITION@0..44
+ ACRONYM_DEFINITION_NAME@0..11 "\\newacronym"
+ CURLY_GROUP_WORD@11..21
+ L_CURLY@11..12 "{"
+ WORD@12..20 "fpsLabel"
+ R_CURLY@20..21 "}"
+ CURLY_GROUP@21..26
+ L_CURLY@21..22 "{"
+ TEXT@22..25
+ WORD@22..25 "FPS"
+ R_CURLY@25..26 "}"
+ CURLY_GROUP@26..44
+ L_CURLY@26..27 "{"
+ TEXT@27..43
+ WORD@27..32 "Frame"
+ WHITESPACE@32..33 " "
+ WORD@33..36 "per"
+ WHITESPACE@36..37 " "
+ WORD@37..43 "Second"
+ R_CURLY@43..44 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_options.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_options.snap
new file mode 100644
index 0000000000..ce5b85c1cf
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_options.snap
@@ -0,0 +1,29 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\acrshort[foo=bar,baz]{fpsLabel}\"#)"
+
+---
+ROOT@0..32
+ PREAMBLE@0..32
+ ACRONYM_REFERENCE@0..32
+ ACRONYM_REFERENCE_NAME@0..9 "\\acrshort"
+ BRACK_GROUP_KEY_VALUE@9..22
+ L_BRACK@9..10 "["
+ KEY_VALUE_BODY@10..21
+ KEY_VALUE_PAIR@10..17
+ KEY@10..13
+ WORD@10..13 "foo"
+ EQUALITY_SIGN@13..14 "="
+ VALUE@14..17
+ TEXT@14..17
+ WORD@14..17 "bar"
+ COMMA@17..18 ","
+ KEY_VALUE_PAIR@18..21
+ KEY@18..21
+ WORD@18..21 "baz"
+ R_BRACK@21..22 "]"
+ CURLY_GROUP_WORD@22..32
+ L_CURLY@22..23 "{"
+ WORD@23..31 "fpsLabel"
+ R_CURLY@31..32 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_simple.snap
new file mode 100644
index 0000000000..5df8059f1b
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\acrshort{fpsLabel}\"#)"
+
+---
+ROOT@0..19
+ PREAMBLE@0..19
+ ACRONYM_REFERENCE@0..19
+ ACRONYM_REFERENCE_NAME@0..9 "\\acrshort"
+ CURLY_GROUP_WORD@9..19
+ L_CURLY@9..10 "{"
+ WORD@10..18 "fpsLabel"
+ R_CURLY@18..19 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_options.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_options.snap
new file mode 100644
index 0000000000..c910699363
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_options.snap
@@ -0,0 +1,30 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\addbibresource[foo=bar, baz]{foo/bar.bib}\"#)"
+
+---
+ROOT@0..42
+ PREAMBLE@0..42
+ BIBLATEX_INCLUDE@0..42
+ BIBLATEX_INCLUDE_NAME@0..15 "\\addbibresource"
+ BRACK_GROUP_KEY_VALUE@15..29
+ L_BRACK@15..16 "["
+ KEY_VALUE_BODY@16..28
+ KEY_VALUE_PAIR@16..23
+ KEY@16..19
+ WORD@16..19 "foo"
+ EQUALITY_SIGN@19..20 "="
+ VALUE@20..23
+ TEXT@20..23
+ WORD@20..23 "bar"
+ COMMA@23..24 ","
+ WHITESPACE@24..25 " "
+ KEY_VALUE_PAIR@25..28
+ KEY@25..28
+ WORD@25..28 "baz"
+ R_BRACK@28..29 "]"
+ CURLY_GROUP_WORD_LIST@29..42
+ L_CURLY@29..30 "{"
+ WORD@30..41 "foo/bar.bib"
+ R_CURLY@41..42 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_simple.snap
new file mode 100644
index 0000000000..3549b985c4
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\addbibresource{foo/bar.bib}\"#)"
+
+---
+ROOT@0..28
+ PREAMBLE@0..28
+ BIBLATEX_INCLUDE@0..28
+ BIBLATEX_INCLUDE_NAME@0..15 "\\addbibresource"
+ CURLY_GROUP_WORD_LIST@15..28
+ L_CURLY@15..16 "{"
+ WORD@16..27 "foo/bar.bib"
+ R_CURLY@27..28 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__bibtex_include_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__bibtex_include_simple.snap
new file mode 100644
index 0000000000..f0d30ebfa3
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__bibtex_include_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\bibliography{foo/bar}\"#)"
+
+---
+ROOT@0..22
+ PREAMBLE@0..22
+ BIBTEX_INCLUDE@0..22
+ BIBTEX_INCLUDE_NAME@0..13 "\\bibliography"
+ CURLY_GROUP_WORD_LIST@13..22
+ L_CURLY@13..14 "{"
+ WORD@14..21 "foo/bar"
+ R_CURLY@21..22 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_missing_end.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_missing_end.snap
new file mode 100644
index 0000000000..c5fc46ec4d
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_missing_end.snap
@@ -0,0 +1,15 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"{hello world\"#)"
+
+---
+ROOT@0..12
+ PREAMBLE@0..12
+ CURLY_GROUP@0..12
+ L_CURLY@0..1 "{"
+ TEXT@1..12
+ WORD@1..6 "hello"
+ WHITESPACE@6..7 " "
+ WORD@7..12 "world"
+ MISSING@12..12 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_simple.snap
new file mode 100644
index 0000000000..10ebc4418e
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_simple.snap
@@ -0,0 +1,15 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"{hello world}\"#)"
+
+---
+ROOT@0..13
+ PREAMBLE@0..13
+ CURLY_GROUP@0..13
+ L_CURLY@0..1 "{"
+ TEXT@1..12
+ WORD@1..6 "hello"
+ WHITESPACE@6..7 " "
+ WORD@7..12 "world"
+ R_CURLY@12..13 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption.snap
new file mode 100644
index 0000000000..f598a8f7e9
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption.snap
@@ -0,0 +1,26 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\caption[qux]{Foo \\Bar Baz}\"#)"
+
+---
+ROOT@0..27
+ PREAMBLE@0..27
+ CAPTION@0..27
+ CAPTION_NAME@0..8 "\\caption"
+ BRACK_GROUP@8..13
+ L_BRACK@8..9 "["
+ TEXT@9..12
+ WORD@9..12 "qux"
+ R_BRACK@12..13 "]"
+ CURLY_GROUP@13..27
+ L_CURLY@13..14 "{"
+ TEXT@14..18
+ WORD@14..17 "Foo"
+ WHITESPACE@17..18 " "
+ GENERIC_COMMAND@18..23
+ GENERIC_COMMAND_NAME@18..22 "\\Bar"
+ WHITESPACE@22..23 " "
+ TEXT@23..26
+ WORD@23..26 "Baz"
+ R_CURLY@26..27 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_error.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_error.snap
new file mode 100644
index 0000000000..13697e882b
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_error.snap
@@ -0,0 +1,26 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\caption[qux]{Foo \\Bar Baz\"#)"
+
+---
+ROOT@0..26
+ PREAMBLE@0..26
+ CAPTION@0..26
+ CAPTION_NAME@0..8 "\\caption"
+ BRACK_GROUP@8..13
+ L_BRACK@8..9 "["
+ TEXT@9..12
+ WORD@9..12 "qux"
+ R_BRACK@12..13 "]"
+ CURLY_GROUP@13..26
+ L_CURLY@13..14 "{"
+ TEXT@14..18
+ WORD@14..17 "Foo"
+ WHITESPACE@17..18 " "
+ GENERIC_COMMAND@18..23
+ GENERIC_COMMAND_NAME@18..22 "\\Bar"
+ WHITESPACE@22..23 " "
+ TEXT@23..26
+ WORD@23..26 "Baz"
+ MISSING@26..26 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_figure.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_figure.snap
new file mode 100644
index 0000000000..4b963caabd
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_figure.snap
@@ -0,0 +1,28 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\begin{figure}\\caption{Foo}\\end{figure}\"#)"
+
+---
+ROOT@0..39
+ PREAMBLE@0..39
+ ENVIRONMENT@0..39
+ BEGIN@0..14
+ BEGIN_ENVIRONMENT_NAME@0..6 "\\begin"
+ CURLY_GROUP_WORD@6..14
+ L_CURLY@6..7 "{"
+ WORD@7..13 "figure"
+ R_CURLY@13..14 "}"
+ CAPTION@14..27
+ CAPTION_NAME@14..22 "\\caption"
+ CURLY_GROUP@22..27
+ L_CURLY@22..23 "{"
+ TEXT@23..26
+ WORD@23..26 "Foo"
+ R_CURLY@26..27 "}"
+ END@27..39
+ END_ENVIRONMENT_NAME@27..31 "\\end"
+ CURLY_GROUP_WORD@31..39
+ L_CURLY@31..32 "{"
+ WORD@32..38 "figure"
+ R_CURLY@38..39 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal.snap
new file mode 100644
index 0000000000..bf9e795e18
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal.snap
@@ -0,0 +1,21 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\caption{Foo \\Bar Baz}\"#)"
+
+---
+ROOT@0..22
+ PREAMBLE@0..22
+ CAPTION@0..22
+ CAPTION_NAME@0..8 "\\caption"
+ CURLY_GROUP@8..22
+ L_CURLY@8..9 "{"
+ TEXT@9..13
+ WORD@9..12 "Foo"
+ WHITESPACE@12..13 " "
+ GENERIC_COMMAND@13..18
+ GENERIC_COMMAND_NAME@13..17 "\\Bar"
+ WHITESPACE@17..18 " "
+ TEXT@18..21
+ WORD@18..21 "Baz"
+ R_CURLY@21..22 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal_error.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal_error.snap
new file mode 100644
index 0000000000..6c932ce883
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal_error.snap
@@ -0,0 +1,21 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\caption{Foo \\Bar Baz\"#)"
+
+---
+ROOT@0..21
+ PREAMBLE@0..21
+ CAPTION@0..21
+ CAPTION_NAME@0..8 "\\caption"
+ CURLY_GROUP@8..21
+ L_CURLY@8..9 "{"
+ TEXT@9..13
+ WORD@9..12 "Foo"
+ WHITESPACE@12..13 " "
+ GENERIC_COMMAND@13..18
+ GENERIC_COMMAND_NAME@13..17 "\\Bar"
+ WHITESPACE@17..18 " "
+ TEXT@18..21
+ WORD@18..21 "Baz"
+ MISSING@21..21 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_empty.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_empty.snap
new file mode 100644
index 0000000000..faad4c0039
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_empty.snap
@@ -0,0 +1,13 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\cite{}\"#)"
+
+---
+ROOT@0..7
+ PREAMBLE@0..7
+ CITATION@0..7
+ CITATION_NAME@0..5 "\\cite"
+ CURLY_GROUP_WORD_LIST@5..7
+ L_CURLY@5..6 "{"
+ R_CURLY@6..7 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_missing_brace.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_missing_brace.snap
new file mode 100644
index 0000000000..363f8b0142
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_missing_brace.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\cite{foo\"#)"
+
+---
+ROOT@0..9
+ PREAMBLE@0..9
+ CITATION@0..9
+ CITATION_NAME@0..5 "\\cite"
+ CURLY_GROUP_WORD_LIST@5..9
+ L_CURLY@5..6 "{"
+ WORD@6..9 "foo"
+ MISSING@9..9 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_multiple_keys.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_multiple_keys.snap
new file mode 100644
index 0000000000..dafc826a97
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_multiple_keys.snap
@@ -0,0 +1,17 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\cite{foo, bar}\"#)"
+
+---
+ROOT@0..15
+ PREAMBLE@0..15
+ CITATION@0..15
+ CITATION_NAME@0..5 "\\cite"
+ CURLY_GROUP_WORD_LIST@5..15
+ L_CURLY@5..6 "{"
+ WORD@6..9 "foo"
+ COMMA@9..10 ","
+ WHITESPACE@10..11 " "
+ WORD@11..14 "bar"
+ R_CURLY@14..15 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote.snap
new file mode 100644
index 0000000000..93d83bb448
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote.snap
@@ -0,0 +1,19 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\cite[foo]{bar}\"#)"
+
+---
+ROOT@0..15
+ PREAMBLE@0..15
+ CITATION@0..15
+ CITATION_NAME@0..5 "\\cite"
+ BRACK_GROUP@5..10
+ L_BRACK@5..6 "["
+ TEXT@6..9
+ WORD@6..9 "foo"
+ R_BRACK@9..10 "]"
+ CURLY_GROUP_WORD_LIST@10..15
+ L_CURLY@10..11 "{"
+ WORD@11..14 "bar"
+ R_CURLY@14..15 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote_postnote.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote_postnote.snap
new file mode 100644
index 0000000000..3acaafea3b
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote_postnote.snap
@@ -0,0 +1,24 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\cite[foo][bar]{baz}\"#)"
+
+---
+ROOT@0..20
+ PREAMBLE@0..20
+ CITATION@0..20
+ CITATION_NAME@0..5 "\\cite"
+ BRACK_GROUP@5..10
+ L_BRACK@5..6 "["
+ TEXT@6..9
+ WORD@6..9 "foo"
+ R_BRACK@9..10 "]"
+ BRACK_GROUP@10..15
+ L_BRACK@10..11 "["
+ TEXT@11..14
+ WORD@11..14 "bar"
+ R_BRACK@14..15 "]"
+ CURLY_GROUP_WORD_LIST@15..20
+ L_CURLY@15..16 "{"
+ WORD@16..19 "baz"
+ R_CURLY@19..20 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_redundant_comma.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_redundant_comma.snap
new file mode 100644
index 0000000000..12f34f16ae
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_redundant_comma.snap
@@ -0,0 +1,16 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\cite{,foo,}\"#)"
+
+---
+ROOT@0..12
+ PREAMBLE@0..12
+ CITATION@0..12
+ CITATION_NAME@0..5 "\\cite"
+ CURLY_GROUP_WORD_LIST@5..12
+ L_CURLY@5..6 "{"
+ COMMA@6..7 ","
+ WORD@7..10 "foo"
+ COMMA@10..11 ","
+ R_CURLY@11..12 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_simple.snap
new file mode 100644
index 0000000000..a22129f85f
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\cite{foo}\"#)"
+
+---
+ROOT@0..10
+ PREAMBLE@0..10
+ CITATION@0..10
+ CITATION_NAME@0..5 "\\cite"
+ CURLY_GROUP_WORD_LIST@5..10
+ L_CURLY@5..6 "{"
+ WORD@6..9 "foo"
+ R_CURLY@9..10 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_star.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_star.snap
new file mode 100644
index 0000000000..4388596dfd
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_star.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\nocite{*}\"#)"
+
+---
+ROOT@0..10
+ PREAMBLE@0..10
+ CITATION@0..10
+ CITATION_NAME@0..7 "\\nocite"
+ CURLY_GROUP_WORD_LIST@7..10
+ L_CURLY@7..8 "{"
+ WORD@8..9 "*"
+ R_CURLY@9..10 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_empty.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_empty.snap
new file mode 100644
index 0000000000..7d84653225
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_empty.snap
@@ -0,0 +1,13 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\documentclass{}\"#)"
+
+---
+ROOT@0..16
+ PREAMBLE@0..16
+ CLASS_INCLUDE@0..16
+ CLASS_INCLUDE_NAME@0..14 "\\documentclass"
+ CURLY_GROUP_WORD_LIST@14..16
+ L_CURLY@14..15 "{"
+ R_CURLY@15..16 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_options.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_options.snap
new file mode 100644
index 0000000000..0d0eb1b250
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_options.snap
@@ -0,0 +1,37 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\documentclass[foo = bar, baz, qux]{article}\"#)"
+
+---
+ROOT@0..44
+ PREAMBLE@0..44
+ CLASS_INCLUDE@0..44
+ CLASS_INCLUDE_NAME@0..14 "\\documentclass"
+ BRACK_GROUP_KEY_VALUE@14..35
+ L_BRACK@14..15 "["
+ KEY_VALUE_BODY@15..34
+ KEY_VALUE_PAIR@15..24
+ KEY@15..19
+ WORD@15..18 "foo"
+ WHITESPACE@18..19 " "
+ EQUALITY_SIGN@19..20 "="
+ WHITESPACE@20..21 " "
+ VALUE@21..24
+ TEXT@21..24
+ WORD@21..24 "bar"
+ COMMA@24..25 ","
+ WHITESPACE@25..26 " "
+ KEY_VALUE_PAIR@26..29
+ KEY@26..29
+ WORD@26..29 "baz"
+ COMMA@29..30 ","
+ WHITESPACE@30..31 " "
+ KEY_VALUE_PAIR@31..34
+ KEY@31..34
+ WORD@31..34 "qux"
+ R_BRACK@34..35 "]"
+ CURLY_GROUP_WORD_LIST@35..44
+ L_CURLY@35..36 "{"
+ WORD@36..43 "article"
+ R_CURLY@43..44 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_simple.snap
new file mode 100644
index 0000000000..85e21b47a8
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\documentclass{article}\"#)"
+
+---
+ROOT@0..23
+ PREAMBLE@0..23
+ CLASS_INCLUDE@0..23
+ CLASS_INCLUDE_NAME@0..14 "\\documentclass"
+ CURLY_GROUP_WORD_LIST@14..23
+ L_CURLY@14..15 "{"
+ WORD@15..22 "article"
+ R_CURLY@22..23 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_definition_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_definition_simple.snap
new file mode 100644
index 0000000000..e4adfd534c
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_definition_simple.snap
@@ -0,0 +1,27 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\definecolor{foo}{rgb}{255,168,0}\"#)"
+
+---
+ROOT@0..33
+ PREAMBLE@0..33
+ COLOR_DEFINITION@0..33
+ COLOR_DEFINITION_NAME@0..12 "\\definecolor"
+ CURLY_GROUP_WORD@12..17
+ L_CURLY@12..13 "{"
+ WORD@13..16 "foo"
+ R_CURLY@16..17 "}"
+ CURLY_GROUP_WORD@17..22
+ L_CURLY@17..18 "{"
+ WORD@18..21 "rgb"
+ R_CURLY@21..22 "}"
+ CURLY_GROUP@22..33
+ L_CURLY@22..23 "{"
+ TEXT@23..32
+ WORD@23..26 "255"
+ COMMA@26..27 ","
+ WORD@27..30 "168"
+ COMMA@30..31 ","
+ WORD@31..32 "0"
+ R_CURLY@32..33 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_reference_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_reference_simple.snap
new file mode 100644
index 0000000000..41a3700122
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_reference_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\color{black}\"#)"
+
+---
+ROOT@0..13
+ PREAMBLE@0..13
+ COLOR_REFERENCE@0..13
+ COLOR_REFERENCE_NAME@0..6 "\\color"
+ CURLY_GROUP_WORD@6..13
+ L_CURLY@6..7 "{"
+ WORD@7..12 "black"
+ R_CURLY@12..13 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error1.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error1.snap
new file mode 100644
index 0000000000..42095ff974
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error1.snap
@@ -0,0 +1,29 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\definecolorset[ty]{rgb,HTML}{foo}{bar}\"#)"
+
+---
+ROOT@0..39
+ PREAMBLE@0..39
+ COLOR_SET_DEFINITION@0..39
+ COLOR_SET_DEFINITION_NAME@0..15 "\\definecolorset"
+ BRACK_GROUP_WORD@15..19
+ L_BRACK@15..16 "["
+ WORD@16..18 "ty"
+ R_BRACK@18..19 "]"
+ CURLY_GROUP_WORD_LIST@19..29
+ L_CURLY@19..20 "{"
+ WORD@20..23 "rgb"
+ COMMA@23..24 ","
+ WORD@24..28 "HTML"
+ R_CURLY@28..29 "}"
+ CURLY_GROUP_WORD@29..34
+ L_CURLY@29..30 "{"
+ WORD@30..33 "foo"
+ R_CURLY@33..34 "}"
+ CURLY_GROUP_WORD@34..39
+ L_CURLY@34..35 "{"
+ WORD@35..38 "bar"
+ R_CURLY@38..39 "}"
+ MISSING@39..39 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error2.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error2.snap
new file mode 100644
index 0000000000..718d49a3e3
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error2.snap
@@ -0,0 +1,22 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\definecolorset{rgb,HTML}{foo}\"#)"
+
+---
+ROOT@0..30
+ PREAMBLE@0..30
+ COLOR_SET_DEFINITION@0..30
+ COLOR_SET_DEFINITION_NAME@0..15 "\\definecolorset"
+ CURLY_GROUP_WORD_LIST@15..25
+ L_CURLY@15..16 "{"
+ WORD@16..19 "rgb"
+ COMMA@19..20 ","
+ WORD@20..24 "HTML"
+ R_CURLY@24..25 "}"
+ CURLY_GROUP_WORD@25..30
+ L_CURLY@25..26 "{"
+ WORD@26..29 "foo"
+ R_CURLY@29..30 "}"
+ MISSING@30..30 ""
+ MISSING@30..30 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error3.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error3.snap
new file mode 100644
index 0000000000..f92d813123
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error3.snap
@@ -0,0 +1,19 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\definecolorset{rgb,HTML}\"#)"
+
+---
+ROOT@0..25
+ PREAMBLE@0..25
+ COLOR_SET_DEFINITION@0..25
+ COLOR_SET_DEFINITION_NAME@0..15 "\\definecolorset"
+ CURLY_GROUP_WORD_LIST@15..25
+ L_CURLY@15..16 "{"
+ WORD@16..19 "rgb"
+ COMMA@19..20 ","
+ WORD@20..24 "HTML"
+ R_CURLY@24..25 "}"
+ MISSING@25..25 ""
+ MISSING@25..25 ""
+ MISSING@25..25 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error4.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error4.snap
new file mode 100644
index 0000000000..3cafa81bae
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error4.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\definecolorset\"#)"
+
+---
+ROOT@0..15
+ PREAMBLE@0..15
+ COLOR_SET_DEFINITION@0..15
+ COLOR_SET_DEFINITION_NAME@0..15 "\\definecolorset"
+ MISSING@15..15 ""
+ MISSING@15..15 ""
+ MISSING@15..15 ""
+ MISSING@15..15 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_simple.snap
new file mode 100644
index 0000000000..f9a736c65f
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_simple.snap
@@ -0,0 +1,32 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\definecolorset[ty]{rgb,HTML}{foo}{bar}{baz}\"#)"
+
+---
+ROOT@0..44
+ PREAMBLE@0..44
+ COLOR_SET_DEFINITION@0..44
+ COLOR_SET_DEFINITION_NAME@0..15 "\\definecolorset"
+ BRACK_GROUP_WORD@15..19
+ L_BRACK@15..16 "["
+ WORD@16..18 "ty"
+ R_BRACK@18..19 "]"
+ CURLY_GROUP_WORD_LIST@19..29
+ L_CURLY@19..20 "{"
+ WORD@20..23 "rgb"
+ COMMA@23..24 ","
+ WORD@24..28 "HTML"
+ R_CURLY@28..29 "}"
+ CURLY_GROUP_WORD@29..34
+ L_CURLY@29..30 "{"
+ WORD@30..33 "foo"
+ R_CURLY@33..34 "}"
+ CURLY_GROUP_WORD@34..39
+ L_CURLY@34..35 "{"
+ WORD@35..38 "bar"
+ R_CURLY@38..39 "}"
+ CURLY_GROUP_WORD@39..44
+ L_CURLY@39..40 "{"
+ WORD@40..43 "baz"
+ R_CURLY@43..44 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_argc.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_argc.snap
new file mode 100644
index 0000000000..2ff3896080
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_argc.snap
@@ -0,0 +1,19 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\newcommand{\\foo}{foo}\"#)"
+
+---
+ROOT@0..22
+ PREAMBLE@0..22
+ COMMAND_DEFINITION@0..22
+ COMMAND_DEFINITION_NAME@0..11 "\\newcommand"
+ CURLY_GROUP_COMMAND@11..17
+ L_CURLY@11..12 "{"
+ GENERIC_COMMAND_NAME@12..16 "\\foo"
+ R_CURLY@16..17 "}"
+ CURLY_GROUP@17..22
+ L_CURLY@17..18 "{"
+ TEXT@18..21
+ WORD@18..21 "foo"
+ R_CURLY@21..22 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl.snap
new file mode 100644
index 0000000000..4602a7a688
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl.snap
@@ -0,0 +1,15 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\newcommand{\\foo}\"#)"
+
+---
+ROOT@0..17
+ PREAMBLE@0..17
+ COMMAND_DEFINITION@0..17
+ COMMAND_DEFINITION_NAME@0..11 "\\newcommand"
+ CURLY_GROUP_COMMAND@11..17
+ L_CURLY@11..12 "{"
+ GENERIC_COMMAND_NAME@12..16 "\\foo"
+ R_CURLY@16..17 "}"
+ MISSING@17..17 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl_error.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl_error.snap
new file mode 100644
index 0000000000..4a20be7d7c
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl_error.snap
@@ -0,0 +1,15 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\newcommand{\\foo\"#)"
+
+---
+ROOT@0..16
+ PREAMBLE@0..16
+ COMMAND_DEFINITION@0..16
+ COMMAND_DEFINITION_NAME@0..11 "\\newcommand"
+ CURLY_GROUP_COMMAND@11..16
+ L_CURLY@11..12 "{"
+ GENERIC_COMMAND_NAME@12..16 "\\foo"
+ MISSING@16..16 ""
+ MISSING@16..16 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_simple.snap
new file mode 100644
index 0000000000..e5f2377fcf
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_simple.snap
@@ -0,0 +1,24 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\newcommand[1]{\\id}{#1}\"#)"
+
+---
+ROOT@0..23
+ PREAMBLE@0..23
+ COMMAND_DEFINITION@0..19
+ COMMAND_DEFINITION_NAME@0..11 "\\newcommand"
+ MISSING@11..11 ""
+ BRACK_GROUP_WORD@11..14
+ L_BRACK@11..12 "["
+ WORD@12..13 "1"
+ R_BRACK@13..14 "]"
+ CURLY_GROUP@14..19
+ L_CURLY@14..15 "{"
+ GENERIC_COMMAND@15..18
+ GENERIC_COMMAND_NAME@15..18 "\\id"
+ R_CURLY@18..19 "}"
+ CURLY_GROUP@19..23
+ L_CURLY@19..20 "{"
+ PARAMETER@20..22 "#1"
+ R_CURLY@22..23 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__empty.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__empty.snap
new file mode 100644
index 0000000000..f01f7f9092
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__empty.snap
@@ -0,0 +1,8 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\"#)"
+
+---
+ROOT@0..0
+ PREAMBLE@0..0
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_definition.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_definition.snap
new file mode 100644
index 0000000000..df3c0d56fb
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_definition.snap
@@ -0,0 +1,38 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\newenvironment{bar}[1]{\\begin{foo}}{\\end{foo}}\"#)"
+
+---
+ROOT@0..47
+ PREAMBLE@0..47
+ ENVIRONMENT_DEFINITION@0..47
+ ENVIRONMENT_DEFINIITION_NAME@0..15 "\\newenvironment"
+ CURLY_GROUP_WORD@15..20
+ L_CURLY@15..16 "{"
+ WORD@16..19 "bar"
+ R_CURLY@19..20 "}"
+ BRACK_GROUP_WORD@20..23
+ L_BRACK@20..21 "["
+ WORD@21..22 "1"
+ R_BRACK@22..23 "]"
+ CURLY_GROUP@23..36
+ L_CURLY@23..24 "{"
+ GENERIC_COMMAND@24..35
+ BEGIN_ENVIRONMENT_NAME@24..30 "\\begin"
+ CURLY_GROUP@30..35
+ L_CURLY@30..31 "{"
+ TEXT@31..34
+ WORD@31..34 "foo"
+ R_CURLY@34..35 "}"
+ R_CURLY@35..36 "}"
+ CURLY_GROUP@36..47
+ L_CURLY@36..37 "{"
+ GENERIC_COMMAND@37..46
+ END_ENVIRONMENT_NAME@37..41 "\\end"
+ CURLY_GROUP@41..46
+ L_CURLY@41..42 "{"
+ TEXT@42..45
+ WORD@42..45 "foo"
+ R_CURLY@45..46 "}"
+ R_CURLY@46..47 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested.snap
new file mode 100644
index 0000000000..a5c566f773
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested.snap
@@ -0,0 +1,37 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\begin{foo} \\begin{qux} \\end{baz} \\end{bar}\"#)"
+
+---
+ROOT@0..43
+ PREAMBLE@0..43
+ ENVIRONMENT@0..43
+ BEGIN@0..12
+ BEGIN_ENVIRONMENT_NAME@0..6 "\\begin"
+ CURLY_GROUP_WORD@6..12
+ L_CURLY@6..7 "{"
+ WORD@7..10 "foo"
+ R_CURLY@10..11 "}"
+ WHITESPACE@11..12 " "
+ ENVIRONMENT@12..34
+ BEGIN@12..24
+ BEGIN_ENVIRONMENT_NAME@12..18 "\\begin"
+ CURLY_GROUP_WORD@18..24
+ L_CURLY@18..19 "{"
+ WORD@19..22 "qux"
+ R_CURLY@22..23 "}"
+ WHITESPACE@23..24 " "
+ END@24..34
+ END_ENVIRONMENT_NAME@24..28 "\\end"
+ CURLY_GROUP_WORD@28..34
+ L_CURLY@28..29 "{"
+ WORD@29..32 "baz"
+ R_CURLY@32..33 "}"
+ WHITESPACE@33..34 " "
+ END@34..43
+ END_ENVIRONMENT_NAME@34..38 "\\end"
+ CURLY_GROUP_WORD@38..43
+ L_CURLY@38..39 "{"
+ WORD@39..42 "bar"
+ R_CURLY@42..43 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested_missing_braces.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested_missing_braces.snap
new file mode 100644
index 0000000000..9ff76605d6
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested_missing_braces.snap
@@ -0,0 +1,42 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\begin{foo \\begin{qux Hello World \\end{baz} \\end{bar\"#)"
+
+---
+ROOT@0..52
+ PREAMBLE@0..52
+ ENVIRONMENT@0..52
+ BEGIN@0..11
+ BEGIN_ENVIRONMENT_NAME@0..6 "\\begin"
+ CURLY_GROUP_WORD@6..11
+ L_CURLY@6..7 "{"
+ WORD@7..10 "foo"
+ WHITESPACE@10..11 " "
+ MISSING@11..11 ""
+ ENVIRONMENT@11..44
+ BEGIN@11..22
+ BEGIN_ENVIRONMENT_NAME@11..17 "\\begin"
+ CURLY_GROUP_WORD@17..22
+ L_CURLY@17..18 "{"
+ WORD@18..21 "qux"
+ WHITESPACE@21..22 " "
+ MISSING@22..22 ""
+ TEXT@22..34
+ WORD@22..27 "Hello"
+ WHITESPACE@27..28 " "
+ WORD@28..33 "World"
+ WHITESPACE@33..34 " "
+ END@34..44
+ END_ENVIRONMENT_NAME@34..38 "\\end"
+ CURLY_GROUP_WORD@38..44
+ L_CURLY@38..39 "{"
+ WORD@39..42 "baz"
+ R_CURLY@42..43 "}"
+ WHITESPACE@43..44 " "
+ END@44..52
+ END_ENVIRONMENT_NAME@44..48 "\\end"
+ CURLY_GROUP_WORD@48..52
+ L_CURLY@48..49 "{"
+ WORD@49..52 "bar"
+ MISSING@52..52 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_simple.snap
new file mode 100644
index 0000000000..d58f5b26c4
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_simple.snap
@@ -0,0 +1,27 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\begin{foo} Hello World \\end{bar}\"#)"
+
+---
+ROOT@0..33
+ PREAMBLE@0..33
+ ENVIRONMENT@0..33
+ BEGIN@0..12
+ BEGIN_ENVIRONMENT_NAME@0..6 "\\begin"
+ CURLY_GROUP_WORD@6..12
+ L_CURLY@6..7 "{"
+ WORD@7..10 "foo"
+ R_CURLY@10..11 "}"
+ WHITESPACE@11..12 " "
+ TEXT@12..24
+ WORD@12..17 "Hello"
+ WHITESPACE@17..18 " "
+ WORD@18..23 "World"
+ WHITESPACE@23..24 " "
+ END@24..33
+ END_ENVIRONMENT_NAME@24..28 "\\end"
+ CURLY_GROUP_WORD@28..33
+ L_CURLY@28..29 "{"
+ WORD@29..32 "bar"
+ R_CURLY@32..33 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation.snap
new file mode 100644
index 0000000000..e146fcaee6
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation.snap
@@ -0,0 +1,17 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\[ foo bar \\]\"#)"
+
+---
+ROOT@0..13
+ PREAMBLE@0..13
+ EQUATION@0..13
+ BEGIN_EQUATION_NAME@0..2 "\\["
+ WHITESPACE@2..3 " "
+ TEXT@3..11
+ WORD@3..6 "foo"
+ WHITESPACE@6..7 " "
+ WORD@7..10 "bar"
+ WHITESPACE@10..11 " "
+ END_EQUATION_NAME@11..13 "\\]"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_label_reference_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_label_reference_simple.snap
new file mode 100644
index 0000000000..7a851233ef
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_label_reference_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\eqref{foo}\"#)"
+
+---
+ROOT@0..11
+ PREAMBLE@0..11
+ LABEL_REFERENCE@0..11
+ LABEL_REFERENCE_NAME@0..6 "\\eqref"
+ CURLY_GROUP_WORD_LIST@6..11
+ L_CURLY@6..7 "{"
+ WORD@7..10 "foo"
+ R_CURLY@10..11 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_begin.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_begin.snap
new file mode 100644
index 0000000000..fe065667c3
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_begin.snap
@@ -0,0 +1,30 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\begin{a} foo bar \\] \\end{b}\"#)"
+
+---
+ROOT@0..28
+ PREAMBLE@0..28
+ ENVIRONMENT@0..28
+ BEGIN@0..10
+ BEGIN_ENVIRONMENT_NAME@0..6 "\\begin"
+ CURLY_GROUP_WORD@6..10
+ L_CURLY@6..7 "{"
+ WORD@7..8 "a"
+ R_CURLY@8..9 "}"
+ WHITESPACE@9..10 " "
+ TEXT@10..18
+ WORD@10..13 "foo"
+ WHITESPACE@13..14 " "
+ WORD@14..17 "bar"
+ WHITESPACE@17..18 " "
+ GENERIC_COMMAND@18..21
+ END_EQUATION_NAME@18..20 "\\]"
+ WHITESPACE@20..21 " "
+ END@21..28
+ END_ENVIRONMENT_NAME@21..25 "\\end"
+ CURLY_GROUP_WORD@25..28
+ L_CURLY@25..26 "{"
+ WORD@26..27 "b"
+ R_CURLY@27..28 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_end.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_end.snap
new file mode 100644
index 0000000000..f7b6205ffb
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_end.snap
@@ -0,0 +1,31 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\begin{a} \\[ foo bar \\end{b}\"#)"
+
+---
+ROOT@0..28
+ PREAMBLE@0..28
+ ENVIRONMENT@0..28
+ BEGIN@0..10
+ BEGIN_ENVIRONMENT_NAME@0..6 "\\begin"
+ CURLY_GROUP_WORD@6..10
+ L_CURLY@6..7 "{"
+ WORD@7..8 "a"
+ R_CURLY@8..9 "}"
+ WHITESPACE@9..10 " "
+ EQUATION@10..21
+ BEGIN_EQUATION_NAME@10..12 "\\["
+ WHITESPACE@12..13 " "
+ TEXT@13..21
+ WORD@13..16 "foo"
+ WHITESPACE@16..17 " "
+ WORD@17..20 "bar"
+ WHITESPACE@20..21 " "
+ MISSING@21..21 ""
+ END@21..28
+ END_ENVIRONMENT_NAME@21..25 "\\end"
+ CURLY_GROUP_WORD@25..28
+ L_CURLY@25..26 "{"
+ WORD@26..27 "b"
+ R_CURLY@27..28 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__escaped_brackets.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__escaped_brackets.snap
new file mode 100644
index 0000000000..c595828583
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__escaped_brackets.snap
@@ -0,0 +1,19 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"{[}{]}\"#)"
+
+---
+ROOT@0..6
+ PREAMBLE@0..6
+ CURLY_GROUP@0..3
+ L_CURLY@0..1 "{"
+ MIXED_GROUP@1..2
+ L_BRACK@1..2 "["
+ MISSING@2..2 ""
+ R_CURLY@2..3 "}"
+ CURLY_GROUP@3..6
+ L_CURLY@3..4 "{"
+ ERROR@4..5
+ R_BRACK@4..5 "]"
+ R_CURLY@5..6 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_args.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_args.snap
new file mode 100644
index 0000000000..b62c8c2559
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_args.snap
@@ -0,0 +1,20 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\foo{bar}[qux]\"#)"
+
+---
+ROOT@0..14
+ PREAMBLE@0..14
+ GENERIC_COMMAND@0..14
+ GENERIC_COMMAND_NAME@0..4 "\\foo"
+ CURLY_GROUP@4..9
+ L_CURLY@4..5 "{"
+ TEXT@5..8
+ WORD@5..8 "bar"
+ R_CURLY@8..9 "}"
+ MIXED_GROUP@9..14
+ L_BRACK@9..10 "["
+ TEXT@10..13
+ WORD@10..13 "qux"
+ R_BRACK@13..14 "]"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_empty.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_empty.snap
new file mode 100644
index 0000000000..84e62ea81d
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_empty.snap
@@ -0,0 +1,10 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\foo\"#)"
+
+---
+ROOT@0..4
+ PREAMBLE@0..4
+ GENERIC_COMMAND@0..4
+ GENERIC_COMMAND_NAME@0..4 "\\foo"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_escape.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_escape.snap
new file mode 100644
index 0000000000..a0d5d2e29b
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_escape.snap
@@ -0,0 +1,10 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\#\"#)"
+
+---
+ROOT@0..2
+ PREAMBLE@0..2
+ GENERIC_COMMAND@0..2
+ GENERIC_COMMAND_NAME@0..2 "\\#"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_definition_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_definition_simple.snap
new file mode 100644
index 0000000000..d1b31e275a
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_definition_simple.snap
@@ -0,0 +1,33 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\newglossaryentry{foo}{bar = baz, qux,}\"#)"
+
+---
+ROOT@0..39
+ PREAMBLE@0..39
+ GLOSSARY_ENTRY_DEFINITION@0..39
+ GLOSSARY_ENTRY_DEFINITION_NAME@0..17 "\\newglossaryentry"
+ CURLY_GROUP_WORD@17..22
+ L_CURLY@17..18 "{"
+ WORD@18..21 "foo"
+ R_CURLY@21..22 "}"
+ CURLY_GROUP_KEY_VALUE@22..39
+ L_CURLY@22..23 "{"
+ KEY_VALUE_BODY@23..38
+ KEY_VALUE_PAIR@23..32
+ KEY@23..27
+ WORD@23..26 "bar"
+ WHITESPACE@26..27 " "
+ EQUALITY_SIGN@27..28 "="
+ WHITESPACE@28..29 " "
+ VALUE@29..32
+ TEXT@29..32
+ WORD@29..32 "baz"
+ COMMA@32..33 ","
+ WHITESPACE@33..34 " "
+ KEY_VALUE_PAIR@34..37
+ KEY@34..37
+ WORD@34..37 "qux"
+ COMMA@37..38 ","
+ R_CURLY@38..39 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_options.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_options.snap
new file mode 100644
index 0000000000..ad5c44885a
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_options.snap
@@ -0,0 +1,32 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\gls[foo = bar, qux]{baz}\"#)"
+
+---
+ROOT@0..25
+ PREAMBLE@0..25
+ GLOSSARY_ENTRY_REFERENCE@0..25
+ GLOSSARY_ENTRY_REFERENCE_NAME@0..4 "\\gls"
+ BRACK_GROUP_KEY_VALUE@4..20
+ L_BRACK@4..5 "["
+ KEY_VALUE_BODY@5..19
+ KEY_VALUE_PAIR@5..14
+ KEY@5..9
+ WORD@5..8 "foo"
+ WHITESPACE@8..9 " "
+ EQUALITY_SIGN@9..10 "="
+ WHITESPACE@10..11 " "
+ VALUE@11..14
+ TEXT@11..14
+ WORD@11..14 "bar"
+ COMMA@14..15 ","
+ WHITESPACE@15..16 " "
+ KEY_VALUE_PAIR@16..19
+ KEY@16..19
+ WORD@16..19 "qux"
+ R_BRACK@19..20 "]"
+ CURLY_GROUP_WORD@20..25
+ L_CURLY@20..21 "{"
+ WORD@21..24 "baz"
+ R_CURLY@24..25 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_simple.snap
new file mode 100644
index 0000000000..2a6b845733
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\gls{foo}\"#)"
+
+---
+ROOT@0..9
+ PREAMBLE@0..9
+ GLOSSARY_ENTRY_REFERENCE@0..9
+ GLOSSARY_ENTRY_REFERENCE_NAME@0..4 "\\gls"
+ CURLY_GROUP_WORD@4..9
+ L_CURLY@4..5 "{"
+ WORD@5..8 "foo"
+ R_CURLY@8..9 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_options.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_options.snap
new file mode 100644
index 0000000000..910a4848ef
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_options.snap
@@ -0,0 +1,25 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\includegraphics[scale=.5]{foo/bar.pdf}\"#)"
+
+---
+ROOT@0..39
+ PREAMBLE@0..39
+ GRAPHICS_INCLUDE@0..39
+ GRAPHICS_INCLUDE_NAME@0..16 "\\includegraphics"
+ BRACK_GROUP_KEY_VALUE@16..26
+ L_BRACK@16..17 "["
+ KEY_VALUE_BODY@17..25
+ KEY_VALUE_PAIR@17..25
+ KEY@17..22
+ WORD@17..22 "scale"
+ EQUALITY_SIGN@22..23 "="
+ VALUE@23..25
+ TEXT@23..25
+ WORD@23..25 ".5"
+ R_BRACK@25..26 "]"
+ CURLY_GROUP_WORD_LIST@26..39
+ L_CURLY@26..27 "{"
+ WORD@27..38 "foo/bar.pdf"
+ R_CURLY@38..39 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_simple.snap
new file mode 100644
index 0000000000..fa6fbf1a14
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\includegraphics{foo/bar.pdf}\"#)"
+
+---
+ROOT@0..29
+ PREAMBLE@0..29
+ GRAPHICS_INCLUDE@0..29
+ GRAPHICS_INCLUDE_NAME@0..16 "\\includegraphics"
+ CURLY_GROUP_WORD_LIST@16..29
+ L_CURLY@16..17 "{"
+ WORD@17..28 "foo/bar.pdf"
+ R_CURLY@28..29 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__hello_world.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__hello_world.snap
new file mode 100644
index 0000000000..ee25cb5453
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__hello_world.snap
@@ -0,0 +1,12 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"Hello World!\"#)"
+
+---
+ROOT@0..12
+ PREAMBLE@0..12
+ TEXT@0..12
+ WORD@0..5 "Hello"
+ WHITESPACE@5..6 " "
+ WORD@6..12 "World!"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_incomplete.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_incomplete.snap
new file mode 100644
index 0000000000..3dae1f433a
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_incomplete.snap
@@ -0,0 +1,15 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\import{foo\"#)"
+
+---
+ROOT@0..11
+ PREAMBLE@0..11
+ IMPORT@0..11
+ IMPORT_NAME@0..7 "\\import"
+ CURLY_GROUP_WORD@7..11
+ L_CURLY@7..8 "{"
+ WORD@8..11 "foo"
+ MISSING@11..11 ""
+ MISSING@11..11 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_simple.snap
new file mode 100644
index 0000000000..6e21f65cbf
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_simple.snap
@@ -0,0 +1,18 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\import{foo}{bar}\"#)"
+
+---
+ROOT@0..17
+ PREAMBLE@0..17
+ IMPORT@0..17
+ IMPORT_NAME@0..7 "\\import"
+ CURLY_GROUP_WORD@7..12
+ L_CURLY@7..8 "{"
+ WORD@8..11 "foo"
+ R_CURLY@11..12 "}"
+ CURLY_GROUP_WORD@12..17
+ L_CURLY@12..13 "{"
+ WORD@13..16 "bar"
+ R_CURLY@16..17 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_options.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_options.snap
new file mode 100644
index 0000000000..d1fd2df652
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_options.snap
@@ -0,0 +1,25 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\includesvg[scale=.5]{foo/bar}\"#)"
+
+---
+ROOT@0..30
+ PREAMBLE@0..30
+ SVG_INCLUDE@0..30
+ SVG_INCLUDE_NAME@0..11 "\\includesvg"
+ BRACK_GROUP_KEY_VALUE@11..21
+ L_BRACK@11..12 "["
+ KEY_VALUE_BODY@12..20
+ KEY_VALUE_PAIR@12..20
+ KEY@12..17
+ WORD@12..17 "scale"
+ EQUALITY_SIGN@17..18 "="
+ VALUE@18..20
+ TEXT@18..20
+ WORD@18..20 ".5"
+ R_BRACK@20..21 "]"
+ CURLY_GROUP_WORD_LIST@21..30
+ L_CURLY@21..22 "{"
+ WORD@22..29 "foo/bar"
+ R_CURLY@29..30 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_simple.snap
new file mode 100644
index 0000000000..59f50c36d3
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\includesvg{foo/bar}\"#)"
+
+---
+ROOT@0..20
+ PREAMBLE@0..20
+ SVG_INCLUDE@0..20
+ SVG_INCLUDE_NAME@0..11 "\\includesvg"
+ CURLY_GROUP_WORD_LIST@11..20
+ L_CURLY@11..12 "{"
+ WORD@12..19 "foo/bar"
+ R_CURLY@19..20 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline.snap
new file mode 100644
index 0000000000..b430de766e
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline.snap
@@ -0,0 +1,26 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"$x \\in [0, \\infty)$\"#)"
+
+---
+ROOT@0..19
+ PREAMBLE@0..19
+ FORMULA@0..19
+ DOLLAR@0..1 "$"
+ TEXT@1..3
+ WORD@1..2 "x"
+ WHITESPACE@2..3 " "
+ GENERIC_COMMAND@3..18
+ GENERIC_COMMAND_NAME@3..6 "\\in"
+ WHITESPACE@6..7 " "
+ MIXED_GROUP@7..18
+ L_BRACK@7..8 "["
+ TEXT@8..11
+ WORD@8..9 "0"
+ COMMA@9..10 ","
+ WHITESPACE@10..11 " "
+ GENERIC_COMMAND@11..17
+ GENERIC_COMMAND_NAME@11..17 "\\infty"
+ R_PAREN@17..18 ")"
+ DOLLAR@18..19 "$"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline_double_dollar.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline_double_dollar.snap
new file mode 100644
index 0000000000..8da22f3ecb
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline_double_dollar.snap
@@ -0,0 +1,26 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"$$x \\in [0, \\infty)$$\"#)"
+
+---
+ROOT@0..21
+ PREAMBLE@0..21
+ FORMULA@0..21
+ DOLLAR@0..2 "$$"
+ TEXT@2..4
+ WORD@2..3 "x"
+ WHITESPACE@3..4 " "
+ GENERIC_COMMAND@4..19
+ GENERIC_COMMAND_NAME@4..7 "\\in"
+ WHITESPACE@7..8 " "
+ MIXED_GROUP@8..19
+ L_BRACK@8..9 "["
+ TEXT@9..12
+ WORD@9..10 "0"
+ COMMA@10..11 ","
+ WHITESPACE@11..12 " "
+ GENERIC_COMMAND@12..18
+ GENERIC_COMMAND_NAME@12..18 "\\infty"
+ R_PAREN@18..19 ")"
+ DOLLAR@19..21 "$$"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_definition_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_definition_simple.snap
new file mode 100644
index 0000000000..7eacd65c59
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_definition_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\label{foo}\"#)"
+
+---
+ROOT@0..11
+ PREAMBLE@0..11
+ LABEL_DEFINITION@0..11
+ LABEL_DEFINITION_NAME@0..6 "\\label"
+ CURLY_GROUP_WORD@6..11
+ L_CURLY@6..7 "{"
+ WORD@7..10 "foo"
+ R_CURLY@10..11 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_number.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_number.snap
new file mode 100644
index 0000000000..46392d7574
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_number.snap
@@ -0,0 +1,23 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\newlabel{foo}{{1.1}}\"#)"
+
+---
+ROOT@0..21
+ PREAMBLE@0..21
+ LABEL_NUMBER@0..21
+ LABEL_NUMBER_NAME@0..9 "\\newlabel"
+ CURLY_GROUP_WORD@9..14
+ L_CURLY@9..10 "{"
+ WORD@10..13 "foo"
+ R_CURLY@13..14 "}"
+ CURLY_GROUP@14..21
+ L_CURLY@14..15 "{"
+ CURLY_GROUP@15..20
+ L_CURLY@15..16 "{"
+ TEXT@16..19
+ WORD@16..19 "1.1"
+ R_CURLY@19..20 "}"
+ R_CURLY@20..21 "}"
+ MISSING@21..21 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_multiple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_multiple.snap
new file mode 100644
index 0000000000..e399397e09
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_multiple.snap
@@ -0,0 +1,17 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\ref{foo, bar}\"#)"
+
+---
+ROOT@0..14
+ PREAMBLE@0..14
+ LABEL_REFERENCE@0..14
+ LABEL_REFERENCE_NAME@0..4 "\\ref"
+ CURLY_GROUP_WORD_LIST@4..14
+ L_CURLY@4..5 "{"
+ WORD@5..8 "foo"
+ COMMA@8..9 ","
+ WHITESPACE@9..10 " "
+ WORD@10..13 "bar"
+ R_CURLY@13..14 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_error.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_error.snap
new file mode 100644
index 0000000000..2894a9c2ec
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_error.snap
@@ -0,0 +1,18 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\crefrange{foo{bar}\"#)"
+
+---
+ROOT@0..19
+ PREAMBLE@0..19
+ LABEL_REFERENCE_RANGE@0..19
+ LABEL_REFERENCE_RANGE_NAME@0..10 "\\crefrange"
+ CURLY_GROUP_WORD@10..14
+ L_CURLY@10..11 "{"
+ WORD@11..14 "foo"
+ MISSING@14..14 ""
+ CURLY_GROUP_WORD@14..19
+ L_CURLY@14..15 "{"
+ WORD@15..18 "bar"
+ R_CURLY@18..19 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_incomplete.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_incomplete.snap
new file mode 100644
index 0000000000..0a1753867e
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_incomplete.snap
@@ -0,0 +1,15 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\crefrange{foo}\"#)"
+
+---
+ROOT@0..15
+ PREAMBLE@0..15
+ LABEL_REFERENCE_RANGE@0..15
+ LABEL_REFERENCE_RANGE_NAME@0..10 "\\crefrange"
+ CURLY_GROUP_WORD@10..15
+ L_CURLY@10..11 "{"
+ WORD@11..14 "foo"
+ R_CURLY@14..15 "}"
+ MISSING@15..15 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_simple.snap
new file mode 100644
index 0000000000..c06c31c1b7
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_simple.snap
@@ -0,0 +1,18 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\crefrange{foo}{bar}\"#)"
+
+---
+ROOT@0..20
+ PREAMBLE@0..20
+ LABEL_REFERENCE_RANGE@0..20
+ LABEL_REFERENCE_RANGE_NAME@0..10 "\\crefrange"
+ CURLY_GROUP_WORD@10..15
+ L_CURLY@10..11 "{"
+ WORD@11..14 "foo"
+ R_CURLY@14..15 "}"
+ CURLY_GROUP_WORD@15..20
+ L_CURLY@15..16 "{"
+ WORD@16..19 "bar"
+ R_CURLY@19..20 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_simple.snap
new file mode 100644
index 0000000000..ed06f5dfc1
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\ref{foo}\"#)"
+
+---
+ROOT@0..9
+ PREAMBLE@0..9
+ LABEL_REFERENCE@0..9
+ LABEL_REFERENCE_NAME@0..4 "\\ref"
+ CURLY_GROUP_WORD_LIST@4..9
+ L_CURLY@4..5 "{"
+ WORD@5..8 "foo"
+ R_CURLY@8..9 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_include_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_include_simple.snap
new file mode 100644
index 0000000000..34565aa072
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_include_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\include{foo/bar}\"#)"
+
+---
+ROOT@0..17
+ PREAMBLE@0..17
+ LATEX_INCLUDE@0..17
+ LATEX_INCLUDE_NAME@0..8 "\\include"
+ CURLY_GROUP_WORD_LIST@8..17
+ L_CURLY@8..9 "{"
+ WORD@9..16 "foo/bar"
+ R_CURLY@16..17 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_input_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_input_simple.snap
new file mode 100644
index 0000000000..208e898407
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_input_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\input{foo/bar.tex}\"#)"
+
+---
+ROOT@0..19
+ PREAMBLE@0..19
+ LATEX_INCLUDE@0..19
+ LATEX_INCLUDE_NAME@0..6 "\\input"
+ CURLY_GROUP_WORD_LIST@6..19
+ L_CURLY@6..7 "{"
+ WORD@7..18 "foo/bar.tex"
+ R_CURLY@18..19 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_no_impl.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_no_impl.snap
new file mode 100644
index 0000000000..2e39ee899c
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_no_impl.snap
@@ -0,0 +1,15 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\DeclareMathOperator{\\foo}\"#)"
+
+---
+ROOT@0..26
+ PREAMBLE@0..26
+ MATH_OPERATOR@0..26
+ MATH_OPERATOR_NAME@0..20 "\\DeclareMathOperator"
+ CURLY_GROUP_COMMAND@20..26
+ L_CURLY@20..21 "{"
+ GENERIC_COMMAND_NAME@21..25 "\\foo"
+ R_CURLY@25..26 "}"
+ MISSING@26..26 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_simple.snap
new file mode 100644
index 0000000000..bb44005153
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_simple.snap
@@ -0,0 +1,19 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\DeclareMathOperator{\\foo}{foo}\"#)"
+
+---
+ROOT@0..31
+ PREAMBLE@0..31
+ MATH_OPERATOR@0..31
+ MATH_OPERATOR_NAME@0..20 "\\DeclareMathOperator"
+ CURLY_GROUP_COMMAND@20..26
+ L_CURLY@20..21 "{"
+ GENERIC_COMMAND_NAME@21..25 "\\foo"
+ R_CURLY@25..26 "}"
+ CURLY_GROUP@26..31
+ L_CURLY@26..27 "{"
+ TEXT@27..30
+ WORD@27..30 "foo"
+ R_CURLY@30..31 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_empty.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_empty.snap
new file mode 100644
index 0000000000..b8851b6e1d
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_empty.snap
@@ -0,0 +1,13 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\usepackage{}\"#)"
+
+---
+ROOT@0..13
+ PREAMBLE@0..13
+ PACKAGE_INCLUDE@0..13
+ PACKAGE_INCLUDE_NAME@0..11 "\\usepackage"
+ CURLY_GROUP_WORD_LIST@11..13
+ L_CURLY@11..12 "{"
+ R_CURLY@12..13 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_multiple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_multiple.snap
new file mode 100644
index 0000000000..e68b98d1b5
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_multiple.snap
@@ -0,0 +1,17 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\usepackage{amsmath, lipsum}\"#)"
+
+---
+ROOT@0..28
+ PREAMBLE@0..28
+ PACKAGE_INCLUDE@0..28
+ PACKAGE_INCLUDE_NAME@0..11 "\\usepackage"
+ CURLY_GROUP_WORD_LIST@11..28
+ L_CURLY@11..12 "{"
+ WORD@12..19 "amsmath"
+ COMMA@19..20 ","
+ WHITESPACE@20..21 " "
+ WORD@21..27 "lipsum"
+ R_CURLY@27..28 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_options.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_options.snap
new file mode 100644
index 0000000000..a33c4e0bd7
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_options.snap
@@ -0,0 +1,37 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\usepackage[foo = bar, baz, qux]{amsmath}\"#)"
+
+---
+ROOT@0..41
+ PREAMBLE@0..41
+ PACKAGE_INCLUDE@0..41
+ PACKAGE_INCLUDE_NAME@0..11 "\\usepackage"
+ BRACK_GROUP_KEY_VALUE@11..32
+ L_BRACK@11..12 "["
+ KEY_VALUE_BODY@12..31
+ KEY_VALUE_PAIR@12..21
+ KEY@12..16
+ WORD@12..15 "foo"
+ WHITESPACE@15..16 " "
+ EQUALITY_SIGN@16..17 "="
+ WHITESPACE@17..18 " "
+ VALUE@18..21
+ TEXT@18..21
+ WORD@18..21 "bar"
+ COMMA@21..22 ","
+ WHITESPACE@22..23 " "
+ KEY_VALUE_PAIR@23..26
+ KEY@23..26
+ WORD@23..26 "baz"
+ COMMA@26..27 ","
+ WHITESPACE@27..28 " "
+ KEY_VALUE_PAIR@28..31
+ KEY@28..31
+ WORD@28..31 "qux"
+ R_BRACK@31..32 "]"
+ CURLY_GROUP_WORD_LIST@32..41
+ L_CURLY@32..33 "{"
+ WORD@33..40 "amsmath"
+ R_CURLY@40..41 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_simple.snap
new file mode 100644
index 0000000000..00c075fdfe
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\usepackage{amsmath}\"#)"
+
+---
+ROOT@0..20
+ PREAMBLE@0..20
+ PACKAGE_INCLUDE@0..20
+ PACKAGE_INCLUDE_NAME@0..11 "\\usepackage"
+ CURLY_GROUP_WORD_LIST@11..20
+ L_CURLY@11..12 "{"
+ WORD@12..19 "amsmath"
+ R_CURLY@19..20 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter.snap
new file mode 100644
index 0000000000..8c6bd90cb8
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter.snap
@@ -0,0 +1,9 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"#1\"#)"
+
+---
+ROOT@0..2
+ PREAMBLE@0..2
+ PARAMETER@0..2 "#1"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter_error.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter_error.snap
new file mode 100644
index 0000000000..1a9b68fa2d
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter_error.snap
@@ -0,0 +1,9 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"#\"#)"
+
+---
+ROOT@0..1
+ PREAMBLE@0..1
+ PARAMETER@0..1 "#"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__pgf_library_import_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__pgf_library_import_simple.snap
new file mode 100644
index 0000000000..8f289f97a4
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__pgf_library_import_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\usepgflibrary{foo}\"#)"
+
+---
+ROOT@0..19
+ PREAMBLE@0..19
+ TIKZ_LIBRARY_IMPORT@0..19
+ TIKZ_LIBRARY_IMPORT_NAME@0..14 "\\usepgflibrary"
+ CURLY_GROUP_WORD_LIST@14..19
+ L_CURLY@14..15 "{"
+ WORD@15..18 "foo"
+ R_CURLY@18..19 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_enum_item.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_enum_item.snap
new file mode 100644
index 0000000000..43dcae1987
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_enum_item.snap
@@ -0,0 +1,45 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\begin{enumerate} \\item 1 \\item[2] 2 \\item 3 \\end{enumerate}\"#)"
+
+---
+ROOT@0..60
+ PREAMBLE@0..60
+ ENVIRONMENT@0..60
+ BEGIN@0..18
+ BEGIN_ENVIRONMENT_NAME@0..6 "\\begin"
+ CURLY_GROUP_WORD@6..18
+ L_CURLY@6..7 "{"
+ WORD@7..16 "enumerate"
+ R_CURLY@16..17 "}"
+ WHITESPACE@17..18 " "
+ ENUM_ITEM@18..26
+ ENUM_ITEM_NAME@18..23 "\\item"
+ WHITESPACE@23..24 " "
+ TEXT@24..26
+ WORD@24..25 "1"
+ WHITESPACE@25..26 " "
+ ENUM_ITEM@26..37
+ ENUM_ITEM_NAME@26..31 "\\item"
+ BRACK_GROUP@31..35
+ L_BRACK@31..32 "["
+ TEXT@32..33
+ WORD@32..33 "2"
+ R_BRACK@33..34 "]"
+ WHITESPACE@34..35 " "
+ TEXT@35..37
+ WORD@35..36 "2"
+ WHITESPACE@36..37 " "
+ ENUM_ITEM@37..45
+ ENUM_ITEM_NAME@37..42 "\\item"
+ WHITESPACE@42..43 " "
+ TEXT@43..45
+ WORD@43..44 "3"
+ WHITESPACE@44..45 " "
+ END@45..60
+ END_ENVIRONMENT_NAME@45..49 "\\end"
+ CURLY_GROUP_WORD@49..60
+ L_CURLY@49..50 "{"
+ WORD@50..59 "enumerate"
+ R_CURLY@59..60 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_invalid_nesting.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_invalid_nesting.snap
new file mode 100644
index 0000000000..f999a50e9a
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_invalid_nesting.snap
@@ -0,0 +1,23 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\section{Foo} \\chapter{Bar}\"#)"
+
+---
+ROOT@0..27
+ PREAMBLE@0..27
+ SECTION@0..14
+ SECTION_NAME@0..8 "\\section"
+ CURLY_GROUP@8..14
+ L_CURLY@8..9 "{"
+ TEXT@9..12
+ WORD@9..12 "Foo"
+ R_CURLY@12..13 "}"
+ WHITESPACE@13..14 " "
+ CHAPTER@14..27
+ CHAPTER_NAME@14..22 "\\chapter"
+ CURLY_GROUP@22..27
+ L_CURLY@22..23 "{"
+ TEXT@23..26
+ WORD@23..26 "Bar"
+ R_CURLY@26..27 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_nested.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_nested.snap
new file mode 100644
index 0000000000..db5602e591
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_nested.snap
@@ -0,0 +1,57 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\part{1}\\chapter{2}\\section{3}\\subsection{4}\\subsubsection{5}\\paragraph{6}\\subparagraph{7}\"#)"
+
+---
+ROOT@0..90
+ PREAMBLE@0..90
+ PART@0..90
+ PART_NAME@0..5 "\\part"
+ CURLY_GROUP@5..8
+ L_CURLY@5..6 "{"
+ TEXT@6..7
+ WORD@6..7 "1"
+ R_CURLY@7..8 "}"
+ CHAPTER@8..90
+ CHAPTER_NAME@8..16 "\\chapter"
+ CURLY_GROUP@16..19
+ L_CURLY@16..17 "{"
+ TEXT@17..18
+ WORD@17..18 "2"
+ R_CURLY@18..19 "}"
+ SECTION@19..90
+ SECTION_NAME@19..27 "\\section"
+ CURLY_GROUP@27..30
+ L_CURLY@27..28 "{"
+ TEXT@28..29
+ WORD@28..29 "3"
+ R_CURLY@29..30 "}"
+ SUBSECTION@30..90
+ SUBSECTION_NAME@30..41 "\\subsection"
+ CURLY_GROUP@41..44
+ L_CURLY@41..42 "{"
+ TEXT@42..43
+ WORD@42..43 "4"
+ R_CURLY@43..44 "}"
+ SUBSUBSECTION@44..90
+ SUBSUBSECTION_NAME@44..58 "\\subsubsection"
+ CURLY_GROUP@58..61
+ L_CURLY@58..59 "{"
+ TEXT@59..60
+ WORD@59..60 "5"
+ R_CURLY@60..61 "}"
+ PARAGRAPH@61..90
+ PARAGRAPH_NAME@61..71 "\\paragraph"
+ CURLY_GROUP@71..74
+ L_CURLY@71..72 "{"
+ TEXT@72..73
+ WORD@72..73 "6"
+ R_CURLY@73..74 "}"
+ SUBPARAGRAPH@74..90
+ SUBPARAGRAPH_NAME@74..87 "\\subparagraph"
+ CURLY_GROUP@87..90
+ L_CURLY@87..88 "{"
+ TEXT@88..89
+ WORD@88..89 "7"
+ R_CURLY@89..90 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_siblings.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_siblings.snap
new file mode 100644
index 0000000000..94d2ef40bd
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_siblings.snap
@@ -0,0 +1,29 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\section{Foo} Foo \\section{Bar} Bar\"#)"
+
+---
+ROOT@0..35
+ PREAMBLE@0..35
+ SECTION@0..18
+ SECTION_NAME@0..8 "\\section"
+ CURLY_GROUP@8..14
+ L_CURLY@8..9 "{"
+ TEXT@9..12
+ WORD@9..12 "Foo"
+ R_CURLY@12..13 "}"
+ WHITESPACE@13..14 " "
+ TEXT@14..18
+ WORD@14..17 "Foo"
+ WHITESPACE@17..18 " "
+ SECTION@18..35
+ SECTION_NAME@18..26 "\\section"
+ CURLY_GROUP@26..32
+ L_CURLY@26..27 "{"
+ TEXT@27..30
+ WORD@27..30 "Bar"
+ R_CURLY@30..31 "}"
+ WHITESPACE@31..32 " "
+ TEXT@32..35
+ WORD@32..35 "Bar"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_options.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_options.snap
new file mode 100644
index 0000000000..863f27cf56
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_options.snap
@@ -0,0 +1,25 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\includesvg[scale=.5]{foo/bar.svg}\"#)"
+
+---
+ROOT@0..34
+ PREAMBLE@0..34
+ SVG_INCLUDE@0..34
+ SVG_INCLUDE_NAME@0..11 "\\includesvg"
+ BRACK_GROUP_KEY_VALUE@11..21
+ L_BRACK@11..12 "["
+ KEY_VALUE_BODY@12..20
+ KEY_VALUE_PAIR@12..20
+ KEY@12..17
+ WORD@12..17 "scale"
+ EQUALITY_SIGN@17..18 "="
+ VALUE@18..20
+ TEXT@18..20
+ WORD@18..20 ".5"
+ R_BRACK@20..21 "]"
+ CURLY_GROUP_WORD_LIST@21..34
+ L_CURLY@21..22 "{"
+ WORD@22..33 "foo/bar.svg"
+ R_CURLY@33..34 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_simple.snap
new file mode 100644
index 0000000000..02db942d99
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\includesvg{foo/bar.svg}\"#)"
+
+---
+ROOT@0..24
+ PREAMBLE@0..24
+ SVG_INCLUDE@0..24
+ SVG_INCLUDE_NAME@0..11 "\\includesvg"
+ CURLY_GROUP_WORD_LIST@11..24
+ L_CURLY@11..12 "{"
+ WORD@12..23 "foo/bar.svg"
+ R_CURLY@23..24 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_full.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_full.snap
new file mode 100644
index 0000000000..d8fe3816ad
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_full.snap
@@ -0,0 +1,27 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\newtheorem{foo}[bar]{Foo}[baz]\"#)"
+
+---
+ROOT@0..31
+ PREAMBLE@0..31
+ THEOREM_DEFINITION@0..31
+ THEOREM_DEFINITION_NAME@0..11 "\\newtheorem"
+ CURLY_GROUP_WORD@11..16
+ L_CURLY@11..12 "{"
+ WORD@12..15 "foo"
+ R_CURLY@15..16 "}"
+ BRACK_GROUP_WORD@16..21
+ L_BRACK@16..17 "["
+ WORD@17..20 "bar"
+ R_BRACK@20..21 "]"
+ CURLY_GROUP@21..26
+ L_CURLY@21..22 "{"
+ TEXT@22..25
+ WORD@22..25 "Foo"
+ R_CURLY@25..26 "}"
+ BRACK_GROUP_WORD@26..31
+ L_BRACK@26..27 "["
+ WORD@27..30 "baz"
+ R_BRACK@30..31 "]"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_counter.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_counter.snap
new file mode 100644
index 0000000000..9e550b19a4
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_counter.snap
@@ -0,0 +1,19 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\newtheorem{foo}[bar]\"#)"
+
+---
+ROOT@0..21
+ PREAMBLE@0..21
+ THEOREM_DEFINITION@0..21
+ THEOREM_DEFINITION_NAME@0..11 "\\newtheorem"
+ CURLY_GROUP_WORD@11..16
+ L_CURLY@11..12 "{"
+ WORD@12..15 "foo"
+ R_CURLY@15..16 "}"
+ BRACK_GROUP_WORD@16..21
+ L_BRACK@16..17 "["
+ WORD@17..20 "bar"
+ R_BRACK@20..21 "]"
+ MISSING@21..21 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description.snap
new file mode 100644
index 0000000000..356dc079f1
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description.snap
@@ -0,0 +1,19 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\newtheorem{foo}{Foo}\"#)"
+
+---
+ROOT@0..21
+ PREAMBLE@0..21
+ THEOREM_DEFINITION@0..21
+ THEOREM_DEFINITION_NAME@0..11 "\\newtheorem"
+ CURLY_GROUP_WORD@11..16
+ L_CURLY@11..12 "{"
+ WORD@12..15 "foo"
+ R_CURLY@15..16 "}"
+ CURLY_GROUP@16..21
+ L_CURLY@16..17 "{"
+ TEXT@17..20
+ WORD@17..20 "Foo"
+ R_CURLY@20..21 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description_and_counter.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description_and_counter.snap
new file mode 100644
index 0000000000..cc6e34d378
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description_and_counter.snap
@@ -0,0 +1,23 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\newtheorem{foo}[bar]{Foo}\"#)"
+
+---
+ROOT@0..26
+ PREAMBLE@0..26
+ THEOREM_DEFINITION@0..26
+ THEOREM_DEFINITION_NAME@0..11 "\\newtheorem"
+ CURLY_GROUP_WORD@11..16
+ L_CURLY@11..12 "{"
+ WORD@12..15 "foo"
+ R_CURLY@15..16 "}"
+ BRACK_GROUP_WORD@16..21
+ L_BRACK@16..17 "["
+ WORD@17..20 "bar"
+ R_BRACK@20..21 "]"
+ CURLY_GROUP@21..26
+ L_CURLY@21..22 "{"
+ TEXT@22..25
+ WORD@22..25 "Foo"
+ R_CURLY@25..26 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_only_name.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_only_name.snap
new file mode 100644
index 0000000000..27a040b7ff
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_only_name.snap
@@ -0,0 +1,15 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\newtheorem{foo}\"#)"
+
+---
+ROOT@0..16
+ PREAMBLE@0..16
+ THEOREM_DEFINITION@0..16
+ THEOREM_DEFINITION_NAME@0..11 "\\newtheorem"
+ CURLY_GROUP_WORD@11..16
+ L_CURLY@11..12 "{"
+ WORD@12..15 "foo"
+ R_CURLY@15..16 "}"
+ MISSING@16..16 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__tikz_library_import_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__tikz_library_import_simple.snap
new file mode 100644
index 0000000000..4915306c55
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__tikz_library_import_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\usetikzlibrary{foo}\"#)"
+
+---
+ROOT@0..20
+ PREAMBLE@0..20
+ TIKZ_LIBRARY_IMPORT@0..20
+ TIKZ_LIBRARY_IMPORT_NAME@0..15 "\\usetikzlibrary"
+ CURLY_GROUP_WORD_LIST@15..20
+ L_CURLY@15..16 "{"
+ WORD@16..19 "foo"
+ R_CURLY@19..20 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_braces.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_braces.snap
new file mode 100644
index 0000000000..df463f501a
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_braces.snap
@@ -0,0 +1,13 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"}{\"#)"
+
+---
+ROOT@0..2
+ PREAMBLE@0..2
+ ERROR@0..1
+ R_CURLY@0..1 "}"
+ CURLY_GROUP@1..2
+ L_CURLY@1..2 "{"
+ MISSING@2..2 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets.snap
new file mode 100644
index 0000000000..578cbf6099
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets.snap
@@ -0,0 +1,13 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"][\"#)"
+
+---
+ROOT@0..2
+ PREAMBLE@0..2
+ ERROR@0..1
+ R_BRACK@0..1 "]"
+ MIXED_GROUP@1..2
+ L_BRACK@1..2 "["
+ MISSING@2..2 ""
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets_with_group.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets_with_group.snap
new file mode 100644
index 0000000000..d929c98c46
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets_with_group.snap
@@ -0,0 +1,16 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"{][}\"#)"
+
+---
+ROOT@0..4
+ PREAMBLE@0..4
+ CURLY_GROUP@0..4
+ L_CURLY@0..1 "{"
+ ERROR@1..2
+ R_BRACK@1..2 "]"
+ MIXED_GROUP@2..3
+ L_BRACK@2..3 "["
+ MISSING@3..3 ""
+ R_CURLY@3..4 "}"
+
diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__verbatim_include_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__verbatim_include_simple.snap
new file mode 100644
index 0000000000..399e16d791
--- /dev/null
+++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__verbatim_include_simple.snap
@@ -0,0 +1,14 @@
+---
+source: src/syntax/latex/parser.rs
+expression: "setup(r#\"\\verbatiminput{foo/bar.txt}\"#)"
+
+---
+ROOT@0..27
+ PREAMBLE@0..27
+ VERBATIM_INCLUDE@0..27
+ VERBATIM_INCLUDE_NAME@0..14 "\\verbatiminput"
+ CURLY_GROUP_WORD_LIST@14..27
+ L_CURLY@14..15 "{"
+ WORD@15..26 "foo/bar.txt"
+ R_CURLY@26..27 "}"
+
diff --git a/support/texlab/src/syntax/latexindent.rs b/support/texlab/src/syntax/latexindent.rs
deleted file mode 100644
index c45c95d4e5..0000000000
--- a/support/texlab/src/syntax/latexindent.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-use std::{io, process::Stdio};
-use tempfile::tempdir;
-use tokio::{fs, process::Command};
-
-pub async fn format(text: &str, extension: &str) -> io::Result<String> {
- let dir = tempdir()?;
- let input = format!("input.{}", extension);
- let output = format!("output.{}", extension);
- fs::write(dir.path().join(&input), text).await?;
-
- Command::new("latexindent")
- .args(&["-o", &output, &input])
- .current_dir(dir.path())
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .stderr(Stdio::null())
- .spawn()?
- .await?;
-
- fs::read_to_string(dir.path().join(&output)).await
-}
diff --git a/support/texlab/src/syntax/lsp_kind.rs b/support/texlab/src/syntax/lsp_kind.rs
deleted file mode 100644
index 7d92fd4ee4..0000000000
--- a/support/texlab/src/syntax/lsp_kind.rs
+++ /dev/null
@@ -1,93 +0,0 @@
-use super::lang_data::BibtexEntryTypeCategory;
-use crate::protocol::{CompletionItemKind, SymbolKind};
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum Structure {
- Command,
- Snippet,
- Environment,
- Section,
- Float,
- Theorem,
- Equation,
- Item,
- Label,
- Folder,
- File,
- PgfLibrary,
- TikzLibrary,
- Color,
- ColorModel,
- Package,
- Class,
- Entry(BibtexEntryTypeCategory),
- Field,
- Argument,
- GlossaryEntry,
-}
-
-impl Structure {
- pub fn completion_kind(self) -> CompletionItemKind {
- match self {
- Self::Command => CompletionItemKind::Function,
- Self::Snippet => CompletionItemKind::Snippet,
- Self::Environment => CompletionItemKind::Enum,
- Self::Section => CompletionItemKind::Module,
- Self::Float => CompletionItemKind::Method,
- Self::Theorem => CompletionItemKind::Variable,
- Self::Equation => CompletionItemKind::Constant,
- Self::Item => CompletionItemKind::EnumMember,
- Self::Label => CompletionItemKind::Constructor,
- Self::Folder => CompletionItemKind::Folder,
- Self::File => CompletionItemKind::File,
- Self::PgfLibrary => CompletionItemKind::Property,
- Self::TikzLibrary => CompletionItemKind::Property,
- Self::Color => CompletionItemKind::Color,
- Self::ColorModel => CompletionItemKind::Color,
- Self::Package => CompletionItemKind::Class,
- Self::Class => CompletionItemKind::Class,
- Self::Entry(BibtexEntryTypeCategory::Misc) => CompletionItemKind::Interface,
- Self::Entry(BibtexEntryTypeCategory::String) => CompletionItemKind::Text,
- Self::Entry(BibtexEntryTypeCategory::Article) => CompletionItemKind::Event,
- Self::Entry(BibtexEntryTypeCategory::Book) => CompletionItemKind::Struct,
- Self::Entry(BibtexEntryTypeCategory::Collection) => CompletionItemKind::TypeParameter,
- Self::Entry(BibtexEntryTypeCategory::Part) => CompletionItemKind::Operator,
- Self::Entry(BibtexEntryTypeCategory::Thesis) => CompletionItemKind::Unit,
- Self::Field => CompletionItemKind::Field,
- Self::Argument => CompletionItemKind::Value,
- Self::GlossaryEntry => CompletionItemKind::Keyword,
- }
- }
-
- pub fn symbol_kind(self) -> SymbolKind {
- match self {
- Self::Command => SymbolKind::Function,
- Self::Snippet => unimplemented!(),
- Self::Environment => SymbolKind::Enum,
- Self::Section => SymbolKind::Module,
- Self::Float => SymbolKind::Method,
- Self::Theorem => SymbolKind::Variable,
- Self::Equation => SymbolKind::Constant,
- Self::Item => SymbolKind::EnumMember,
- Self::Label => SymbolKind::Constructor,
- Self::Folder => SymbolKind::Namespace,
- Self::File => SymbolKind::File,
- Self::PgfLibrary => SymbolKind::Property,
- Self::TikzLibrary => SymbolKind::Property,
- Self::Color => unimplemented!(),
- Self::ColorModel => unimplemented!(),
- Self::Package => SymbolKind::Class,
- Self::Class => SymbolKind::Class,
- Self::Entry(BibtexEntryTypeCategory::Misc) => SymbolKind::Interface,
- Self::Entry(BibtexEntryTypeCategory::String) => SymbolKind::String,
- Self::Entry(BibtexEntryTypeCategory::Article) => SymbolKind::Event,
- Self::Entry(BibtexEntryTypeCategory::Book) => SymbolKind::Struct,
- Self::Entry(BibtexEntryTypeCategory::Collection) => SymbolKind::TypeParameter,
- Self::Entry(BibtexEntryTypeCategory::Part) => SymbolKind::Operator,
- Self::Entry(BibtexEntryTypeCategory::Thesis) => SymbolKind::Object,
- Self::Field => SymbolKind::Field,
- Self::Argument => SymbolKind::Number,
- Self::GlossaryEntry => unimplemented!(),
- }
- }
-}
diff --git a/support/texlab/src/syntax/mod.rs b/support/texlab/src/syntax/mod.rs
deleted file mode 100644
index 87b33792df..0000000000
--- a/support/texlab/src/syntax/mod.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-pub mod bibtex;
-mod generic_ast;
-mod lang_data;
-pub mod latex;
-pub mod latexindent;
-mod lsp_kind;
-mod text;
-
-pub use self::{
- generic_ast::{Ast, AstNodeIndex},
- lang_data::*,
- lsp_kind::Structure,
- text::{CharStream, Span, SyntaxNode},
-};
diff --git a/support/texlab/src/syntax/text.rs b/support/texlab/src/syntax/text.rs
deleted file mode 100644
index 17d6876126..0000000000
--- a/support/texlab/src/syntax/text.rs
+++ /dev/null
@@ -1,260 +0,0 @@
-use crate::protocol::{Position, Range};
-use serde::{Deserialize, Serialize};
-use std::{iter::Peekable, str::CharIndices};
-
-pub trait SyntaxNode {
- fn range(&self) -> Range;
-
- fn start(&self) -> Position {
- self.range().start
- }
-
- fn end(&self) -> Position {
- self.range().end
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct Span {
- pub range: Range,
- pub text: String,
-}
-
-impl Span {
- pub fn new(range: Range, text: String) -> Self {
- Self { range, text }
- }
-}
-
-impl SyntaxNode for Span {
- fn range(&self) -> Range {
- self.range
- }
-}
-
-#[derive(Debug)]
-pub struct CharStream<'a> {
- text: &'a str,
- chars: Peekable<CharIndices<'a>>,
- pub current_position: Position,
- pub current_index: usize,
- start_position: Position,
- start_index: usize,
-}
-
-impl<'a> CharStream<'a> {
- pub fn new(text: &'a str) -> Self {
- Self {
- text,
- chars: text.char_indices().peekable(),
- current_position: Position::new(0, 0),
- current_index: 0,
- start_position: Position::new(0, 0),
- start_index: 0,
- }
- }
-
- pub fn peek(&mut self) -> Option<char> {
- self.chars.peek().map(|(_, c)| *c)
- }
-
- pub fn satifies<P: FnOnce(&char) -> bool>(&mut self, predicate: P) -> bool {
- self.peek().filter(predicate).is_some()
- }
-
- pub fn skip_rest_of_line(&mut self) {
- loop {
- match self.peek() {
- Some('\n') => {
- self.next();
- break;
- }
- Some(_) => {
- self.next();
- }
- None => {
- break;
- }
- }
- }
- }
-
- pub fn start_span(&mut self) {
- self.start_index = self.current_index;
- self.start_position = self.current_position;
- }
-
- pub fn end_span(&mut self) -> Span {
- let range = Range::new(self.start_position, self.current_position);
- let text = &self.text[self.start_index..self.current_index];
- Span::new(range, text.to_owned())
- }
-
- pub fn seek(&mut self, position: Position) {
- while self.current_position < position {
- self.next();
- }
- }
-
- pub fn command(&mut self) -> Span {
- self.start_span();
- self.next();
- let mut escape = true;
- while self.satifies(|c| is_command_char(*c)) {
- self.next();
- escape = false;
- }
-
- if let Some(c) = self.peek() {
- if c != '\r' && c != '\n' && (escape || c == '*') {
- self.next();
- }
- }
-
- self.end_span()
- }
-
- fn update_position(&mut self, c: char) {
- if c == '\n' {
- self.current_position.line += 1;
- self.current_position.character = 0;
- } else {
- self.current_position.character += 1;
- }
- }
-
- pub fn extract(text: &'a str, range: Range) -> String {
- let mut stream = Self::new(text);
- stream.seek(range.start);
- stream.start_span();
- stream.seek(range.end);
- stream.end_span().text
- }
-}
-
-impl<'a> Iterator for CharStream<'a> {
- type Item = char;
-
- fn next(&mut self) -> Option<char> {
- if let Some((i, c)) = self.chars.next() {
- self.current_index = i + c.len_utf8();
- self.update_position(c);
- Some(c)
- } else {
- None
- }
- }
-}
-
-fn is_command_char(c: char) -> bool {
- c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '@'
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::protocol::RangeExt;
-
- #[test]
- fn peek() {
- let mut stream = CharStream::new("ab\nc");
- assert_eq!(Some('a'), stream.peek());
- assert_eq!(Some('a'), stream.next());
- assert_eq!(Some('b'), stream.peek());
- assert_eq!(Some('b'), stream.next());
- assert_eq!(Some('\n'), stream.peek());
- assert_eq!(Some('\n'), stream.next());
- assert_eq!(Some('c'), stream.peek());
- assert_eq!(Some('c'), stream.next());
- assert_eq!(None, stream.peek());
- assert_eq!(None, stream.next());
- }
-
- #[test]
- fn span() {
- let mut stream = CharStream::new("abc\ndef");
- stream.next();
- stream.start_span();
- stream.next();
- stream.next();
- let span = stream.end_span();
- assert_eq!(
- Span::new(Range::new_simple(0, 1, 0, 3), "bc".to_owned()),
- span
- );
- assert_eq!(Position::new(0, 1), span.start());
- assert_eq!(Position::new(0, 3), span.end());
- }
-
- #[test]
- fn span_unicode() {
- let mut stream = CharStream::new("😀😃😄😁");
- stream.next();
- stream.start_span();
- stream.next();
- stream.next();
- let span = stream.end_span();
- assert_eq!(
- Span::new(Range::new_simple(0, 1, 0, 3), "😃😄".to_owned()),
- span
- );
- }
-
- #[test]
- fn satifies() {
- let mut stream = CharStream::new("aBc");
- assert_eq!(true, stream.satifies(|c| c.is_lowercase()));
- stream.next();
- assert_eq!(false, stream.satifies(|c| c.is_lowercase()));
- }
-
- #[test]
- fn skip_rest_of_line() {
- let mut stream = CharStream::new("abc\ndef");
- stream.skip_rest_of_line();
- assert_eq!(Some('d'), stream.next());
- stream.skip_rest_of_line();
- assert_eq!(None, stream.next());
- stream.skip_rest_of_line();
- assert_eq!(None, stream.next());
- }
-
- #[test]
- fn seek() {
- let mut stream = CharStream::new("abc\ndefghi");
- let pos = Position::new(1, 2);
- stream.seek(pos);
- assert_eq!(Some('f'), stream.peek());
- }
-
- #[test]
- fn command_basic() {
- let mut stream = CharStream::new("\\foo@bar");
- let span = stream.command();
- assert_eq!(
- Span::new(Range::new_simple(0, 0, 0, 8), "\\foo@bar".to_owned()),
- span
- );
- }
-
- #[test]
- fn command_star() {
- let mut stream = CharStream::new("\\foo*");
- let span = stream.command();
- assert_eq!(
- Span::new(Range::new_simple(0, 0, 0, 5), "\\foo*".to_owned()),
- span
- );
- }
-
- #[test]
- fn command_escape() {
- let mut stream = CharStream::new("\\**");
- let span = stream.command();
- assert_eq!(
- Span::new(Range::new_simple(0, 0, 0, 2), "\\*".to_owned()),
- span
- );
- }
-}