% Copyright 2012-2024, Alexander Shibakov
% This file is part of SPLinT
%
% SPLinT is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% SPLinT is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with SPLinT.  If not, see <http://www.gnu.org/licenses/>.

% indexing macros

% set up the list of unindexable terms

\newwrite\gindex

% macros to determine if the current term is indexable

\newif\ifindexverbose

\def\yyifindexable#1{%
    {%
        \def\n@xt{#1}%
        \expandafter\s@tindexable\unindexable\relax\end % the \relax is to preserve the braces
                                                        % in the last list element
    }%
}

\def\s@tindexable#1#2\end{%
    {%
        \def\next{#1}%
        \ifx\next\n@xt % match found, stop
            \yybreak{\aftergroup\s@t@ndexable}%
        \else
            \yybreak{%
                \yystringempty{#2}{% unindexable sequence list has been exhausted, stop
                    \aftergroup\s@t@nd@xable
                }{% no match yet, continue
                    \aftergroup\s@tindexable
                }%
            }%
        \yycontinue
    }%
    #2\end
}

\def\s@t@ndexable#1\end{\aftergroup\yysecondoftwo}

\def\s@t@nd@xable#1\end{\aftergroup\yyfirstoftwo}

\def\makeunindexable#1{%
    \expandafter\m@keunindexable\expandafter{\unindexable}{#1}%
}

\def\m@keunindexable#1#2{%
    \def\unindexable{#1#2}%
}

\def\makeunindexablex#1{%
    {\edef\unindexable{#1}\expandafter}\expandafter\makeunindexable\expandafter{\unindexable}%
}

% \Cee\ index entry (produced by \CWEAVE\ and typeset directly from the file)

\def\Ii#1, #2.{%
    \makeoneindexentry{#1}{#1}{#2}{\setxreflistplain}{\filterxrefsplain}%
}

% other language index entries (produced by \GI, \FI, and \HI macros)

%     the standard reference format (no page number references)

\def\Ji#1#2, #3.{% #1 is the `visible key', #2 and #3 are similar to #1 and #2 in \I above
    \makeoneindexentry{#1}{#2}{#3}{\setxreflistplain}{\filterxrefsplain}%
}%

%     the `fine' reference format

\def\Fi#1#2, #3.{% #1 is the `visible key', #2 and #3 are similar to #1 and #2 in \I above
    \makeoneindexentry{#1}{#2}{#3}{\setxreflist}{\filterxrefs}%
}%

% special index entries: redirects, etc. (generated by bindx.pl)

\def\Jk#1#2, #3.{% 
    \makeoneindexentry{#1}{#2}{#3}{\yyid}{\eatone}%
}%

\def\makeoneindexentry#1#2#3#4#5{% #1 is the `visible key'
                                 % #2 is the term (including typesetting)
                                 % #3 is the list of references
                                 % #4 is the command to process #3
                                 % #5 is the command to filter and process #3
    \yyifindexable{#1}{\oneindexentry{#2}{#3}{#4}}{%
        \ifindexverbose{\toksa{#1}\message{filtering term: \the\toksa}}\fi
        \oneindexentryfiltered{#2}{#3}{#5}%
    }%
}

% insert the original control sequence name after the `graphic' version (\thisnamex
% is set by yytexlex.sty macros

\def\defypostambleshowcs{{ \rm(}\hbox{\sixpoint\tt\char`\\}\.{\thisnamex}{\rm)}}%

% let the author set the format for a cross reference list.

\def\setxreflistplain#1{%
    \ifacro\pdfnote#1.\else#1.\fi
}

\def\oneindexentry#1#2#3{%
    \checkforninecs#1\9\end{%
        \the\toksg\toksg{}% set the index section header
        \let\defypreamble\empty
        \let\defypostamble\defypostambleshowcs
        \hangindent\inxhangindent\noindent\strut#1:%
            \hskip0pt plus2em\penalty1000\hskip0pt plus-2em\relax\kern\inxaiskip
        #3{#2}%
        \par
    }{%
        \checkforthree#1\end{%
            \toksg{#1}%
        }{%
            \the\toksg\toksg{}% set the index section header
            \let\defypreamble\empty
            \let\defypostamble\defypostambleshowcs
            \hangindent\inxhangindent\noindent\strut\ninercs#1:%
                \hskip0pt plus2em\penalty1000\hskip0pt plus-2em\relax\kern\inxaiskip
            #3{#2}%
            \par
        }%          
    }%
}

