summaryrefslogtreecommitdiff
path: root/macros/latex-dev/base/lthooks.dtx
diff options
context:
space:
mode:
Diffstat (limited to 'macros/latex-dev/base/lthooks.dtx')
-rw-r--r--macros/latex-dev/base/lthooks.dtx3925
1 files changed, 3925 insertions, 0 deletions
diff --git a/macros/latex-dev/base/lthooks.dtx b/macros/latex-dev/base/lthooks.dtx
new file mode 100644
index 0000000000..603b472e44
--- /dev/null
+++ b/macros/latex-dev/base/lthooks.dtx
@@ -0,0 +1,3925 @@
+% \iffalse meta-comment
+%
+%% File: lthooks.dtx (C) Copyright 2020 Frank Mittelbach, LaTeX Team
+%
+% It may be distributed and/or modified under the conditions of the
+% LaTeX Project Public License (LPPL), either version 1.3c of this
+% license or (at your option) any later version. The latest version
+% of this license is in the file
+%
+% https://www.latex-project.org/lppl.txt
+%
+%
+% The development version of the bundle can be found below
+%
+% https://github.com/FrankMittelbach/...
+%
+% for those people who are interested or want to report an issue.
+%
+% \begin{macrocode}
+\providecommand\lthooksversion{v0.9b}
+\providecommand\lthooksdate{2020/07/19}
+% \end{macrocode}
+%
+%<*driver>
+\RequirePackage[debug]{lthooks}
+
+\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.\footnote{For
+% legacy hooks such as \hook{begindocument} it is also
+% possible to call \cs{@...hook}, e.g., \cs{@begindocumenthook},
+% but this syntax is discouraged.}
+%
+% 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
+%
+% 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
+%
+% The 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.
+%
+% 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}{.}{<}{babel}
+% \AddToHook {file/after/foo.tex}{code}
+% \end{verbatim}
+% are equivalent to:
+% \begin{verbatim}
+% \NewHook {mypackage/hook}[mypackage]{code}
+% \AddToHook {mypackage/hook}[mypackage]{code}
+% \AddToHook {mypackage/hook}[mypackage/sub]{code}
+% \DeclareHookRule{begindocument}{mypackage}{<}{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{IfHookExistTF} (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
+% 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{removes}]
+%
+% 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}
+%
+% 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]{\IfHookExistTF}
+% \begin{syntax}
+% \cs{IfHookExistTF} \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.
+%
+% 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}
+% \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,
+% \fmi{currently this is missing the default rules that apply,
+% guess that needs fixing}
+% \item
+% the computed order (if already defined),
+% \item
+% any code executed on the next invocation only.
+% \end{itemize}
+%
+% 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}{\DebugHookOn,\DebugHookOff}
+% \begin{syntax}
+% \cs{DebugHookOn}
+% \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:n} \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:n} \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.
+%
+% \fmi{what are the results for generic hooks that do not need to be declared?}
+%
+% 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.
+%
+% The \meta{hook} \emph{cannot} be specified using the dot-syntax.
+% A leading |.| is treated literally.
+% \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{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}
+%
+%
+%
+% \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 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 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.
+%
+% This 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}
+% In contrast to other hooks these hooks do not need to be declared
+% 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.
+%
+%
+%
+% \subsubsection{Hooks provided by \cs{begin}\texttt{\{document\}}}
+%
+% Until 2020 \cs{begin}\texttt{\{document\}} offered exactly one
+% hook that one had to fill using \cs{AtBeginDocument}. Experience
+% has 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
+% 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:
+% \begin{description}
+%
+% \item[\hook{env/document/before}]
+%
+% This is the generic environment hook executed effectively
+% before \verb=\begin{document}= starts, i.e., one can think of
+% it as a hook for code at the end of the preamble section.
+%
+% \item[\hook{env/document/begin}]
+%
+% This is the second generic environment hook that is executed
+% after the environment has started its group. But given that for the
+% \texttt{document} environment this group is canceled there is
+% little difference to the previous one as the two are directly
+% executed one after another (the only difference is that in this
+% hook \cs{@currenvir} is now set to \texttt{document} but
+% anybody adding to this hook would know that already).
+%
+%
+% \item[\hook{begindocument}]
+%
+% This hook is added to by \cs{AtBeginDocument} and 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.
+%
+% \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}
+%
+%
+%
+%
+% \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:
+% \begin{description}
+%
+% \item[\hook{env/document/end}] The generic hook inside \cs{end}.
+%
+% \item[\hook{enddocument}]
+
+% The hook associated with \cs{AtEndDocument}. It is immediately
+% called after the previous hook so there could be just
+% one.\footnote{We could make \cs{AtEndDocument} just fill the
+% \hook{env/document/end} but maybe that is a bit confusing.}
+%
+% 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.
+%
+%
+% \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 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}.
+%
+%
+
+% \StopEventually{\setlength\IndexMin{200pt} \PrintIndex }
+%
+%
+% \section{The Implementation}
+% \begin{macrocode}
+%<@@=hook>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<*2ekernel>
+% \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_@@_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 property 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}{\g_@@_code_temp_prop}
+% A property list to temporarily save the original one so that it
+% isn't permanently changed during sorting.
+% \begin{macrocode}
+\prop_new:N \g_@@_code_temp_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}
+% Some variants of \pkg{expl3} functions. \fmi{should be moved to expl3}
+% \begin{macrocode}
+\cs_generate_variant:Nn \tl_gremove_once:Nn { Nx }
+% \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,\g_@@_..._rules_prop,
+% \g_@@_..._code_tl,\g_@@_..._next_code_tl}
+%
+% 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{g_@@_\meta{name}_rules_prop}] A property listing holding
+% relation info how the code chunks should be ordered within a
+% hook. This is used for debugging only. The actual rule for a
+% \meta{hook} is stored in a separate token lists named
+% \cs[no-index]{g_@@_\meta{hook}_rule_\meta{label1}\string|\meta{label2}_tl}
+% for a pair of labels.
+%
+% \item[\cs{g_@@_\meta{name}_code_tl}] 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.
+%
+% \item[\cs{g_@@_\meta{name}_next_code_tl}] 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.
+%
+% \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
+ {
+ \exp_args:Nx \@@_new:n
+ { \@@_parse_label_default:nn {#1} { top-level } }
+ }
+\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}
+ { \ErrorHookExists }
+% \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 { g_@@_#1_code_tl }
+% \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{} for use in the current kernel. This
+% is done in a separate macro.
+% \begin{macrocode}
+ \@@_provide_legacy_interface: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 { g_@@_#1_next_code_tl }
+ \prop_new:c { g_@@_#1_rules_prop } % only for debugging
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+
+
+
+% \begin{macro}{\hook_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 {
+ \hook_new:n {#1}
+ \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}{\@@_provide_legacy_interface:n}
+% The \LaTeX{} legacy concept for hooks uses with hooks the
+% following naming scheme in the code: \cs{@...hook}.
+%
+% We follow this convention and insert the hook code using this
+% naming scheme in \LaTeXe{}. At least as long as this code is in a
+% package, some such hooks are already filled with data when we move
+% them over to the new scheme. We therefore insert already existing
+% code under the label \texttt{legacy} into the hook management
+% machinery and then replace the \cs{@...hook} with its counterpart
+% which is \cs{g_@@_\#1_code_tl}.\footnote{This means one extra
+% unnecessary expansion on each invocation in the document but
+% keeps the \LaTeXe{} and the L3 coding side properly separated.}
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_provide_legacy_interface: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:
+ \tl_if_exist:cT { @#1hook }
+% \end{macrocode}
+% Of course if the hook exists but is still empty, there is no need
+% to add anything under \texttt{legacy} or the current package name.
+% \begin{macrocode}
+ {
+ \tl_if_empty:cF { @#1hook }
+ {
+ \@@_gput_code:nxv {#1}
+ { \@@_parse_label_default:Vn \c_novalue_tl { legacy } }
+ { @#1hook }
+ }
+ }
+% \end{macrocode}
+% We need a global definition in case the declaration is done
+% inside a group (which happens below at the end of the file).
+% This is another reason why need to suspend checking, otherwise
+% \cs{tl_gset:co} would complain about \cs{@...hook} not starting
+% with \cs{g_}.
+% \begin{macrocode}
+ \tl_gset:co{@#1hook}{\cs:w g_@@_#1_code_tl\cs_end:}
+ \debug_resume:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Parsing a label}
+%
+% \begin{macro}[EXP]{\@@_parse_label_default:nn,\@@_parse_label_default:Vn}
+% 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.
+% \begin{macrocode}
+\cs_new:Npn \@@_parse_label_default:nn #1 #2
+ {
+ \tl_if_novalue:nTF {#1}
+ { \@@_currname_or_default:n {#2} }
+ { \tl_trim_spaces_apply:nN {#1} \@@_parse_dot_label:nn {#2} }
+ }
+\cs_generate_variant:Nn \@@_parse_label_default:nn { V }
+% \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 {#1} }
+ { \@@_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}
+ {#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} / #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
+ {#1}
+ { \@currname }
+ }
+ { \g_@@_hook_curr_name_tl }
+ }
+% \end{macrocode}
+% \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
+ {
+ \exp_args:Nxx \@@_gput_code:nnn
+ { \@@_parse_label_default:nn {#1} { top-level } }
+ { \@@_parse_label_default:nn {#2} { top-level } }
+ }
+\cs_new_protected:Npn \@@_gput_code:nnn #1 #2 #3
+ {
+% \end{macrocode}
+% First we check if the hook exists.
+% \begin{macrocode}
+ \@@_if_marked_removal:nnTF {#1} {#2}
+ { \@@_unmark_removal:nn {#1} {#2} }
+ {
+% \end{macrocode}
+% First we check 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}
+%
+% \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
+ {
+% 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_normalise: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_normalise: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_normalise: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_normalise: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}
+% Clist 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}{\@@_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}{\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
+ {
+ \exp_args:Nxx \@@_gremove_code:nn
+ { \@@_parse_label_default:nn {#1} { top-level } }
+ { \@@_parse_label_default:nn {#2} { top-level } }
+ }
+\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 }
+ \clist_gclear:c { g_@@_#1_labels_clist } % for debugging only
+ }
+ {
+% \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} }
+ }
+ { \@@_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}
+% Removing the dropped label from \verb=\g_@@_#1_labels_clist= is
+% rather tricky, because that clists holds the labels as strings
+% (i.e., not ordinary text which is what we have in \verb=#2=).
+% \begin{macrocode}
+ \exp_args:Nco \clist_gremove_all:Nn
+ { g_@@_#1_labels_clist } { \tl_to_str:n {#2} } % for debugging only
+ }
+% \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=).
+% \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=.
+% \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_@@_??_rules_prop,
+% \g_@@_??_code_prop,
+% \g_@@_??_code_tl,
+% \g_@@_??_reversed_tl,
+% }
+%
+% Default rules applying to all hooks are stored in this property
+% list. Initially it 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{g_@@_??_code_tl} 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_@@_??_rules_prop}
+\prop_new:c {g_@@_??_code_prop}
+\prop_new:c {g_@@_??_code_tl}
+% \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}
+
+
+
+% \begin{macro}{\@@_debug_gset_rule:nnnn}
+%
+% \fmi{this needs cleanup and docu correction!}
+%
+% With
+% \cs{@@_debug_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 \@@_debug_gset_rule:nnnn #1#2#3#4
+ {
+% \end{macrocode}
+% If so we drop any existing rules with the two labels (in case
+% there are any).
+% \begin{macrocode}
+ \prop_gremove:cn{g_@@_#1_rules_prop}{#2|#4}
+ \prop_gremove:cn{g_@@_#1_rules_prop}{#4|#2}
+% \end{macrocode}
+% Then we add the new one (normalizing the input a bit, e.g., we
+% always use \texttt{before} and not \texttt{after} and
+% instead reorder the labels):
+% \begin{macrocode}
+ \str_case_e:nnF {#3}
+ {
+ {before} { \prop_gput:cnn {g_@@_#1_rules_prop}{#2|#4}{<} }
+ {after} { \prop_gput:cnn {g_@@_#1_rules_prop}{#4|#2}{<} }
+% \end{macrocode}
+% More special rule types \ldots
+% \begin{macrocode}
+ {incompatible-error} { \prop_gput:cnn {g_@@_#1_rules_prop}{#2|#4}{xE} }
+ {incompatible-warning} { \prop_gput:cnn {g_@@_#1_rules_prop}{#2|#4}{xW} }
+ {removes} { \prop_gput:cnn {g_@@_#1_rules_prop}{#2|#4}{->} }
+% \end{macrocode}
+% Undo a setting:
+% \begin{macrocode}
+ {unrelated}{ \prop_gremove:cn {g_@@_#1_rules_prop}{#2|#4}
+ \prop_gremove:cn {g_@@_#1_rules_prop}{#4|#2} }
+ }
+ { \ERRORunknownrule }
+ }
+% \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
+ {
+ \use:x
+ {
+ \@@_gset_rule:nnnn
+ { \@@_parse_label_default:nn {#1} { top-level } }
+ { \@@_parse_label_default:nn {#2} { top-level } }
+ {#3}
+ { \@@_parse_label_default:nn {#4} { top-level } }
+ }
+ }
+% \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}
+ }
+ { \ERRORunknownrule }
+ \debug_resume:
+ \@@_debug_gset_rule:nnnn {#1} {#2} {#3} {#4} % for debugging
+ }
+% \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$\fmi{}
+% said differently. But normalizing the
+% forms of the rule to a single representation, say, $l_B<l_A$, then
+% the time spent looking for the rules later is considerably reduced.
+%
+% 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_removes_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_removes_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:NNNNn}
+% (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}{\@@_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 {g_@@_##1_code_tl}~ }
+ }
+ }
+%
+% \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:n{ \iow_term:x{^^JUpdate~ code~ for~ hook~
+ '#1' \on@line :^^J} }
+% \end{macrocode}
+% This does the sorting and the updates. 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{g_@@_\meta{hook}_code_tl} to hold the next
+% code. If there are code chunks we call
+% \cs{@@_initialize_single:NNNNn} 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 {g_@@_#1_code_tl}
+ {\cs:w g_@@_#1_next_code_tl \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|->| \verb|<-|) need to
+% act destructively on the code property lists to remove code that
+% shouldn't appear in the sorted hook token list.
+% \begin{macrocode}
+ \prop_gset_eq:Nc \g_@@_code_temp_prop { g_@@_#1_code_prop }
+ \@@_initialize_single:ccccn
+ { g_@@_#1_code_prop } { g_@@_#1_code_tl }
+ { g_@@_#1_next_code_tl } { g_@@_#1_labels_clist }
+ {#1}
+ \prop_gset_eq:cN { g_@@_#1_code_prop } \g_@@_code_temp_prop
+% \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}{} }
+ }
+ }
+}
+% \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}[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:NNNNn,\@@_initialize_single:ccccn}
+%
+% \cs{@@_initialize_single:NNNNn} 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:NNNNn #1#2#3#4#5 {
+% \end{macrocode}
+%
+% \begin{macrocode}
+ \debug_suspend:
+% \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 {#5}
+% \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 #1
+ {
+ \int_incr:N \l_@@_labels_int
+ \seq_put_right:Nn \l_@@_labels_seq {##1}
+ \tl_set:cn { \@@_tl_csname:n {##1} }{0} % the counter k for number of
+ % j before k rules
+ \seq_clear_new:c { \@@_seq_csname:n {##1} } % sequence of successors to k
+ % i.e., k before j rules (stores
+ % the names of the j's)
+ }
+% \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 |#1| 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 #1
+ {
+ \prop_map_inline:Nn #1
+ {
+ \@@_if_label_case:nnnnn {##1} {####1}
+ { \prop_map_break: }
+ { \@@_apply_label_pair:nnn {##1} {####1} }
+ { \@@_apply_label_pair:nnn {####1} {##1} }
+ {#5}
+ }
+ }
+% \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 #1 }
+% \end{macrocode}
+%
+%
+% Step T4:
+% \begin{macrocode}
+ \tl_set:Nn \l_@@_rear_tl { 0 }
+ \tl_set:cn { \@@_tl_csname:n { 0 } } { 0 } % really {l_@@_label_ \l_@@_rear_tl _tl}
+ \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 #2
+ \clist_gclear:N #4
+% \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 #1 \l_@@_front_tl \l_@@_return_tl
+ \@@_tl_gput:NV #2 \l_@@_return_tl
+% \end{macrocode}
+%
+% \begin{macrocode}
+ \@@_clist_gput:NV #4 \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 #1
+ \iow_term:x{====================}
+ }
+% \end{macrocode}
+% After we have added all hook code to \verb=#2= we finish it off
+% with adding extra code for a one time execution. That is stored
+% in \verb=#3= but is normally empty.
+% \begin{macrocode}
+ \tl_gput_right:Nn #2 {#3}
+% \end{macrocode}
+%
+% \begin{macrocode}
+ \debug_resume:
+}
+% \end{macrocode}
+%
+% \begin{macrocode}
+\cs_generate_variant:Nn \@@_initialize_single:NNNNn {cccc}
+% \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 trop
+% 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. 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_gput:cnn { g_@@_ \l_@@_cur_hook_tl _code_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_gput:cnn { g_@@_ \l_@@_cur_hook_tl _code_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~} } % fix name!
+ \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_log:n}
+% This writes out information about the hook given in its argument
+% onto the terminal and the \texttt{.log} file.
+% \begin{macrocode}
+\cs_new_protected:Npn \hook_log:n #1
+ {
+ \exp_args:Nx \@@_log:n
+ { \@@_parse_label_default:nn {#1} { top-level } }
+ }
+\cs_new_protected:Npn \@@_log:n #1
+ {
+ \iow_term:x{^^JThe~ hook~ '#1':}
+% \end{macrocode}
+%
+% \begin{macrocode}
+ \hook_if_exist:nF {#1}
+ { \iow_term:x {~Hook~ is~ not~ declared!} }
+ \@@_if_exist:nTF {#1}
+ {
+ \iow_term:x{~Code~ chunks:}
+ \prop_if_empty:cTF {g_@@_#1_code_prop}
+ { \iow_term:x{\@spaces ---} }
+ {
+ \prop_map_inline:cn {g_@@_#1_code_prop}
+ { \iow_term:x{\@spaces ##1~ ->~ \tl_to_str:n{##2} } }
+ }
+% \end{macrocode}
+%
+% \begin{macrocode}
+ \iow_term:x{~Extra~ code~ next~ invocation:}
+ \iow_term:x{\@spaces
+ \tl_if_empty:cTF { g_@@_#1_next_code_tl }
+ {---} {->~ \str_use:c{g_@@_#1_next_code_tl} } }
+% \end{macrocode}
+%
+% \fmi{This is currently only displaying the local rules, but it
+% should also show the matching global rules!}
+%
+% \begin{macrocode}
+ \iow_term:x{~Rules:}
+ \prop_if_empty:cTF {g_@@_#1_rules_prop}
+ { \iow_term:x{\@spaces ---} }
+ { \prop_map_inline:cn {g_@@_#1_rules_prop}
+ { \iow_term:x{\@spaces ##1~ with~ relation~ ##2} }
+ }
+% \end{macrocode}
+%
+% \begin{macrocode}
+ \hook_if_exist:nT {#1}
+ { \iow_term:x { ~Execution~ order
+ \prop_if_empty:cTF {g_@@_#1_rules_prop}
+ { \@@_if_reversed:nT {#1}
+ { ~ (after~ reversal) }
+ }
+ { ~ (after~
+ \@@_if_reversed:nT {#1} {reversal~ and~}
+ applying~ rules)
+ }
+ :
+ }
+ \iow_term:x { \@spaces
+ \clist_if_empty:cTF{g_@@_#1_labels_clist}
+ {not~ set~ yet}
+ { \clist_use:cnnn {g_@@_#1_labels_clist}
+ { ,~ } { ,~ } { ,~ } }
+ }
+ }
+ }
+ { \iow_term:n { ~The~hook~is~empty. } }
+ \iow_term:n { }
+ }
+% \end{macrocode}
+%
+% \end{macro}
+
+
+
+% \subsection{Specifying code for next invocation}
+%
+%
+%
+%
+%
+% \begin{macro}{\hook_gput_next_code:nn}
+%
+% \begin{macrocode}
+\cs_new_protected:Npn \hook_gput_next_code:nn #1
+ {
+ \exp_args:Nx \@@_gput_next_code:nn
+ { \@@_parse_label_default:nn {#1} { top-level } }
+ }
+\cs_new_protected:Npn \@@_gput_next_code:nn #1 #2
+ {
+ \@@_declare:n {#1}
+ \hook_if_exist:nTF {#1}
+ { \@@_gput_next_do:nn {#1} {#2} }
+ { \@@_try_declaring_generic_next_hook:nn {#1} {#2} }
+ }
+\cs_new_protected:Npn \@@_gput_next_do:nn #1 #2
+ {
+ \tl_gput_right:cn { g_@@_#1_next_code_tl }
+ { #2 \tl_gclear:c { g_@@_#1_next_code_tl } }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \subsection{Using the hook}
+%
+% \begin{macro}{\hook_use:n}
+% \begin{macro}[EXP]{\@@_use_initialized:n}
+% \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 { g_@@_#1_code_tl }
+ {
+ \@@_preamble_hook:n {#1}
+ \cs:w g_@@_#1_code_tl \cs_end:
+ }
+ { \@@_use:wn #1 / \s_@@_mark {#1} }
+ }
+\cs_new:Npn \@@_use_initialized:n #1
+ {
+ \tl_if_exist:cTF { g_@@_#1_code_tl }
+ { \cs:w g_@@_#1_code_tl \cs_end: }
+ { \@@_use:wn #1 / \s_@@_mark {#1} }
+ }
+\cs_new_protected:Npn \@@_preamble_hook:n #1
+ { \@@_initialize_hook_code:n {#1} }
+% \end{macrocode}
+% \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_normalise: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 { g_@@_#1_code_tl }
+ {
+ \@@_preamble_hook:n {#1}
+ \cs:w g_@@_#1_code_tl \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_clist}. This has the effect that any
+% further code added to the hook is executed immediately rather
+% than stored in the hook.
+% \begin{macrocode}
+\cs_new_protected:Npn \hook_use_once:n #1
+ {
+ \tl_if_exist:cT { g_@@_#1_code_tl }
+ {
+ \clist_gput_left:Nn \g_@@_execute_immediately_clist {#1}
+ \hook_use:n {#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]{g_@@_\meta{hook}_next_code_tl} 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 { g_@@_#1_next_code_tl } }
+ { \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]{g_@@_\meta{hook}_code_tl}, 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 { g_@@_#1_code_tl }
+ { \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}
+
+
+% \begin{macro}{\g_@@_execute_immediately_clist}
+% List of hooks that from no on should not longer receive code.
+% \begin{macrocode}
+\clist_new:N \g_@@_execute_immediately_clist
+% \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:nNnT {#4} = { 1 }
+ { The~code~for~both~labels~will~be~dropped. }
+ }
+ {
+ LaTeX~found~two~incompatible~labels~in~the~same~hook.~
+ This~indicates~an~incompatibility~between~packages.
+ }
+\msg_new:nnn { hooks } { empty-label }
+ { Empty~code~label~\msg_line_context:.~Using~`#1'~instead. }
+% \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 }
+ {
+ \clist_if_in:NnTF \g_@@_execute_immediately_clist {#1}
+ {#3}
+ { \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, so we loop through \LaTeXe's
+% file name stack, and add empty entries to \cs{g_@@_name_stack_seq}
+% for each item in that stack. The last item is the \verb|top-level|,
+% which also gets an empty entry.
+%
+% Also check for the case we're loading \texttt{lthooks} in the
+% \LaTeXe{} kernel. In that case, \cs{@currname} isn't \verb|lthooks|
+% and just the top-level is added to the stack as an empty entry.
+% \begin{macrocode}
+\str_if_eq:VnTF \@currname { lthooks }
+ {
+ \seq_gpush:Nn \g_@@_name_stack_seq { lthooks }
+ \cs_set_protected:Npn \@@_tmp:w #1 #2 #3
+ {
+ \quark_if_recursion_tail_stop:n {#1}
+ \seq_gput_right:Nn \g_@@_name_stack_seq { }
+ \@@_tmp:w
+ }
+ \exp_after:wN \@@_tmp:w
+ \@currnamestack
+ \q_recursion_tail \q_recursion_tail
+ \q_recursion_tail \q_recursion_stop
+ }
+ { \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 }
+ { \ERROR_should_not_happen }
+ }
+% \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}
+ }
+% \begin{macrocode}
+%
+% The push and pop macros are injected in \cs{@pushfilename} and
+% \cs{@popfilename} so that they correctly keep track of the label.s
+% \begin{macrocode}
+% TODO! \pho{Properly integrate in the kernel}
+\tl_gput_left:Nn \@pushfilename { \@@_curr_name_push:n { } }
+\tl_gput_left:Nn \@popfilename { \@@_curr_name_pop: }
+% TODO! \pho{Properly integrate in the kernel}
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+%
+%
+%
+% \begin{macro}{\UseHook}
+% 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}
+\newcommand \UseHook { \hook_use:n }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\UseOneTimeHook}
+%
+% \begin{macrocode}
+\cs_new_protected:Npn \UseOneTimeHook { \hook_use_once:n }
+% \end{macrocode}
+% \end{macro}
+
+
+
+% \begin{macro}{\ShowHook}
+%
+% \begin{macrocode}
+\cs_new_protected:Npn \ShowHook { \hook_log:n }
+% \end{macrocode}
+% \end{macro}
+
+% \begin{macro}{\DebugHookOn,\DebugHookOff}
+%
+% \begin{macrocode}
+\cs_new_protected:Npn \DebugHookOn { \hook_debug_on: }
+\cs_new_protected:Npn \DebugHookOff { \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}{\IfHookExistTF,\IfHookEmptyTF}
+% \begin{macrocode}
+\NewExpandableDocumentCommand \IfHookExistTF { m }
+ { \hook_if_exist:nTF {#1} }
+\NewExpandableDocumentCommand \IfHookEmptyTF { m }
+ { \hook_if_empty:nTF {#1} }
+% \end{macrocode}
+% \end{macro}
+
+
+
+
+% \begin{macro}{\AtBeginDocument}
+%
+% \begin{macrocode}
+\renewcommand\AtBeginDocument{\AddToHook{begindocument}}
+% \end{macrocode}
+% \end{macro}
+
+% \begin{macro}{\AtEndDocument}
+%
+% \begin{macrocode}
+\renewcommand\AtEndDocument {\AddToHook{enddocument}}
+%\renewcommand\AtEndDocument {\AddToHook{env/document/end}} % alternative impl
+% \end{macrocode}
+%
+% \end{macro}
+
+
+
+% \subsection{Set up existing \LaTeXe{} hooks}
+%
+% As we are in a package calling \cs{NewHook} would label any
+% already set up hook code under the package name, but we want it
+% under the name \hook{top-level} so we pretend that \cs{@currname}
+% is empty.
+% \begin{macrocode}
+\begingroup
+ \def\@currname{}
+% \end{macrocode}
+%
+% \begin{macrocode}
+ \NewHook{begindocument}
+ \NewHook{enddocument}
+% \end{macrocode}
+% We need to initialize the mechanism at \verb=\begin{document}=
+% but obviously before everything else, so we sneak\footnote{This
+% needs to move to \cs{document} directly.}
+% \cs{@@_initialize_all:} into the \LaTeXe{} hook name.
+%
+% We can't use \cs{tl_gput_left:Nn} because that complains about
+% \cs{@begindocumenthook} not starting with \cs{g_} so we do this
+% through the backdoor.
+% \begin{macrocode}
+% \tex_global:D\tl_put_left:Nn \@begindocumenthook
+% {\@@_initialize_all:}
+% \end{macrocode}
+% There aren't many other hooks at the moment:
+% \begin{macrocode}
+ \NewHook{rmfamily}
+ \NewHook{sffamily}
+ \NewHook{ttfamily}
+ \NewHook{defaultfamily}
+% \end{macrocode}
+% Not checked what this one does and whether it should be there (or
+% is a real ``hook''.
+% \begin{macrocode}
+ \NewHook{documentclass}
+% \end{macrocode}
+%
+% \begin{macrocode}
+\endgroup
+% \end{macrocode}
+%
+%
+%
+% \section{Generic hooks for environments}
+%
+%
+% \begin{macrocode}
+\let\begin\relax % avoid redeclaration message
+% \end{macrocode}
+%
+% \begin{macrocode}
+\DeclareRobustCommand*\begin[1]{%
+ \UseHook{env/#1/before}%
+ \@ifundefined{#1}%
+ {\def\reserved@a{\@latex@error{Environment~#1~undefined}\@eha}}%
+ {\def\reserved@a{\def\@currenvir{#1}%
+ \edef\@currenvline{\on@line}%
+ \@execute@begin@hook{#1}%
+ \csname #1\endcsname}}%
+ \@ignorefalse
+ \begingroup\@endpefalse\reserved@a}
+% \end{macrocode}
+%
+% Before the \cs{document} code is executed we have to first undo
+% the \cs{endgroup} as there should be none for this environment to
+% avoid that changes on top-level unnecessarily go to \TeX's
+% savestack, and we have to initialize all hooks in the hook system.
+% So we need to test for this environment name. But once it has be
+% found all this testing is no longer needed and so we redefine
+% \cs{@execute@begin@hook} to simply use the hook
+% \begin{macrocode}
+\def\@execute@begin@hook #1{%
+ \expandafter\ifx\csname #1\endcsname\document
+ \endgroup
+ \gdef\@execute@begin@hook##1{\UseHook{env/##1/begin}}%
+ \@@_initialize_all:
+ \@execute@begin@hook{#1}%
+% \end{macrocode}
+% If this is an environment before \verb=\begin{document}= we just
+% run the hook.
+% \begin{macrocode}
+ \else
+ \UseHook{env/#1/begin}%
+ \fi
+}
+% \end{macrocode}
+%
+% \begin{macrocode}
+\@namedef{end~}#1{%
+ \UseHook{env/#1/end}%
+ \csname end#1\endcsname\@checkend{#1}%
+ \expandafter\endgroup\if@endpe\@doendpe\fi
+ \UseHook{env/#1/after}%
+ \if@ignore\@ignorefalse\ignorespaces\fi}%
+% \end{macrocode}
+% Version that fixes tlb3722 but the change should perhaps be made in
+% \pkg{tabularx} instead.
+% \begin{macrocode}
+\@namedef{end~}#1{%
+\romannumeral
+\IfHookEmptyTF{env/#1/end}%
+ {\expandafter\z@}%
+ {\z@\UseHook{env/#1/end}}%
+\csname end#1\endcsname\@checkend{#1}%
+\expandafter\endgroup\if@endpe\@doendpe\fi
+\UseHook{env/#1/after}%
+\if@ignore\@ignorefalse\ignorespaces\fi}%
+% \end{macrocode}
+%
+%
+%
+% We provide 4 high-level hook interfaces directly, the others only when
+% etoolbox is loaded
+% \begin{macrocode}
+\newcommand\AtBeginEnvironment[1] {\AddToHook{env/#1/begin}}
+\newcommand\AtEndEnvironment[1] {\AddToHook{env/#1/end}}
+\newcommand\BeforeBeginEnvironment[1]{\AddToHook{env/#1/before}}
+\newcommand\AfterEndEnvironment[1] {\AddToHook{env/#1/after}}
+% \end{macrocode}
+%
+%
+%
+%
+%
+%
+%
+%
+%
+%
+%
+% \section{Generic hooks for file loads}
+%
+%
+%
+%
+%
+%
+%
+% \section{Hooks in \cs{begin} document}
+%
+% Can't have \texttt{@{}@} notation here as this is \LaTeXe{} code
+% \ldots{} and makes for puzzling errors if the double \texttt{@}
+% signs get substituted.
+% \begin{macrocode}
+%<@@=>
+\ExplSyntaxOff
+% \end{macrocode}
+%
+% The \hook{begindocument} hook was already set up earlier, here is now
+% the additional one (which was originally from the \pkg{etoolbox}
+% package under the name \texttt{afterpreamble}.
+% \begin{macrocode}
+\NewHook{begindocument/end}
+% \end{macrocode}
+%
+%
+
+% \begin{macro}{\document}
+%
+% \begin{macrocode}
+\def\document{%
+% \end{macrocode}
+% We do cancel the grouping as part of the \cs{begin} handling
+% (this is now done inside \cs{begin} instead) so that the
+% \hook{env/\meta{env}/begin} hook is not hidden inside \cs{begingroup}
+% \texttt{...} \cs{endgroup}.
+% \begin{macrocode}
+% \endgroup
+% \end{macrocode}
+%
+% \begin{macrocode}
+ \@kernel@after@env@document@begin
+% \end{macrocode}
+%
+% Added hook to load \textsf{l3backend} code:
+% \begin{macrocode}
+ \@expl@sys@load@backend@@
+ \ifx\@unusedoptionlist\@empty\else
+ \@latex@warning@no@line{Unused global option(s):^^J%
+ \@spaces[\@unusedoptionlist]}%
+ \fi
+ \@colht\textheight
+ \@colroom\textheight \vsize\textheight
+ \columnwidth\textwidth
+ \@clubpenalty\clubpenalty
+ \if@twocolumn
+ \advance\columnwidth -\columnsep
+ \divide\columnwidth\tw@ \hsize\columnwidth \@firstcolumntrue
+ \fi
+ \hsize\columnwidth \linewidth\hsize
+ \begingroup\@floatplacement\@dblfloatplacement
+ \makeatletter\let\@writefile\@gobbletwo
+ \global \let \@multiplelabels \relax
+ \@input{\jobname.aux}%
+ \endgroup
+ \if@filesw
+ \immediate\openout\@mainaux\jobname.aux
+ \immediate\write\@mainaux{\relax}%
+ \fi
+ \process@table
+ \let\glb@currsize\@empty % Force math initialization.
+ \normalsize
+ \everypar{}%
+ \ifx\normalsfcodes\@empty
+ \ifnum\sfcode`\.=\@m
+ \let\normalsfcodes\frenchspacing
+ \else
+ \let\normalsfcodes\nonfrenchspacing
+ \fi
+ \fi
+ \ifx\document@default@language\m@ne
+ \chardef\document@default@language\language
+ \fi
+ \@noskipsecfalse
+ \let \@refundefined \relax
+% \end{macrocode}
+%
+% \begin{macrocode}
+% \let\AtBeginDocument\@firstofone
+% \@begindocumenthook
+ \UseOneTimeHook{begindocument}%
+ \@kernel@after@begindocument
+% \end{macrocode}
+%
+% \begin{macrocode}
+ \ifdim\topskip<1sp\global\topskip 1sp\relax\fi
+ \global\@maxdepth\maxdepth
+ \global\let\@begindocumenthook\@undefined
+ \ifx\@listfiles\@undefined
+ \global\let\@filelist\relax
+ \global\let\@addtofilelist\@gobble
+ \fi
+ \gdef\do##1{\global\let ##1\@notprerr}%
+ \@preamblecmds
+ \global\let \@nodocument \relax
+ \global\let\do\noexpand
+% \end{macrocode}
+%
+% \begin{macrocode}
+ \UseOneTimeHook{begindocument/end}%
+ \ignorespaces}
+% \end{macrocode}
+%
+% \begin{macrocode}
+\let\@kernel@after@begindocument\@empty
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \begin{macro}{\@kernel@after@env@document@begin,\@kernel@hook@begindocument}
+%
+% \begin{macrocode}
+\edef \@kernel@after@env@document@begin{%
+ \let\expandafter\noexpand\csname
+ g__hook_env/document/begin_code_tl\endcsname
+ \noexpand\@empty}
+% \end{macrocode}
+% \begin{macrocode}
+\let\@kernel@hook@begindocument\@empty
+% \end{macrocode}
+%
+% \end{macro}
+%
+%
+% \section{Hooks in \cs{enddocument}}
+%
+%
+%
+% The \hook{enddocument} hook was already set up earlier, here are now
+% the additional ones:
+% \begin{macrocode}
+\NewHook{enddocument/afterlastpage}
+\NewHook{enddocument/afteraux}
+\NewHook{enddocument/info}
+\NewHook{enddocument/end}
+% \end{macrocode}
+
+
+
+% \begin{macro}{\enddocument}
+%
+%
+% \begin{macrocode}
+\def\enddocument{%
+ \UseHook{enddocument}%
+ \@kernel@after@enddocument
+ \@checkend{document}%
+ \clearpage
+ \UseHook{enddocument/afterlastpage}%
+ \@kernel@after@enddocument@afterlastpage
+ \begingroup
+ \if@filesw
+ \immediate\closeout\@mainaux
+ \let\@setckpt\@gobbletwo
+ \let\@newl@bel\@testdef
+ \@tempswafalse
+ \makeatletter \@@input\jobname.aux
+ \fi
+ \UseHook{enddocument/afteraux}%
+% \end{macrocode}
+% Next hook is expect to contain only code for writing info
+% messages on the terminal.
+% \begin{macrocode}
+ \UseHook{enddocument/info}%
+ \endgroup
+ \UseHook{enddocument/end}%
+ \deadcycles\z@\@@end}
+% \end{macrocode}
+% The two kernel hooks above are used by the shipout code.
+% \begin{macrocode}
+\let\@kernel@after@enddocument\@empty
+\let\@kernel@after@enddocument@afterlastpage\@empty
+% \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \begin{macro}{\@enddocument@kernel@warnings}
+%
+% \begin{macrocode}
+\def\@enddocument@kernel@warnings{%
+ \ifdim \font@submax >\fontsubfuzz\relax
+ \@font@warning{Size substitutions with differences\MessageBreak
+ up to \font@submax\space have occurred.\@gobbletwo}%
+ \fi
+ \@defaultsubs
+ \@refundefined
+ \if@filesw
+ \ifx \@multiplelabels \relax
+ \if@tempswa
+ \@latex@warning@no@line{Label(s) may have changed.
+ Rerun to get cross-references right}%
+ \fi
+ \else
+ \@multiplelabels
+ \fi
+ \fi
+}
+% \end{macrocode}
+%
+% \begin{macrocode}
+\AddToHook{enddocument/info}[kernel/filelist]{\@dofilelist}
+\AddToHook{enddocument/info}[kernel/warnings]{\@enddocument@kernel@warnings}
+\DeclareHookRule{enddocument/info}{kernel/filelist}{before}{kernel/warnings}
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \subsection{Adjusting at \pkg{atveryend} interfaces}
+%
+% With the new hook management all of \pkg{atveryend} is taken care
+% of.
+%
+% We therefore prevent the package from loading:
+% \begin{macrocode}
+\expandafter\let\csname ver@atveryend.sty\endcsname\fmtversion
+% \end{macrocode}
+%
+%
+% Here are new definitions for its interfaces now pointing to the
+% hooks in \cs{enddocument}
+% \begin{macrocode}
+\newcommand\AfterLastShipout {\AddToHook{enddocument/afterlastpage}}
+\newcommand\AtVeryEndDocument {\AddToHook{enddocument/afteraux}}
+% \end{macrocode}
+% Next one is a bit of a fake, but the result should normally be as
+% expected. If not one needs to add a rule to sort the code chunks
+% in \hook{enddocument/info}.
+% \begin{macrocode}
+\newcommand\AtEndAfterFileList{\AddToHook{enddocument/info}}
+% \end{macrocode}
+%
+% \begin{macrocode}
+\newcommand\AtVeryVeryEnd {\AddToHook{enddocument/end}}
+% \end{macrocode}
+
+% \begin{macro}{\BeforeClearDocument}
+% This one is the only one we don't implement or rather don't have
+% a dedicated hook in the code.
+% \begin{macrocode}
+\ExplSyntaxOn
+\newcommand\BeforeClearDocument[1]
+ { \AtEndDocument{#1}
+ \@DEPRECATED{BeforeClearDocument \tl_to_str:n{#1}}
+ }
+\cs_new:Npn\@DEPRECATED #1
+ {\iow_term:x{======~DEPRECATED~USAGE~#1~==========}}
+% \end{macrocode}
+%
+% \begin{macrocode}
+\ExplSyntaxOff
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macrocode}
+%</2ekernel>
+% \end{macrocode}
+
+%
+% \section{A package version of the code for testing}
+%
+
+% \begin{macrocode}
+%<*package>
+% \end{macrocode}
+
+
+
+% \begin{macrocode}
+\RequirePackage{xparse}
+\ProvidesExplPackage{lthooks}{\lthooksdate}{\lthooksversion}
+ {Hook management interface for LaTeX2e}
+% \end{macrocode}
+%
+%
+% \subsection{Core hook management code (kernel part)}
+%
+% This should run in older formats so we can't use
+% \cs{IfFormatAtLeastTF} right now.
+% \begin{macrocode}
+\@ifl@t@r\fmtversion{2020/10/01}
+ {}
+ {\input{lthooks.ltx}
+ \input{ltshipout.ltx}
+ \input{ltfilehook.ltx}
+ }
+% \end{macrocode}
+%
+% \subsection{Package options}
+%
+% For now we offer a simple debug option which turns on a lot of
+% strange \cs{typeout} messages, nothing fancy.
+% \begin{macrocode}
+\ExplSyntaxOn
+% \end{macrocode}
+%
+% \begin{macrocode}
+\hook_debug_off:
+\DeclareOption { debug } { \hook_debug_on:
+ \shipout_debug_on: }
+% \end{macrocode}
+%
+%
+% For now we offer a simple debug option which turns on a lot of
+% strange \cs{typeout} messages, nothing fancy.
+% \begin{macrocode}
+\shipout_debug_off:
+\DeclareOption { debug-shipout } { \shipout_debug_on: }
+% \end{macrocode}
+%
+% \begin{macrocode}
+\ProcessOptions
+% \end{macrocode}
+%
+%
+% \subsection{Temporarily patching package until changed}
+%
+%
+%
+% \pkg{filehook} support until that package is patched:
+% \begin{macrocode}
+\RequirePackage{filehook-ltx}
+% \end{macrocode}
+%
+%
+%
+% \begin{macrocode}
+%</package>
+% \end{macrocode}
+%
+%
+%
+% \Finale
+%
+
+