use rowan::{GreenNode, GreenNodeBuilder}; use super::{ lexer::Lexer, SyntaxKind::{self, *}, }; #[derive(Clone)] pub struct Parse { pub green: GreenNode, } #[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)] struct Parser<'a> { lexer: Lexer<'a>, builder: GreenNodeBuilder<'static>, } impl<'a> Parser<'a> { pub fn new(text: &'a str) -> Self { Self { lexer: Lexer::new(text), builder: GreenNodeBuilder::new(), } } fn eat(&mut self) { let (kind, text) = self.lexer.eat().unwrap(); self.builder.token(kind.into(), text); } fn peek(&self) -> Option { self.lexer.peek() } fn expect(&mut self, kind: SyntaxKind) { if self.peek() == Some(kind) { self.eat(); self.trivia(); } else { self.builder.token(MISSING.into(), ""); } } 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 trivia(&mut self) { while self .peek() .filter(|&kind| matches!(kind, LINE_BREAK | 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 = self.builder.finish(); Parse { green } } fn content(&mut self, context: ParserContext) { match self.peek().unwrap() { LINE_BREAK | WHITESPACE | COMMENT | VERBATIM => 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(); } 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_DEFINITION_NAME => self.environment_definition(), BEGIN_BLOCK_COMMENT_NAME => self.block_comment(), END_BLOCK_COMMENT_NAME => self.generic_command(), GRAPHICS_PATH_NAME => self.graphics_path(), _ => unreachable!(), } } fn text(&mut self, context: ParserContext) { self.builder.start_node(TEXT.into()); self.eat(); while self .peek() .filter(|&kind| { matches!(kind, LINE_BREAK | WHITESPACE | COMMENT | WORD | COMMA) && (context.allow_comma || kind != COMMA) }) .is_some() { self.eat(); } 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_impl(&mut self) { self.builder.start_node(CURLY_GROUP.into()); self.eat(); while let Some(kind) = self.peek() { match kind { R_CURLY => break, BEGIN_ENVIRONMENT_NAME => self.begin(), END_ENVIRONMENT_NAME => self.end(), _ => 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.builder.start_node(CURLY_GROUP_WORD.into()); self.eat(); self.trivia(); match self.peek() { Some(WORD) => { self.key(); } Some(kind) if kind.is_command_name() => { self.content(ParserContext::default()); } Some(_) | None => { self.builder.token(MISSING.into(), ""); } } self.expect(R_CURLY); self.builder.finish_node(); } 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, LINE_BREAK | WHITESPACE | COMMENT | WORD | COMMA)) .is_some() { if self.peek() == Some(WORD) { self.key(); } else { self.eat(); } } self.expect(R_CURLY); self.builder.finish_node(); } fn curly_group_command(&mut self) { self.builder.start_node(CURLY_GROUP_COMMAND.into()); self.eat(); self.trivia(); match self.peek() { Some(kind) if kind.is_command_name() => { self.eat(); self.trivia(); } Some(_) | None => { self.builder.token(MISSING.into(), ""); } } self.expect(R_CURLY); self.builder.finish_node(); } 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.builder.start_node(BRACK_GROUP_WORD.into()); self.eat(); self.trivia(); match self.peek() { Some(WORD) => { self.key(); } Some(_) | None => { self.builder.token(MISSING.into(), ""); } } self.expect(R_BRACK); 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.trivia(); self.builder.finish_node(); } fn value(&mut self) { self.builder.start_node(VALUE.into()); while let Some(kind) = self.lexer.peek() { match kind { COMMA | R_BRACK | R_CURLY => break, _ => 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 { LINE_BREAK | WHITESPACE | COMMENT => self.eat(), WORD => { self.key_value_pair(); if self.peek() == Some(COMMA) { self.eat(); } else { break; } } _ => break, } } 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 { LINE_BREAK | 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(); } fn begin(&mut self) { self.builder.start_node(BEGIN.into()); self.eat(); self.trivia(); 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 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(), ""); } self.builder.finish_node(); } 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 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 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 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(|&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 block_comment(&mut self) { self.builder.start_node(BLOCK_COMMENT.into()); self.eat(); if self.peek() == Some(VERBATIM) { self.eat(); } if self.peek() == Some(END_BLOCK_COMMENT_NAME) { self.eat(); } else { self.builder.token(MISSING.into(), ""); } 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_path_list(); } else { self.builder.token(MISSING.into(), ""); } self.builder.finish_node(); } fn curly_group_path(&mut self) { self.builder.start_node(CURLY_GROUP_WORD.into()); self.eat(); while matches!( self.peek(), Some(WORD | L_BRACK | R_BRACK | GENERIC_COMMAND_NAME) ) { self.path(); } self.expect(R_CURLY); self.builder.finish_node(); } fn curly_group_path_list(&mut self) { self.builder.start_node(CURLY_GROUP_WORD_LIST.into()); self.eat(); while self.peek().map_or(false, |kind| { matches!( kind, LINE_BREAK | WHITESPACE | COMMENT | WORD | COMMA | EQUALITY_SIGN | L_BRACK | R_BRACK | GENERIC_COMMAND_NAME ) }) { if matches!( self.peek(), Some(WORD | L_BRACK | R_BRACK | GENERIC_COMMAND_NAME) ) { self.path(); } else { self.eat(); } } self.expect(R_CURLY); self.builder.finish_node(); } fn path(&mut self) { self.builder.start_node(KEY.into()); self.eat(); while self.peek().map_or(false, |kind| { matches!( kind, WHITESPACE | COMMENT | WORD | EQUALITY_SIGN | L_BRACK | R_BRACK | GENERIC_COMMAND_NAME ) }) { self.eat(); } self.trivia(); 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_BRACK) { self.brack_group(); } } if self.lexer.peek() == Some(L_CURLY) { self.curly_group_impl(); } 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_impl(); } 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(); if self.lexer.peek() == Some(L_BRACK) { self.brack_group(); } } 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(); } fn graphics_path(&mut self) { self.builder.start_node(GRAPHICS_PATH.into()); self.eat(); self.trivia(); if self.lexer.peek() == Some(L_CURLY) { self.eat(); self.trivia(); while matches!(self.lexer.peek(), Some(L_CURLY)) { self.curly_group_path(); } } else { self.builder.token(MISSING.into(), ""); } self.expect(R_CURLY); self.builder.finish_node(); } } pub fn parse(text: &str) -> Parse { Parser::new(text).parse() } #[cfg(test)] mod tests { use insta::assert_debug_snapshot; use crate::syntax::latex; use super::*; fn setup(text: &str) -> latex::SyntaxNode { latex::SyntaxNode::new_root(parse(&text.trim().replace('\r', "")).green) } #[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_include_equality_sign() { 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_latex_input_path_with_brackets() { 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_graphics_include_complicated_options() { assert_debug_snapshot!(setup(r#"\includegraphics[width=0.5\textwidth]{}"#)); } #[test] fn test_graphics_include_command() { assert_debug_snapshot!(setup( r#"\includegraphics[width=0.5\textwidth]{\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_label_reference_incomplete() { assert_debug_snapshot!(setup(r#"Equation \eqref{eq is a \emph{useful} identity."#)); } #[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_optional() { assert_debug_snapshot!(setup(r#"\newcommand{\foo}[1][def]{#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_command_definition_with_begin() { assert_debug_snapshot!(setup( r#"\newcommand{\CVSubHeadingListStart}{\begin{itemize}[leftmargin=0.5cm, label={}]}"# )); } #[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_environment_definition_optional_arg() { assert_debug_snapshot!(setup(r#"\newenvironment{foo}[1][default]{begin}{end}"#)); } #[test] fn test_acronym_declaration() { assert_debug_snapshot!(setup( r#"\DeclareAcronym{eg}{short = e.g,long = for example,tag = abbrev}"# )); } #[test] fn test_label_definition_line_break() { assert_debug_snapshot!(setup("\\label{hello\nworld}")); } #[test] fn test_block_comments() { assert_debug_snapshot!(setup( r#"Foo \iffalse Test1 \fi Bar \iffalse \fii \fi Baz"# )); } #[test] fn test_asymptote() { assert_debug_snapshot!(setup( r#"\begin{asy} printf("Hello World\n"); \end{asy}"# )); } #[test] fn test_graphics_path() { assert_debug_snapshot!(setup(r#"\graphicspath{{../figures/}}"#)); } }