/// // @ts-check const sepBy1 = (rule, sep) => seq(rule, repeat(seq(sep, rule))); const sepBy = (rule, sep) => optional(sepBy1(rule, sep)); const specialEnvironment = ({ rule, name, content, options }) => { const beginRule = `_${rule}_begin`; const endRule = `_${rule}_end`; const groupRule = `_${rule}_group`; const nameRule = `_${rule}_name`; return { [rule]: $ => seq( field('begin', alias($[beginRule], $.begin)), content($), field('end', alias($[endRule], $.end)), ), [beginRule]: $ => seq( field('command', '\\begin'), field('name', alias($[groupRule], $.curly_group_text)), options ? options($) : seq(), ), [endRule]: $ => seq( field('command', '\\end'), field('name', alias($[groupRule], $.curly_group_text)), ), [groupRule]: $ => seq('{', field('text', alias($[nameRule], $.text)), '}'), [nameRule]: $ => seq(field('word', alias(name, $.word))), }; }; module.exports = grammar({ name: 'latex', extras: $ => [$._whitespace, $.line_comment], externals: $ => [ $._trivia_raw_fi, $._trivia_raw_env_comment, $._trivia_raw_env_verbatim, $._trivia_raw_env_listing, $._trivia_raw_env_minted, $._trivia_raw_env_asy, $._trivia_raw_env_asydef, $._trivia_raw_env_pycode, $._trivia_raw_env_luacode, $._trivia_raw_env_luacode_star, $._trivia_raw_env_sagesilent, $._trivia_raw_env_sageblock, ], word: $ => $.command_name, rules: { source_file: $ => repeat($._root_content), //--- Trivia _whitespace: $ => /\s+/, line_comment: $ => /%[^\r\n]*/, block_comment: $ => seq( field('begin', '\\iffalse'), field('comment', optional(alias($._trivia_raw_fi, $.comment))), field('end', optional('\\fi')), ), //--- Content _root_content: $ => choice($._section, $._paragraph, $._flat_content), _flat_content: $ => prec.right(choice($._text_with_env_content, '[', ']')), _text_with_env_content: $ => choice( ',', '=', $.comment_environment, $.verbatim_environment, $.listing_environment, $.minted_environment, $.asy_environment, $.asydef_environment, $.pycode_environment, $.luacode_environment, $.sagesilent_environment, $.sageblock_environment, $.generic_environment, $.math_environment, $._text_content, ), _text_content: $ => prec.right( 1, choice( $.curly_group, $.block_comment, $._command, $.text, $.displayed_equation, $.inline_formula, $.math_delimiter, $.text_mode, '(', ')', ), ), //--- Sections _section: $ => prec.right( choice( repeat1($.part), repeat1($.chapter), repeat1($.section), repeat1($.subsection), repeat1($.subsubsection), ), ), _paragraph: $ => prec.right( choice( repeat1($.paragraph), repeat1($.subparagraph), repeat1($.enum_item), ), ), _section_part: $ => seq(field('toc', optional($.brack_group)), field('text', $.curly_group)), _part_declaration: $ => prec.right( seq( field( 'command', choice('\\part', '\\part*', '\\addpart', '\\addpart*'), ), optional($._section_part), ), ), part: $ => prec.right( -1, seq( $._part_declaration, repeat($._flat_content), optional(prec.right(-1, $._paragraph)), optional( prec.right( choice( repeat1($.chapter), repeat1($.section), repeat1($.subsection), repeat1($.subsubsection), ), ), ), ), ), _chapter_declaration: $ => prec.right( seq( field( 'command', choice('\\chapter', '\\chapter*', '\\addchap', '\\addchap*'), ), optional($._section_part), ), ), chapter: $ => prec.right( -1, seq( $._chapter_declaration, repeat($._flat_content), optional(prec.right(-1, $._paragraph)), optional( prec.right( choice( repeat1($.section), repeat1($.subsection), repeat1($.subsubsection), ), ), ), ), ), _section_declaration: $ => prec.right( seq( field( 'command', choice('\\section', '\\section*', '\\addsec', '\\addsec*'), ), optional($._section_part), ), ), section: $ => prec.right( -1, seq( $._section_declaration, repeat($._flat_content), optional(prec.right(-1, $._paragraph)), optional( prec.right(choice(repeat1($.subsection), repeat1($.subsubsection))), ), ), ), _subsection_declaration: $ => prec.right( seq( field('command', choice('\\subsection', '\\subsection*')), optional($._section_part), ), ), subsection: $ => prec.right( -1, seq( $._subsection_declaration, repeat($._flat_content), optional(prec.right(-1, $._paragraph)), optional(prec.right(repeat1($.subsubsection))), ), ), _subsubsection_declaration: $ => prec.right( seq( field('command', choice('\\subsubsection', '\\subsubsection*')), optional($._section_part), ), ), subsubsection: $ => prec.right( -1, seq( $._subsubsection_declaration, repeat($._flat_content), optional(prec.right(-1, $._paragraph)), ), ), _paragraph_declaration: $ => prec.right( seq( field('command', choice('\\paragraph', '\\paragraph*')), optional($._section_part), ), ), paragraph: $ => prec.right( -1, seq( $._paragraph_declaration, repeat($._flat_content), optional( prec.right(choice(repeat1($.subparagraph), repeat1($.enum_item))), ), ), ), _subparagraph_declaration: $ => prec.right( seq( field('command', choice('\\subparagraph', '\\subparagraph*')), optional($._section_part), ), ), subparagraph: $ => prec.right( -1, seq( $._subparagraph_declaration, repeat($._flat_content), optional(prec.right(choice(repeat1($.enum_item)))), ), ), _enum_itemdeclaration: $ => prec.right( seq( field('command', choice('\\item', '\\item*')), field('label', optional($.brack_group_text)), ), ), enum_item: $ => prec.right( -1, seq( $._enum_itemdeclaration, repeat($._flat_content), optional(prec.right(choice())), ), ), //--- Group curly_group: $ => seq('{', repeat($._root_content), '}'), curly_group_text: $ => seq('{', field('text', $.text), '}'), curly_group_text_list: $ => seq('{', sepBy(field('text', $.text), ','), '}'), curly_group_path: $ => seq('{', field('path', $.path), '}'), curly_group_path_list: $ => seq('{', sepBy(field('path', $.path), ','), '}'), curly_group_uri: $ => seq('{', field('uri', $.uri), '}'), curly_group_command_name: $ => seq('{', field('command', $.command_name), '}'), curly_group_key_value: $ => seq('{', sepBy(field('pair', $.key_value_pair), ','), '}'), curly_group_glob_pattern: $ => seq('{', field('pattern', $.glob_pattern), '}'), curly_group_impl: $ => seq('{', repeat(choice($._text_content, '[', ']', ',', '=')), '}'), curly_group_author_list: $ => seq( '{', sepBy( alias(repeat1($._text_content), $.author), alias('\\and', $.command_name), ), '}', ), brack_group: $ => seq('[', repeat(choice($._text_with_env_content, $.brack_group)), ']'), brack_group_text: $ => seq('[', field('text', $.text), ']'), brack_group_argc: $ => seq('[', field('value', $.argc), ']'), brack_group_key_value: $ => seq('[', sepBy(field('pair', $.key_value_pair), ','), ']'), //--- Text text: $ => prec.right( repeat1( field( 'word', choice( $.operator, $.word, $.placeholder, $.block_comment, $._command, $.superscript, $.subscript, ), ), ), ), word: $ => /[^\s\\%\{\},\$\[\]\(\)=\#_\^\-\+\/\*]+/, placeholder: $ => /#\d/, path: $ => /[^\*\"\[\]:;,\|\{\}<>]+/, uri: $ => /[^\[\]\{\}]+/, argc: $ => /\d/, glob_pattern: $ => repeat1($._glob_pattern_fragment), _glob_pattern_fragment: $ => choice( seq('{', repeat($._glob_pattern_fragment), '}'), /[^\"\[\]:;\|\{\}<>]+/, ), operator: $ => choice('+', '-', '*', '/', '<', '>', '!', '|', ':', "'"), letter: $ => /[^\\%\{\}\$\#_\^]/, subscript: $ => seq( '_', field('subscript', choice($.curly_group, $.letter, $.command_name)), ), superscript: $ => seq( '^', field('superscript', choice($.curly_group, $.letter, $.command_name)), ), //--- Key / Value key_value_pair: $ => seq(field('key', $.text), optional(seq('=', field('value', $.value)))), value: $ => repeat1(choice($._text_content, $.brack_group)), //--- Math displayed_equation: $ => prec.left( seq(choice('$$', '\\['), repeat($._root_content), choice('$$', '\\]')), ), inline_formula: $ => prec.left( seq(choice('$', '\\('), repeat($._root_content), choice('$', '\\)')), ), //--- Environments begin: $ => prec.right( seq( field('command', '\\begin'), field('name', $.curly_group_text), field('options', optional($.brack_group)), ), ), end: $ => prec.right( seq(field('command', '\\end'), field('name', $.curly_group_text)), ), generic_environment: $ => seq( field('begin', $.begin), repeat($._root_content), field('end', $.end), ), //--- Trivia environments ...specialEnvironment({ rule: 'comment_environment', name: 'comment', content: $ => field('comment', alias($._trivia_raw_env_comment, $.comment)), options: undefined, }), ...specialEnvironment({ rule: 'verbatim_environment', name: 'verbatim', content: $ => field('verbatim', alias($._trivia_raw_env_verbatim, $.comment)), options: undefined, }), ...specialEnvironment({ rule: 'listing_environment', name: 'lstlisting', content: $ => field('code', alias($._trivia_raw_env_listing, $.source_code)), options: undefined, }), ...specialEnvironment({ rule: 'minted_environment', name: 'minted', content: $ => field('code', alias($._trivia_raw_env_minted, $.source_code)), options: $ => seq( field('options', optional($.brack_group_key_value)), field('language', $.curly_group_text), ), }), ...specialEnvironment({ rule: 'asy_environment', name: 'asy', content: $ => field('code', alias($._trivia_raw_env_asy, $.source_code)), options: undefined, }), ...specialEnvironment({ rule: 'asydef_environment', name: 'asydef', content: $ => field('code', alias($._trivia_raw_env_asydef, $.source_code)), options: undefined, }), ...specialEnvironment({ rule: 'pycode_environment', name: 'pycode', content: $ => field('code', alias($._trivia_raw_env_pycode, $.source_code)), options: undefined, }), luacode_environment: $ => choice($._luacode_environment, $._luacode_environment_star), ...specialEnvironment({ rule: '_luacode_environment', name: 'luacode', content: $ => field('code', alias($._trivia_raw_env_luacode, $.source_code)), options: undefined, }), ...specialEnvironment({ rule: '_luacode_environment_star', name: 'luacode*', content: $ => field('code', alias($._trivia_raw_env_luacode_star, $.source_code)), options: undefined, }), ...specialEnvironment({ rule: 'sagesilent_environment', name: 'sagesilent', content: $ => field('code', alias($._trivia_raw_env_sagesilent, $.source_code)), options: undefined, }), ...specialEnvironment({ rule: 'sageblock_environment', name: 'sageblock', content: $ => field('code', alias($._trivia_raw_env_sageblock, $.source_code)), options: undefined, }), ...specialEnvironment({ rule: 'math_environment', name: choice( 'math', 'displaymath', 'displaymath*', 'equation', 'equation*', 'multline', 'multline*', 'eqnarray', 'eqnarray*', 'align', 'align*', 'array', 'array*', 'split', 'split*', 'alignat', 'alignat*', 'gather', 'gather*', 'flalign', 'flalign*', ), content: $ => repeat($._flat_content), options: undefined, }), //--- Command _command: $ => choice( $.title_declaration, $.author_declaration, $.package_include, $.class_include, $.latex_include, $.biblatex_include, $.bibstyle_include, $.bibtex_include, $.graphics_include, $.svg_include, $.inkscape_include, $.verbatim_include, $.import_include, $.caption, $.citation, $.label_definition, $.label_reference, $.label_reference_range, $.label_number, $.new_command_definition, $.old_command_definition, $.let_command_definition, $.paired_delimiter_definition, $.environment_definition, $.glossary_entry_definition, $.glossary_entry_reference, $.acronym_definition, $.acronym_reference, $.theorem_definition, $.color_definition, $.color_set_definition, $.color_reference, $.tikz_library_import, $.hyperlink, $.generic_command, ), generic_command: $ => prec.right( seq( field('command', $.command_name), repeat(field('arg', $.curly_group)), ), ), command_name: $ => /\\([^\r\n]|[@a-zA-Z]+\*?)?/, title_declaration: $ => seq( field('command', '\\title'), field('options', optional($.brack_group)), field('text', $.curly_group), ), author_declaration: $ => seq( field('command', '\\author'), field('options', optional($.brack_group)), field('authors', $.curly_group_author_list), ), package_include: $ => seq( field('command', choice('\\usepackage', '\\RequirePackage')), field('options', optional($.brack_group_key_value)), field('paths', $.curly_group_path_list), ), class_include: $ => seq( field('command', '\\documentclass'), field('options', optional($.brack_group_key_value)), field('path', $.curly_group_path), ), latex_include: $ => seq( field( 'command', choice('\\include', '\\subfileinclude', '\\input', '\\subfile'), ), field('path', $.curly_group_path), ), biblatex_include: $ => seq( '\\addbibresource', field('options', optional($.brack_group_key_value)), field('glob', $.curly_group_glob_pattern), ), bibstyle_include: $ => seq( field('command', '\\bibliographystyle'), field('path', $.curly_group_path), ), bibtex_include: $ => seq( field('command', '\\bibliography'), field('paths', $.curly_group_path_list), ), graphics_include: $ => seq( field('command', '\\includegraphics'), field('options', optional($.brack_group_key_value)), field('path', $.curly_group_path), ), svg_include: $ => seq( field('command', '\\includesvg'), field('options', optional($.brack_group_key_value)), field('path', $.curly_group_path), ), inkscape_include: $ => seq( field('command', '\\includeinkscape'), field('options', optional($.brack_group_key_value)), field('path', $.curly_group_path), ), verbatim_include: $ => seq( field('command', choice('\\verbatiminput', '\\VerbatimInput')), field('path', $.curly_group_path), ), import_include: $ => seq( field( 'command', choice( '\\import', '\\subimport', '\\inputfrom', '\\subimportfrom', '\\includefrom', '\\subincludefrom', ), ), field('directory', $.curly_group_path), field('file', $.curly_group_path), ), caption: $ => seq( field('command', '\\caption'), field('short', optional($.brack_group)), field('long', $.curly_group), ), citation: $ => seq( field( 'command', choice( '\\cite', '\\cite*', '\\Cite', '\\nocite', '\\citet', '\\citep', '\\citet*', '\\citep*', '\\citeA', '\\citeR', '\\citeS', '\\citeyearR', '\\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', ), ), optional( seq( field('prenote', $.brack_group), field('postnote', optional($.brack_group)), ), ), field('keys', $.curly_group_text_list), ), label_definition: $ => seq(field('command', '\\label'), field('name', $.curly_group_text)), label_reference: $ => seq( field( 'command', choice( '\\ref', '\\eqref', '\\vref', '\\Vref', '\\autoref', '\\pageref', '\\cref', '\\Cref', '\\cref*', '\\Cref*', '\\namecref', '\\nameCref', '\\lcnamecref', '\\namecrefs', '\\nameCrefs', '\\lcnamecrefs', '\\labelcref', '\\labelcpageref', ), ), field('names', $.curly_group_text_list), ), label_reference_range: $ => seq( field( 'command', choice('\\crefrange', '\\crefrange*', '\\Crefrange', '\\Crefrange*'), ), field('from', $.curly_group_text), field('to', $.curly_group_text), ), label_number: $ => seq( field('command', '\\newlabel'), field('name', $.curly_group_text), field('number', $.curly_group), ), new_command_definition: $ => seq( field( 'command', choice( '\\newcommand', '\\newcommand*', '\\renewcommand', '\\renewcommand*', '\\DeclareRobustCommand', '\\DeclareRobustCommand*', '\\DeclareMathOperator', '\\DeclareMathOperator*', '\\NewDocumentCommand', '\\RenewDocumentCommand', '\\ProvideDocumentCommand', '\\DeclareDocumentCommand', '\\NewExpandableDocumentCommand', '\\RenewExpandableDocumentCommand', '\\ProvideExpandableDocumentCommand', '\\DeclareExpandableDocumentCommand', ), ), field('declaration', $.curly_group_command_name), optional( seq( field('argc', $.brack_group_argc), field('default', optional($.brack_group)), ), ), field('implementation', $.curly_group), ), old_command_definition: $ => seq(field('command', '\\def'), field('declaration', $.command_name)), let_command_definition: $ => seq( field('command', '\\let'), field('declaration', $.command_name), optional('='), field('implementation', $.command_name), ), _math_delimiter_part: $ => choice($.word, $.command_name, '[', ']', '(', ')', '|'), math_delimiter: $ => prec.left( seq( field( 'left_command', choice('\\left', '\\bigl', '\\Bigl', '\\biggl', '\\Biggl'), ), field('left_delimiter', $._math_delimiter_part), repeat($._root_content), field( 'right_command', choice('\\right', '\\bigr', '\\Bigr', '\\biggr', '\\Biggr'), ), field('right_delimiter', $._math_delimiter_part), ), ), paired_delimiter_definition: $ => prec.right( seq( field( 'command', choice('\\DeclarePairedDelimiter', '\\DeclarePairedDelimiterX'), ), field('declaration', $.curly_group_command_name), field('argc', optional($.brack_group_argc)), field('left', choice($.curly_group_impl, $.command_name)), field('right', choice($.curly_group_impl, $.command_name)), field('body', optional($.curly_group)), ), ), environment_definition: $ => seq( field( 'command', choice( '\\newenvironment', '\\renewenvironment', '\\NewDocumentEnvironment', '\\RenewDocumentEnvironment', '\\ProvideDocumentEnvironment', '\\DeclareDocumentEnvironment', ), ), field('name', $.curly_group_text), field('argc', optional($.brack_group_argc)), field('begin', $.curly_group_impl), field('end', $.curly_group_impl), ), glossary_entry_definition: $ => seq( field('command', '\\newglossaryentry'), field('name', $.curly_group_text), field('options', $.curly_group_key_value), ), glossary_entry_reference: $ => seq( field( 'command', choice( '\\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', ), ), field('options', optional($.brack_group_key_value)), field('name', $.curly_group_text), ), acronym_definition: $ => seq( field('command', '\\newacronym'), field('options', optional($.brack_group_key_value)), field('name', $.curly_group_text), field('short', $.curly_group), field('long', $.curly_group), ), acronym_reference: $ => seq( field( 'command', choice( '\\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', ), ), field('options', optional($.brack_group_key_value)), field('name', $.curly_group_text), ), theorem_definition: $ => prec.right( seq( field( 'command', choice( '\\newtheorem', '\\newtheorem*', '\\declaretheorem', '\\declaretheorem*', ), ), optional(field('options', $.brack_group_key_value)), field('name', $.curly_group_text_list), optional( choice( seq( field('title', $.curly_group), field('counter', optional($.brack_group_text)), ), seq( field('counter', $.brack_group_text), field('title', $.curly_group), ), ), ), ), ), color_definition: $ => seq( field('command', '\\definecolor'), optional($.brack_group_text), field('name', $.curly_group_text), field('model', $.curly_group_text), field('spec', $.curly_group), ), color_set_definition: $ => seq( field('command', '\\definecolorset'), field('ty', optional($.brack_group_text)), field('model', $.curly_group_text_list), field('head', $.curly_group), field('tail', $.curly_group), field('spec', $.curly_group), ), color_reference: $ => seq( field( 'command', choice('\\color', '\\colorbox', '\\textcolor', '\\pagecolor'), ), field('name', $.curly_group_text), ), tikz_library_import: $ => seq( field('command', choice('\\usepgflibrary', '\\usetikzlibrary')), field('paths', $.curly_group_path_list), ), text_mode: $ => seq( field('command', choice('\\text', '\\intertext', '\\shortintertext')), field('content', $.curly_group), ), hyperlink: $ => prec.right( seq( field('command', choice('\\url', '\\href')), field('uri', $.curly_group_uri), field('label', optional($.curly_group)), ), ), }, });