\def\filterxrefsplain#1{\setxrefsplain{\f@lterdefsplain}{#1}}

\def\filterxrefs#1{\setxrefs{\f@lterdefs}{#1}}

\def\oneindexentryfiltered#1#2#3{%
    \the\toksg\toksg{}% set the index section header
    \let\defypreamble\empty
    \let\defypostamble\defypostambleshowcs
    \hangindent\inxhangindent\noindent\strut#1:%
        \hskip0pt plus2em\penalty1000\hskip0pt plus-2em\relax\kern\inxaiskip
    #3{#2}%
    \par
}

\def\checkforninecs#1\9#2\end{%
    \yystringempty{#2}{\yyfirstoftwo}{\yysecondoftwo}%
}

\def\checkforthree#1#2#3\end{%
    \yystringempty{#3}{\yyfirstoftwo}{\yysecondoftwo}%    
}
% if the term is unindexable, only collect the definitions (i.e. references enclosed in \[ and \])

\def\filterdefs#1#2{%
    #1{}{}, #2, \[]{}% the last empty braces are not necessary but this way
                     % both \f@lterdefs and \f@lterdefsplain can be defined
}

\def\f@lterdefsplain#1#2#3, \[#4]{%
    \yystringempty{#4}{ {#1}{#2}}{%
        \yystringempty{#1}{%
            \f@lterdefsplain{\[#4]}{#2#3}%
        }{%
            \f@lterdefsplain{#1, \[#4]}{#2#3}%
        }%
    }%
}

\def\f@lterdefs#1#2#3, \[#4]#5{%
    \yystringempty{#4}{ {#1}{#2}}{%
        \yystringempty{#1}{%
            \f@lterdefs{\[#4]{#5}}{#2#3}%
        }{%
            \f@lterdefs{#1, \[#4]{#5}}{#2#3}%
        }%
    }%
}

\def\setxrefs#1#2{% typeset filtered references (with page references)
    \expandafter\s@txrefs\romannumeral0\filterdefs{#1}{#2}%
}

\def\s@txrefs#1#2{%
    \yystringempty{#1}{%
        several refs.%
    }{%
        \setxreflist{#1}%
        \yystringempty{#2}{}{, other refs.}%
    }%
}

\def\setxrefsplain#1#2{% typeset filtered references (without page references)
    \expandafter\s@txrefsplain\romannumeral0\filterdefs{#1}{#2}%
}

\def\s@txrefsplain#1#2{%
    \yystringempty{#1}{%
        several refs.%
    }{%
        \ifacro\pdfnote#1.\else#1.\fi
        \yystringempty{#2}{}{, other refs.}%
    }%
}

\def\setxreflist#1{%
    \yystringempty{#1}{}{%
        \grabfinexrefs{}, #1, {}{}%
    }%
}

\def\grabfinexrefs#1, #2#3#{% collect (and process) fine references (i.e. references like lnnnr{p1, p2, ... }
    \yystringempty{#2#3}{% this is the last reference, clean up (the last empty group is left unchanged)
        \expandafter\relax\eatacomma#1%
    }{%
        \ifcat\noexpand#20% this is a bare number
            \yybreak{\consumeonexref{{}{}{#1}}{#2#3}}%
        \else % this is a qualified number
            \yybreak{\stripxrefdelims{{#2}{#1}}{}#3}%
        \yycontinue
    }%
}

\def\eatacomma, {}

% get the right delimeter; we assume that at least one token is present

\def\stripxrefdelims#1#2#3{%
    \ifnum`#3<`0
        \yybreak{\consumeonexref{{#3}#1}{#2}}% this is a delimeter
    \else
        \ifnum`#3>`9
            \yybreak@{\consumeonexref{{#3}#1}{#2}}% this is a delimeter
        \else % `0<=`#4<=`9
            \yybreak@{\stripxrefdelims{#1}{#2#3}}% keep looking for the next digit
        \fi
    \yycontinue
}

\ifx\consumeonexref\UNDEFINED
    \def\consumeonexref#1#2#3{% #1 is the accumulated references
                              % #2 is the section number
                              % #3 is the list of pages
        \addnewpair#1{#2}%
    }
\fi

% ignore paging information

\def\addnewpair#1#2#3#4{% #1 is the right delimeter
                        % #2 is the left delimeter
                        % #3 is the list of references
                        % #4 is the section number
    \grabfinexrefs{#3, #2\compoundlink{#4}{#4}#1}%
}


\def\compoundlink#1#2{%
    \ifacro
        \pdflink{#1}{#2}%
    \else
        #2%
    \fi
}

\def\pagelink#1{%
    \ifacro
        \pdfpagelink{#1}% use the section number
    \else
        #1%
    \fi
}

% indexing macros for grammar terms

\def\termidstring#1{% processed name in italics
    \numberstocharsandspaces#1\end
    \let\optstrextra\optstrextraesc
    \expandafter\nameproc\expandafter{\the\toksa}\with\parsebin
    \def\idxentry{{\it\the\toksa}}%
}%

\def\termvstring#1{% processed name in typewriter style
    \numberstochars#1\end
    \let\optstrextra\optstrextraesc
    \expandafter\nameproc\expandafter{\the\toksa}\with\parsebin
    \def\idxentry{{\tt\def\_{\char`\_}\the\toksa}}%
}%

\def\termttstring#1{% straightforward typewriter text
    \numberstocharsandspaces#1\end
    \def\idxentry{{\tt\the\toksa}}%
}%

\def\termhostidstring#1{% processed name in italics (using the host name parser)
    \numberstocharsandspaces#1\end
    \let\optstrextra\optstrextraesc
    {%
        \expandafter\let\expandafter\tosmallparser
            \csname to\stripbrackets\hostparsernamespace parser\endcsname
        \expandafter\nameproc\expandafter{\the\toksa}\with\parsebin
        \aftergroup\toksa
        \expandafter
    }\expandafter{\the\toksa}%
    \def\idxentry{{\it\the\toksa}}%
}%

\def\termhostvstring#1{% processed name in typewriter style (using the host name parser)
    \numberstocharsandspaces#1\end
    \let\optstrextra\optstrextraesc
    {%
        \expandafter\let\expandafter\tosmallparser
            \csname to\stripbrackets\hostparsernamespace parser\endcsname
        \expandafter\nameproc\expandafter{\the\toksa}\with\parsebin
        \aftergroup\toksa
        \expandafter
    }\expandafter{\the\toksa}%
    \def\idxentry{{\tt\def\_{\char`\_}\the\toksa}}%
}%

\def\termostring#1{% options (e.g. \flex\ and \bison\)
    \numberstocharsandspaces#1\end
    \def\idxentry{{$\langle$\bf\the\toksa$\rangle$}}%
}%

\def\termfsrestring#1{% flex regular expression definition names
    \numberstocharsandspaces#1\end
    \let\optstrextra\optstrextraesc
    \expandafter\nameproc\expandafter{\the\toksa}\with\parsebin
    \def\idxentry{{\def\_{\char`\_}\flexrendisplay{\the\toksa}}}%
}%

\def\termfsopstring#1{% flex option names
    \numberstocharsandspaces#1\end
    \let\optstrextra\optstrextraesc
    \expandafter\nameproc\expandafter{\the\toksa}\with\parsebin
    \def\idxentry{{\def\_{\char`\_}\hbox{\tt$\langle$\the\toksa$\rangle_{\rm f}$}}}%
}%

\def\termfsscstring#1{% flex sate names
    \numberstocharsandspaces#1\end
    \let\optstrextra\optstrextraesc
    \expandafter\nameproc\expandafter{\the\toksa}\with\parsebin
    \def\idxentry{{\def\_{\char`\_}\hbox{\flexsnstyle{\the\toksa}}}}%
}%

\def\termstring#1{%
    \numberstocharsandspaces#1\end
    \let\optstrextra\optstrextraesc
    \expandafter\nameproc\expandafter{\the\toksa}\with\parsebin
    \def\idxentry{{\tt"\the\toksa"}}%
}%

\def\termexception#1{% special names
    \numberstocharsandspaces#1\end
    \toksc\toksa
    \expandafter\nameproc\expandafter{\the\toksa}\with\parsebin
    \ifyyparsefail
        \expandafter\ifx\csname\prettynamecs\hostparsernamespace{\the\toksa}\endcsname\relax
            \errmessage{The name \the\toksc\space is exceptional but is not defined.}%
        \else
            \def\idxentry{{\it\csname\prettynamecs\hostparsernamespace{\the\toksa}\endcsname{#1}}}%
        \fi
    \else
        \errmessage{The name \the\toksc\space is not exceptional.}%
    \fi
}%

% \TeX\ conrol sequence output
\def\texcsstring#1{%
    \numberstocharsandspaces#1\end
    \def\idxentry{%
        \let\texnspace\hostparsernamespace
        \def\getcescape{% an \ seen is really an \, and will not go through C string processing
            \def\next{escape}%
            \switchon\next\in\currentstate
        }%
        \termindexfalse\expandafter\inlineTeXx\expandafter{\expandafter/\the\toksa}%
    }%
}%

\def\termtrivial#1{% trivial index entry for debugging purposes
                   % can also be used as a starting point for other index processors
    \def\idxentry{%
        {\rm index entry goes here}%
    }%
}%

% the raw/direct macros below should be used sparingly because they potentially introduce
% style inconsistencies while outputting index terms; a special scheme can be designed to counter
% that ... or one can just be careful; in addition, \termdirect relies on the exact number of expansions
% (2) undergone by the entry's contents;
% of the two options, \termraw is probably preferable as it does not pass any volatile information
% (such as expandable sequences) in the index entries and thus allows much more elaborate and
% safe setup of index terms; the only advantage of the \termdirect variation is the lack of global 
% assignments (the \termraw changes the \indexterms array); indices by their nature are global
% so this should be a secondary consideration in the majority of cases.

\def\termdirect#1{% 
    \def\idxentry{%
        {\t@rmdirect#1}%
    }%
}%

\def\t@rmdirect#1\end\toksa#2\eatone{#2}

\let\vend\relax % the delimeter to let the indexing script know that this is a raw entry

% here is an example of the \termdirect macros in action:
%
% the entry setup below is a bit elaborate since parts of the entry must be passed in a safe way
% in case it contains dangerous characters like _ 
%     \toksa{\noexpand\tt{\noexpand\rm(}#1{\noexpand\rm)}\noexpand\charstocharsx#2\end}%
% the \noexpand's above are necessary to block the final expansion by \write
% the assignment below would also work; note the '0 ' which is required to stop the expansion
% of \romannumeral which is part of \charstonumberse; in this case \vend can be left undefined
%     \toksa{\end0 \noexpand\noexpand\noexpand\vend(#1)#2\noexpand\noexpand\noexpand\vend\eatone}%

% the setup above works with the following indexing command
%\ridxentry{\termdirect}{\the\toksa}{#2}%

\def\termraw#1{% raw entry, shows terms saved in a global array, #1 is the index into the array
    \numberstocharsandspaces#1\end
    \def\idxentry{%
        {\let\parsernamespace\empty\getmidstackcs\indexterms{\the\toksa}}%
    }%
}%

\setnulstack{indexterms} % the array for the terms processed by \termraw

\expandafter\def\csname acharswitch:index\endcsname{% correct reserved \TeX\ characters a la CWEB verbatim
    %$\%\\ % unaffected
    %\#\   % these never appear
    _{%
        \yybyte\expandafter{\csname \the\yybyte\endcsname}%
        \expandafter\yycp@\expandafter`\the\yybyte\relax
        \mkpurebyte
        \yyreturn
     }
}%

\expandafter\setspecialcharsfrom\csname acharswitch:index\endcsname

\def\texlexerindex{% now that all character codes are 12
    \let\default\yygetchar
    \let\next\yycp@
    \ifnum\yycp@>"3F %
        \ifnum\yycp@<"5B % an uppercase letter or @
                \def\next{letter}%
        \fi
    \fi
    \ifnum\yycp@>"60 %
        \ifnum\yycp@<"7B % 
                \def\next{letter}%
        \fi
    \fi
    \switchonwithtype\next\in\currentstate
}%

\def\indexseparator#1#2{% generic separator
    \vskip.5\baselineskip
    \centerline{\dinkus}%
    \vskip.5\baselineskip
}

\def\indexseparator#1#2{%
    \vskip.3\baselineskip
    \centerline{%
        \toksa\expandafter\expandafter\expandafter
            {\expandafter\yyfirstofthree\romannumeral0\csname index domain translation [#1]\endcsname}%
        \toksb\expandafter\expandafter\expandafter
            {\expandafter\yysecondofthree\romannumeral0\csname index domain translation [#1]\endcsname}%
        \toksc\expandafter\expandafter\expandafter
            {\expandafter\yythirdofthree\romannumeral0\csname index domain translation [#1]\endcsname}%
        \the\toksc
        \edef\next{\write\cont{%\noexpand\noexpand\noexpand\eatone{\noexpand\meaning\noexpand\ZZ}%
            \noexpand\noexpand\noexpand\YY{\the\toksa}{2}{\secno}% write to contents file
            {\noexpand\the\pageno}{\the\toksb}}}\next % \YY{title}{depth}{sec}{page}{ss}
    }%
    \vskip.3\baselineskip
}

\expandafter\def\csname index domain translation [F]\endcsname{{\noexpand\flex\ index}{flex index}{F{\sc LEX INDEX}}}
\expandafter\def\csname index domain translation [T]\endcsname{{\noexpand\TeX\ index}{TeX index}{\TeX\ {\sc INDEX}}}

\def\indexsection#1{%
    \vskip.3\baselineskip
    \penalty-1000
    \hbox to \hsize{\strut\ssfbn #1\ \cdotfill}%
    \penalty10000
    \vskip.2\baselineskip
}

\def\9#1{%
    \indexs@ction#1\end
}

\def\indexs@ction#1#2\end{
    \indexsection{\uppercase{#1}}%
}

\def\otherlangindexseparator{%
  \enddoublecols
  \vskip.8\baselineskip
  \centerline{\otherlangindexheader
        \edef\next{\write\cont{%\noexpand\noexpand\noexpand\eatone{\noexpand\meaning\noexpand\ZZ}%
            \noexpand\noexpand\noexpand\YY{\noexpand\noexpand\noexpand\bison\ index}{2}{\secno}% write to contents file
            {\noexpand\the\pageno}{bison Index}}}\next % \ZZ{title}{depth}{sec}{page}{ss}
  }%
  \vskip.5\baselineskip
  \begindoublecols
}

\def\otherlangindexheader{%
    B{\sc ISON}, F{\sc LEX, AND} \TeX\ {\sc INDICES}%
}

%\def\otherlangindexseparator{}

% general index entries (generated by bindx.pl)

\def\GI#1#2#3#4.{% raw index entries (standard format, with no page references)
    {%
        \edef\hostparsernamespace{\yysecondoftwo#1}%
        \edef\currentrulecontext{\yyfirstoftwo#1}%
        \toksa{}\numberstocharsandspaces#3\end
        \edef\indexkeyseq{\the\toksa}%        
        \toksa{}#2{#3}%
        \expandafter\Ji\expandafter{\indexkeyseq}{\idxentry}#4.%
    }%
}%

\def\FI#1#2#3#4.{% raw index entries (fine format)
    {%
        \edef\hostparsernamespace{\yysecondoftwo#1}%
        \edef\currentrulecontext{\yyfirstoftwo#1}%
        \toksa{}\numberstocharsandspaces#3\end
        \edef\indexkeyseq{\the\toksa}%        
        \toksa{}#2{#3}%
        \expandafter\Fi\expandafter{\indexkeyseq}{\idxentry}#4.%
    }%
}%

\def\HI#1#2#3#4{% special raw index entries
    {%
        \def\hostparsernamespace{#2}%
        \let\defypreamble\empty
        \let\defypostamble\empty
        \toksa{}\numberstocharsandspaces#4\end
        \edef\indexkeyseq{\the\toksa}%
        \toksa{}#3{#4}%
        \expandafter\Jk\expandafter{\indexkeyseq}{\idxentry}, see  
            {\tt\hbox{\sixpoint\tt\char`\\}\indexkeyseq}.%
    }%
}%

% reference styles (ordinary terms are set in roman face)

\def\[#1]{{\it#1}} % term definitions (such as lhs in productions)
\def\(#1){$\underline{#1}$} % declarations (such as token declarations), underlined index item
\def\(#1){{\bf #1}} % declarations, an alternative to the above
\def\e#1e{#1{\sevenpoint$^\circ\!$}} % terms in examples
\def\f#1f{{\it#1\/\kern.2ex}${}^\circ\!$} % lhs in examples (italic correction does not seem to be enough)
\def\g#1g{$\underline{#1}^\circ\!$} % declarations in examples
\def\g#1g{{\bf #1}$^\circ\!$} % declarations in examples, an alternative to the above

\def\inxhangindent{1em}
\def\inxaiskip{.5em}
\def\inxicgap{5pt}

\ifx\unindexable\UNDEFINED
    \def\unindexable{{$\TeXx$}{$\TeXa$}{$\TeXb$}{$\TeXf$}{$\TeXao$}{$\TeXfo$}{$\BZ$}}%
\else
    \expandafter\def\expandafter\unindexable\expandafter
        {\unindexable{$\TeXx$}{$\TeXa$}{$\TeXb$}{$\TeXf$}{$\TeXao$}{$\TeXfo$}{$\BZ$}}%
\fi

\def\idxsafe{% redefine some control sequences to behave safely in the index
    \def\TeXx{\hbox{\let\_\UL\tt\TeX\_}}
    \def\TeXa{\hbox{\tt\TeX\rm(a)}}
    \def\TeXb{\hbox{\tt\TeX\rm(b)}}
    \def\TeXf{\hbox{\tt\TeX\rm(f)}}
    \def\TeXao{\hbox{\tt\TeX\rm(ao)}}
    \def\TeXfo{\hbox{\tt\TeX\rm(fo)}}
    \def\BZ{\hbox{\tt BZ}}
}

\def\inxmod{% new indexing macro
    \closeout\exampletable
    \termindexfalse
    \idxsafe
    \closeout\gindex
    \message{Index:}
    \medskip
    \eightpoint\raggedright
    \emergencystretch=.1em % for especially tight cases
    \fnotesstart=2
    \fnotesspan=1
    \noofcolumns=3
    \icgap=\inxicgap%
    \linecount=3
    \setmcparams
    \dsskip=0pt%
    \adjskip=0pt plus 9pt%
    % \TeX\ conrol sequence output
    \expandafter\let\expandafter\acharswitch\csname acharswitch:index\endcsname
    \let\texlexer\texlexerindex
    \let\*=\lapstar
    \let\I\Ii % make \CWEAVE\ generated entries typeset in our style
    \begindoublecols
    \readindex
    \readgindex
    \write\cont{}% ensure that the contents file isn't empty
    \write\cont{\catcode `\noexpand\@=12\relax}% \makeatother
    \closeout\cont % the contents information has been fully gathered
    \enddoublecols
}

\newread\trygindex

\def\readgindex{%
    \openin\trygindex=\jobname.gdy
    \ifeof\trygindex
    \else
        \closein\trygindex
        \otherlangindexseparator
        \input \jobname.gdy
    \fi
}
