| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757 |
- /* Hand-written tokenizer for LaTeX. */
- import { ExternalTokenizer, ContextTracker } from '@lezer/lr'
- import {
- LiteralArgContent,
- SpaceDelimitedLiteralArgContent,
- VerbContent,
- VerbatimContent,
- LstInlineContent,
- Begin,
- End,
- KnownEnvironment,
- Csname,
- TrailingWhitespaceOnly,
- TrailingContent,
- RefCtrlSeq,
- RefStarrableCtrlSeq,
- CiteCtrlSeq,
- CiteStarrableCtrlSeq,
- LabelCtrlSeq,
- MathTextCtrlSeq,
- HboxCtrlSeq,
- TitleCtrlSeq,
- AuthorCtrlSeq,
- AffilCtrlSeq,
- AffiliationCtrlSeq,
- DateCtrlSeq,
- DocumentClassCtrlSeq,
- UsePackageCtrlSeq,
- HrefCtrlSeq,
- UrlCtrlSeq,
- VerbCtrlSeq,
- LstInlineCtrlSeq,
- IncludeGraphicsCtrlSeq,
- IncludeSvgCtrlSeq,
- CaptionCtrlSeq,
- DefCtrlSeq,
- LetCtrlSeq,
- LeftCtrlSeq,
- RightCtrlSeq,
- NewCommandCtrlSeq,
- RenewCommandCtrlSeq,
- NewEnvironmentCtrlSeq,
- RenewEnvironmentCtrlSeq,
- DocumentEnvName,
- TabularEnvName,
- EquationEnvName,
- EquationArrayEnvName,
- VerbatimEnvName,
- TikzPictureEnvName,
- FigureEnvName,
- OpenParenCtrlSym,
- CloseParenCtrlSym,
- OpenBracketCtrlSym,
- CloseBracketCtrlSym,
- LineBreakCtrlSym,
- // Sectioning commands
- BookCtrlSeq,
- PartCtrlSeq,
- ChapterCtrlSeq,
- SectionCtrlSeq,
- SubSectionCtrlSeq,
- SubSubSectionCtrlSeq,
- ParagraphCtrlSeq,
- SubParagraphCtrlSeq,
- InputCtrlSeq,
- IncludeCtrlSeq,
- SubfileCtrlSeq,
- ItemCtrlSeq,
- NewTheoremCtrlSeq,
- TheoremStyleCtrlSeq,
- BibliographyCtrlSeq,
- BibliographyStyleCtrlSeq,
- CenteringCtrlSeq,
- ListEnvName,
- MaketitleCtrlSeq,
- TextColorCtrlSeq,
- ColorBoxCtrlSeq,
- HLineCtrlSeq,
- TopRuleCtrlSeq,
- MidRuleCtrlSeq,
- BottomRuleCtrlSeq,
- TableEnvName,
- MultiColumnCtrlSeq,
- ParBoxCtrlSeq,
- // Marker for end of argument lists
- endOfArguments,
- hasMoreArguments,
- hasMoreArgumentsOrOptionals,
- endOfArgumentsAndOptionals,
- TextBoldCtrlSeq,
- TextItalicCtrlSeq,
- TextSmallCapsCtrlSeq,
- TextTeletypeCtrlSeq,
- TextMediumCtrlSeq,
- TextSansSerifCtrlSeq,
- TextSuperscriptCtrlSeq,
- TextSubscriptCtrlSeq,
- TextStrikeOutCtrlSeq,
- EmphasisCtrlSeq,
- UnderlineCtrlSeq,
- SetLengthCtrlSeq,
- FootnoteCtrlSeq,
- EndnoteCtrlSeq,
- } from './latex.terms.mjs'
- const MAX_ARGUMENT_LOOKAHEAD = 100
- function nameChar(ch) {
- // we accept A-Z a-z 0-9 * + @ in environment names
- return (
- (ch >= 65 && ch <= 90) ||
- (ch >= 97 && ch <= 122) ||
- (ch >= 48 && ch <= 57) ||
- ch === 42 ||
- ch === 43 ||
- ch === 64
- )
- }
- // match [a-zA-Z]
- function alphaChar(ch) {
- return (ch >= 65 && ch <= 90) || (ch >= 97 && ch <= 122)
- }
- let cachedName = null
- let cachedInput = null
- let cachedPos = 0
- function envNameAfter(input, offset) {
- const pos = input.pos + offset
- if (cachedInput === input && cachedPos === pos) {
- return cachedName
- }
- if (input.peek(offset) !== '{'.charCodeAt(0)) return
- offset++
- let name = ''
- for (;;) {
- const next = input.peek(offset)
- if (!nameChar(next)) break
- name += String.fromCharCode(next)
- offset++
- }
- cachedInput = input
- cachedPos = pos
- return (cachedName = name || null)
- }
- function ElementContext(name, parent) {
- this.name = name
- this.parent = parent
- this.hash = parent ? parent.hash : 0
- for (let i = 0; i < name.length; i++)
- this.hash +=
- (this.hash << 4) + name.charCodeAt(i) + (name.charCodeAt(i) << 8)
- }
- export const elementContext = new ContextTracker({
- start: null,
- shift(context, term, stack, input) {
- return term === Begin
- ? new ElementContext(envNameAfter(input, '\\begin'.length) || '', context)
- : context
- },
- reduce(context, term) {
- return term === KnownEnvironment && context ? context.parent : context
- },
- reuse(context, node, _stack, input) {
- const type = node.type.id
- return type === Begin
- ? new ElementContext(envNameAfter(input, 0) || '', context)
- : context
- },
- hash(context) {
- return context ? context.hash : 0
- },
- strict: false,
- })
- // tokenizer for \verb|...| commands
- export const verbTokenizer = new ExternalTokenizer(
- (input, stack) => {
- if (input.next === '*'.charCodeAt(0)) input.advance()
- const delimiter = input.next
- if (delimiter === -1) return // hit end of file
- if (/\s|\*/.test(String.fromCharCode(delimiter))) return // invalid delimiter
- input.advance()
- for (;;) {
- const next = input.next
- if (next === -1 || next === CHAR_NEWLINE) return
- input.advance()
- if (next === delimiter) break
- }
- return input.acceptToken(VerbContent)
- },
- { contextual: false }
- )
- // tokenizer for \lstinline|...| commands
- export const lstinlineTokenizer = new ExternalTokenizer(
- (input, stack) => {
- let delimiter = input.next
- if (delimiter === -1) return // hit end of file
- if (/\s/.test(String.fromCharCode(delimiter))) {
- return // invalid delimiter
- }
- if (delimiter === CHAR_OPEN_BRACE) {
- delimiter = CHAR_CLOSE_BRACE
- }
- input.advance()
- for (;;) {
- const next = input.next
- if (next === -1 || next === CHAR_NEWLINE) return
- input.advance()
- if (next === delimiter) break
- }
- return input.acceptToken(LstInlineContent)
- },
- { contextual: false }
- )
- const matchForward = (input, expected, offset = 0) => {
- for (let i = 0; i < expected.length; i++) {
- if (String.fromCharCode(input.peek(offset + i)) !== expected[i]) {
- return false
- }
- }
- return true
- }
- // tokenizer for \begin{verbatim}...\end{verbatim} environments
- export const verbatimTokenizer = new ExternalTokenizer(
- (input, stack) => {
- const delimiter = '\\end{' + stack.context.name + '}'
- for (let offset = 0; ; offset++) {
- const next = input.peek(offset)
- if (next === -1 || matchForward(input, delimiter, offset)) {
- return input.acceptToken(VerbatimContent, offset)
- }
- }
- },
- { contextual: false }
- )
- // tokenizer for \href{...} and similar commands
- export const literalArgTokenizer = new ExternalTokenizer(
- input => {
- for (let offset = 0; ; offset++) {
- const next = input.peek(offset)
- if (next === -1 || next === CHAR_CLOSE_BRACE) {
- return input.acceptToken(LiteralArgContent, offset)
- }
- }
- },
- { contextual: false }
- )
- // tokenizer for literal content delimited by whitespace, such as in `\input foo.tex`
- export const spaceDelimitedLiteralArgTokenizer = new ExternalTokenizer(
- input => {
- for (let offset = 0; ; offset++) {
- const next = input.peek(offset)
- if (next === -1 || next === CHAR_SPACE || next === CHAR_NEWLINE) {
- return input.acceptToken(SpaceDelimitedLiteralArgContent, offset)
- }
- }
- },
- { contextual: false }
- )
- // helper function to look up charCodes
- function _char(s) {
- return s.charCodeAt(0)
- }
- const CHAR_BACKSLASH = _char('\\')
- const CHAR_OPEN_BRACE = _char('{')
- const CHAR_OPEN_BRACKET = _char('[')
- const CHAR_CLOSE_BRACE = _char('}')
- const CHAR_TAB = _char('\t')
- const CHAR_SPACE = _char(' ')
- const CHAR_NEWLINE = _char('\n')
- const lookaheadTokenizer = getToken =>
- new ExternalTokenizer(
- input => {
- for (let i = 0; i < MAX_ARGUMENT_LOOKAHEAD; ++i) {
- const next = input.peek(i)
- if (next === CHAR_SPACE || next === CHAR_TAB) {
- continue
- }
- const token = getToken(next)
- if (token) {
- input.acceptToken(token)
- return
- }
- }
- },
- { contextual: false, fallback: true }
- )
- export const argumentListTokenizer = lookaheadTokenizer(next => {
- if (next === CHAR_OPEN_BRACE) {
- return hasMoreArguments
- } else {
- return endOfArguments
- }
- })
- export const argumentListWithOptionalTokenizer = lookaheadTokenizer(next => {
- if (next === CHAR_OPEN_BRACE || next === CHAR_OPEN_BRACKET) {
- return hasMoreArgumentsOrOptionals
- } else {
- return endOfArgumentsAndOptionals
- }
- })
- const CHAR_AT_SYMBOL = _char('@')
- export const csnameTokenizer = new ExternalTokenizer((input, stack) => {
- let offset = 0
- let end = -1
- // look at the first character, we are looking for acceptable control sequence names
- // including @ signs, \\[a-zA-Z@]+
- const next = input.peek(offset)
- if (next === -1) {
- return
- }
- // reject anything not starting with a backslash,
- // we only accept control sequences
- if (next !== CHAR_BACKSLASH) {
- return
- }
- offset++
- for (;;) {
- const next = input.peek(offset)
- // stop when we reach the end of file or a non-csname character
- if (next === -1 || !(alphaChar(next) || next === CHAR_AT_SYMBOL)) {
- end = offset - 1
- break
- }
- end = offset
- offset++
- }
- if (end === -1) return
- // accept the content as a valid control sequence
- return input.acceptToken(Csname, end + 1)
- })
- const END_DOCUMENT_MARK = '\\end{document}'.split('').reverse()
- export const trailingContentTokenizer = new ExternalTokenizer(
- (input, stack) => {
- if (input.next === -1) return // no trailing content
- // Look back for end-document mark, bail out if any characters do not match
- for (let i = 1; i < END_DOCUMENT_MARK.length + 1; i++) {
- if (String.fromCharCode(input.peek(-i)) !== END_DOCUMENT_MARK[i - 1]) {
- return
- }
- }
- while (input.next === CHAR_SPACE || input.next === CHAR_NEWLINE) {
- const next = input.advance()
- if (next === -1) return input.acceptToken(TrailingWhitespaceOnly) // trailing whitespace only
- }
- // accept the all content up to the end of the document
- while (input.advance() !== -1) {
- //
- }
- return input.acceptToken(TrailingContent)
- }
- )
- const refCommands = new Set([
- '\\fullref',
- '\\Vref',
- '\\autopageref',
- '\\autoref',
- '\\eqref',
- '\\labelcpageref',
- '\\labelcref',
- '\\lcnamecref',
- '\\lcnamecrefs',
- '\\namecref',
- '\\nameCref',
- '\\namecrefs',
- '\\nameCrefs',
- '\\thnameref',
- '\\thref',
- '\\titleref',
- '\\vrefrange',
- '\\Crefrange',
- '\\Crefrang',
- '\\fref',
- '\\pref',
- '\\tref',
- '\\Aref',
- '\\Bref',
- '\\Pref',
- '\\Sref',
- '\\vref',
- '\\nameref',
- ])
- const refStarrableCommands = new Set([
- '\\vpageref',
- '\\vref',
- '\\zcpageref',
- '\\zcref',
- '\\zfullref',
- '\\zref',
- '\\zvpageref',
- '\\zvref',
- '\\cref',
- '\\Cref',
- '\\pageref',
- '\\ref',
- '\\Ref',
- '\\subref',
- '\\zpageref',
- '\\ztitleref',
- '\\vpagerefrange',
- '\\zvpagerefrange',
- '\\zvrefrange',
- '\\crefrange',
- ])
- const citeCommands = new Set([
- '\\autocites',
- '\\Autocites',
- '\\Cite',
- '\\citeA',
- '\\citealp',
- '\\Citealp',
- '\\citealt',
- '\\Citealt',
- '\\citeauthorNP',
- '\\citeauthorp',
- '\\Citeauthorp',
- '\\citeauthort',
- '\\Citeauthort',
- '\\citeNP',
- '\\citenum',
- '\\citen',
- '\\citeonline',
- '\\cites',
- '\\Cites',
- '\\citeurl',
- '\\citeyearpar',
- '\\defcitealias',
- '\\fnotecite',
- '\\footcite',
- '\\footcitetext',
- '\\footfullcite',
- '\\footnotecites',
- '\\Footnotecites',
- '\\fullcite',
- '\\fullciteA',
- '\\fullciteauthor',
- '\\fullciteauthorNP',
- '\\maskcite',
- '\\maskciteA',
- '\\maskcitealp',
- '\\maskCitealp',
- '\\maskcitealt',
- '\\maskCitealt',
- '\\maskciteauthor',
- '\\maskciteauthorNP',
- '\\maskciteauthorp',
- '\\maskCiteauthorp',
- '\\maskciteauthort',
- '\\maskCiteauthort',
- '\\maskciteNP',
- '\\maskcitenum',
- '\\maskcitep',
- '\\maskCitep',
- '\\maskcitepalias',
- '\\maskcitet',
- '\\maskCitet',
- '\\maskcitetalias',
- '\\maskciteyear',
- '\\maskciteyearNP',
- '\\maskciteyearpar',
- '\\maskfullcite',
- '\\maskfullciteA',
- '\\maskfullciteauthor',
- '\\maskfullciteauthorNP',
- '\\masknocite',
- '\\maskshortcite',
- '\\maskshortciteA',
- '\\maskshortciteauthor',
- '\\maskshortciteauthorNP',
- '\\maskshortciteNP',
- '\\mautocite',
- '\\Mautocite',
- '\\mcite',
- '\\Mcite',
- '\\mfootcite',
- '\\mfootcitetext',
- '\\mparencite',
- '\\Mparencite',
- '\\msupercite',
- '\\mtextcite',
- '\\Mtextcite',
- '\\nocite',
- '\\nocitemeta',
- '\\notecite',
- '\\Parencite',
- '\\parencites',
- '\\Parencites',
- '\\pnotecite',
- '\\shortcite',
- '\\shortciteA',
- '\\shortciteauthor',
- '\\shortciteauthorNP',
- '\\shortciteNP',
- '\\smartcite',
- '\\Smartcite',
- '\\smartcites',
- '\\Smartcites',
- '\\supercite',
- '\\supercites',
- '\\textcite',
- '\\Textcite',
- '\\textcites',
- '\\Textcites',
- ])
- const citeStarredCommands = new Set([
- '\\cite',
- '\\citeauthor',
- '\\Citeauthor',
- '\\citedate',
- '\\citep',
- '\\citepalias',
- '\\Citep',
- '\\citetitle',
- '\\citeyear',
- '\\parencite',
- '\\citet',
- '\\citetalias',
- '\\autocite',
- '\\Autocite',
- ])
- const labelCommands = new Set(['\\label', '\\thlabel', '\\zlabel'])
- const mathTextCommands = new Set(['\\text', '\\tag', '\\textrm', '\\intertext'])
- const otherKnowncommands = {
- '\\hbox': HboxCtrlSeq,
- '\\title': TitleCtrlSeq,
- '\\author': AuthorCtrlSeq,
- '\\affil': AffilCtrlSeq,
- '\\affiliation': AffiliationCtrlSeq,
- '\\date': DateCtrlSeq,
- '\\documentclass': DocumentClassCtrlSeq,
- '\\usepackage': UsePackageCtrlSeq,
- '\\href': HrefCtrlSeq,
- '\\url': UrlCtrlSeq,
- '\\verb': VerbCtrlSeq,
- '\\lstinline': LstInlineCtrlSeq,
- '\\includegraphics': IncludeGraphicsCtrlSeq,
- '\\includesvg': IncludeSvgCtrlSeq,
- '\\caption': CaptionCtrlSeq,
- '\\def': DefCtrlSeq,
- '\\let': LetCtrlSeq,
- '\\left': LeftCtrlSeq,
- '\\right': RightCtrlSeq,
- '\\newcommand': NewCommandCtrlSeq,
- '\\renewcommand': RenewCommandCtrlSeq,
- '\\newenvironment': NewEnvironmentCtrlSeq,
- '\\renewenvironment': RenewEnvironmentCtrlSeq,
- '\\book': BookCtrlSeq,
- '\\part': PartCtrlSeq,
- '\\addpart': PartCtrlSeq,
- '\\chapter': ChapterCtrlSeq,
- '\\addchap': ChapterCtrlSeq,
- '\\section': SectionCtrlSeq,
- '\\addseq': SectionCtrlSeq,
- '\\subsection': SubSectionCtrlSeq,
- '\\subsubsection': SubSubSectionCtrlSeq,
- '\\paragraph': ParagraphCtrlSeq,
- '\\subparagraph': SubParagraphCtrlSeq,
- '\\input': InputCtrlSeq,
- '\\include': IncludeCtrlSeq,
- '\\subfile': SubfileCtrlSeq,
- '\\item': ItemCtrlSeq,
- '\\centering': CenteringCtrlSeq,
- '\\newtheorem': NewTheoremCtrlSeq,
- '\\theoremstyle': TheoremStyleCtrlSeq,
- '\\bibliography': BibliographyCtrlSeq,
- '\\bibliographystyle': BibliographyStyleCtrlSeq,
- '\\maketitle': MaketitleCtrlSeq,
- '\\textcolor': TextColorCtrlSeq,
- '\\colorbox': ColorBoxCtrlSeq,
- '\\hline': HLineCtrlSeq,
- '\\toprule': TopRuleCtrlSeq,
- '\\midrule': MidRuleCtrlSeq,
- '\\bottomrule': BottomRuleCtrlSeq,
- '\\multicolumn': MultiColumnCtrlSeq,
- '\\parbox': ParBoxCtrlSeq,
- '\\textbf': TextBoldCtrlSeq,
- '\\textit': TextItalicCtrlSeq,
- '\\textsc': TextSmallCapsCtrlSeq,
- '\\texttt': TextTeletypeCtrlSeq,
- '\\textmd': TextMediumCtrlSeq,
- '\\textsf': TextSansSerifCtrlSeq,
- '\\textsuperscript': TextSuperscriptCtrlSeq,
- '\\textsubscript': TextSubscriptCtrlSeq,
- '\\sout': TextStrikeOutCtrlSeq,
- '\\emph': EmphasisCtrlSeq,
- '\\underline': UnderlineCtrlSeq,
- '\\setlength': SetLengthCtrlSeq,
- '\\footnote': FootnoteCtrlSeq,
- '\\endnote': EndnoteCtrlSeq,
- }
- // specializer for control sequences
- // return new tokens for specific control sequences
- export const specializeCtrlSeq = (name, terms) => {
- if (name === '\\begin') return Begin
- if (name === '\\end') return End
- if (refCommands.has(name)) {
- return RefCtrlSeq
- }
- if (refStarrableCommands.has(name)) {
- return RefStarrableCtrlSeq
- }
- if (citeCommands.has(name)) {
- return CiteCtrlSeq
- }
- if (citeStarredCommands.has(name)) {
- return CiteStarrableCtrlSeq
- }
- if (labelCommands.has(name)) {
- return LabelCtrlSeq
- }
- if (mathTextCommands.has(name)) {
- return MathTextCtrlSeq
- }
- return otherKnowncommands[name] || -1
- }
- const tabularEnvNames = new Set([
- 'tabular',
- 'xltabular',
- 'tabularx',
- 'longtable',
- ])
- const equationEnvNames = new Set([
- 'equation',
- 'equation*',
- 'displaymath',
- 'displaymath*',
- 'math',
- 'math*',
- 'multline',
- 'multline*',
- 'matrix',
- 'tikzcd',
- ])
- const equationArrayEnvNames = new Set([
- 'array',
- 'eqnarray',
- 'eqnarray*',
- 'align',
- 'align*',
- 'alignat',
- 'alignat*',
- 'flalign',
- 'flalign*',
- 'gather',
- 'gather*',
- 'pmatrix',
- 'pmatrix*',
- 'bmatrix',
- 'bmatrix*',
- 'Bmatrix',
- 'Bmatrix*',
- 'vmatrix',
- 'vmatrix*',
- 'Vmatrix',
- 'Vmatrix*',
- 'smallmatrix',
- 'smallmatrix*',
- 'split',
- 'split*',
- 'gathered',
- 'gathered*',
- 'aligned',
- 'aligned*',
- 'alignedat',
- 'alignedat*',
- 'cases',
- 'cases*',
- 'dcases',
- 'dcases*',
- 'rcases',
- 'rcases*',
- 'IEEEeqnarray',
- 'IEEEeqnarray*',
- 'subeqnarray',
- 'subeqnarray*',
- ])
- const verbatimEnvNames = new Set([
- 'verbatim',
- 'boxedverbatim',
- 'lstlisting',
- 'minted',
- 'Verbatim',
- 'lstlisting',
- 'tcblisting',
- 'codeexample',
- 'comment',
- ])
- const otherKnownEnvNames = {
- document: DocumentEnvName,
- tikzpicture: TikzPictureEnvName,
- figure: FigureEnvName,
- 'figure*': FigureEnvName,
- subfigure: FigureEnvName,
- enumerate: ListEnvName,
- itemize: ListEnvName,
- table: TableEnvName,
- description: ListEnvName,
- }
- export const specializeEnvName = (name, terms) => {
- if (tabularEnvNames.has(name)) {
- return TabularEnvName
- }
- if (equationEnvNames.has(name)) {
- return EquationEnvName
- }
- if (equationArrayEnvNames.has(name)) {
- return EquationArrayEnvName
- }
- if (verbatimEnvNames.has(name)) {
- return VerbatimEnvName
- }
- return otherKnownEnvNames[name] || -1
- }
- const otherKnownCtrlSyms = {
- '\\(': OpenParenCtrlSym,
- '\\)': CloseParenCtrlSym,
- '\\[': OpenBracketCtrlSym,
- '\\]': CloseBracketCtrlSym,
- '\\\\': LineBreakCtrlSym,
- }
- export const specializeCtrlSym = (name, terms) => {
- return otherKnownCtrlSyms[name] || -1
- }
|