summaryrefslogtreecommitdiff
path: root/macros/latex/base/lthooks.dtx
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2020-10-03 03:01:09 +0000
committerNorbert Preining <norbert@preining.info>2020-10-03 03:01:09 +0000
commit0e644e6805342838052f46e6af93f81644d0390b (patch)
tree26169230ce62dc353013ad08fb824f46d9511980 /macros/latex/base/lthooks.dtx
parent37f9a57b2f39142fbccb20b758af8e0502499671 (diff)
CTAN sync 202010030301
Diffstat (limited to 'macros/latex/base/lthooks.dtx')
-rw-r--r--macros/latex/base/lthooks.dtx4012
1 files changed, 4012 insertions, 0 deletions
diff --git a/macros/latex/base/lthooks.dtx b/macros/latex/base/lthooks.dtx
new file mode 100644
index 0000000000..cb48570a0d
--- /dev/null
+++ b/macros/latex/base/lthooks.dtx
@@ -0,0 +1,4012 @@
+% \iffalse meta-comment
+%%
+%% File: lthooks.dtx (C) Copyright 2020 Frank Mittelbach,
+%% Phelype Oleinik & LaTeX Team
+%
+% This file is part of the LaTeX base system.
+% -------------------------------------------
+%
+% It may be distributed and/or modified under the
+% conditions of the LaTeX Project Public License, either version 1.3c
+% of this license or (at your option) any later version.
+% The latest version of this license is in
+% https://www.latex-project.org/lppl.txt
+% and version 1.3c or later is part of all distributions of LaTeX
+% version 2008 or later.
+%
+% This file has the LPPL maintenance status "maintained".
+%
+% The list of all files belonging to the LaTeX base distribution is
+% given in the file `manifest.txt'. See also `legal.txt' for additional
+% information.
+%
+% The list of derived (unpacked) files belonging to the distribution
+% and covered by LPPL is defined by the unpacking scripts (with
+% extension .ins) which are part of the distribution.
+%
+% \fi
+%
+% \iffalse
+%
+%%% From File: lthooks.dtx
+%
+% \begin{macrocode}
+\def\lthooksversion{v1.0b}
+\def\lthooksdate{2020/09/21}
+% \end{macrocode}
+%
+%<*driver>
+\documentclass{l3doc}
+
+% bug fix fo l3doc.cls
+\ExplSyntaxOn
+\cs_set_protected:Npn \__codedoc_macro_typeset_one:nN #1#2
+ {
+ \vbox_set:Nn \l__codedoc_macro_box
+ {
+ \vbox_unpack_drop:N \l__codedoc_macro_box
+ \hbox { \llap { \__codedoc_print_macroname:nN {#1} #2
+ \MacroFont % <----- without it the \ is in lmr10 if a link is made
+ \
+ } }
+ }
+ \int_incr:N \l__codedoc_macro_int
+ }
+\ExplSyntaxOff
+
+\EnableCrossrefs
+\CodelineIndex
+\begin{document}
+ \DocInput{lthooks.dtx}
+\end{document}
+%</driver>
+%
+% \fi
+%
+%
+% \long\def\fmi#1{\begin{quote}\itshape FMi: #1\end{quote}}
+% \long\def\pho#1{\begin{quote}\itshape PhO: #1\end{quote}}
+%
+% \newcommand\hook[1]{\texttt{#1}}
+%
+%
+% \title{The \texttt{lthooks} package\thanks{This package has version
+% \lthooksversion\ dated \lthooksdate, \copyright\ \LaTeX\
+% Project.}}
+%
+% \author{Frank Mittelbach\thanks{Code improvements for speed and other goodies by Phelype Oleinik}}
+%
+% \maketitle
+%
+%
+% \tableofcontents
+%
+% \section{Introduction}
+%
+% Hooks are points in the code of commands or environments where it
+% is possible to add processing code into existing commands. This
+% can be done by different packages that do not know about each
+% other and to allow for hopefully safe processing it is necessary
+% to sort different chunks of code added by different packages into
+% a suitable processing order.
+%
+% This is done by the packages adding chunks of code (via
+% \cs{AddToHook}) and labeling their code with some label by
+% default using the package name as a label.
+%
+% At \verb=\begin{document}= all code for a hook is then sorted
+% according to some rules (given by \cs{DeclareHookRule}) for fast
+% execution without processing overhead. If the hook code is
+% modified afterwards (or the rules are changed),
+% a new version for fast processing is generated.
+%
+% Some hooks are used already in the preamble of the document. If
+% that happens then the hook is prepared for execution (and sorted)
+% already at that point.
+%
+%
+% \section{Package writer interface}
+%
+% The hook management system is offered as a set of CamelCase
+% commands for traditional \LaTeXe{} packages (and for use in the
+% document preamble if needed) as well as \texttt{expl3} commands
+% for modern packages, that use the L3 programming layer of
+% \LaTeX{}. Behind the scenes, a single set of data structures is
+% accessed so that packages from both worlds can coexist and access
+% hooks in other packages.
+%
+%
+%
+% \subsection{\LaTeXe\ interfaces}
+%
+% \subsubsection{Declaring hooks and using them in code}
+%
+% With two exceptions, hooks have to be declared before they can be
+% used. The exceptions are hooks in environments (i.e., executed at
+% \cs{begin} and \cs{end}) and hooks run when loading files,
+% e.g. before and after a package is loaded, etc. Their hook names
+% depend on the environment or the file name and so declaring them
+% beforehand is difficult.
+%
+%
+% \begin{function}{\NewHook}
+% \begin{syntax}
+% \cs{NewHook} \Arg{hook}
+% \end{syntax}
+% Creates a new \meta{hook}.
+% If this is a hook provided as part of a package it is suggested
+% that the \meta{hook} name is always structured as follows:
+% \meta{package-name}\texttt{/}\meta{hook-name}. If necessary you
+% can further subdivide the name by adding more \texttt{/} parts.
+% If a hook name is already taken, an error is raised and the hook
+% is not created.
+%
+% The \meta{hook} can be specified using the dot-syntax to denote
+% the current package name. See section~\ref{sec:default-label}.
+% \end{function}
+%
+% \begin{function}{\NewReversedHook}
+% \begin{syntax}
+% \cs{NewReversedHook} \Arg{hook}
+% \end{syntax}
+% Like \cs{NewHook} declares a new \meta{hook}.
+% the difference is that the code chunks for this hook are in
+% reverse order by default (those added last are executed first).
+% Any rules for the hook are applied after the default ordering.
+% See sections~\ref{sec:order} and \ref{sec:reversed-order}
+% for further details.
+%
+% The \meta{hook} can be specified using the dot-syntax to denote
+% the current package name. See section~\ref{sec:default-label}.
+% \end{function}
+%
+%
+% \begin{function}{\NewMirroredHookPair}
+% \begin{syntax}
+% \cs{NewMirroredHookPair} \Arg{hook-1} \Arg{hook-2}
+% \end{syntax}
+% A shorthand for
+% \cs{NewHook}\Arg{hook-1}\cs{NewReversedHook}\Arg{hook-2}.
+%
+% The \meta{hooks} can be specified using the dot-syntax to denote
+% the current package name. See section~\ref{sec:default-label}.
+% \end{function}
+%
+%
+%
+%
+% \begin{function}{\UseHook}
+% \begin{syntax}
+% \cs{UseHook} \Arg{hook}
+% \end{syntax}
+% Execute the hook code inside a command or environment.
+%
+% Before \verb=\begin{document}= the fast execution code for a hook
+% is not set up, so in order to use a hook there it is explicitly
+% initialized first. As that involves assignments using a hook at
+% those times is not 100\% the same as using it after
+% \verb=\begin{document}=.
+%
+% The \meta{hook} \emph{cannot} be specified using the dot-syntax.
+% A leading |.| is treated literally.
+% \end{function}
+%
+% \begin{function}{\UseOneTimeHook}
+% \begin{syntax}
+% \cs{UseOneTimeHook} \Arg{hook}
+% \end{syntax}
+% Some hooks are only used (and can be only used) in one place, for
+% example, those in \verb=\begin{document}= or
+% \verb=\end{document}=. Once we have passed that point adding to
+% the hook through a defined \cs{\meta{addto-cmd}} command (e.g.,
+% \cs{AddToHook} or \cs{AtBeginDocument}, etc.\@) would have no
+% effect (as would the use of such a command inside the hook code
+% itself). It is therefore customary to redefine
+% \cs{\meta{addto-cmd}} to simply process its argument, i.e.,
+% essentially make it behave like \cs{@firstofone}.
+%
+% \cs{UseOneTimeHook} does that: it records that the hook has been
+% consumed and any further attempt to add to it will result in
+% executing the code to be added immediately.
+%
+% \fmi{Maybe add an error version as well?}
+%
+% The \meta{hook} \emph{cannot} be specified using the dot-syntax.
+% A leading |.| is treated literally.
+% \end{function}
+%
+%
+% \subsubsection{Updating code for hooks}
+%
+% \begin{function}{\AddToHook}
+% \begin{syntax}
+% \cs{AddToHook} \Arg{hook}\oarg{label}\Arg{code}
+% \end{syntax}
+% Adds \meta{code} to the \meta{hook} labeled by \meta{label}. If
+% the optional argument \meta{label} is not provided, if \cs{AddToHook}
+% is used in a package/class, then the current
+% package/class name is used, otherwise \hook{top-level} is
+% used~(see section~\ref{sec:default-label}).
+%
+% If there already exists code under the \meta{label} then the new
+% \meta{code} is appended to the existing one (even if this is a reversed hook).
+% If you want to replace existing code under the
+% \meta{label}, first apply \cs{RemoveFromHook}.
+%
+% The hook doesn't have to exist for code to be added to
+% it. However, if it is not declared later then obviously the
+% added \meta{code} will never be executed. This
+% allows for hooks to work regardless of package loading order and
+% enables packages to add to hook of other packages without
+% worrying whether they are actually used in the current document.
+% See section~\ref{sec:querying}.
+%
+% The \meta{hook} and \meta{label} can be specified using the
+% dot-syntax to denote the current package name.
+% See section~\ref{sec:default-label}.
+% \end{function}
+%
+% \begin{function}{\RemoveFromHook}
+% \begin{syntax}
+% \cs{RemoveFromHook} \Arg{hook}\oarg{label}
+% \end{syntax}
+% Removes any code labeled by \meta{label} from the \meta{hook}.
+% If the optional
+% argument \meta{label} is not provided, if \cs{AddToHook}
+% is used in a package/class, then the current
+% package/class name is used, otherwise \hook{top-level} is used.
+%
+% If the optional argument is \texttt{*}, then all code chunks are
+% removed. This is rather dangerous as it drops code from other
+% packages one may not know about!
+%
+% The \meta{hook} and \meta{label} can be specified using the
+% dot-syntax to denote the current package name.
+% See section~\ref{sec:default-label}.
+% \end{function}
+%
+% \medskip
+%
+% In contrast to the \texttt{voids} relationship between two labels
+% in a \cs{DeclareHookrule} this is a destructive operation as the
+% labeled code is removed from the hook data structure, whereas the
+% relationship setting can be undone by providing a different
+% relationship later.
+%
+% A useful application for this declaration inside the document body
+% is when one wants to temporarily add code to hooks and later remove
+% it again, e.g.,
+%\begin{verbatim}
+% \AddToHook{env/quote/before}{\small}
+% \begin{quote}
+% A quote set in a smaller typeface
+% \end{quote}
+% ...
+% \RemoveFromHook{env/quote/before}
+% ... now back to normal for further quotes
+%\end{verbatim}
+% Note that you can't cancel the setting with
+%\begin{verbatim}
+% \AddToHook{env/quote/before}{}
+%\end{verbatim}
+% because that only \enquote{adds} a further empty chunk of code to
+% the hook. Adding \cs{normalsize} would work but that means the hook
+% then contained \cs{small}\cs{normalsize} which means to font size
+% changes for no good reason.
+%
+% The above is only needed if one wants to typeset several quotes in a
+% smaller typeface. If the hook is only needed once then
+% \cs{AddToHookNext} is simpler, because it resets itself after one use.
+%
+%
+% \begin{function}{\AddToHookNext}
+% \begin{syntax}
+% \cs{AddToHookNext} \Arg{hook}\Arg{code}
+% \end{syntax}
+% Adds \meta{code} to the next invocation of the \meta{hook}.
+% The code is executed after the normal hook code has finished and
+% it is executed only once, i.e. it is deleted after it was used.
+%
+% Using the declaration is a global operation, i.e., the code is
+% not lost, even if the declaration is used inside a group and the
+% next invocation happens after the group. If the declaration is
+% used several times before the hook is executed then all code is
+% executed in the order in which it was declared.\footnotemark
+%
+% It is possible to nest declarations using the same hook (or
+% different hooks), e.g.,
+% \begin{quote}
+% \cs{AddToHookNext}\Arg{hook}\verb={=\meta{code-1}^^A
+% \cs{AddToHookNext}\Arg{hook}\Arg{code-2}\verb=}=
+% \end{quote}
+% will execute \meta{code-1} next time the \meta{hook} is used and at
+% that point puts \meta{code-2} into the \meta{hook} so that it gets
+% executed on following time the hook is run.
+%
+% A hook doesn't have to exist for code to be added to it. This
+% allows for hooks to work regardless of package loading
+% order.
+% See section~\ref{sec:querying}.
+%
+% The \meta{hook} can be specified using the dot-syntax to denote
+% the current package name. See section~\ref{sec:default-label}.
+% \end{function}\footnotetext{There is
+% no mechanism to reorder such code chunks (or delete them).}
+%
+% \subsubsection{Hook names and default labels}
+% \label{sec:default-label}
+%
+% It is best practice to use \cs{AddToHook} in packages or classes
+% \emph{without specifying a \meta{label}} because then the package
+% or class name is automatically used, which is helpful if rules are
+% needed, and avoids mistyping the \meta{label}.
+%
+% Using an explicit \meta{label} is only necessary in very specific
+% situations, e.g., if you want to add several chunks of code into a
+% single hook and have them placed in different parts of the hook
+% (by providing some rules).
+%
+% The other case is when you develop a larger package with several
+% sub-packages. In that case you may want to use the same
+% \meta{label} throughout the sub-packages in order to avoid
+% that the labels change if you internally reorganize your code.
+%
+% Except for \cs{UseHook}, \cs{UseOneTimeHook}, \cs{IfHookEmptyTF}, and
+% \cs{IfHookExistsTF} (and their \pkg{expl3} interfaces \cs{hook_use:n},
+% \cs{hook_use_once:n}, \cs{hook_if_empty:nTF}, and
+% \cs{hook_if_exist:nTF}), all \meta{hook}
+% and \meta{label} arguments are processed in the same way: first,
+% spaces are trimmed around the argument, then it is fully expanded
+% until only character tokens remain. If the full expansion of the
+% \meta{hook} or \meta{label} contains a non-expandable non-character
+% token, a low-level \TeX{} error is raised (namely, the \meta{hook} is
+% expanded using \TeX's \cs{csname}\ldots\cs{endcsname}, as such,
+% Unicode characters are allowed in \meta{hook} and \meta{label}
+% arguments). The arguments of \cs{UseHook} and \cs{UseOneTimeHook} are
+% processed much in the same way except that spaces are not trimmed
+% around the argument, for better performance.
+%
+% It is not enforced, but highly recommended that the hooks defined by
+% a package, and the \meta{labels} used to add code to other hooks
+% contain the package name to easily identify the source of the code
+% chunk and to prevent clashes. This should be the standard practice,
+% so this hook management code provides a shortcut to refer to the
+% current package in the name of a \meta{hook} and in a \meta{label}.
+% If \meta{hook} name or \meta{label} consist just of a single dot
+% (|.|), or starts with a dot followed by a slash (|./|) then the dot
+% denotes the \meta{default label} (usually the current package or class
+% name---see~\cs{DeclareDefaultHookLabel}).
+% A \enquote{|.|} or \enquote{|./|} anywhere else in a \meta{hook} or in
+% \meta{label} is treated literally and is not replaced.
+%
+% For example,
+% inside the package \texttt{mypackage.sty}, the default label is
+% \texttt{mypackage}, so the instructions:
+% \begin{verbatim}
+% \NewHook {./hook}
+% \AddToHook {./hook}[.]{code} % Same as \AddToHook{./hook}{code}
+% \AddToHook {./hook}[./sub]{code}
+% \DeclareHookRule{begindocument}{.}{before}{babel}
+% \AddToHook {file/after/foo.tex}{code}
+% \end{verbatim}
+% are equivalent to:
+% \begin{verbatim}
+% \NewHook {mypackage/hook}
+% \AddToHook {mypackage/hook}[mypackage]{code}
+% \AddToHook {mypackage/hook}[mypackage/sub]{code}
+% \DeclareHookRule{begindocument}{mypackage}{before}{babel}
+% \AddToHook {file/after/foo.tex}{code} % unchanged
+% \end{verbatim}
+%
+% The \meta{default label} is automatically set to the name of the
+% current package or class (using \cs{@currname}). If \cs{@currname}
+% is not set (because the hook command is used outside of a package, or
+% the current file wasn't loaded with \cs{usepackage} or
+% \cs{documentclass}), then the \texttt{top-level} is used as the
+% \meta{default label}.
+%
+% This syntax is available in all \meta{label} arguments and most
+% \meta{hook}, both in the \LaTeXe{} interface, and the \LaTeX3
+% interface described in section~\ref{sec:l3hook-interface}.
+%
+% Note, however, that the replacement of |.| by the \meta{default label}
+% takes place when the hook command is executed, so actions that are
+% somehow executed after the package ends will have the wrong
+% \meta{default label} if the dot-syntax is used. For that reason,
+% this syntax is not available in \cs{UseHook} (and \cs{hook_use:n})
+% because the hook is most of the time used outside of the package file
+% in which it was defined. This syntax is also not available in the hook
+% conditionals \cs{IfHookEmptyTF} (and \cs{hook_if_empty:nTF}) and
+% \cs{IfHookExistsTF} (and \cs{hook_if_exist:nTF}) because these
+% conditionals are used in some performance-critical parts of the hook
+% management code, and because they are usually used to refer to other
+% package's hooks, so the dot-syntax doesn't make much sense.
+%
+% In some cases, for example in large packages, one may want to separate
+% it in logical parts, but still use the main package name as
+% \meta{label}, then the \meta{default label} can be set using
+% \cs{DeclareDefaultHookLabel}:
+%
+% \begin{function}{\DeclareDefaultHookLabel}
+% \begin{syntax}
+% \cs{DeclareDefaultHookLabel} \Arg{default label}
+% \end{syntax}
+% Sets the \meta{default label} to be used in \meta{label} arguments.
+% If \cs{DeclareDefaultHookLabel} is not used in the
+% current package, \cs{@currname} is used instead. If \cs{@currname}
+% is not set, the code is assumed to be in the main document, in which
+% case \texttt{top-level} is used.
+%
+% The effect of \cs{DeclareDefaultHookLabel} holds for the current
+% file, and is reset to the previous value when the file is closed.
+% \end{function}
+%
+%
+% \subsubsection{Defining relations between hook code}
+%
+% The default assumption is that code added to hooks by different
+% packages is independent and the order in which it is executed is
+% irrelevant. While this is true in many case it is obviously false
+% in many others.
+%
+% Before the hook management system was introduced
+% packages had to take elaborate precaution to determine of some other
+% package got loaded as well (before or after) and find some ways to
+% alter its behavior accordingly. In addition is was often the user's
+% responsibility to load packages in the right order so that code
+% added to hooks got added in the right order and some cases even
+% altering the loading order wouldn't resolve the conflicts.
+%
+% With the new hook management system it is now possible to define
+% rules (i.e., relationships) between code chunks added by different
+% packages and explicitly describe in which order they should be
+% processed.
+%
+% \begin{function}{\DeclareHookRule}
+% \begin{syntax}
+% \cs{DeclareHookRule} \Arg{hook}\Arg{label1}\Arg{relation}\Arg{label2}
+% \end{syntax}
+% Defines a relation between \meta{label1} and \meta{label2} for a
+% given \meta{hook}. If \meta{hook} is \texttt{??} this defines a default
+% relation for all hooks that use the two labels, i.e., that have
+% chunks of code labeled with \meta{label1} and \meta{label2}.
+% Rules specific to a given hook take precedence over default
+% rules that use \texttt{??} as the \meta{hook}.
+%
+% Currently, the supported relations are the following:
+% \begin{itemize}
+%
+% \item[\texttt{before} or \texttt{\string<}]
+%
+% Code for \meta{label1} comes before code for \meta{label2}.
+%
+% \item[\texttt{after} or \texttt{\string>}]
+% Code for \meta{label1} comes after code for \meta{label2}.
+%
+% \item[\texttt{incompatible-warning}]
+%
+% Only code for either \meta{label1} or \meta{label2} can appear
+% for that hook (a way to say that two packages---or parts of
+% them---are incompatible). A warning is raised if both labels
+% appear in the same hook.
+%
+% \item[\texttt{incompatible-error}]
+%
+% Like \texttt{incompatible-error} but instead of a warning a
+% \LaTeX{} error is raised, and the code for both labels are
+% dropped from that hook until the conflict is resolved.
+%
+% \item[\texttt{voids}]
+%
+% Code for \meta{label1} overwrites code for \meta{label2}. More
+% precisely, code for \meta{label2} is dropped for that
+% hook. This can be used, for example if one package is a
+% superset in functionality of another one and therefore wants to
+% undo code in some hook and replace it with its own version.
+%
+% \item[\texttt{unrelated}]
+%
+% The order of code for \meta{label1} and \meta{label2} is
+% irrelevant. This rule is there to undo an incorrect rule
+% specified earlier.
+%
+% \end{itemize}
+% There can only be a single relation between two labels for a
+% given hook,
+% i.e., a later \cs{DeclareHookrule} overwrites any previous
+% delcaration.
+%
+% The \meta{hook} and \meta{label} can be specified using the
+% dot-syntax to denote the current package name.
+% See section~\ref{sec:default-label}.
+%
+% \end{function}
+%
+%
+% \begin{function}{\ClearHookRule}
+% \begin{syntax}
+% \cs{ClearHookRule}\Arg{hook}\Arg{label1}\Arg{label2}
+% \end{syntax}
+% Syntactic sugar for saying that \meta{label1} and \meta{label2}
+% are unrelated for the given \meta{hook}.
+% \end{function}
+%
+%
+%
+% \begin{function}{\DeclareDefaultHookRule}
+% \begin{syntax}
+% \cs{DeclareDefaultHookRule}\Arg{label1}\Arg{relation}\Arg{label2}
+% \end{syntax}
+% This sets up a relation between \meta{label1} and \meta{label2}
+% for all hooks unless overwritten by a specific rule for a hook.
+% Useful for cases where one package has a specific relation to
+% some other package, e.g., is \texttt{incompatible} or always
+% needs a special ordering \texttt{before} or \texttt{after}.
+% (Technically it is just a shorthand for using \cs{DeclareHookRule}
+% with \texttt{??} as the hook name.)
+%
+% Declaring default rules is only supported in the document
+% preamble.\footnotemark{}
+%
+% The \meta{label} can be specified using the dot-syntax to denote
+% the current package name. See section~\ref{sec:default-label}.
+% \end{function}\footnotetext{Trying to do so, e.g., via
+% \cs{DeclareHookRule} with \texttt{??} has bad side-effects and
+% is not supported (though not explicitly caught for performance
+% reasons).}
+%
+%
+%
+% \subsubsection{Querying hooks}
+% \label{sec:querying}
+%
+% Simpler data types, like token lists, have three possible states; they
+% can:
+% \begin{itemize}
+% \item exist and be empty;
+% \item exist and be non-empty; and
+% \item not exist (in which case emptiness doesn't apply);
+% \end{itemize}
+% Hooks are a bit more complicated: they have four possible states.
+% A hook may exist or not, and either way it may or may not be empty.
+% This means that even a hook that doesn't exist may be non-empty.
+%
+% This seemingly strange state may happen when, for example, package~$A$
+% defines hook \hook{A/foo}, and package $B$ adds some code to that
+% hook. However, a document may load package $B$ before package $A$, or
+% may not load package $A$ at all. In both cases some code is added to
+% hook \hook{A/foo} without that hook being defined yet, thus that
+% hook is said to be non-empty, whereas it doesn't exist. Therefore,
+% querying the existence of a hook doesn't imply its emptiness, neither
+% does the other way around.
+%
+% A hook is said to be empty when no code was added to it, either to
+% its permanent code pool, or to its ``next'' token list. The hook
+% doesn't need to be declared to have code added to its code pool.
+% A hook is said to exist when it was declared with \cs{NewHook} or
+% some variant thereof.
+%
+% \begin{function}[EXP]{\IfHookEmptyTF}
+% \begin{syntax}
+% \cs{IfHookEmptyTF} \Arg{hook} \Arg{true code} \Arg{false code}
+% \end{syntax}
+% Tests if the \meta{hook} is empty (\emph{i.e.}, no code was added to
+% it using either \cs{AddToHook} or \cs{AddToHookNext}), and
+% branches to either \meta{true code} or \meta{false code} depending
+% on the result.
+%
+% The \meta{hook} \emph{cannot} be specified using the dot-syntax.
+% A leading |.| is treated literally.
+% \end{function}
+%
+% \begin{function}[EXP]{\IfHookExistsTF}
+% \begin{syntax}
+% \cs{IfHookExistsTF} \Arg{hook} \Arg{true code} \Arg{false code}
+% \end{syntax}
+% Tests if the \meta{hook} exists (if it was created with either
+% \cs{NewHook}, \cs{NewReversedHook}, or \cs{NewMirroredHookPair}), and
+% branches to either \meta{true code} or \meta{false code} depending
+% on the result.
+%
+% The existence of a hook usually doesn't mean much from the viewpoint
+% of code that tries to add/remove code from that hook, since package
+% loading order may vary, thus the creation of hooks is asynchronous
+% to adding and removing code from it, so this test should be used
+% sparingly.
+%
+% Generic hooks are declared at the time code is added to them, so the
+% result of \cs{hook_if_exist:n} will change once code is added to
+% said hook (unless the hook was previously declared).
+%
+% The \meta{hook} \emph{cannot} be specified using the dot-syntax.
+% A leading |.| is treated literally.
+% \end{function}
+%
+% \fmi{Would be helpful if we provide some use cases}
+%
+% \subsubsection{Displaying hook code}
+%
+% If one has to adjust the code execution in a hook using a hook
+% rule it is helpful to get some information about the code
+% associated with a hook, its current order and the existing rules.
+%
+% \begin{function}{\ShowHook,\LogHook}
+% \begin{syntax}
+% \cs{ShowHook} \Arg{hook}
+% \end{syntax}
+% Displays information about the \meta{hook} such as
+% \begin{itemize}
+% \item
+% the code chunks (and their labels) added to it,
+% \item
+% any rules set up to order them,
+% \item
+% the computed order in which the chunks are executed,
+% \item
+% any code executed on the next invocation only.
+% \end{itemize}
+%
+% \cs{LogHook} prints the information to the |.log| file, and
+% \cs{ShowHook} prints them to the terminal/command window and starts
+% \TeX's prompt (only if \cs{errorstopmode}) to wait for user action.
+%
+% The \meta{hook} can be specified using the dot-syntax to denote
+% the current package name. See section~\ref{sec:default-label}.
+% \end{function}
+%
+%
+% \subsubsection{Debugging hook code}
+%
+% \begin{function}{\DebugHooksOn,\DebugHooksOff}
+% \begin{syntax}
+% \cs{DebugHooksOn}
+% \end{syntax}
+% Turn the debugging of hook code on or off. This displays changes
+% made to the hook data structures. The output is rather coarse and
+% not really intended for normal use.
+% \end{function}
+%
+%
+% \subsection{L3 programming layer (\texttt{expl3}) interfaces}
+% \label{sec:l3hook-interface}
+%
+%
+% This is a quick summary of the \LaTeX3 programming interfaces for
+% use with packages written in \texttt{expl3}. In contrast to the
+% \LaTeXe{} interfaces they always use mandatory arguments only, e.g.,
+% you always have to specify the \meta{label} for a code chunk. We
+% therefore suggest to use the declarations discussed in the previous
+% section even in \texttt{expl3} packages, but the choice is yours.
+%
+%
+% \begin{function}
+% {\hook_new:n,\hook_new_reversed:n,\hook_new_pair:nn}
+% \begin{syntax}
+% \cs{hook_new:n}\Arg{hook}
+% \cs{hook_new_pair:nn}\Arg{hook-1}\Arg{hook-2}
+% \end{syntax}
+% Creates a new \meta{hook} with normal or reverse ordering of code
+% chunks. \cs{hook_new_pair:nn} creates a pair of such hooks with
+% \Arg{hook-2} being a reversed hook.
+% If a hook name is already taken, an error is raised and the hook
+% is not created.
+%
+% The \meta{hook} can be specified using the dot-syntax to denote
+% the current package name. See section~\ref{sec:default-label}.
+% \end{function}
+%
+%
+%
+% \begin{function}{\hook_use:n}
+% \begin{syntax}
+% \cs{hook_use:n} \Arg{hook}
+% \end{syntax}
+% Executes the \Arg{hook} code followed (if set up) by the code for next
+% invocation only, then empties that next invocation code.
+%
+% The \meta{hook} \emph{cannot} be specified using the dot-syntax.
+% A leading |.| is treated literally.
+% \end{function}
+%
+% \begin{function}{\hook_use_once:n}
+% \begin{syntax}
+% \cs{hook_use_once:n} \Arg{hook}
+% \end{syntax}
+% Changes the \Arg{hook} status so that from now on any addition to
+% the hook code is executed immediately. Then execute any
+% \Arg{hook} code already set up.
+% \fmi{better L3 name?}
+%
+% The \meta{hook} \emph{cannot} be specified using the dot-syntax.
+% A leading |.| is treated literally.
+% \end{function}
+%
+% \begin{function}{\hook_gput_code:nnn}
+% \begin{syntax}
+% \cs{hook_gput_code:nnn} \Arg{hook} \Arg{label} \Arg{code}
+% \end{syntax}
+% Adds a chunk of \meta{code} to the \meta{hook} labeled
+% \meta{label}. If the label already exists the \meta{code} is
+% appended to the already existing code.
+%
+% If code is added to an external \meta{hook} (of the kernel or
+% another package) then the convention is to use the package name
+% as the \meta{label} not some internal module name or some other
+% arbitrary string.
+%
+% The \meta{hook} and \meta{label} can be specified using the
+% dot-syntax to denote the current package name.
+% See section~\ref{sec:default-label}.
+% \end{function}
+%
+% \begin{function}
+% {\hook_gput_next_code:nn}
+% \begin{syntax}
+% \cs{hook_gput_next_code:nn} \Arg{hook} \Arg{code}
+% \end{syntax}
+% Adds a chunk of \meta{code} for use only in the next invocation of the
+% \meta{hook}. Once used it is gone.
+%
+% This is simpler than \cs{hook_gput_code:nnn}, the code is simply
+% appended to the hook in the order of declaration at the very end,
+% i.e., after all standard code for the hook got executed.
+%
+% Thus if one needs to undo what the standard does one has to do
+% that as part of \meta{code}.
+%
+% The \meta{hook} can be specified using the dot-syntax to denote
+% the current package name. See section~\ref{sec:default-label}.
+% \end{function}
+%
+%
+%
+% \begin{function}{\hook_gremove_code:nn}
+% \begin{syntax}
+% \cs{hook_gremove_code:nn} \Arg{hook} \Arg{label}
+% \end{syntax}
+% Removes any code for \meta{hook} labeled \meta{label}.
+%
+% If the code for that \meta{label} wasn't yet added to the
+% \meta{hook}, an order is set so that when some code attempts to add
+% that label, the removal order takes action and the code is not
+% added.
+%
+% If the second argument is \texttt{*}, then all code chunks are
+% removed. This is rather dangerous as it drops code from other
+% packages one may not know about, so think twice before using
+% that!
+%
+% The \meta{hook} and \meta{label} can be specified using the
+% dot-syntax to denote the current package name.
+% See section~\ref{sec:default-label}.
+% \end{function}
+%
+%
+% \begin{function}{\hook_gset_rule:nnnn}
+% \begin{syntax}
+% \cs{hook_gset_rule:nnnn} \Arg{hook} \Arg{label1} \Arg{relation} \Arg{label2}
+% \end{syntax}
+% Relate \meta{label1} with \meta{label2} when used in \meta{hook}.
+% See \cs{DeclareHookRule} for the allowed \meta{relation}s.
+% If \meta{hook} is \texttt{??} a default rule is specified.
+%
+% The \meta{hook} and \meta{label} can be specified using the
+% dot-syntax to denote the current package name.
+% See section~\ref{sec:default-label}.
+% The dot-syntax is parsed in both \meta{label} arguments, but it
+% usually makes sense to be used in only one of them.
+% \end{function}
+%
+% \begin{function}[pTF]{\hook_if_empty:n}
+% \begin{syntax}
+% \cs{hook_if_empty:nTF} \Arg{hook} \Arg{true code} \Arg{false code}
+% \end{syntax}
+% Tests if the \meta{hook} is empty (\emph{i.e.}, no code was added to
+% it using either \cs{AddToHook} or \cs{AddToHookNext}), and
+% branches to either \meta{true code} or \meta{false code} depending
+% on the result.
+%
+% The \meta{hook} \emph{cannot} be specified using the dot-syntax.
+% A leading |.| is treated literally.
+% \end{function}
+%
+% \begin{function}[pTF]{\hook_if_exist:n}
+% \begin{syntax}
+% \cs{hook_if_exist:nTF} \Arg{hook} \Arg{true code} \Arg{false code}
+% \end{syntax}
+% Tests if the \meta{hook} exists (if it was created with either
+% \cs{NewHook}, \cs{NewReversedHook}, or \cs{NewMirroredHookPair}), and
+% branches to either \meta{true code} or \meta{false code} depending
+% on the result.
+%
+% The existence of a hook usually doesn't mean much from the viewpoint
+% of code that tries to add/remove code from that hook, since package
+% loading order may vary, thus the creation of hooks is asynchronous
+% to adding and removing code from it, so this test should be used
+% sparingly.
+%
+% Generic hooks are declared at the time code is added to them, so the
+% result of \cs{hook_if_exist:n} will change once code is added to
+% said hook (unless the hook was previously declared).
+%
+% The \meta{hook} \emph{cannot} be specified using the dot-syntax.
+% A leading |.| is treated literally.
+% \end{function}
+%
+% \begin{function}{\hook_show:n,\hook_log:n}
+% \begin{syntax}
+% \cs{hook_show:n} \Arg{hook}
+% \end{syntax}
+% Displays information about the \meta{hook} such as
+% \begin{itemize}
+% \item
+% the code chunks (and their labels) added to it,
+% \item
+% any rules set up to order them,
+% \item
+% the computed order in which the chunks are executed,
+% \item
+% any code executed on the next invocation only.
+% \end{itemize}
+%
+% \cs{hook_log:n} prints the information to the |.log| file, and
+% \cs{hook_show:n} prints them to the terminal/command window and starts
+% \TeX's prompt (only if \cs{errorstopmode}) to wait for user action.
+%
+% The \meta{hook} can be specified using the dot-syntax to denote
+% the current package name. See section~\ref{sec:default-label}.
+% \end{function}
+%
+% \begin{function}{\hook_debug_on:,\hook_debug_off:}
+% \begin{syntax}
+% \cs{hook_debug_on:}
+% \end{syntax}
+% Turns the debugging of hook code on or off. This displays changes
+% to the hook data.
+% \end{function}
+%
+%
+%
+% \subsection{On the order of hook code execution} \label{sec:order}
+%
+% Chunks of code for a \meta{hook} under different labels are supposed
+% to be independent if there are no special rules set up that
+% define a relation between the chunks. This means that you can't
+% make assumptions about the order of execution!
+%
+% Suppose you have the following declarations:
+%\begin{verbatim}
+% \NewHook{myhook}
+% \AddToHook{myhook}[packageA]{\typeout{A}}
+% \AddToHook{myhook}[packageB]{\typeout{B}}
+% \AddToHook{myhook}[packageC]{\typeout{C}}
+%\end{verbatim}
+% then executing the hook with \cs{UseHook} will produce the
+% typeout \texttt{A} \texttt{B} \texttt{C} in that order. In other
+% words, the execution order is computed to be \texttt{packageA},
+% \texttt{packageB}, \texttt{packageC} which you can verify with
+% \cs{ShowHook}\texttt{\{myhook\}}:
+%\begin{verbatim}
+% The hook 'myhook':
+% Code chunks:
+% packageA -> \typeout {A}
+% packageB -> \typeout {B}
+% packageC -> \typeout {C}
+% Extra code next invocation:
+% ---
+% Rules:
+% ---
+% Execution order:
+% packageA, packageB, packageC
+%\end{verbatim}
+% The reason is that the code chunks are internally saved in a property list
+% and the initial order of such a property list is the order in
+% which key-value pairs got added. However, that is only true if
+% nothing other than adding happens!
+%
+% Suppose, or example, you want to replace the code chunk for
+% \texttt{packageA}, e.g.,
+%\begin{verbatim}
+% \RemoveFromHook{myhook}[packageA]
+% \AddToHook{myhook}[packageA]{\typeout{A alt}}
+%\end{verbatim}
+% then your order becomes \texttt{packageB},
+% \texttt{packageC}, \texttt{packageA} because the label got removed
+% from the property list and then re-added (at its end).
+%
+% While that may not be too surprising, the execution order is
+% also sometimes altered if you add a redundant rule, e.g. if you specify
+%\begin{verbatim}
+% \DeclareHookRule{myhook}{packageA}{before}{packageB}
+%\end{verbatim}
+% instead of the previous lines we get
+%\begin{verbatim}
+% The hook 'myhook':
+% Code chunks:
+% packageA -> \typeout {A}
+% packageB -> \typeout {B}
+% packageC -> \typeout {C}
+% Extra code next invocation:
+% ---
+% Rules:
+% packageA|packageB with relation before
+% Execution order (after applying rules):
+% packageA, packageC, packageB
+%\end{verbatim}
+% As you can see the code chunks are still in the same order, but
+% in the execution order for the labels \texttt{packageB} and
+% \texttt{packageC} have
+% swapped places.
+% The reason is that, with the rule there are two orders that
+% satisfy it, and the algorithm for sorting happened to pick a
+% different one compared to the case without rules (where it
+% doesn't run at all as there is nothing to resolve).
+% Incidentally, if we had instead specified the redundant rule
+%\begin{verbatim}
+% \DeclareHookRule{myhook}{packageB}{before}{label-3}
+%\end{verbatim}
+% the execution order would not have changed.
+%
+% In summary: it is not possible to rely on the order of execution
+% unless there are rules that partially or fully define the order
+% (in which you can rely on them being fulfilled).
+%
+%
+% \subsection{The use of \enquote{reversed} hooks} \label{sec:reversed-order}
+%
+% You may have wondered why you can declare a \enquote{reversed} hook
+% with \cs{NewReversedHook} and what that does exactly.
+%
+% In short: the execution order of a reversed hook (without any
+% rules!) is exactly reversed to the order you would have gotten for
+% a hook declared with \cs{NewHook}.
+%
+% This is helpful if you have a pair of hooks where you expect to see
+% code added that involves grouping, e.g., starting an environment
+% in the first and closing that environment in the second hook.
+% To give a somewhat contrived example\footnote{there are simpler
+% ways to achieve the same effect.}, suppose there is a package
+% adding the following:
+%\begin{verbatim}
+% \AddToHook{env/quote/before}[package-1]{\begin{itshape}}
+% \AddToHook{env/quote/after} [package-1]{\end{itshape}}
+%\end{verbatim}
+% As a result, all quotes will be in italics.
+% Now suppose further that the user wants the quotes also in blue
+% and therefore adds:
+%\begin{verbatim}
+% \usepackage{color}
+% \AddToHook{env/quote/before}{\begin{color}{blue}}
+% \AddToHook{env/quote/after} {\end{color}}
+%\end{verbatim}
+% Now if the \hook{env/quote/after} hook would be a normal hook we
+% would get the same execution order in both hooks, namely:
+%\begin{verbatim}
+% package-1, top-level
+%\end{verbatim}
+% (or vice versa) and as a result, would get:
+%\begin{verbatim}
+% \begin{itshape}\begin{color}{blue} ...
+% \end{itshape}\end{color}
+%\end{verbatim}
+% and an error message that \verb=\begin{color}= ended by
+% \verb=\end{itshape}=.
+% With \hook{env/quote/after} declared as a reversed hook the
+% execution order is reversed and so all environments are closed in
+% the correct sequence and \cs{ShowHook} would give us the
+% following output:
+%\begin{verbatim}
+% The hook 'env/quote/after':
+% Code chunks:
+% package-1 -> \end {itshape}
+% top-level -> \end {color}
+% Extra code next invocation:
+% ---
+% Rules:
+% ---
+% Execution order (after reversal):
+% top-level, package-1
+%\end{verbatim}
+%
+% The reversal of the execution order happens before applying any
+% rules, so if you alter the order you will probably have to alter
+% it in both hooks, not just in one, but that depends on the use case.
+%
+%
+%
+%
+% \subsection{Difference between \enquote{normal} and
+% \enquote{one-time} hooks}
+%
+% When executing a hook a developer has the choice of using
+% either \cs{UseHook} or \cs{UseOneTimeHook} (or their \pkg{expl3}
+% equivalents \cs{hook_use:n} and \cs{hook_use_once:n}).
+% This choice affects how \cs{AddToHook} is handled after the hook
+% has been executed for the first time.
+%
+% With normal hooks adding code via \cs{AddToHook} means that the
+% code chunk is added to the hook data structure and then used each time
+% \cs{UseHook} is called.
+%
+% With one-time hooks it this is handled slightly differently:
+% After \cs{UseOneTimeHook} has been called, any further attempts to
+% add code to the hook via \cs{AddToHook} will simply execute the
+% \meta{code} immediately.
+%
+% This has some consequences one needs to be aware of:
+% \begin{itemize}
+% \item
+%
+% If \meta{code} is added to a normal hook after the hook was
+% executed and it is never executed again for one or the other
+% reason, then this new \meta{code} will never be executed.
+%
+% \item
+%
+% In contrast if that happens with a one-time hook the \meta{code} is
+% executed immediately.
+%
+% \end{itemize}
+% In particular this means that construct such as
+%\begin{quote}
+% \cs{AddToHook}\verb={myhook}=\\
+% \phantom{\cs{AddToHook}}\verb={= \meta{code-1}
+% \cs{AddToHook}\verb={myhook}=\Arg{code-2}
+% \meta{code-3} \verb=}=
+%\end{quote}
+% works for one-time hooks\footnote{This is sometimes used with
+% \cs{AtBeginDocument} which is why it is supported.} (all three
+% code chunks are executed one after another), but it makes little
+% sense with a normal hook, because with a normal hook the first time
+% \verb=\UseHook{myhook}= is executed it would
+% \begin{itemize}
+% \item
+% execute \meta{code-1},
+% \item
+% then execute \verb=\AddToHook{myhook}{code-2}= which adds the
+% code chunk \meta{code-2} to the hook for use on the next invocation,
+% \item
+% and finally execute \meta{code-3}.
+% \end{itemize}
+% The second time \cs{UseHook} is called it would execute the
+% above and in addition \meta{code-2} as that was added as a code
+% chunk to the hook in the meantime. So each time the hook is used
+% another copy of \meta{code-2} is added and so that code chunk
+% is executed $\meta{\# of invocations} -1$ times.
+%
+%
+%
+%
+%
+% \subsection{Private \LaTeX{} kernel hooks}
+%
+% There are a few places where it is absolutely essential for
+% \LaTeX{} to function correctly that code is executed in a precisely
+% defined order. Even that could have been implemented with the
+% hook management (by adding various rules to ensure the
+% appropriate ordering with respect to other code added by
+% packages). However, this makes every document unnecessary
+% slow, because there has to be sorting even through the result is
+% predetermined. Furthermore it forces package writers to
+% unnecessarily add such rules if they add further code to the hook
+% (or break \LaTeX{}).
+%
+% For that reason such code is not using the hook management, but
+% instead private kernel commands directly before or after a public
+% hook with the following naming
+% convention: \cs{@kernel@before@\meta{hookname}} or
+% \cs{@kernel@after@\meta{hookname}}. For example, in
+% \cs{enddocument} you find
+%\begin{verbatim}
+% \UseHook{enddocument}%
+% \@kernel@after@enddocument
+%\end{verbatim}
+% which means first the user/package-accessible \hook{enddocument}
+% hook is executed and then the internal kernel hook. As their name
+% indicates these kernel commands should not be altered by third-party
+% packages, so please refrain from that in the interest of
+% stability and instead use the public hook next to it.\footnote{As
+% with everything in \TeX{} there is not enforcement of this rule,
+% and by looking at the code it is easy to find out how the kernel
+% adds to them. The main reason of this section is therefore to say
+% \enquote{please don't do that, this is unconfigurable code!}}
+%
+%
+%
+% \subsection{Legacy \LaTeXe{} interfaces}
+%
+% \LaTeXe{} offered a small number of hooks together with commands to
+% add to them. They are listed here and are retained for backwards
+% compatibility.
+%
+% With the new hook management several additional hooks have been added
+% to \LaTeX\ and more will follow. See the next section for what
+% is already available.
+%
+%
+% \begin{function}{\AtBeginDocument}
+% \begin{syntax}
+% \cs{AtBeginDocument} \oarg{label} \Arg{code}
+% \end{syntax}
+% If used without the optional argument \meta{label}, it works essentially
+% like before, i.e., it is adding \meta{code} to the hook
+% \hook{begindocument}
+% (which is executed inside \verb=\begin{document}=).
+% However, all code added this way is labeled with the label
+% \hook{top-level} if done outside of a package or class or with the
+% package/class name if called inside such a file.
+%
+% This way one can add further code to the hook using
+% \cs{AddToHook} or \cs{AtBeginDocument} using a different label
+% and explicitly order the code chunks as necessary, e.g., run some
+% code before or after the \hook{top-level} code. When using the
+% optional argument the call is equivalent to running
+% \cs{AddToHook} \texttt{\{begindocument\}} \oarg{label}
+% \Arg{code}.
+%
+% For important packages with known order requirement we may over
+% time add rules to the kernel (or to those packages) so that they
+% work regardless of the loading-order in the document.
+% \end{function}
+%
+% \begin{function}{\AtEndDocument}
+% \begin{syntax}
+% \cs{AtEndDocument} \oarg{label} \Arg{code}
+% \end{syntax}
+% Like \cs{AtBeginDocument} but for the \hook{enddocument} hook.
+% \end{function}
+%
+%
+%
+% \begin{function}{\AtBeginDvi}
+% \begin{syntax}
+% \cs{AtBeginDvi} \oarg{label} \Arg{code}
+% \end{syntax}
+% This hook is discussed in conjunction with the shipout hooks.
+% \end{function}
+%
+% \bigskip
+%
+% The few hooks that existed previously in \LaTeXe{} used internally
+% commands such as \cs{@begindocumenthook} and packages sometimes
+% augemented them directly rather than working through
+% \cs{AtBeginDocumement}. For that reason there is currently support
+% for this, that is, if the system detects that such an internal
+% legacy hook command contains code it adds it to the new hook
+% system under the label \texttt{legacy} so that it doesn't get
+% lost.
+%
+% However, over time the remaining cases of direct usage need
+% updating because in one of the future release of \LaTeX{} we will
+% turn this legacy support off, as it does unnecessary slow down
+% the processing.
+%
+%
+% \subsection{\LaTeXe{} commands and environments augmented by
+% hooks}
+%
+% \emph{intro to be written}
+%
+% \subsubsection{Generic hooks for all environments}
+%
+% Every environment \meta{env} has now four associated hooks coming
+% with it:
+% \begin{description}
+% \item[\hook{env/\meta{env}/before}]
+%
+% This hook is executed as part of \cs{begin} as the very first
+% action, in particular prior to starting the environment group.
+% Its scope is therefore not restricted by the environment.
+%
+% \item[\hook{env/\meta{env}/begin}]
+%
+% This hook is executed as part of \cs{begin} directly in front
+% of the code specific to the environment start (e.g., the
+% second argument of \cs{newenvironment}). Its scope is the
+% environment body.
+%
+% \item[\hook{env/\meta{env}/end}]
+%
+% This hook is executed as part of \cs{end} directly in front of the
+% code specific to the end of the environment (e.g., the third
+% argument of \cs{newenvironment}).
+%
+% \item[\hook{env/\meta{env}/after}]
+%
+% This hook is executed as part of \cs{end} after the
+% code specific to the environment end and after the environment
+% group has ended.
+% Its scope is therefore not restricted by the environment.
+%
+% The hook is implemented as a reversed hook so if two packages
+% add code to \hook{env/\meta{env}/before} and to
+% \hook{env/\meta{env}/after} they can add surrounding
+% environments and the order of closing them happens in the
+% right sequence.
+%
+% \end{description}
+% Generic environment hooks are never one-time hooks even with
+% environments that are supposed to appear only once in a
+% document.\footnote{Thus if one adds code to such hooks after the
+% environment has been processed, it will only be executed if the
+% environment appears again and if that doesn't happen the code
+% will never get executed.} In contrast to other hooks there is
+% also no need to declare them using \cs{NewHook}.
+%
+% The hooks are only executed if \cs{begin}\Arg{env} and
+% \cs{end}\Arg{env} is used. If the environment code is executed
+% via low-level calls to \cs{\meta{env}} and \cs{end\meta{env}}
+% (e.g., to avoid the environment grouping) they are not
+% available. If you want them available in code using this method,
+% you would need to add them yourself, i.e., write something like
+%\begin{verbatim}
+% \UseHook{env/quote/before}\quote
+% ...
+% \endquote\UseHook{env/quote/after}
+%\end{verbatim}
+% to add the outer hooks, etc.
+%
+%
+% \begin{function}{\BeforeBeginEnvironment}
+% \begin{syntax}
+% \cs{BeforeBeginEnvironment} \oarg{label} \Arg{code}
+% \end{syntax}
+% This declaration adds to the \hook{env/\meta{env}/before} hook
+% using by default the current package or class name as a label or
+% \texttt{top-level} if used in the document directly.
+% \end{function}
+%
+% \begin{function}{\AtBeginEnvironment}
+% \begin{syntax}
+% \cs{AtBeginEnvironment} \oarg{label} \Arg{code}
+% \end{syntax}
+% Like \cs{BeforeBeginEnvironment} but adds to the \hook{env/\meta{env}/begin} hook.
+% \end{function}
+%
+% \begin{function}{\AtEndEnvironment}
+% \begin{syntax}
+% \cs{AtEndEnvironment} \oarg{label} \Arg{code}
+% \end{syntax}
+% Like \cs{BeforeBeginEnvironment} but adds to the \hook{env/\meta{env}/end} hook.
+% \end{function}
+%
+% \begin{function}{\AfterEndEnvironment}
+% \begin{syntax}
+% \cs{AfterEndEnvironment} \oarg{label} \Arg{code}
+% \end{syntax}
+% Like \cs{BeforeBeginEnvironment} but adds to the \hook{env/\meta{env}/after} hook.
+% \end{function}
+%
+%
+%
+%
+% \subsubsection{Hooks provided by \cs{begin}\texttt{\{document\}}}
+%
+% Until 2020 \cs{begin}\texttt{\{document\}} offered exactly one
+% hook that one could add to using
+% \cs{AtBeginDocument}. Experiences over the years have shown that
+% this single hook in one place was not enough and as part of
+% adding the general hook management system a number of additional
+% hooks have been added at this point. The places for these hooks have
+% been chosen to provide the same support as offered by external
+% packages, such as \pkg{etoolbox} and others that augmented
+% \cs{document} to gain better control.
+%
+% Supported are now the following hooks (all of them one-time hooks):
+% \begin{description}
+%
+%
+% \item[\hook{begindocument/before}]
+%
+% This hook is executed at the very start of \cs{document}, one can
+% think of it as a hook for code at the end of the preamble
+% section and this is how it is used by \pkg{etoolbox}'s
+% \cs{AtEndPreamble}.
+%
+%
+%
+% \item[\hook{begindocument}]
+%
+% This hook is added to when using \cs{AtBeginDocument} and it is executed
+% after the \texttt{.aux} file as be read in and most
+% initialization are done, so they can be altered and inspected by
+% the hook code. It is followed by a small number of further
+% initializations that shouldn't be altered and are therefore
+% coming later.
+%
+% The hook should not be used to add material for typesetting as
+% we are still in \LaTeX's initialization phase and not in the
+% document body. If such material needs to be added to the document
+% body use the next hook instead.
+%
+% \item[\hook{begindocument/end}]
+%
+% This hook is executed at the end of the \cs{document} code in
+% other words at the beginning of the document body. The only
+% command that follows it is \cs{ignorespaces}.
+%
+% \end{description}
+% The generic hooks executed by \cs{begin} also exist, i.e.,
+% \hook{env/document/before} and \hook{env/document/begin}, but
+% with this special environment it is better use the dedicated
+% one-time hooks above.
+%
+%
+%
+%
+% \subsubsection{Hooks provided by \cs{end}\texttt{\{document\}}}
+%
+% \LaTeXe{} always provided \cs{AtEndDocument} to add code to the
+% execution of \verb=\end{document}= just in front of the code that
+% is normally executed there. While this was a big improvement over
+% the situation in \LaTeX\,2.09 it was not flexible enough for a
+% number of use cases and so packages, such as \pkg{etoolbox},
+% \pkg{atveryend} and others patched \cs{enddocument} to add
+% additional points where code could be hooked into.
+%
+% Patching using packages is always problematical as leads to
+% conflicts (code availability, ordering of patches, incompatible
+% patches, etc.). For this reason a number of additional hooks
+% have been added to the \cs{enddocument} code to allow packages
+% to add code in various places in a controlled way without the
+% need for overwriting or patching the core code.
+%
+% Supported are now the following hooks (all of them one-time hooks):
+% \begin{description}
+%
+% \item[\hook{enddocument}]
+%
+% The hook associated with \cs{AtEndDocument}. It is immediately
+% called at the beginning of \cs{enddocument}.
+%
+% When this hook is executed there may be still unprocessed
+% material (e.g., floats on the deferlist) and the hook may add
+% further material to be typeset. After it, \cs{clearpage} is
+% called to ensure that all such material gets typeset. If there
+% is nothing waiting the \cs{clearpage} has no effect.
+%
+%
+% \item[\hook{enddocument/afterlastpage}]
+%
+% As the name indicates this hook should not receive code that
+% generates material for further pages. It is the right place to
+% do some final housekeeping and possibly write out some
+% information to the \texttt{.aux} file (which is still open at
+% this point to receive data). It is also the correct place to
+% set up any testing code to be run when the \texttt{.aux} file
+% is re-read in the next step.
+%
+%
+% After this hook has been executed the \texttt{.aux} file is
+% closed for writing and then read back in to do some tests
+% (e.g., looking for missing references or duplicated labels, etc.).
+%
+% \item[\hook{enddocument/afteraux}]
+%
+% At this point, the \texttt{.aux} file has been reprocessed and so
+% this is a possible place for final checks and display of
+% information to the user. However, for the latter you might
+% prefer the next hook, so that your information is displayed after the
+% (possibly longish) list of files if that got requested via \cs{listfiles}.
+%
+% \item[\hook{enddocument/info}]
+%
+% This hook is meant to receive code that write final information
+% messages to the terminal. It follows immediately after the
+% previous hook (so both could have been combined, but then
+% packages adding further code would always need to also supply
+% an explicit rule to specify where it should go.
+%
+% This hook already contains some code added by the kernel (under
+% the labels \texttt{kernel/filelist} and
+% \texttt{kernel/warnings}), namely the list of files when
+% \cs{listfiles} has been used and the warnings for duplicate
+% labels, missing references, font substitutions etc.
+%
+% \item[\hook{enddocument/end}]
+%
+% Finally, this hook is executed just in front of the final call
+% to \cs{@{}@end}.
+%
+% \end{description}
+%
+%
+% There is also the hook \hook{shipout/lastpage}. This hook is
+% executed as part of the last \cs{shipout} in the document to
+% allow package to add final \cs{special}'s to that page. Where
+% this hook is executed in relation to those from the above list
+% can vary from document to document. Furthermore to determine correctly
+% which of the \cs{shipout}s is the last one, \LaTeX{} needs to be run
+% several times, so initially it might get executed on the wrong
+% page. See section~\ref{sec:shipout} for where to find the details.
+%
+%
+% It is in also possible to use the generic \hook{env/document/end}
+% hook which is execuded by \cs{end}, i.e., just in front of the
+% first hook above. Note however that the other generic \cs{end}
+% environment hook, i.e., \hook{env/document/after} will never get
+% executed, because by that time \LaTeX{} has finished the document
+% processing.
+%
+%
+%
+%
+% \subsubsection{Hooks provided \cs{shipout} operations}
+% \label{sec:shipout}
+%
+% There are several hooks and mechanisms added to \LaTeX{}'s
+% process of generating pages. These are documented in
+% \texttt{ltshipout-doc.pdf} or with code in
+% \texttt{ltshipout-code.pdf}.
+%
+%
+% \subsubsection{Hooks provided by file loading operations}
+%
+% There are several hooks added to \LaTeX{}'s
+% process of loading file via its high-level interfaces such as
+% \cs{input}, \cs{include}, \cs{usepackage}, etc. These are documented in
+% \texttt{ltfilehook-doc.pdf} or with code in
+% \texttt{ltfilehook-code.pdf}.
+%
+%
+% \subsubsection{Hooks provided in NFSS commands}
+%
+% In languages that need to support for more than one script in
+% parallel (and thus several sets of fonts), e.g., Latin and
+% Japanese fonts, NFSS font commands, such as \cs{sffamily}, need
+% to switch both the Latin family to ``Sans Serif'' and in addition
+% alter a second set of fonts.
+%
+% To support this several NFSS have hooks in which such support can
+% be added.
+% \begin{description}
+%
+% \item[\hook{rmfamily}]
+%
+% After \cs{rmfamily} has done its initial checks and prepared a
+% any font series update this hook is executed and only
+% afterwards \cs{selectfont}.
+%
+% \item[\hook{sffamily}]
+%
+% Like the \hook{rmfamily} hook but for the \cs{sffamily} command.
+%
+% \item[\hook{ttfamily}]
+%
+% Like the \hook{rmfamily} hook but for the \cs{ttfamily} command.
+%
+% \item[\hook{normalfont}]
+%
+% The \cs{normalfont} command resets font encoding family series
+% and shape to their document defaults. It then executes this
+% hook and finally calls \cs{selectfont}.
+%
+% \item[\hook{expand@font@defaults}]
+%
+% The internal \cs{expand@font@defaults} command expands and
+% saves the current defaults for the meta families (rm/sf/tt) and
+% the meta series (bf/md). If the NFSS machinery has been
+% augmented, e.g., for Chinese or Japanese fonts, then further
+% defaults may need to be set at this point. This can be done in
+% this hook which is executed at the end of this macro.
+%
+% \item[\hook{bfseries/defaults}, \hook{bfseries}]
+%
+% If the \cs{bfdefault} was explicitly changed by the user its
+% new value is used to set the bf series defaults for the meta
+% families (rm/sf/tt) when \cs{bfseries} is called. In the
+% \hook{bfseries/defaults} hook further adjustments can be made
+% in this case. This hook is only executed if such a change is
+% detected. In contrast the \hook{bfseries} hook is always
+% executed just before \cs{selectfont} is called to change to the
+% new series.
+%
+%
+% \item[\hook{mdseries/defaults}, \hook{mdseries}]
+%
+% These two hooks are like the previous ones but used in
+% \cs{mdseries} command.
+%
+% \end{description}
+%
+%
+% \StopEventually{\setlength\IndexMin{200pt} \PrintIndex }
+%
+%
+% \section{The Implementation}
+%
+%
+% \subsection{Loading further extensions}
+%
+% \begin{macrocode}
+%<@@=hook>
+% \end{macrocode}
+%
+%
+% At the moment the whole module rolls back in one go, but if we
+% make any modifications in later releases this will then need
+% splitting.
+% \begin{macrocode}
+%<*2ekernel|latexrelease>
+%<latexrelease>\IncludeInRelease{2020/10/01}%
+%<latexrelease> {\NewHook}{The hook management}%
+% \end{macrocode}
+%
+% \begin{macrocode}
+\ExplSyntaxOn
+% \end{macrocode}
+%
+%
+% \subsection{Debugging}
+%
+% \begin{macro}{\g_@@_debug_bool}
+% Holds the current debugging state.
+% \begin{macrocode}
+\bool_new:N \g_@@_debug_bool
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hook_debug_on:,\hook_debug_off:}
+% \begin{macro}{\@@_debug:n}
+% \begin{macro}{\@@_debug_gset:}
+% Turns debugging on and off by redefining \cs{@@_debug:n}.
+% \begin{macrocode}
+\cs_new_eq:NN \@@_debug:n \use_none:n
+\cs_new_protected:Npn \hook_debug_on:
+ {
+ \bool_gset_true:N \g_@@_debug_bool
+ \@@_debug_gset:
+ }
+\cs_new_protected:Npn \hook_debug_off:
+ {
+ \bool_gset_false:N \g_@@_debug_bool
+ \@@_debug_gset:
+ }
+\cs_new_protected:Npn \@@_debug_gset:
+ {
+ \cs_gset_protected:Npx \@@_debug:n ##1
+ { \bool_if:NT \g_@@_debug_bool {##1} }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+%
+%
+% \subsection{Borrowing from internals of other kernel modules}
+%
+%
+% \begin{macro}[EXP]{\@@_str_compare:nn}
+% Private copy of \cs{__str_if_eq:nn}
+% \begin{macrocode}
+\cs_new_eq:NN \@@_str_compare:nn \__str_if_eq:nn
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Declarations}
+%
+% \begin{macro}{\l_@@_tmpa_bool}
+% Scratch boolean used throughout the package.
+% \begin{macrocode}
+\bool_new:N \l_@@_tmpa_bool
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\l_@@_return_tl,\l_@@_tmpa_tl,\l_@@_tmpb_tl}
+% Scratch variables used throughout the package.
+% \begin{macrocode}
+\tl_new:N \l_@@_return_tl
+\tl_new:N \l_@@_tmpa_tl
+\tl_new:N \l_@@_tmpb_tl
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g_@@_all_seq}
+% In a few places we need a list of all hook names ever defined so
+% we keep track if them in this sequence.
+% \begin{macrocode}
+\seq_new:N \g_@@_all_seq
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g_@@_removal_list_prop}
+% A token list to hold delayed removals.
+% \begin{macrocode}
+\tl_new:N \g_@@_removal_list_tl
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\l_@@_cur_hook_tl}
+% Stores the name of the hook currently being sorted.
+% \begin{macrocode}
+\tl_new:N \l_@@_cur_hook_tl
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\l_@@_work_prop}
+% A property list holding a copy of the
+% \cs[no-index]{g_@@_\meta{hook}_code_prop} of the hook being sorted
+% to work on, so that changes don't act destructively on the hook data
+% structure.
+% \begin{macrocode}
+\prop_new:N \l_@@_work_prop
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g_@@_execute_immediately_prop}
+% List of hooks that from no on should not longer receive code.
+% \begin{macrocode}
+\prop_new:N \g_@@_execute_immediately_prop
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g_@@_used_prop}
+% All hooks that receive code (for use in debugging display).
+% \begin{macrocode}
+\prop_new:N \g_@@_used_prop
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g_@@_hook_curr_name_tl,\g_@@_name_stack_seq}
+% Default label used for hook commands, and a stack to keep track of
+% packages within packages.
+% \begin{macrocode}
+\tl_new:N \g_@@_hook_curr_name_tl
+\seq_new:N \g_@@_name_stack_seq
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_tmp:w}
+% Temporary macro for generic usage.
+% \begin{macrocode}
+\cs_new_eq:NN \@@_tmp:w ?
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\tl_gremove_once:Nx,\tl_show:x,\tl_log:x}
+% Some variants of \pkg{expl3} functions. \fmi{should be moved to expl3}
+% \begin{macrocode}
+\cs_generate_variant:Nn \tl_gremove_once:Nn { Nx }
+\cs_generate_variant:Nn \tl_show:n { x }
+\cs_generate_variant:Nn \tl_log:n { x }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\s_@@_mark}
+% Scan mark used for delimited arguments.
+% \begin{macrocode}
+\scan_new:N \s_@@_mark
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \subsection{Providing new hooks}
+%
+% \begin{macro}{\g_@@_..._code_prop,\@@~...,\@@_next~...}
+%
+% Hooks have a \meta{name} and for each hook we have to provide a number of
+% data structures. These are
+% \begin{description}
+% \item[\cs{g_@@_\meta{name}_code_prop}] A property list holding the code
+% for the hook in separate chunks. The keys are by default the
+% package names that add code to the hook, but it is possible
+% for packages to define other keys.
+%
+% \item[{\cs[no-index]{g_@@_\meta{name}_rule_\meta{label1}\string|\meta{label2}_tl}}]
+% A token list holding the relation between \meta{label1} and
+% \meta{label2} in the \meta{name}. The \meta{labels} are lexically
+% (reverse) sorted to ensure that two labels always point to the same
+% token list. For global rules, the \meta{name} is |??|.
+%
+% \item[\cs{@@~\meta{name}}] The code that is actually executed
+% when the hook is called in the document is stored in this token
+% list. It is constructed from the code chunks applying the
+% information.
+% This token list is named like that so that in case of an error
+% inside the hook, the reported token list in the error is shorter,
+% and to make it simpler to normalize hook names in
+% \cs{@@_make_name:n}.
+%
+% \item[\cs{g_@@_\meta{name}_reversed_tl}] Some hooks are
+% \enquote{reversed}. This token list stores a |-| for such hook
+% so that it can be identified. The |-| character is used because
+% $\meta{reversed}1$ is $+1$ for normal hooks and $-1$ for reversed
+% ones.
+%
+% \item[\cs{@@_next~\meta{name}}] Finally there is extra code
+% (normally empty) that is used on the next invocation of the hook
+% (and then deleted). This can be used to define some special
+% behavior for a single occasion from within the document. This token
+% list follows the same naming scheme than the main \cs{@@~\meta{name}}
+% token list. It is called \cs{@@_next~\meta{name}} rather than
+% \cs{@@~next_\meta{name}} because otherwise a hook whose name is
+% |next_|\meta{name} would clash with the next code-token list of the
+% hook called \meta{name}.
+%
+% \end{description}
+% \end{macro}
+%
+%
+%
+%
+% \begin{macro}{\hook_new:n}
+% The \cs{hook_new:n} declaration declare a new hook and expects
+% the hook \meta{name} as its argument, e.g.,
+% \hook{begindocument}.
+% \begin{macrocode}
+\cs_new_protected:Npn \hook_new:n #1
+ { \@@_normalize_hook_args:Nn \@@_new:n {#1} }
+\cs_new_protected:Npn \@@_new:n #1
+ {
+% \end{macrocode}
+% We check for one of the internal data structures and if it
+% already exists we complain.
+% \begin{macrocode}
+ \hook_if_exist:nTF {#1}
+ { \msg_error:nnn { hooks } { exists } {#1} }
+% \end{macrocode}
+% Otherwise we add the hook name to the list of all hooks and
+% allocate the necessary data structures for the new hook.
+% \begin{macrocode}
+ {
+ \seq_gput_right:Nn \g_@@_all_seq {#1}
+% \end{macrocode}
+% This is only used by the actual code of the current hook, so
+% declare it normally:
+% \begin{macrocode}
+ \tl_new:c { @@~#1 }
+% \end{macrocode}
+% Now ensure that the base data structure for the hook exists:
+% \begin{macrocode}
+ \@@_declare:n {#1}
+% \end{macrocode}
+% The \cs{g_@@_\meta{hook}_labels_clist} holds the sorted list of
+% labels (once it got sorted). This is used only for debugging.
+% \begin{macrocode}
+ \clist_new:c {g_@@_#1_labels_clist}
+% \end{macrocode}
+% Some hooks should reverse the default order of code chunks. To
+% signal this we have a token list which is empty for normal hooks
+% and contains a \verb=-= for reversed hooks.
+% \begin{macrocode}
+ \tl_new:c { g_@@_#1_reversed_tl }
+% \end{macrocode}
+% The above is all in L3 convention, but we also provide an
+% interface to legacy \LaTeXe{} hooks of the form \cs{@...hook},
+% e.g., \cs{@begindocumenthook}.
+% there have been a few of them and they have been added to
+% using \cs{g@addto@macro}. If there exists such a macro matching
+% the name of the new hook, i.e.,
+% \verb+\@+\meta{hook-name}\texttt{hook} and it is not empty then
+% we add its contents as a code chunk under the label \texttt{legacy}.
+% \begin{quote}
+% \textbf{Warning: this support will vanish in future releases!}
+% \end{quote}
+%
+% \begin{macrocode}
+ \@@_include_legacy_code_chunk:n {#1}
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \begin{macro}{\@@_declare:n}
+% This function declares the basic data structures for a hook without
+% actually declaring the hook itself. This is needed to allow adding
+% to undeclared hooks. Here it is unnecessary to check whether both
+% variables exist, since both are declared at the same time (either
+% both exist, or neither).
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_declare:n #1
+ {
+ \@@_if_exist:nF {#1}
+ {
+ \prop_new:c { g_@@_#1_code_prop }
+ \tl_new:c { @@_next~#1 }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \begin{macro}{\hook_new_reversed:n,\@@_new_reversed:n}
+%
+% Declare a new hook. The default ordering of code chunks is
+% reversed, signaled by setting the token list to a minus sign.
+% \begin{macrocode}
+\cs_new_protected:Npn \hook_new_reversed:n #1
+ { \@@_normalize_hook_args:Nn \@@_new_reversed:n {#1} }
+\cs_new_protected:Npn \@@_new_reversed:n #1
+ {
+ \@@_new:n {#1}
+% \end{macrocode}
+% If the hook already exists the above will generate an error
+% message, so the next line should be executed (but it is --- too
+% bad).
+% \begin{macrocode}
+ \tl_gset:cn { g_@@_#1_reversed_tl } { - }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hook_new_pair:nn}
+% A shorthand for declaring a normal and a (matching) reversed hook in one go.
+% \begin{macrocode}
+\cs_new_protected:Npn \hook_new_pair:nn #1#2
+ { \hook_new:n {#1} \hook_new_reversed:n {#2} }
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \begin{macro}{\@@_include_legacy_code_chunk:n}
+% The \LaTeX{} legacy concept for hooks uses with hooks the
+% following naming scheme in the code: \cs{@...hook}.
+%
+% If this macro is not empty we add it under the label
+% \texttt{legacy} to the current hook and then empty it globally.
+% This way packages or classes directly manipulating commands such
+% as \cs{@begindocumenthook} still get their hook data added.
+% \begin{quote}
+% \textbf{Warning: this support will vanish in future releases!}
+% \end{quote}
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_include_legacy_code_chunk:n #1
+ {
+% \end{macrocode}
+% If the \pkg{expl3} code is run with checking on then assigning or
+% using non L3 names such as \cs{@enddocumenthook} with \pkg{expl3}
+% functions will trigger warnings so we run this code with
+% debugging explicitly suspended.
+% \begin{macrocode}
+ \debug_suspend:
+% \end{macrocode}
+% If the macro doesn't exist (which is the usual case) then nothing
+% needs to be done.
+% \begin{macrocode}
+ \tl_if_exist:cT { @#1hook }
+% \end{macrocode}
+% Of course if the legacy hook exists but is empty, there is no need
+% to add anything under \texttt{legacy} the legacy label.
+% \begin{macrocode}
+ {
+ \tl_if_empty:cF { @#1hook }
+ {
+ \exp_args:Nnnv \@@_hook_gput_code_do:nnn {#1}
+ { legacy } { @#1hook }
+% \end{macrocode}
+% Once added to the hook, we need to clear it otherwise it might
+% get added again later if the hook data gets updated.
+% \begin{macrocode}
+ \tl_gclear:c { @#1hook }
+ }
+ }
+ \debug_resume:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \subsection{Parsing a label}
+%
+% \begin{macro}[EXP]{\@@_parse_label_default:n}
+% This macro checks if a label was given (not \cs{c_novalue_tl}), and
+% if so, tries to parse the label looking for a leading \verb|.| to
+% replace for \cs{@currname}. Otherwise \cs{@@_currname_or_default:n}
+% is used to pick \cs{@currname} or the fallback value.
+%
+% The default |top-level| is hard-coded here. It once was an
+% argument, but it's no longer needed. \pho{can't remember why}
+% \begin{macrocode}
+\cs_new:Npn \@@_parse_label_default:n #1
+ {
+ \tl_if_novalue:nTF {#1}
+ { \@@_currname_or_default:n { top-level } }
+ {
+ \tl_trim_spaces_apply:nN {#1}
+ \@@_parse_dot_label:nn { top-level }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_parse_dot_label:nn}
+% \begin{macro}[EXP]{
+% \@@_parse_dot_label:nw,
+% \@@_parse_dot_label_cleanup:w,
+% \@@_parse_dot_label_aux:nw
+% }
+% Start by checking if the label is empty, which raises an error, and
+% uses the fallback value. If not,
+% split the label at a \verb|./|, if any, and check if no tokens are
+% before the \verb|./|, or if the only character is a \verb|.|.
+% If these requirements are fulfilled, the leading
+% \verb|.| is replaced with \cs{@@_currname_or_default:n}. Otherwise
+% the label is returned unchanged.
+% \begin{macrocode}
+\cs_new:Npn \@@_parse_dot_label:nn #1 #2
+ {
+ \tl_if_empty:nTF {#1}
+ {
+ \msg_expandable_error:nnn { hooks } { empty-label } {#2}
+ #2
+ }
+ {
+ \str_if_eq:nnTF {#1} { . }
+ { \@@_currname_or_default:n {#2} }
+ { \@@_parse_dot_label:nw {#2} #1 ./ \s_@@_mark }
+ }
+ }
+\cs_new:Npn \@@_parse_dot_label:nw #1 #2 ./ #3 \s_@@_mark
+ {
+ \tl_if_empty:nTF {#2}
+ { \@@_parse_dot_label_aux:nw {#1} #3 \s_@@_mark }
+ {
+ \tl_if_empty:nTF {#3}
+ { \@@_make_name:n {#2} }
+ { \@@_parse_dot_label_cleanup:w #2 ./ #3 \s_@@_mark }
+ }
+ }
+\cs_new:Npn \@@_parse_dot_label_cleanup:w #1 ./ \s_@@_mark {#1}
+\cs_new:Npn \@@_parse_dot_label_aux:nw #1 #2 ./ \s_@@_mark
+ { \@@_currname_or_default:n {#1} / \@@_make_name:n {#2} }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_currname_or_default:n}
+% Uses \cs{g_@@_hook_curr_name_tl} if it is set, otherwise tries
+% \cs{@currname}. If neither is set, uses the fallback value
+% \verb|#1| (usually \texttt{top-level}).
+% \begin{macrocode}
+\cs_new:Npn \@@_currname_or_default:n #1
+ {
+ \tl_if_empty:NTF \g_@@_hook_curr_name_tl
+ {
+ \tl_if_empty:NTF \@currname
+ { \@@_make_name:n {#1} }
+ { \@currname }
+ }
+ { \g_@@_hook_curr_name_tl }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_make_name:n,\@@_make_name:w}
+% Provides a standard sanitisation of a hook's name.
+% It uses \cs{cs:w} to build a control sequence out of the hook name,
+% then uses \cs{cs_to_str:N} to get the string representation of that,
+% without the escape character. \cs{cs:w}-based expansion is used
+% instead of |e|-based because Unicode characters don't behave well
+% inside \cs{expanded}. The macro adds the \cs{@@~} prefix to the
+% hook name to reuse the hook's code token list to build the csname
+% and avoid leaving \enquote{public} control sequences defined
+% (as~\cs{relax}) in TeX's memory.
+% \begin{macrocode}
+\cs_new:Npn \@@_make_name:n #1
+ {
+ \exp_after:wN \exp_after:wN \exp_after:wN \@@_make_name:w
+ \exp_after:wN \token_to_str:N \cs:w @@~ #1 \cs_end:
+ }
+\exp_last_unbraced:NNNNo
+\cs_new:Npn \@@_make_name:w #1 \tl_to_str:n { @@~ } { }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_normalize_hook_args:Nn}
+% \begin{macro}{\@@_normalize_hook_args:Nnn}
+% \begin{macro}{\@@_normalize_hook_rule_args:Nnnnn}
+% \begin{macro}{\@@_normalize_hook_args_aux:Nn}
+% Standard route for normalising hook and label arguments. The main
+% macro does the entire operation within a group so that csnames made
+% by \cs{@@_make_name:n} are wiped off before continuing. This means
+% that this function cannot be used for \cs{hook_use:n}!
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_normalize_hook_args_aux:Nn #1 #2
+ {
+ \group_begin:
+ \use:e
+ {
+ \group_end:
+ \exp_not:N #1 #2
+ }
+ }
+\cs_new_protected:Npn \@@_normalize_hook_args:Nn #1 #2
+ {
+ \@@_normalize_hook_args_aux:Nn #1
+ { { \@@_parse_label_default:n {#2} } }
+ }
+\cs_new_protected:Npn \@@_normalize_hook_args:Nnn #1 #2 #3
+ {
+ \@@_normalize_hook_args_aux:Nn #1
+ {
+ { \@@_parse_label_default:n {#2} }
+ { \@@_parse_label_default:n {#3} }
+ }
+ }
+\cs_new_protected:Npn \@@_normalize_hook_rule_args:Nnnnn #1 #2 #3 #4 #5
+ {
+ \@@_normalize_hook_args_aux:Nn #1
+ {
+ { \@@_parse_label_default:n {#2} }
+ { \@@_parse_label_default:n {#3} }
+ { \tl_trim_spaces:n {#4} }
+ { \@@_parse_label_default:n {#5} }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+%
+% \begin{macro}{\hook_gput_code:nnn}
+% \begin{macro}{\@@_gput_code:nnn,\@@_gput_code:nxv,\@@_hook_gput_code_do:nnn}
+%
+% With \cs{hook_gput_code:nnn}\Arg{hook}\Arg{label}\Arg{code} a
+% chunk of \meta{code} is added to an existing \meta{hook} labeled
+% with \meta{label}.
+% \begin{macrocode}
+\cs_new_protected:Npn \hook_gput_code:nnn #1 #2
+ { \@@_normalize_hook_args:Nnn \@@_gput_code:nnn {#1} {#2} }
+\cs_new_protected:Npn \@@_gput_code:nnn #1 #2 #3
+ {
+% \end{macrocode}
+% First check if the hook was used as a one-time hook:
+% \begin{macrocode}
+ \prop_if_in:NnTF \g_@@_execute_immediately_prop {#1}
+ {#3}
+ {
+% \end{macrocode}
+% Then check if the current \meta{hook}/\meta{label} pair was marked
+% for removal, in which case \cs{@@_unmark_removal:nn} is used to
+% remove that mark (once). This may happen when a package removes
+% code from another package which was not yet loaded: the removal
+% order is stored, and at this stage it is executed by not adding to
+% the hook.
+% \begin{macrocode}
+ \@@_if_marked_removal:nnTF {#1} {#2}
+ { \@@_unmark_removal:nn {#1} {#2} }
+ {
+% \end{macrocode}
+% If no removal is queued, we are free to add. Start by checking if
+% the hook exists.
+% \begin{macrocode}
+ \hook_if_exist:nTF {#1}
+% \end{macrocode}
+% If so we simply add (or append) the new code to the property list
+% holding different chunks for the hook. At \verb=\begin{document}=
+% this is then sorted into a token list for fast execution.
+% \begin{macrocode}
+ {
+ \@@_hook_gput_code_do:nnn {#1} {#2} {#3}
+% \end{macrocode}
+% However, if there is an update within the document we need to alter
+% this execution code which is done by
+% \cs{@@_update_hook_code:n}. In the preamble this does nothing.
+% \begin{macrocode}
+ \@@_update_hook_code:n {#1}
+ }
+% \end{macrocode}
+%
+% If the hook does not exist, however, before giving up try to
+% declare it as a generic hook, if its name matches one of the valid
+% patterns.
+% \begin{macrocode}
+ { \@@_try_declaring_generic_hook:nnn {#1} {#2} {#3} }
+ }
+ }
+ }
+\cs_generate_variant:Nn \@@_gput_code:nnn { nxv }
+% \end{macrocode}
+%
+% This macro will unconditionally add a chunk of code to the given hook.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_hook_gput_code_do:nnn #1 #2 #3
+ {
+% \end{macrocode}
+% However, first some debugging info if debugging is enabled:
+% \begin{macrocode}
+ \@@_debug:n{\iow_term:x{****~ Add~ to~
+ \hook_if_exist:nF {#1} { undeclared~ }
+ hook~ #1~ (#2)
+ \on@line\space <-~ \tl_to_str:n{#3}} }
+% \end{macrocode}
+% Then try to get the code chunk labeled \verb=#2= from the hook.
+% If there's code already there, then append \verb=#3= to that,
+% otherwise just put \verb=#3=.
+% \begin{macrocode}
+ \prop_get:cnNTF { g_@@_#1_code_prop } {#2} \l_@@_return_tl
+ {
+ \prop_gput:cno { g_@@_#1_code_prop } {#2}
+ { \l_@@_return_tl #3 }
+ }
+ { \prop_gput:cnn { g_@@_#1_code_prop } {#2} {#3} }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_gput_undeclared_hook:nnn}
+% Often it may happen that a package $A$ defines a hook \verb=foo=,
+% but package $B$, that adds code to that hook, is loaded before $A$.
+% In such case we need to add code to the hook before its declared.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_gput_undeclared_hook:nnn #1 #2 #3
+ {
+ \@@_declare:n {#1}
+ \@@_hook_gput_code_do:nnn {#1} {#2} {#3}
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_try_declaring_generic_hook:nnn}
+% \begin{macro}{\@@_try_declaring_generic_next_hook:nn}
+% These entry-level macros just pass the arguments along to the
+% common \cs{@@_try_declaring_generic_hook:nNNnn} with the right
+% functions to execute when some action is to be taken.
+%
+% The wrapper \cs{@@_try_declaring_generic_hook:nnn} then defers
+% \cs{hook_gput_code:nnn} if the generic hook was declared, or to
+% \cs{@@_gput_undeclared_hook:nnn} otherwise (the hook was tested for
+% existence before, so at this point if it isn't generic, it doesn't
+% exist).
+%
+% The wrapper \cs{@@_try_declaring_generic_next_hook:nn} for
+% next-execution hooks does the same: it defers the code to
+% \cs{hook_gput_next_code:nn} if the generic hook was declared, or
+% to \cs{@@_gput_next_do:nn} otherwise.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_try_declaring_generic_hook:nnn #1
+ {
+ \@@_try_declaring_generic_hook:nNNnn {#1}
+ \hook_gput_code:nnn \@@_gput_undeclared_hook:nnn
+ }
+\cs_new_protected:Npn \@@_try_declaring_generic_next_hook:nn #1
+ {
+ \@@_try_declaring_generic_hook:nNNnn {#1}
+ \hook_gput_next_code:nn \@@_gput_next_do:nn
+ }
+% \end{macrocode}
+%
+% \begin{macro}{
+% \@@_try_declaring_generic_hook:nNNnn,
+% \@@_try_declaring_generic_hook_split:nNNnn
+% }
+% \begin{macro}[TF]{\@@_try_declaring_generic_hook:wn}
+% \cs{@@_try_declaring_generic_hook:nNNnn} now splits the hook name
+% at the first \texttt{/} (if any) and first checks if it is a
+% file-specific hook (they require some normalization) using
+% \cs{@@_if_file_hook:wTF}. If not then check it is one of a
+% predefined set for generic names. We also split off the second
+% component to see if we have to make a reversed hook. In either case
+% the function returns \meta{true} for a generic hook and \meta{false}
+% in other cases.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_try_declaring_generic_hook:nNNnn #1
+ {
+ \@@_if_file_hook:wTF #1 / / \s_@@_mark
+ {
+ \exp_args:Ne \@@_try_declaring_generic_hook_split:nNNnn
+ { \exp_args:Ne \@@_file_hook_normalize:n {#1} }
+ }
+ { \@@_try_declaring_generic_hook_split:nNNnn {#1} }
+ }
+\cs_new_protected:Npn \@@_try_declaring_generic_hook_split:nNNnn #1 #2 #3
+ {
+ \@@_try_declaring_generic_hook:wnTF #1 / / / \scan_stop: {#1}
+ { #2 }
+ { #3 } {#1}
+ }
+\prg_new_protected_conditional:Npnn \@@_try_declaring_generic_hook:wn
+ #1 / #2 / #3 / #4 \scan_stop: #5 { TF }
+ {
+ \tl_if_empty:nTF {#2}
+ { \prg_return_false: }
+ {
+ \prop_if_in:NnTF \c_@@_generics_prop {#1}
+ {
+ \hook_if_exist:nF {#5} { \hook_new:n {#5} }
+% \end{macrocode}
+% After having declared the hook we check the second component (for
+% file hooks) or the third component for environment hooks) and
+% if it is on the list of components for which we should have declared
+% a reversed hook we alter the hook data structure accordingly.
+% \begin{macrocode}
+ \prop_if_in:NnTF \c_@@_generics_reversed_ii_prop {#2}
+ { \tl_gset:cn { g_@@_#5_reversed_tl } { - } }
+ {
+ \prop_if_in:NnT \c_@@_generics_reversed_iii_prop {#3}
+ { \tl_gset:cn { g_@@_#5_reversed_tl } { - } }
+ }
+% \end{macrocode}
+% Now that we know that the hook is declared we can add the code to it.
+% \begin{macrocode}
+ \prg_return_true:
+ }
+ { \prg_return_false: }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[pTF]{\@@_if_file_hook:w}
+% \cs{@@_if_file_hook:wTF} checks if the argument is a valid
+% file-specific hook (not, for example, |file/before|, but
+% |file/before/foo.tex|). If it is a file-specific hook, then it
+% executes the \meta{true} branch, otherwise \meta{false}.
+%
+% A file-specific hook is \texttt{file/\meta{position}/\meta{name}}.
+% If any of these parts don't exist, it is a general file hook or not
+% a file hook at all, so the conditional evaluates to \meta{false}.
+% Otherwise, it checks that the first part is |file| and that the
+% \meta{position} is in the \cs{c_@@_generics_file_prop}.
+%
+% A property list is used here to avoid having to worry with catcodes,
+% because \pkg{expl3}'s file name parsing turns all characters into
+% catcode-12 tokens, which might differ from hand-input letters.
+% \begin{macrocode}
+\prg_new_conditional:Npnn \@@_if_file_hook:w
+ #1 / #2 / #3 \s_@@_mark { TF }
+ {
+ \str_if_eq:nnTF {#1} { file }
+ {
+ \bool_lazy_or:nnTF
+ { \tl_if_empty_p:n {#3} }
+ { \str_if_eq_p:nn {#3} { / } }
+ { \prg_return_false: }
+ {
+ \prop_if_in:NnTF \c_@@_generics_file_prop {#2}
+ { \prg_return_true: }
+ { \prg_return_false: }
+ }
+ }
+ { \prg_return_false: }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_file_hook_normalize:n}
+% \begin{macro}[EXP]{\@@_strip_double_slash:n,\@@_strip_double_slash:w}
+% When a file-specific hook is found, before being declared it is
+% lightly normalized by \cs{@@_file_hook_normalize:n}. The current
+% implementation just replaces two consecutive slashes (|//|) by a
+% single one, to cope with simple cases where the user did something
+% like \verb|\def\input@path{{./mypath/}}|, in which case a hook would
+% have to be \verb|\AddToHook{file/after/./mypath//file.tex}|.
+% \begin{macrocode}
+\cs_new:Npn \@@_file_hook_normalize:n #1
+ { \@@_strip_double_slash:n {#1} }
+\cs_new:Npn \@@_strip_double_slash:n #1
+ { \@@_strip_double_slash:w #1 // \s_@@_mark }
+\cs_new:Npn \@@_strip_double_slash:w #1 // #2 \s_@@_mark
+ {
+ \tl_if_empty:nTF {#2}
+ {#1}
+ { \@@_strip_double_slash:w #1 / #2 \s_@@_mark }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\c_@@_generics_prop}
+% Property list holding the generic names. We don't provide any user
+% interface to this as this is meant to be static.
+% \begin{description}
+% \item[\texttt{env}]
+% The generic hooks used in \cs{begin} and \cs{end}.
+% \item[\texttt{file}]
+% The generic hooks used when loading a file
+% \end{description}
+% \begin{macrocode}
+\prop_const_from_keyval:Nn \c_@@_generics_prop
+ {env=,file=,package=,class=,include=}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\c_@@_generics_reversed_ii_prop,
+% \c_@@_generics_reversed_iii_prop,
+% \c_@@_generics_file_prop}
+% Some of the generic hooks are supposed to use reverse ordering, these are
+% the following (only the second or third sub-component is checked):
+% \begin{macrocode}
+\prop_const_from_keyval:Nn \c_@@_generics_reversed_ii_prop {after=,end=}
+\prop_const_from_keyval:Nn \c_@@_generics_reversed_iii_prop {after=}
+\prop_const_from_keyval:Nn \c_@@_generics_file_prop {before=,after=}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hook_gremove_code:nn}
+% \begin{macro}{\@@_gremove_code:nn}
+%
+% With \cs{hook_gremove_code:nn}\Arg{hook}\Arg{label} any code
+% for \meta{hook} stored under \meta{label} is removed.
+% \begin{macrocode}
+\cs_new_protected:Npn \hook_gremove_code:nn #1 #2
+ { \@@_normalize_hook_args:Nnn \@@_gremove_code:nn {#1} {#2} }
+\cs_new_protected:Npn \@@_gremove_code:nn #1 #2
+ {
+% \end{macrocode}
+% First check that the hook code pool exists. \cs{hook_if_exist:nTF}
+% isn't used here because it should be possible to remove code from a
+% hook before its defined (see section~\ref{sec:querying}).
+% \begin{macrocode}
+ \@@_if_exist:nTF {#1}
+% \end{macrocode}
+% Then remove the chunk and run \cs{@@_update_hook_code:n} so
+% that the execution token list reflects the change if we are after
+% \verb=\begin{document}=.
+% \begin{macrocode}
+ {
+ \str_if_eq:nnTF {#2} {*}
+ { \prop_gclear:c { g_@@_#1_code_prop } }
+ {
+% \end{macrocode}
+% Check if the label being removed exists in the code pool. If it does,
+% just call \cs{@@_gremove_code_do:nn} to do the removal, otherwise mark it
+% to be removed.
+% \begin{macrocode}
+ \prop_get:cnNTF { g_@@_#1_code_prop } {#2} \l_@@_return_tl
+ { \@@_gremove_code_do:nn }
+ { \@@_mark_removal:nn }
+ {#1} {#2}
+ }
+% \end{macrocode}
+% Finally update the code, if the hook exists.
+% \begin{macrocode}
+ \hook_if_exist:nT {#1}
+ { \@@_update_hook_code:n {#1} }
+ }
+% \end{macrocode}
+%
+% If the code pool for this hook doesn't exist it means that nothing
+% tried to add to it before, so we just queue this removal order for
+% later.
+% \begin{macrocode}
+ { \@@_mark_removal:nn {#1} {#2} }
+ }
+% \end{macrocode}
+%
+% \begin{macro}{\@@_gremove_code_do:nn}
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_gremove_code_do:nn #1 #2
+ { \prop_gremove:cn { g_@@_#1_code_prop } {#2} }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_mark_removal:nn}
+% Marks \meta{label} (\verb=#2=) to be removed from \meta{hook}
+% (\verb=#1=). The number of removals should be fairly small, and
+% \cs{tl_gremove_once:Nx} is fairly efficient even for longer token
+% lists, so we use a single global token list, rather than one for
+% each hook.
+%
+% A hand-crafted token list is used here because property lists don't
+% hold repeated items, so multiple usages of \cs{@@_mark_removal:nn}
+% would be cancelled by a single \cs{@@_unmark_removal:nn}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_mark_removal:nn #1 #2
+ {
+ \tl_gput_right:Nx \g_@@_removal_list_tl
+ { \@@_removal_tl:nn {#1} {#2} }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_unmark_removal:nn}
+% Unmarks \meta{label} (\verb=#2=) to be removed from \meta{hook}
+% (\verb=#1=). \cs{tl_gremove_once:Nx} is used rather than
+% \cs{tl_gremove_all:Nx} so that two additions are needed to cancel
+% two marked removals, rather than only one.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_unmark_removal:nn #1 #2
+ {
+ \tl_gremove_once:Nx \g_@@_removal_list_tl
+ { \@@_removal_tl:nn {#1} {#2} }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[TF]{\@@_if_marked_removal:nn}
+% Checks if the \cs{g_@@_removal_list_tl} contains the current
+% \meta{label} (\verb=#2=) and \meta{hook} (\verb=#1=).
+% \begin{macrocode}
+\prg_new_protected_conditional:Npnn \@@_if_marked_removal:nn #1 #2 { TF }
+ {
+ \exp_args:NNx \tl_if_in:NnTF \g_@@_removal_list_tl
+ { \@@_removal_tl:nn {#1} {#2} }
+ { \prg_return_true: } { \prg_return_false: }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[rEXP]{\@@_removal_tl:nn}
+% Builds a token list with \verb=#1= and \verb=#2= which can only be
+% matched by \verb=#1= and \verb=#2=. The |&|$_4$ anchors a removal,
+% so that \verb=#1= can't be mistaken by \verb=#2= and vice versa, and
+% the two |$|$_3$ delimit the two arguments
+% \begin{macrocode}
+\cs_new:Npn \@@_removal_tl:nn #1 #2
+ { & \tl_to_str:n {#2} $ \tl_to_str:n {#1} $ }
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \begin{macro}{
+% \g_@@_??_code_prop,
+% \@@~??,
+% \g_@@_??_reversed_tl,
+% }
+%
+% Initially these variables simply used an empty ``label'' name (not
+% two question marks). This was a bit unfortunate, because then
+% \texttt{l3doc} complains about \verb=__= in the middle of a
+% command name when trying to typeset the documentation. However
+% using a ``normal'' name such as \texttt{default} has the
+% disadvantage of that being not really distinguishable from a real
+% hook name. I now have settled for \texttt{??} which needs some
+% gymnastics to get it into the csname, but since this is used a
+% lot things should be fast, so this is not done with \texttt{c}
+% expansion in the code later on.
+%
+% \cs{@@~??} isn't used, but it has to be defined to trick
+% the code into thinking that \verb=??= is actually a hook.
+% \begin{macrocode}
+\prop_new:c {g_@@_??_code_prop}
+\prop_new:c {@@~??}
+% \end{macrocode}
+%
+% Default rules are always given in normal ordering (never in
+% reversed ordering). If such a rule is applied to a reversed
+% hook it behaves as if the rule is reversed (e.g.,
+% \texttt{after} becomes \texttt{before})
+% because those rules are applied first and then the order is reversed.
+% \begin{macrocode}
+\tl_new:c {g_@@_??_reversed_tl}
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Setting rules for hooks code}
+%
+% \begin{macro}{\hook_gset_rule:nnnn}
+% \begin{macro}{\@@_gset_rule:nnnn}
+%
+% \fmi{needs docu correction given new implementation}
+%
+% With
+% \cs{hook_gset_rule:nnnn}\Arg{hook}\Arg{label1}\Arg{relation}\Arg{label2}
+% a relation is defined between the two code labels for the given
+% \meta{hook}. The special hook \texttt{??} stands for \emph{any}
+% hook describing a default rule.
+% \begin{macrocode}
+\cs_new_protected:Npn \hook_gset_rule:nnnn #1#2#3#4
+ {
+ \@@_normalize_hook_rule_args:Nnnnn \@@_gset_rule:nnnn
+ {#1} {#2} {#3} {#4}
+ }
+% \end{macrocode}
+%
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_gset_rule:nnnn #1#2#3#4
+ {
+% \end{macrocode}
+% First we ensure the basic data structure of the hook exists:
+% \begin{macrocode}
+ \@@_declare:n {#1}
+% \end{macrocode}
+% Then we clear any previous relationship between both labels.
+% \begin{macrocode}
+ \@@_rule_gclear:nnn {#1} {#2} {#4}
+% \end{macrocode}
+% Then we call the function to handle the given rule. Throw an error if the
+% rule is invalid.
+% \begin{macrocode}
+ \debug_suspend:
+ \cs_if_exist_use:cTF { @@_rule_#3_gset:nnn }
+ {
+ {#1} {#2} {#4}
+ \@@_update_hook_code:n {#1}
+ }
+ { \msg_error:nnnnnn { hooks } { unknown-rule }
+ {#1} {#2} {#3} {#4} }
+ \debug_resume:
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_rule_before_gset:nnn, \@@_rule_after_gset:nnn,
+% \@@_rule_<_gset:nnn, \@@_rule_>_gset:nnn}
+% Then we add the new rule. We need to normalize the rules here to
+% allow for faster processing later. Given a pair of labels
+% $l_A$ and $l_B$, the rule $l_A>l_B$ is the same as $l_B<l_A$
+% only presented differently. But by normalizing the
+% forms of the rule to a single representation, say, $l_B<l_A$, reduces
+% the time spent looking for the rules later considerably.
+%
+% Here we do that normalization by using \cs[no-index]{(pdf)strcmp} to
+% lexically sort labels $l_A$ and $l_B$ to a fixed order. This order
+% is then enforced every time these two labels are used together.
+%
+% Here we use \cs{@@_label_pair:nn}~\Arg{hook}~\Arg{l_A}~\Arg{l_B}
+% to build a string \texttt{$l_B$\string|$l_A$} with a fixed order, and
+% use \cs{@@_label_ordered:nnTF} to apply the correct rule to the pair
+% of labels, depending if it was sorted or not.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_rule_before_gset:nnn #1#2#3
+ {
+ \tl_gset:cx { g_@@_#1_rule_ \@@_label_pair:nn {#2} {#3} _tl }
+ { \@@_label_ordered:nnTF {#2} {#3} { < } { > } }
+ }
+\cs_new_eq:cN { @@_rule_<_gset:nnn } \@@_rule_before_gset:nnn
+% \end{macrocode}
+%
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_rule_after_gset:nnn #1#2#3
+ {
+ \tl_gset:cx { g_@@_#1_rule_ \@@_label_pair:nn {#3} {#2} _tl }
+ { \@@_label_ordered:nnTF {#3} {#2} { < } { > } }
+ }
+\cs_new_eq:cN { @@_rule_>_gset:nnn } \@@_rule_after_gset:nnn
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_rule_voids_gset:nnn}
+% This rule removes (clears, actually) the code from label |#3| if
+% label |#2| is in the hook |#1|.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_rule_voids_gset:nnn #1#2#3
+ {
+ \tl_gset:cx { g_@@_#1_rule_ \@@_label_pair:nn {#2} {#3} _tl }
+ { \@@_label_ordered:nnTF {#2} {#3} { -> } { <- } }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{
+% \@@_rule_incompatible-error_gset:nnn,
+% \@@_rule_incompatible-warning_gset:nnn,
+% }
+% These relations make an error/warning if labels |#2| and |#3| appear
+% together in hook |#1|.
+% \begin{macrocode}
+\cs_new_protected:cpn { @@_rule_incompatible-error_gset:nnn } #1#2#3
+ { \tl_gset:cn { g_@@_#1_rule_ \@@_label_pair:nn {#2} {#3} _tl } { xE } }
+\cs_new_protected:cpn { @@_rule_incompatible-warning_gset:nnn } #1#2#3
+ { \tl_gset:cn { g_@@_#1_rule_ \@@_label_pair:nn {#2} {#3} _tl } { xW } }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_rule_unrelated_gset:nnn, \@@_rule_gclear:nnn}
+% Undo a setting. \cs{@@_rule_unrelated_gset:nnn} doesn't need to do anything,
+% since we use \cs{@@_rule_gclear:nnn} before setting any rule.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_rule_unrelated_gset:nnn #1#2#3 { }
+\cs_new_protected:Npn \@@_rule_gclear:nnn #1#2#3
+ { \cs_undefine:c { g_@@_#1_rule_ \@@_label_pair:nn {#2} {#3} _tl } }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_label_pair:nn}
+% Ensure that the lexically greater label comes first.
+% \begin{macrocode}
+\cs_new:Npn \@@_label_pair:nn #1#2
+ {
+ \if_case:w \@@_str_compare:nn {#1} {#2} \exp_stop_f:
+ #1 | #1 % 0
+ \or: #1 | #2 % +1
+ \else: #2 | #1 % -1
+ \fi:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[pTF]{\@@_label_ordered:nn}
+% Check that labels |#1| and |#2| are in the correct order (as
+% returned by \cs{@@_label_pair:nn}) and if so return true, else
+% return false.
+% \begin{macrocode}
+\prg_new_conditional:Npnn \@@_label_ordered:nn #1#2 { TF }
+ {
+ \if_int_compare:w \@@_str_compare:nn {#1} {#2} > 0 \exp_stop_f:
+ \prg_return_true:
+ \else
+ \prg_return_false:
+ \fi:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_if_label_case:nnnnn}
+% To avoid doing the string comparison twice in \cs{@@_initialize_single:NNNn}
+% (once with \cs{str_if_eq:nn} and again with \cs{@@_label_ordered:nn}),
+% we use a three-way branching macro that will compare |#1| and |#2|
+% and expand to \cs{use_i:nnn} if they are equal, \cs{use_ii:nn} if
+% |#1| is lexically greater, and \cs{use_iii:nn} otherwise.
+% \begin{macrocode}
+\cs_new:Npn \@@_if_label_case:nnnnn #1#2
+ {
+ \cs:w use_
+ \if_case:w \@@_str_compare:nn {#1} {#2}
+ i \or: ii \else: iii \fi: :nnn
+ \cs_end:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_update_hook_code:n}
+% Before \verb=\begin{document}= this does nothing, in the body it
+% reinitializes the hook code using the altered data.
+% \begin{macrocode}
+\cs_new_eq:NN \@@_update_hook_code:n \use_none:n
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_initialize_all:}
+% Initialize all known hooks (at \verb=\begin{document}=), i.e.,
+% update the fast execution token lists to hold the necessary code
+% in the right order.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_initialize_all: {
+% \end{macrocode}
+% First we change \cs{@@_update_hook_code:n} which so far was a
+% no-op to now initialize one hook. This way any later updates to
+% the hook will run that code and also update the execution token
+% list.
+% \begin{macrocode}
+ \cs_gset_eq:NN \@@_update_hook_code:n \@@_initialize_hook_code:n
+% \end{macrocode}
+% Now we loop over all hooks that have been defined and update each
+% of them.
+% \begin{macrocode}
+ \@@_debug:n { \prop_gclear:N \g_@@_used_prop }
+ \seq_map_inline:Nn \g_@@_all_seq
+ {
+ \@@_update_hook_code:n {##1}
+ }
+% \end{macrocode}
+% If we are debugging we show results hook by hook for all hooks
+% that have data.
+% \begin{macrocode}
+ \@@_debug:n
+ { \iow_term:x{^^JAll~ initialized~ (non-empty)~ hooks:}
+ \prop_map_inline:Nn \g_@@_used_prop
+ { \iow_term:x{^^J~ ##1~ ->~
+ \exp_not:v {@@~##1}~ }
+ }
+ }
+% \end{macrocode}
+% After all hooks are initialized we change the ``use'' to just
+% call the hook code and not initialize it (as it was done in the
+% preamble.
+% \begin{macrocode}
+ \cs_gset_eq:NN \hook_use:n \@@_use_initialized:n
+ \cs_gset_eq:NN \@@_preamble_hook:n \use_none:n
+}
+% \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \begin{macro}{\@@_initialize_hook_code:n}
+% Initializing or reinitializing the fast execution hook code. In
+% the preamble this is selectively done in case a hook gets used
+% and at \verb=\begin{document}= this is done for all hooks and
+% afterwards only if the hook code changes.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_initialize_hook_code:n #1
+ {
+ \debug_suspend:
+ \@@_debug:n{ \iow_term:x{^^JUpdate~ code~ for~ hook~
+ '#1' \on@line :^^J} }
+% \end{macrocode}
+% This does the sorting and the updates.
+% First thing we do is to check if a legacy hook macro exists and
+% if so we add it to the hook under the label \texttt{legacy}. This
+% might make the hook non-empty so we have to do this before
+% the then following test.
+% \begin{macrocode}
+ \@@_include_legacy_code_chunk:n {#1}
+% \end{macrocode}
+% If there aren't any code
+% chunks for the current hook, there is no point in even starting
+% the sorting routine so we make a quick test for that and in that
+% case just update \cs{@@~\meta{hook}} to hold the next
+% code. If there are code chunks we call
+% \cs{@@_initialize_single:NNNn} and pass to it ready made csnames
+% as they are needed several times inside. This way we save a bit
+% on processing time if we do that up front.
+% \begin{macrocode}
+ \hook_if_exist:nT {#1}
+ {
+ \prop_if_empty:cTF {g_@@_#1_code_prop}
+ { \tl_gset:co { @@~#1 }
+ { \cs:w @@_next~#1 \cs_end: } }
+ {
+% \end{macrocode}
+% By default the algorithm sorts the code chunks and then saves the
+% result in a token list for fast execution by adding the code one
+% after another using \cs{tl_gput_right:NV}. When we sort code for
+% a reversed hook, all we have to do is to add the code chunks in
+% the opposite order into the token list. So all we have to do
+% in preparation is to change two definitions used later on.
+% \begin{macrocode}
+ \@@_if_reversed:nTF {#1}
+ { \cs_set_eq:NN \@@_tl_gput:NV \tl_gput_left:NV
+ \cs_set_eq:NN \@@_clist_gput:NV \clist_gput_left:NV }
+ { \cs_set_eq:NN \@@_tl_gput:NV \tl_gput_right:NV
+ \cs_set_eq:NN \@@_clist_gput:NV \clist_gput_right:NV }
+% \end{macrocode}
+%
+% When sorting, some relations (namely \verb|voids|) need to
+% act destructively on the code property lists to remove code that
+% shouldn't appear in the sorted hook token list, so we temporarily
+% save the old code property list so that it can be restored later.
+% \begin{macrocode}
+ \prop_set_eq:Nc \l_@@_work_prop { g_@@_#1_code_prop }
+ \@@_initialize_single:cccn
+ { @@~#1 } { @@_next~#1 }
+ { g_@@_#1_labels_clist } {#1}
+% \end{macrocode}
+% For debug display we want to keep track of those hooks that
+% actually got code added to them, so we record that in plist. We
+% use a plist to ensure that we record each hook name only once,
+% i.e., we are only interested in storing the keys and the value is
+% arbitrary.
+% \begin{macrocode}
+ \@@_debug:n{ \exp_args:NNx \prop_gput:Nnn
+ \g_@@_used_prop {#1}{} }
+ }
+ }
+ \debug_resume:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \begin{macro}[EXP]{\@@_tl_csname:n,\@@_seq_csname:n}
+% It is faster to pass a single token and expand it when necessary
+% than to pass a bunch of character tokens around.
+% \fmi{note to myself: verify}
+% \begin{macrocode}
+\cs_new:Npn \@@_tl_csname:n #1 { l_@@_label_#1_tl }
+\cs_new:Npn \@@_seq_csname:n #1 { l_@@_label_#1_seq }
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \begin{macro}{\l_@@_labels_seq,\l_@@_labels_int,\l_@@_front_tl,
+% \l_@@_rear_tl,\l_@@_label_0_tl}
+%
+% For the sorting I am basically implementing Knuth's algorithm for
+% topological sorting as given in TAOCP volume 1 pages 263--266.
+% For this algorithm we need a number of local variables:
+% \begin{itemize}
+% \item
+% List of labels used in the current hook to label code chunks:
+% \begin{macrocode}
+\seq_new:N \l_@@_labels_seq
+% \end{macrocode}
+% \item
+% Number of labels used in the current hook. In Knuth's algorithm
+% this is called $N$:
+% \begin{macrocode}
+\int_new:N \l_@@_labels_int
+% \end{macrocode}
+% \item
+% The sorted code list to be build is managed using two pointers
+% one to the front of the queue and one to the rear. We model this
+% using token list pointers. Knuth calls them $F$ and $R$:
+% \begin{macrocode}
+\tl_new:N \l_@@_front_tl
+\tl_new:N \l_@@_rear_tl
+% \end{macrocode}
+% \item
+% The data for the start of the queue is kept in this token list,
+% it corresponds to what Don calls \texttt{QLINK[0]} but since we
+% aren't manipulating individual words in memory it is slightly
+% differently done:
+% \begin{macrocode}
+\tl_new:c { \@@_tl_csname:n { 0 } }
+% \end{macrocode}
+%
+% \end{itemize}
+% \end{macro}
+%
+%
+% \begin{macro}{\@@_initialize_single:NNNn,\@@_initialize_single:cccn}
+%
+% \cs{@@_initialize_single:NNNn} implements the sorting of the code
+% chunks for a hook and saves the result in the token list for fast
+% execution (\verb=#3=). The arguments are \meta{hook-code-plist},
+% \meta{hook-code-tl}, \meta{hook-next-code-tl},
+% \meta{hook-ordered-labels-clist} and \meta{hook-name} (the latter
+% is only used for debugging---the \meta{hook-rule-plist} is accessed
+% using the \meta{hook-name}).
+%
+% The additional complexity compared to Don's algorithm is that we
+% do not use simple positive integers but have arbitrary
+% alphanumeric labels. As usual Don's data structures are chosen in
+% a way that one can omit a lot of tests and I have mimicked that as
+% far as possible. The result is a restriction I do not test for at
+% the moment: a label can't be equal to the number 0! \fmi{Needs
+% checking for, just in case}
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_initialize_single:NNNn #1#2#3#4
+ {
+% \end{macrocode}
+% Step T1: Initialize the data structure \ldots
+% \begin{macrocode}
+ \seq_clear:N \l_@@_labels_seq
+ \int_zero:N \l_@@_labels_int
+% \end{macrocode}
+%
+% Store the name of the hook:
+% \begin{macrocode}
+ \tl_set:Nn \l_@@_cur_hook_tl {#4}
+% \end{macrocode}
+%
+% We loop over the property list holding the code and record all
+% labels listed there. Only rules for those labels are of interest
+% to us. While we are at it we count them (which gives us the $N$
+% in Knuth's algorithm. The prefix |label_| is added to the variables
+% to ensure that labels named |front|, |rear|, |labels|, or |return|
+% don't interact with our code.
+% \begin{macrocode}
+ \prop_map_inline:Nn \l_@@_work_prop
+ {
+ \int_incr:N \l_@@_labels_int
+ \seq_put_right:Nn \l_@@_labels_seq {##1}
+ \tl_set:cn { \@@_tl_csname:n {##1} }{0}
+ \seq_clear_new:c { \@@_seq_csname:n {##1} }
+ }
+% \end{macrocode}
+% Steps T2 and T3: Sort the relevant rules into the data structure\ldots
+%
+% This loop constitutes a square matrix of the labels in
+% \cs{l_@@_work_prop} in the
+% vertical and the horizontal directions. However since the rule
+% $l_A\meta{rel}l_B$ is the same as $l_B\meta{rel}^{-1}l_A$ we can cut
+% the loop short at the diagonal of the matrix (\emph{i.e.}, when
+% both labels are equal), saving a good amount of time. The way the
+% rules were set up (see the implementation of \cs{@@_rule_before_gset:nnn}
+% above) ensures that we have no rule in the ignored side of the
+% matrix, and all rules are seen. The rules are applied in
+% \cs{@@_apply_label_pair:nnn}, which takes the properly-ordered pair
+% of labels as argument.
+% \begin{macrocode}
+ \prop_map_inline:Nn \l_@@_work_prop
+ {
+ \prop_map_inline:Nn \l_@@_work_prop
+ {
+ \@@_if_label_case:nnnnn {##1} {####1}
+ { \prop_map_break: }
+ { \@@_apply_label_pair:nnn {##1} {####1} }
+ { \@@_apply_label_pair:nnn {####1} {##1} }
+ {#4}
+ }
+ }
+% \end{macrocode}
+% Take a breath and take a look at the data structures that have
+% been set up:
+% \begin{macrocode}
+ \@@_debug:n { \@@_debug_label_data:N \l_@@_work_prop }
+% \end{macrocode}
+%
+%
+% Step T4:
+% \begin{macrocode}
+ \tl_set:Nn \l_@@_rear_tl { 0 }
+ \tl_set:cn { \@@_tl_csname:n { 0 } } { 0 }
+ \seq_map_inline:Nn \l_@@_labels_seq
+ {
+ \int_compare:nNnT { \cs:w \@@_tl_csname:n {##1} \cs_end: } = 0
+ {
+ \tl_set:cn { \@@_tl_csname:n { \l_@@_rear_tl } }{##1}
+ \tl_set:Nn \l_@@_rear_tl {##1}
+ }
+ }
+ \tl_set_eq:Nc \l_@@_front_tl { \@@_tl_csname:n { 0 } }
+% \end{macrocode}
+%
+% \begin{macrocode}
+ \tl_gclear:N #1
+ \clist_gclear:N #3
+% \end{macrocode}
+%
+% The whole loop combines steps T5--T7:
+% \begin{macrocode}
+ \bool_while_do:nn { ! \str_if_eq_p:Vn \l_@@_front_tl { 0 } }
+ {
+% \end{macrocode}
+% This part is step T5:
+% \begin{macrocode}
+ \int_decr:N \l_@@_labels_int
+ \prop_get:NVN \l_@@_work_prop \l_@@_front_tl \l_@@_return_tl
+ \@@_tl_gput:NV #1 \l_@@_return_tl
+% \end{macrocode}
+%
+% \begin{macrocode}
+ \@@_clist_gput:NV #3 \l_@@_front_tl
+ \@@_debug:n{ \iow_term:x{Handled~ code~ for~ \l_@@_front_tl} }
+% \end{macrocode}
+%
+% This is step T6 except that we don't use a pointer $P$ to move
+% through the successors, but instead use \verb=##1= of the mapping
+% function.
+% \begin{macrocode}
+ \seq_map_inline:cn { \@@_seq_csname:n { \l_@@_front_tl } }
+ {
+ \tl_set:cx { \@@_tl_csname:n {##1} }
+ { \int_eval:n
+ { \cs:w \@@_tl_csname:n {##1} \cs_end: - 1 }
+ }
+ \int_compare:nNnT
+ { \cs:w \@@_tl_csname:n {##1} \cs_end: } = 0
+ {
+ \tl_set:cn { \@@_tl_csname:n { \l_@@_rear_tl } } {##1}
+ \tl_set:Nn \l_@@_rear_tl {##1}
+ }
+ }
+% \end{macrocode}
+% and step T7:
+% \begin{macrocode}
+ \tl_set_eq:Nc \l_@@_front_tl
+ { \@@_tl_csname:n { \l_@@_front_tl } }
+% \end{macrocode}
+%
+% This is step T8: If we haven't moved the code for all labels
+% (i.e., if \cs{l_@@_labels_int} is still greater than zero) we
+% have a loop and our partial order can't be flattened out.
+% \begin{macrocode}
+ }
+ \int_compare:nNnF \l_@@_labels_int = 0
+ {
+ \iow_term:x{====================}
+ \iow_term:x{Error:~ label~ rules~ are~ incompatible:}
+% \end{macrocode}
+%
+% This is not really the information one needs in the error case
+% but will do for now \ldots \fmi{fix}
+% \begin{macrocode}
+ \@@_debug_label_data:N \l_@@_work_prop
+ \iow_term:x{====================}
+ }
+% \end{macrocode}
+% After we have added all hook code to \verb=#1= we finish it off
+% with adding extra code for a one time execution. That is stored
+% in \verb=#2= but is normally empty.
+% \begin{macrocode}
+ \tl_gput_right:Nn #1 {#2}
+ }
+% \end{macrocode}
+%
+% \begin{macrocode}
+\cs_generate_variant:Nn \@@_initialize_single:NNNn {ccc}
+% \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \begin{macro}{\@@_tl_gput:NV,\@@_clist_gput:NV}
+% These append either on the right (normal hook) or on the left
+% (reversed hook). This is setup up in
+% \cs{@@_initialize_hook_code:n}, elsewhere their behavior is undefined.
+% \begin{macrocode}
+\cs_new:Npn \@@_tl_gput:NV {\ERROR}
+\cs_new:Npn \@@_clist_gput:NV {\ERROR}
+% \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \begin{macro}{\@@_apply_label_pair:nnn,\@@_label_if_exist_apply:nnnF}
+%
+% This is the payload of steps T2 and T3 executed in the loop described
+% above. This macro assumes |#1| and |#2| are ordered, which means that
+% any rule pertaining the pair |#1| and |#2| is
+% \cs{g_@@_\meta{hook}_rule_\#1\string|\#2_tl}, and not
+% \cs{g_@@_\meta{hook}_rule_\#2\string|\#1_tl}. This also saves a great deal
+% of time since we only need to check the order of the labels once.
+%
+% The arguments here are \meta{label1}, \meta{label2}, \meta{hook}, and
+% \meta{hook-code-plist}. We are about to apply the next rule and
+% enter it into the data structure. \cs{@@_apply_label_pair:nnn} will
+% just call \cs{@@_label_if_exist_apply:nnnF} for the \meta{hook}, and
+% if no rule is found, also try the \meta{hook} name \verb=??=
+% denoting a default hook rule.
+%
+% \cs{@@_label_if_exist_apply:nnnF} will check if the rule exists for
+% the given hook, and if so call \cs{@@_apply_rule:nnn}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_apply_label_pair:nnn #1#2#3
+ {
+% \end{macrocode}
+% Extra complication: as we use default rules and local hook specific
+% rules we first have to check if there is a local rule and if that
+% exist use it. Otherwise check if there is a default rule and use
+% that.
+% \begin{macrocode}
+ \@@_label_if_exist_apply:nnnF {#1} {#2} {#3}
+ {
+% \end{macrocode}
+% If there is no hook-specific rule we check for a default one and
+% use that if it exists.
+% \begin{macrocode}
+ \@@_label_if_exist_apply:nnnF {#1} {#2} { ?? } { }
+ }
+ }
+\cs_new_protected:Npn \@@_label_if_exist_apply:nnnF #1#2#3
+ {
+ \if_cs_exist:w g_@@_ #3 _rule_ #1 | #2 _tl \cs_end:
+% \end{macrocode}
+% What to do precisely depends on the type of rule we have
+% encountered. If it is a \texttt{before} rule it will be handled by the
+% algorithm but other types need to be managed differently. All
+% this is done in \cs{@@_apply_rule:nnnN}.
+% \begin{macrocode}
+ \@@_apply_rule:nnn {#1} {#2} {#3}
+ \exp_after:wN \use_none:n
+ \else:
+ \use:nn
+ \fi:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+%
+%
+%
+% \begin{macro}{\@@_apply_rule:nnn}
+% This is the code executed in steps T2 and T3 while looping through
+% the matrix This is part of step T3. We are about to apply the next
+% rule and enter it into the data structure. The arguments are
+% \meta{label1}, \meta{label2}, \meta{hook-name}, and \meta{hook-code-plist}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_apply_rule:nnn #1#2#3
+ {
+ \cs:w @@_apply_
+ \cs:w g_@@_#3_reversed_tl \cs_end: rule_
+ \cs:w g_@@_ #3 _rule_ #1 | #2 _tl \cs_end: :nnn \cs_end:
+ {#1} {#2} {#3}
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_apply_rule_<:nnn,\@@_apply_rule_>:nnn}
+% The most common cases are \texttt{\string<} and \texttt{\string>} so we handle
+% that first. They are relations $\prec$ and $\succ$ in TAOCP, and
+% they dictate sorting.
+% \begin{macrocode}
+\cs_new_protected:cpn { @@_apply_rule_<:nnn } #1#2#3
+ {
+ \@@_debug:n { \@@_msg_pair_found:nnn {#1} {#2} {#3} }
+ \tl_set:cx { \@@_tl_csname:n {#2} }
+ { \int_eval:n{ \cs:w \@@_tl_csname:n {#2} \cs_end: + 1 } }
+ \seq_put_right:cn{ \@@_seq_csname:n {#1} }{#2}
+ }
+\cs_new_protected:cpn { @@_apply_rule_>:nnn } #1#2#3
+ {
+ \@@_debug:n { \@@_msg_pair_found:nnn {#1} {#2} {#3} }
+ \tl_set:cx { \@@_tl_csname:n {#1} }
+ { \int_eval:n{ \cs:w \@@_tl_csname:n {#1} \cs_end: + 1 } }
+ \seq_put_right:cn{ \@@_seq_csname:n {#2} }{#1}
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_apply_rule_xE:nnn,\@@_apply_rule_xW:nnn}
+% These relations make two labels incompatible within a hook.
+% |xE| makes raises an error if the labels are found in the same
+% hook, and |xW| makes it a warning.
+% \begin{macrocode}
+\cs_new_protected:cpn { @@_apply_rule_xE:nnn } #1#2#3
+ {
+ \@@_debug:n { \@@_msg_pair_found:nnn {#1} {#2} {#3} }
+ \msg_error:nnnnnn { hooks } { labels-incompatible }
+ {#1} {#2} {#3} { 1 }
+ \use:c { @@_apply_rule_->:nnn } {#1} {#2} {#3}
+ \use:c { @@_apply_rule_<-:nnn } {#1} {#2} {#3}
+ }
+\cs_new_protected:cpn { @@_apply_rule_xW:nnn } #1#2#3
+ {
+ \@@_debug:n { \@@_msg_pair_found:nnn {#1} {#2} {#3} }
+ \msg_warning:nnnnnn { hooks } { labels-incompatible }
+ {#1} {#2} {#3} { 0 }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_apply_rule_->:nnn,\@@_apply_rule_<-:nnn}
+% If we see \texttt{\detokenize{->}} we have to drop code for label
+% \verb=#3= and carry on. We could do a little better and drop
+% everything for that label since it doesn't matter where we sort
+% in the empty code. However that would complicate the algorithm a
+% lot with little gain.\footnote{This also hase the advantage that
+% the result of the sorting doesn't change which might otherwise
+% (for unrelated chunks) if we aren't careful.} So we still
+% unnecessarily try to sort it in and depending on the rules that
+% might result in a loop that is otherwise resolved. If that turns
+% out to be a real issue, we can improve the code.
+%
+% Here the code is removed from \cs{l_@@_cur_hook_tl} rather than
+% \verb=#3= because the latter may be \verb=??=, and the default
+% hook doesn't store any code. Removing from \cs{l_@@_cur_hook_tl}
+% makes default rules \verb=->= and \verb=<-= work properly.
+% \begin{macrocode}
+\cs_new_protected:cpn { @@_apply_rule_->:nnn } #1#2#3
+ {
+ \@@_debug:n
+ {
+ \@@_msg_pair_found:nnn {#1} {#2} {#3}
+ \iow_term:x{--->~ Drop~ '#2'~ code~ from~
+ \iow_char:N \\ g_@@_ \l_@@_cur_hook_tl _code_prop ~
+ because~ of~ '#1' }
+ }
+ \prop_put:Nnn \l_@@_work_prop {#2} { }
+ }
+\cs_new_protected:cpn { @@_apply_rule_<-:nnn } #1#2#3
+ {
+ \@@_debug:n
+ {
+ \@@_msg_pair_found:nnn {#1} {#2} {#3}
+ \iow_term:x{--->~ Drop~ '#1'~ code~ from~
+ \iow_char:N \\ g_@@_ \l_@@_cur_hook_tl _code_prop ~
+ because~ of~ '#2' }
+ }
+ \prop_put:Nnn \l_@@_work_prop {#1} { }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{
+% \@@_apply_-rule_<:nnn,
+% \@@_apply_-rule_>:nnn,
+% \@@_apply_-rule_<-:nnn,
+% \@@_apply_-rule_->:nnn,
+% \@@_apply_-rule_x:nnn,
+% }
+% Reversed rules.
+% \begin{macrocode}
+\cs_new_eq:cc { @@_apply_-rule_<:nnn } { @@_apply_rule_>:nnn }
+\cs_new_eq:cc { @@_apply_-rule_>:nnn } { @@_apply_rule_<:nnn }
+\cs_new_eq:cc { @@_apply_-rule_<-:nnn } { @@_apply_rule_<-:nnn }
+\cs_new_eq:cc { @@_apply_-rule_->:nnn } { @@_apply_rule_->:nnn }
+\cs_new_eq:cc { @@_apply_-rule_xE:nnn } { @@_apply_rule_xE:nnn }
+\cs_new_eq:cc { @@_apply_-rule_xW:nnn } { @@_apply_rule_xW:nnn }
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \begin{macro}{\@@_msg_pair_found:nnn}
+% A macro to avoid moving this many tokens around.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_msg_pair_found:nnn #1#2#3
+ {
+ \iow_term:x{~ \str_if_eq:nnTF {#3} {??} {default} {~normal} ~
+ rule~ \@@_label_pair:nn {#1} {#2}:~
+ \use:c { g_@@_#3_rule_ \@@_label_pair:nn {#1} {#2} _tl } ~
+ found}
+ }
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \begin{macro}{\@@_debug_label_data:N}
+%
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_debug_label_data:N #1 {
+ \iow_term:x{Code~ labels~ for~ sorting:}
+ \iow_term:x{~ \seq_use:Nnnn\l_@@_labels_seq {~and~}{,~}{~and~} }
+ \iow_term:x{^^J Data~ structure~ for~ label~ rules:}
+ \prop_map_inline:Nn #1
+ {
+ \iow_term:x{~ ##1~ =~ \tl_use:c{ \@@_tl_csname:n {##1} }~ ->~
+ \seq_use:cnnn{ \@@_seq_csname:n {##1} }{~->~}{~->~}{~->~}
+ }
+ }
+ \iow_term:x{}
+}
+% \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \begin{macro}{\hook_show:n,\hook_log:n}
+% \begin{macro}{\@@_log_line:x,\@@_log_line_indent:x}
+% \begin{macro}{\@@_log:nN}
+% This writes out information about the hook given in its argument
+% onto the \texttt{.log} file and the terminal, if \cs{show_hook:n} is
+% used. Internally both share the same structure, except that at the
+% end, \cs{hook_show:n} triggers \TeX's prompt.
+% \begin{macrocode}
+\cs_new_protected:Npn \hook_log:n #1
+ {
+ \cs_set_eq:NN \@@_log_cmd:x \iow_log:x
+ \@@_normalize_hook_args:Nn \@@_log:nN {#1} \tl_log:x
+ }
+\cs_new_protected:Npn \hook_show:n #1
+ {
+ \cs_set_eq:NN \@@_log_cmd:x \iow_term:x
+ \@@_normalize_hook_args:Nn \@@_log:nN {#1} \tl_show:x
+ }
+\cs_new_protected:Npn \@@_log_line:x #1
+ { \@@_log_cmd:x { >~#1 } }
+\cs_new_protected:Npn \@@_log_line_indent:x #1
+ { \@@_log_cmd:x { >~\@spaces #1 } }
+\cs_new_protected:Npn \@@_log:nN #1 #2
+ {
+ \@@_preamble_hook:n {#1}
+ \iow_term:x { ^^J ->~The~hook~'#1': }
+% \end{macrocode}
+%
+% \begin{macrocode}
+ \hook_if_exist:nF {#1}
+ { \@@_log_line:x { is~not~declared! } }
+ \@@_if_exist:nTF {#1}
+ {
+ \@@_log_line:x { Code~chunks: }
+ \prop_if_empty:cTF { g_@@_#1_code_prop }
+ { \@@_log_line_indent:x { --- } }
+ {
+ \prop_map_inline:cn { g_@@_#1_code_prop }
+ { \@@_log_line_indent:x { ##1~->~\tl_to_str:n {##2} } }
+ }
+% \end{macrocode}
+%
+% \begin{macrocode}
+ \@@_log_line:x { Extra~code~for~next~invocation:}
+ \@@_log_line_indent:x
+ {
+ \tl_if_empty:cTF { @@_next~#1 }
+ { --- }
+% \end{macrocode}
+%
+% If the token list is not empty we want to display it but without
+% the first tokens (the code to clear itself) so we call a helper
+% command to get rid of them.
+% \begin{macrocode}
+ { ->~ \exp_args:Nv \@@_log_next_code:n { @@_next~#1 } }
+ }
+% \end{macrocode}
+%
+% Loop through the rules in a hook and for every rule found, print it.
+% If no rule is there, print |---|. The boolean \cs{l_@@_tmpa_bool}
+% here indicates if the hook has no rules.
+% \begin{macrocode}
+ \@@_log_line:x { Rules: }
+ \bool_set_true:N \l_@@_tmpa_bool
+ \@@_list_rules:nn {#1}
+ {
+ \bool_set_false:N \l_@@_tmpa_bool
+ \@@_log_line_indent:x
+ {
+ ##2~ with~
+ \str_if_eq:nnT {##3} {??} { default~ }
+ relation~ ##1
+ }
+ }
+ \bool_if:NT \l_@@_tmpa_bool
+ { \@@_log_line_indent:x { --- } }
+% \end{macrocode}
+%
+% When the hook is declared (that is, the sorting algorithm is applied
+% to that hook) and not empty
+% \begin{macrocode}
+ \bool_lazy_and:nnTF
+ { \hook_if_exist_p:n {#1} }
+ { ! \hook_if_empty_p:n {#1} }
+ {
+ \@@_log_line:x
+ {
+ Execution~order
+ \bool_if:NTF \l_@@_tmpa_bool
+ { \@@_if_reversed:nT {#1} { ~(after~reversal) } }
+ { ~(after~
+ \@@_if_reversed:nT {#1} { reversal~and~ }
+ applying~rules)
+ } :
+ }
+ #2 % \tl_show:n
+ {
+ \@spaces
+ \clist_if_empty:cTF { g_@@_#1_labels_clist }
+ { --- }
+ { \clist_use:cn {g_@@_#1_labels_clist} { ,~ } }
+ }
+ }
+ {
+ #2
+ {
+ Hook~ \hook_if_exist:nTF {#1}
+ {code~pool~empty} {not~declared}
+ }
+ }
+ }
+ { #2 { The~hook~is~empty. } }
+ }
+% \end{macrocode}
+%
+% \begin{macro}{\@@_log_next_code:n}
+% To display the code for next invocation only (i.e., from
+% \cs{AddToHookNext} we have to remove the first two tokens at the
+% front which are \cs{tl_gclear:N} and the token list to clear.
+% \begin{macrocode}
+\cs_new:Npn \@@_log_next_code:n #1
+ { \exp_args:No \tl_to_str:n { \use_none:nn #1 } }
+% \end{macrocode}
+% \end{macro}
+%
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_list_rules:nn}
+% \begin{macro}{\@@_list_one_rule:nnn,\@@_list_if_rule_exists:nnnF}
+% This macro takes a \meta{hook} and an \meta{inline function} and
+% loops through each pair of \meta{labels} in the \meta{hook}, and if
+% there is a relation between this pair of \meta{labels}, the
+% \meta{inline function} is executed with |#1|${}={}$\meta{relation},
+% |#2|${}={}$\meta{label_1}\verb=|=\meta{label_2},
+% and |#3|${}={}$\meta{hook} (the latter may be the argument |#1| to
+% \cs{@@_list_rules:nn}, or |??| if it is a default rule).
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_list_rules:nn #1 #2
+ {
+ \cs_set_protected:Npn \@@_tmp:w ##1 ##2 ##3 {#2}
+ \prop_map_inline:cn { g_@@_#1_code_prop }
+ {
+ \prop_map_inline:cn { g_@@_#1_code_prop }
+ {
+ \@@_if_label_case:nnnnn {##1} {####1}
+ { \prop_map_break: }
+ { \@@_list_one_rule:nnn {##1} {####1} }
+ { \@@_list_one_rule:nnn {####1} {##1} }
+ {#1}
+ }
+ }
+ }
+% \end{macrocode}
+%
+% These two are quite similar to \cs{@@_apply_label_pair:nnn} and
+% \cs{@@_label_if_exist_apply:nnnF}, respectively, but rather than
+% applying the rule, they pass it to the \meta{inline function}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_list_one_rule:nnn #1#2#3
+ {
+ \@@_list_if_rule_exists:nnnF {#1} {#2} {#3} { }
+ { \@@_list_if_rule_exists:nnnF {#1} {#2} { ?? } { } }
+ }
+\cs_new_protected:Npn \@@_list_if_rule_exists:nnnF #1#2#3
+ {
+ \if_cs_exist:w g_@@_ #3 _rule_ #1 | #2 _tl \cs_end:
+ \exp_args:Nv \@@_tmp:w
+ { g_@@_ #3 _rule_ #1 | #2 _tl } { #1 | #2 } {#3}
+ \fi:
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_debug_print_rules:n}
+% A shorthand for debugging that prints similar to \cs{prop_show:N}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_debug_print_rules:n #1
+ {
+ \iow_term:n { The~hook~#1~contains~the~rules: }
+ \cs_set_protected:Npn \@@_tmp:w ##1
+ {
+ \@@_list_rules:nn {#1}
+ {
+ \iow_term:x
+ {
+ > ##1 {####2} ##1 => ##1 {####1}
+ \str_if_eq:nnT {####3} {??} { ~(default) }
+ }
+ }
+ }
+ \exp_args:No \@@_tmp:w { \use:nn { ~ } { ~ } }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Specifying code for next invocation}
+%
+% \begin{macro}{\hook_gput_next_code:nn}
+% \begin{macro}{%
+% \@@_gput_next_code:nn,
+% \@@_gput_next_do:nn,
+% \@@_gput_next_do:Nnn,
+% \@@_clear_next:n
+% }
+% \begin{macrocode}
+\cs_new_protected:Npn \hook_gput_next_code:nn #1
+ { \@@_normalize_hook_args:Nn \@@_gput_next_code:nn {#1} }
+\cs_new_protected:Npn \@@_gput_next_code:nn #1 #2
+ {
+ \debug_suspend:
+ \@@_declare:n {#1}
+ \hook_if_exist:nTF {#1}
+ { \@@_gput_next_do:nn {#1} {#2} }
+ { \@@_try_declaring_generic_next_hook:nn {#1} {#2} }
+ \debug_resume:
+ }
+\cs_new_protected:Npn \@@_gput_next_do:nn #1
+ {
+ \exp_args:Nc \@@_gput_next_do:Nnn
+ { @@_next~#1 } {#1}
+ }
+% \end{macrocode}
+% First check if the ``next code'' token list is empty: if so we need
+% to add a \cs{tl_gclear:c} to clear it, so the code lasts for one
+% usage only. The token list is cleared early so that nested usages
+% don't get lost. \cs{tl_gclear:c} is used instead of
+% \cs{tl_gclear:N} in case the hook is used in an expansion-only
+% context, so the token list doesn't expand before \cs{tl_gclear:N}:
+% that would make an infinite loop. Also in case the main code token
+% list is empty, the hook code has to be updated to add the next
+% execution token list.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_gput_next_do:Nnn #1 #2
+ {
+ \tl_if_empty:cT { @@~#2 }
+ { \@@_update_hook_code:n {#2} }
+ \tl_if_empty:NT #1
+ { \tl_gset:Nn #1 { \@@_clear_next:n {#2} } }
+ \tl_gput_right:Nn #1
+ }
+\cs_new_protected:Npn \@@_clear_next:n #1
+ { \cs_gset_eq:cN { @@_next~#1 } \c_empty_tl }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+%
+% \subsection{Using the hook}
+%
+% \begin{macro}{\hook_use:n}
+% \begin{macro}[EXP]{\@@_use_initialized:n}
+% \begin{macro}{\@@_use_undefined:w,\@@_use_end:}
+% \begin{macro}{\@@_preamble_hook:n}
+% \cs{hook_use:n} as defined here is used in the preamble, where
+% hooks aren't initialized by default. \cs{@@_use_initialized:n} is
+% also defined, which is the non-\tn{protected} version for use within
+% the document. Their definition is identical, except for the
+% \cs{@@_preamble_hook:n} (which wouldn't hurt in the expandable
+% version, but it would be an unnecessary extra expansion).
+%
+% \cs{@@_use_initialized:n} holds the expandable definition while in
+% the preamble. \cs{@@_preamble_hook:n} initializes the hook in the
+% preamble, and is redefined to \cs{use_none:n} at |\begin{document}|.
+%
+% Both versions do the same internally: check if the hook exist as
+% given, and if so use it as quickly as possible. If it doesn't
+% exist, the a call to \cs{@@_use:wn} checks for file hooks.
+%
+% At |\begin{document}|, all hooks are initialized, and any change in
+% them causes an update, so \cs{hook_use:n} can be made expandable.
+% This one is better not protected so that it can expand into nothing
+% if containing no code. Also important in case of generic hooks that
+% we do not generate a \cs{relax} as a side effect of checking for a
+% csname. In contrast to the \TeX{} low-level
+% \verb=\csname ...\endcsname= construct \cs{tl_if_exist:c} is
+% careful to avoid this.
+% \begin{macrocode}
+\cs_new_protected:Npn \hook_use:n #1
+ {
+ \tl_if_exist:cTF { @@~#1 }
+ {
+ \@@_preamble_hook:n {#1}
+ \cs:w @@~#1 \cs_end:
+ }
+ { \@@_use:wn #1 / \s_@@_mark {#1} }
+ }
+\cs_new:Npn \@@_use_initialized:n #1
+ {
+ \if_cs_exist:w @@~#1 \cs_end:
+ \else:
+ \@@_use_undefined:w
+ \fi:
+ \cs:w @@~#1 \@@_use_end:
+ }
+\cs_new:Npn \@@_use_undefined:w #1 #2 @@~#3 \@@_use_end:
+ {
+ #1 % fi
+ \@@_use:wn #3 / \s_@@_mark {#3}
+ }
+\cs_new_protected:Npn \@@_preamble_hook:n #1
+ { \@@_initialize_hook_code:n {#1} }
+\cs_new_eq:NN \@@_use_end: \cs_end:
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_use:wn}
+% \begin{macro}{\@@_try_file_hook:n,\@@_if_exist_use:n}
+% \cs{@@_use:wn} does a quick check to test if the current hook is a
+% file hook: those need a special treatment. If it is not, the hook
+% does not exist. If it is, then \cs{@@_try_file_hook:n} is called,
+% and checks that the current hook is a file-specific hook using
+% \cs{@@_if_file_hook:wTF}. If it's not, then it's a generic |file/|
+% hook and is used if it exist.
+%
+% If it is a file-specific hook, it passes through the same
+% normalization as during declaration, and then it is used if defined.
+% \cs{@@_if_exist_use:n} checks if the hook exist, and calls
+% \cs{@@_preamble_hook:n} if so, then uses the hook.
+% \begin{macrocode}
+\cs_new:Npn \@@_use:wn #1 / #2 \s_@@_mark #3
+ {
+ \str_if_eq:nnTF {#1} { file }
+ { \@@_try_file_hook:n {#3} }
+ { } % Hook doesn't exist
+ }
+\cs_new_protected:Npn \@@_try_file_hook:n #1
+ {
+ \@@_if_file_hook:wTF #1 / / \s_@@_mark
+ {
+ \exp_args:Ne \@@_if_exist_use:n
+ { \exp_args:Ne \@@_file_hook_normalize:n {#1} }
+ }
+ { \@@_if_exist_use:n {#1} } % file/ generic hook (e.g. file/before)
+ }
+\cs_new_protected:Npn \@@_if_exist_use:n #1
+ {
+ \tl_if_exist:cT { @@~#1 }
+ {
+ \@@_preamble_hook:n {#1}
+ \cs:w @@~#1 \cs_end:
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\hook_use_once:n}
+% For hooks that can and should be used only once we have a special
+% use command that remembers the hook name in
+% \cs{g_@@_execute_immediately_prop}. This has the effect that any
+% further code added to the hook is executed immediately rather
+% than stored in the hook.
+%
+% The code needs some gymnastics to prevent space trimming from the
+% hook name, since \cs{hook_use:n} and \cs{hook_use_once:n} are
+% documented to not trim spaces.
+%
+% \pho{Should this raise an error if the hook doesn't exist?}
+% \begin{macrocode}
+\cs_new_protected:Npn \hook_use_once:n #1
+ {
+ \tl_if_exist:cT { @@~#1 }
+ {
+ \tl_set:Nn \l_@@_return_tl {#1}
+ \@@_normalize_hook_args:Nn \@@_use_once_store:n
+ { \l_@@_return_tl }
+ \hook_use:n {#1}
+ }
+ }
+\cs_new_protected:Npn \@@_use_once_store:n #1
+ { \prop_gput:Nnn \g_@@_execute_immediately_prop {#1} { } }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Querying a hook}
+%
+% Simpler data types, like token lists, have three possible states; they
+% can exist and be empty, exist and be non-empty, and they may not
+% exist, in which case emptiness doesn't apply (though
+% \cs{tl_if_empty:N} returns false in this case).
+%
+% Hooks are a bit more complicated: they have four possible states.
+% A hook may exist or not, and either way it may or may not be empty
+% (even a hook that doesn't exist may be non-empty).
+%
+% A hook is said to be empty when no code was added to it, either to
+% its permanent code pool, or to its ``next'' token list. The hook
+% doesn't need to be declared to have code added to its code pool
+% (it may happen that a package $A$ defines a hook \hook{foo}, but
+% it's loaded after package $B$, which adds some code to that hook.
+% In this case it is important that the code added by package $B$ is
+% remembered until package $A$ is loaded).
+%
+% A hook is said to exist when it was declared with \cs{hook_new:n} or
+% some variant thereof.
+%
+% \begin{macro}[pTF]{\hook_if_empty:n}
+% Test if a hook is empty (that is, no code was added to that hook).
+% A hook being empty means that \emph{both} its
+% \cs[no-index]{g_@@_\meta{hook}_code_prop} and its
+% \cs[no-index]{@@_next~\meta{hook}} are empty.
+% \begin{macrocode}
+\prg_new_conditional:Npnn \hook_if_empty:n #1 { p , T , F , TF }
+ {
+ \@@_if_exist:nTF {#1}
+ {
+ \bool_lazy_and:nnTF
+ { \prop_if_empty_p:c { g_@@_#1_code_prop } }
+ { \tl_if_empty_p:c { @@_next~#1 } }
+ { \prg_return_true: }
+ { \prg_return_false: }
+ }
+ { \prg_return_true: }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[pTF]{\hook_if_exist:n}
+% A canonical way to test if a hook exists. A hook exists if the
+% token list that stores the sorted code for that hook,
+% \cs[no-index]{@@~\meta{hook}}, exists. The property list
+% \cs[no-index]{g_@@_\meta{hook}_code_prop} cannot be used here
+% because often it is necessary to add code to a hook without knowing
+% if such hook was already declared, or even if it will ever be
+% (for example, in case the package that defines it isn't loaded).
+% \begin{macrocode}
+\prg_new_conditional:Npnn \hook_if_exist:n #1 { p , T , F , TF }
+ {
+ \tl_if_exist:cTF { @@~#1 }
+ { \prg_return_true: }
+ { \prg_return_false: }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[pTF]{\@@_if_exist:n}
+% An internal check if the hook has already been declared with
+% \cs{@@_declare:n}. This means that the hook was already used somehow
+% (a code chunk or rule was added to it), but it still wasn't declared
+% with \cs{hook_new:n}.
+% \begin{macrocode}
+\prg_new_conditional:Npnn \@@_if_exist:n #1 { p , T , F , TF }
+ {
+ \prop_if_exist:cTF { g_@@_#1_code_prop }
+ { \prg_return_true: }
+ { \prg_return_false: }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[pTF]{\@@_if_reversed:n}
+% An internal conditional that checks if a hook is reversed.
+% \begin{macrocode}
+\prg_new_conditional:Npnn \@@_if_reversed:n #1 { p , T , F , TF }
+ {
+ \if_int_compare:w \cs:w g_@@_#1_reversed_tl \cs_end: 1 < 0 \exp_stop_f:
+ \prg_return_true:
+ \else:
+ \prg_return_false:
+ \fi:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \subsection{Messages}
+%
+% \begin{macrocode}
+\msg_new:nnnn { hooks } { labels-incompatible }
+ {
+ Labels~`#1'~and~`#2'~are~incompatible
+ \str_if_eq:nnF {#3} {??} { ~in~hook~`#3' } .~
+ \int_compare:nNnTF {#4} = { 1 }
+ { The~ code~ for~ both~ labels~ will~ be~ dropped. }
+ { You~ may~ see~ errors~ later. }
+ }
+ { LaTeX~found~two~incompatible~labels~in~the~same~hook.~
+ This~indicates~an~incompatibility~between~packages. }
+% \end{macrocode}
+%
+% \begin{macrocode}
+\msg_new:nnnn { hooks } { exists }
+ { Hook~`#1'~ has~ already~ been~ declared. }
+ { There~ already~ exists~ a~ hook~ declaration~ with~ this~
+ name.\\
+ Please~ use~ a~ different~ name~ for~ your~ hook.}
+% \end{macrocode}
+%
+% \begin{macrocode}
+\msg_new:nnn { hooks } { empty-label }
+ { Empty~code~label~\msg_line_context:.~Using~`#1'~instead. }
+% \end{macrocode}
+%
+% \begin{macrocode}
+\msg_new:nnnn { hooks } { unknown-rule }
+ { Unknown~ relationship~ `#3'~
+ between~ labels~ `#2'~ and~ `#4'~
+ \str_if_eq:nnF {#1} {??} { ~in~hook~`#1' }. ~
+ Perhaps~ a~ missspelling?
+ }
+ {
+ The~ relation~ used~ not~ known~ to~ the~ system.~ Allowed~ values~ are~
+ `before'~ or~ `<',~
+ `after'~ or~ `>',~
+ `incompatible-warning',~
+ `incompatible-error',~
+ `voids'~ or~
+ `unrelated'.
+ }
+% \end{macrocode}
+%
+% \begin{macrocode}
+\msg_new:nnn { hooks } { should-not-happen }
+ {
+ ERROR!~This~should~not~happen.~#1 \\
+ Please~report~at~https://github.com/latex3/latex2e.
+ }
+% \end{macrocode}
+%
+% \subsection{\LaTeXe{} package interface commands}
+%
+%
+%
+% \begin{macro}{\NewHook,\NewReversedHook,\NewMirroredHookPair}
+% Declaring new hooks \ldots
+% \begin{macrocode}
+\NewDocumentCommand \NewHook { m }{ \hook_new:n {#1} }
+\NewDocumentCommand \NewReversedHook { m }{ \hook_new_reversed:n {#1} }
+\NewDocumentCommand \NewMirroredHookPair { mm }{ \hook_new_pair:nn {#1}{#2} }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\AddToHook}
+%
+% \begin{macrocode}
+\NewDocumentCommand \AddToHook { m o +m }
+ { \hook_gput_code:nnn {#1} {#2} {#3} }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\AddToHookNext}
+%
+% \begin{macrocode}
+\NewDocumentCommand \AddToHookNext { m +m }
+ { \hook_gput_next_code:nn {#1} {#2} }
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \begin{macro}{\RemoveFromHook}
+%
+% \begin{macrocode}
+\NewDocumentCommand \RemoveFromHook { m o }
+ { \hook_gremove_code:nn {#1} {#2} }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\DeclareDefaultHookLabel}
+% \begin{macro}{\@@_curr_name_push:n,\@@_curr_name_pop:}
+% The token list \cs{g_@@_hook_curr_name_tl} stores the name of the
+% current package/file to be used as label for hooks.
+% Providing a consistent interface is tricky, because packages can
+% be loaded within packages, and some packages may not use
+% \cs{DeclareDefaultHookLabel} to change the default label (in which case
+% \cs{@currname} is used, if set).
+%
+% To pull that off, we keep a stack that contains the default label
+% for each level of input. The bottom of the stack contains the
+% default label for the top-level. Since the string \verb|top-level|
+% is hardcoded, here this item of the stack is empty. Also, since
+% we're in an input level, add \verb|lthooks| to the stack as well.
+% This stack should never go empty. An empty entry is added to the
+% stack to account for the \verb|top-level|. The item is empty so
+% that the hard-coded default is used, but a call to
+% \cs{DeclareDefaultHookLabel} will change it if needed.
+% \begin{macrocode}
+\seq_gpush:Nn \g_@@_name_stack_seq { }
+% \end{macrocode}
+%
+%
+% Two commands keep track of the stack: when a file is input,
+% \cs{@@_curr_name_push:n} pushes an (empty by default) label to the
+% stack:
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_curr_name_push:n #1
+ {
+ \seq_gpush:Nn \g_@@_name_stack_seq {#1}
+ \tl_gset:Nn \g_@@_hook_curr_name_tl {#1}
+ }
+%
+% \end{macrocode}
+% and when an input is over, the topmost item of the stack is popped,
+% since the label will not be used again, and \cs{g_@@_hook_curr_name_tl}
+% is updated to the now topmost item of the stack:
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_curr_name_pop:
+ {
+ \seq_gpop:NN \g_@@_name_stack_seq \l_@@_return_tl
+ \seq_get:NNTF \g_@@_name_stack_seq \l_@@_return_tl
+ { \tl_gset_eq:NN \g_@@_hook_curr_name_tl \l_@@_return_tl }
+ {
+ \msg_error:nnn { hooks } { should-not-happen }
+ { Tried~to~pop~from~an~empty~default~label~stack. }
+ }
+ }
+% \end{macrocode}
+%
+% The token list \cs{g_@@_hook_curr_name_tl} is but a mirror of the top
+% of the stack.
+%
+% Now define a wrapper that replaces the top of the stack with the
+% argument, and updates \cs{g_@@_hook_curr_name_tl} accordingly.
+% \begin{macrocode}
+\NewDocumentCommand \DeclareDefaultHookLabel { m }
+ {
+ \seq_gpop:NN \g_@@_name_stack_seq \l_@@_return_tl
+ \@@_curr_name_push:n {#1}
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+%
+%
+%
+% \begin{macro}{\UseHook,\UseOneTimeHook}
+% Avoid the overhead of \pkg{xparse} and its protection that we
+% don't want here (since the hook should vanish without trace if empty)!
+% \begin{macrocode}
+\cs_new:Npn \UseHook { \hook_use:n }
+\cs_new:Npn \UseOneTimeHook { \hook_use_once:n }
+% \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \begin{macro}{\ShowHook,\LogHook}
+% \begin{macrocode}
+\cs_new_protected:Npn \ShowHook { \hook_show:n }
+\cs_new_protected:Npn \LogHook { \hook_log:n }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\DebugHooksOn,\DebugHooksOff}
+%
+% \begin{macrocode}
+\cs_new_protected:Npn \DebugHooksOn { \hook_debug_on: }
+\cs_new_protected:Npn \DebugHooksOff { \hook_debug_off: }
+% \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \begin{macro}{\DeclareHookRule}
+%
+% \begin{macrocode}
+\NewDocumentCommand \DeclareHookRule { m m m m }
+ { \hook_gset_rule:nnnn {#1}{#2}{#3}{#4} }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\DeclareDefaultHookRule}
+% This declaration is only supported before \verb=\begin{document}=.
+% \begin{macrocode}
+\NewDocumentCommand \DeclareDefaultHookRule { m m m }
+ { \hook_gset_rule:nnnn {??}{#1}{#2}{#3} }
+\@onlypreamble\DeclareDefaultHookRule
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\ClearHookRule}
+% A special setup rule that removes an existing relation.
+% Basically {@@_rule_gclear:nnn} plus fixing the property list for debugging.
+% \fmi{Need an L3 interface, or maybe it should get dropped?}
+% \begin{macrocode}
+\NewDocumentCommand \ClearHookRule { m m m }
+{ \hook_gset_rule:nnnn {#1}{#2}{unrelated}{#3} }
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \begin{macro}[EXP]{\IfHookExistsTF,\IfHookEmptyTF}
+% Here we avoid the overhead of \pkg{xparse}, since \cs{IfHookEmptyTF}
+% is used in \cs{end} (that is, every \LaTeX{} environment). As a
+% further optimisation, use \cs{let} rather than \cs{def} to avoid one
+% expansion step.
+% \begin{macrocode}
+\cs_new_eq:NN \IfHookExistsTF \hook_if_exist:nTF
+\cs_new_eq:NN \IfHookEmptyTF \hook_if_empty:nTF
+% \end{macrocode}
+% \end{macro}
+%
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%5
+%
+% \subsection{Internal commands needed elsewhere}
+%
+% Here we set up a few horrible (but consistent) \LaTeXe{} names to
+% allow for internal commands to be used outside this module. We
+% have to unset the \texttt{@\/@} since we want double ``at'' sign
+% in place of double underscores.
+%
+% \begin{macrocode}
+%<@@=>
+% \end{macrocode}
+%
+% \begin{macro}{\@expl@@@initialize@all@@,
+% \@expl@@@hook@curr@name@push@@n,
+% \@expl@@@hook@curr@name@pop@@}
+%
+% \begin{macrocode}
+\cs_new_eq:NN \@expl@@@initialize@all@@
+ \__hook_initialize_all:
+% \end{macrocode}
+%
+% \begin{macrocode}
+\cs_new_eq:NN \@expl@@@hook@curr@name@push@@n
+ \__hook_curr_name_push:n
+% \end{macrocode}
+%
+% \begin{macrocode}
+\cs_new_eq:NN \@expl@@@hook@curr@name@pop@@
+ \__hook_curr_name_pop:
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \begin{macrocode}
+\ExplSyntaxOff
+% \end{macrocode}
+%
+%
+% Rolling back here doesn't undefine the interface commands as they
+% may be used in packages without rollback functionality. So we
+% just make them do nothing which may or may not work depending on
+% the code usage.
+% \begin{macrocode}
+%</2ekernel|latexrelease>
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{0000/00/00}%
+%<latexrelease> {\NewHook}{The hook management}%
+%<latexrelease>
+%<latexrelease>\def \NewHook#1{}
+%<latexrelease>\def \NewReversedHook#1{}
+%<latexrelease>\def \NewMirroredHookPair#1#2{}
+%<latexrelease>
+%<latexrelease>\long\def \AddToHook#1#2{}
+%<latexrelease>
+%<latexrelease>\def \AddToHookNext#1{\@gobble@AddToHook@args}
+%<latexrelease>\providecommand\@gobble@AddToHook@args[2][]{}
+%<latexrelease>
+%<latexrelease>\def\RemoveFromHook#1{\@gobble@RemoveFromHook@arg}
+%<latexrelease>\providecommand\@gobble@RemoveFromHook@arg[1][]{}
+%<latexrelease>
+%<latexrelease>\def \UseHook #1{}
+%<latexrelease>\def \UseOneTimeHook #1{}
+%<latexrelease>\def \ShowHook #1{}
+%<latexrelease>\let \DebugHooksOn \@empty
+%<latexrelease>\let \DebugHooksOff\@empty
+%<latexrelease>
+%<latexrelease>\def \DeclareHookRul#1#2#3#4{}
+%<latexrelease>\def \DeclareDefaultHookRule #1#2#3{}
+%<latexrelease>\def \ClearHookRule#1#2#3{}
+% \end{macrocode}
+% If the hook management is not provided we make the test for existence
+% false and the test for empty true in the hope that this is most
+% of the time reasonable. If not a package would need to guard
+% against running in an old kernel.
+% \begin{macrocode}
+%<latexrelease>\def \IfHookExistTF #1#2#3{#3}
+%<latexrelease>\def \IfHookEmptyTF #1#2#3{#2}
+%<latexrelease>
+%<latexrelease>\EndIncludeInRelease
+% \end{macrocode}
+%
+%
+% \Finale
+%
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\endinput
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%