From bea33e16f382ecf8188f0b613d553f8367f8d0d3 Mon Sep 17 00:00:00 2001
From: Karl Berry <karl@freefriends.org>
Date: Fri, 9 Jun 2023 20:19:52 +0000
Subject: latex2e (9jun23)

git-svn-id: svn://tug.org/texlive/trunk@67314 c570f23f-e606-0410-a88d-b1316a301751
---
 Master/texmf-dist/source/latex/base/lthooks.dtx | 2823 ++++++++++++++++++++---
 1 file changed, 2502 insertions(+), 321 deletions(-)

(limited to 'Master/texmf-dist/source/latex/base/lthooks.dtx')

diff --git a/Master/texmf-dist/source/latex/base/lthooks.dtx b/Master/texmf-dist/source/latex/base/lthooks.dtx
index cfa1454775d..3807d25c44b 100644
--- a/Master/texmf-dist/source/latex/base/lthooks.dtx
+++ b/Master/texmf-dist/source/latex/base/lthooks.dtx
@@ -1,7 +1,7 @@
 % \iffalse meta-comment
 %
-% Copyright (C) 2020-2022
-%       Frank Mittelbach, Phelype Oleinik & LaTeX Team
+% Copyright (C) 2020-2023
+%       Frank Mittelbach, Phelype Oleinik, The LaTeX Project
 %
 % This file is part of the LaTeX base system.
 % -------------------------------------------
@@ -31,8 +31,8 @@
 %%% From File: lthooks.dtx
 %
 %    \begin{macrocode}
-\def\lthooksversion{v1.0v}
-\def\lthooksdate{2022/06/15}
+\def\lthooksversion{v1.1e}
+\def\lthooksdate{2023/06/06}
 %    \end{macrocode}
 %
 %<*driver>
@@ -148,7 +148,6 @@
 %    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}
@@ -156,7 +155,45 @@
 %     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 \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}{\NewHookWithArguments}
+%   \begin{syntax}
+%     \cs{NewHookWithArguments} \Arg{hook} \Arg{number}
+%   \end{syntax}
+%   Creates a new \meta{hook} whose code takes \meta{number} arguments,
+%   and otherwise works exactly like \cs{NewHook}.
+%   Section~\ref{sec:hook-args} explains hooks with arguments.
+%
+%    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}{\NewReversedHookWithArguments}
+%   \begin{syntax}
+%     \cs{NewReversedHookWithArguments} \Arg{hook} \Arg{number}
+%   \end{syntax}
+%   Like \cs{NewReversedHook}, but creates a hook whose code takes
+%   \meta{number} arguments.
+%   Section~\ref{sec:hook-args} explains hooks with arguments.
+%
+%    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}{\NewMirroredHookPairWithArguments}
+%   \begin{syntax}
+%     \cs{NewMirroredHookPairWithArguments} \Arg{hook-1} \Arg{hook-2} \Arg{number}
+%   \end{syntax}
+%   A shorthand for
+%   \cs{NewHookWithArguments}\Arg{hook-1}\Arg{number}\\
+%   \cs{NewReversedHookWithArguments}\Arg{hook-2}\Arg{number}.
+%   Section~\ref{sec:hook-args} explains hooks with arguments.
+%
+%    The \meta{hook} can be specified using the dot-syntax to denote
 %    the current package name. See section~\ref{sec:default-label}.
 % \end{function}
 %
@@ -173,9 +210,9 @@
 %   \begin{syntax}
 %     \cs{DisableGenericHook} \Arg{hook}
 %   \end{syntax}
-%    After this declaration\footnotemark{} the \meta{hook} is no longer usable: Any
-%    attempt to add further code to it will result in an error and any
-%    use, e.g., via \cs{UseHook}, will simply do nothing.
+%    After this declaration\footnotemark{} the \meta{hook} is no longer
+%    usable: Any further attempt to add code to it will result in an
+%    error and any use, e.g., via \cs{UseHook}, will simply do nothing.
 %
 %    This is intended to be used with generic command hooks (see
 %    \texttt{ltcmdhooks-doc}) as depending on the definition of the
@@ -215,7 +252,7 @@
 %   \begin{syntax}
 %     \cs{UseHook} \Arg{hook}
 %   \end{syntax}
-%    Execute the hook code inside a command or environment.
+%    Execute the code stored in the \meta{hook}.
 %
 %    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
@@ -227,14 +264,30 @@
 %    A leading |.| is treated literally.
 % \end{function}
 %
+% \begin{function}{\UseHookWithArguments}
+%   \begin{syntax}
+%     \cs{UseHookWithArguments} \Arg{hook} \Arg{number} \Arg{arg_1} \ldots \Arg{arg_n}
+%   \end{syntax}
+%    Execute the code stored in the \meta{hook} and pass the arguments
+%    \Arg{arg_1} through \Arg{arg_n} to the \meta{hook}.  Otherwise, it
+%    works exactly like \cs{UseHook}.
+%    The \meta{number} should be the number of arguments declared for
+%    the hook.  If the hook is not declared, this command does nothing
+%    and it will remove \meta{number} items from the input.
+%   Section~\ref{sec:hook-args} explains hooks with arguments.
+%
+%    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[no-index]{\meta{addto-cmd}} command (e.g.,
+%    \verb=\end{document}=. From that point onwards, adding to the hook
+%    through a defined \cs[no-index]{\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
@@ -250,15 +303,30 @@
 %    section~\ref{sec:default-label} for details.
 %
 %    Using \cs{UseOneTimeHook} several times with the same
-%    \Arg{hook} means that it only executes the first time it is used. 
+%    \Arg{hook} means that it only executes the first time it is used.
 %    For example, if it is used in a command that can be called several times
-%    then the hook executes during only the \emph{first} invocation of that 
+%    then the hook executes during only the \emph{first} invocation of that
 %    command; this allows its use as an \enquote{initialization hook}.
 %
 %    Mixing \cs{UseHook} and \cs{UseOneTimeHook} for the same
 %    \Arg{hook} should be avoided, but if this is done then neither will execute
 %    after the first \cs{UseOneTimeHook}.
+% \end{function}
 %
+% \begin{function}{\UseOneTimeHookWithArguments}
+%   \begin{syntax}
+%     \cs{UseOneTimeHookWithArguments} \Arg{hook} \Arg{number} \Arg{arg_1} \ldots \Arg{arg_n}
+%   \end{syntax}
+%    Works exactly like \cs{UseOneTimeHook}, but passes arguments
+%    \Arg{arg_1} through \Arg{arg_n} to the \meta{hook}.
+%    The \meta{number} should be the number of arguments declared for
+%    the hook.  If the hook is not declared, this command does nothing
+%    and it will remove \meta{number} items from the input.
+%
+%    It should be noted that after a one-time hook is used, it is no
+%    longer possible to use \cs{AddToHookWithArguments} or similar with
+%    that hook.  \cs{AddToHook} continues to work as normal.
+%   Section~\ref{sec:hook-args} explains hooks with arguments.
 % \end{function}
 %
 %
@@ -294,6 +362,34 @@
 %    See section~\ref{sec:default-label}.
 % \end{function}
 %
+% \begin{function}{\AddToHookWithArguments}
+%   \begin{syntax}
+%     \cs{AddToHookWithArguments} \Arg{hook}\oarg{label}\Arg{code}
+%   \end{syntax}
+%    Works exactly like \cs{AddToHook}, except that the \meta{code} can
+%    access the arguments passed to the hook using \verb|#1|, \verb|#2|,
+%    \ldots, \verb|#n| (up to the number of arguments declared for the
+%    hook).  If the \meta{code} should contain \emph{parameter tokens}
+%    (\verb|#|) that are not supposed to be understood as the arguments
+%    of the hook, such tokens should be doubled.  For example, with
+%    \cs{AddToHook} one can write:
+%\begin{verbatim}
+%   \AddToHook{myhook}{\def\foo#1{Hello, #1!}}
+%\end{verbatim}
+%    but to achieve the same with \cs{AddToHookWithArguments}, one should
+%    write:
+%\begin{verbatim}
+%   \AddToHookWithArguments{myhook}{\def\foo##1{Hello, ##1!}}
+%\end{verbatim}
+%    because in the latter case, \verb|#1| refers to the first argument
+%    of the hook \hook{myhook}.
+%   Section~\ref{sec:hook-args} explains hooks with arguments.
+%
+%    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}
@@ -403,6 +499,19 @@
 % \end{function}\footnotetext{There is
 %    no mechanism to reorder such code chunks (or delete them).}
 %
+% \begin{function}{\AddToHookNextWithArguments}
+%   \begin{syntax}
+%     \cs{AddToHookNextWithArguments} \Arg{hook}\Arg{code}
+%   \end{syntax}
+%    Works exactly like \cs{AddToHookNext}, but the \meta{code} can
+%    contain references to the arguments of the \meta{hook} as described
+%    for \cs{AddToHookWithArguments} above.
+%   Section~\ref{sec:hook-args} explains hooks with arguments.
+%
+%    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}{\ClearHookNext}
@@ -511,10 +620,10 @@
 % 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
+% the code in logical parts, but still use the main package name as the
 % \meta{label}, then the \meta{default label} can be set using
-% \cs{SetDefaultHookLabel} or
-% \cs{PushDefaultHookLabel}\verb={..}=\,\ldots\cs{PopDefaultHookLabel}.
+% \cs{PushDefaultHookLabel}\verb={...}=\,\ldots\cs{PopDefaultHookLabel}
+% or \cs{SetDefaultHookLabel}\verb={...}=.
 %
 % \begin{function}{\PushDefaultHookLabel,\PopDefaultHookLabel}
 %   \begin{syntax}
@@ -906,8 +1015,11 @@
 % 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{function}{
+%     \hook_new:n,
+%     \hook_new_reversed:n,
+%     \hook_new_pair:nn
+%   }
 %   \begin{syntax}
 %     \cs{hook_new:n} \Arg{hook}
 %     \cs{hook_new_reversed:n} \Arg{hook}
@@ -923,6 +1035,27 @@
 %    the current package name. See section~\ref{sec:default-label}.
 % \end{function}
 %
+% \begin{function}{
+%     \hook_new_with_args:nn,
+%     \hook_new_reversed_with_args:nn,
+%     \hook_new_pair_with_args:nnn
+%   }
+%   \begin{syntax}
+%     \cs{hook_new_with_args:nn} \Arg{hook} \Arg{number}
+%     \cs{hook_new_reversed_with_args:nn} \Arg{hook} \Arg{number}
+%     \cs{hook_new_pair_with_args:nnn} \Arg{hook-1} \Arg{hook-2} \Arg{number}
+%   \end{syntax}
+%   Creates a new \meta{hook} with normal or reverse ordering of code
+%    chunks, that takes \meta{number} arguments from the input stream
+%    when it is used. \cs{hook_new_pair_with_args: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_disable_generic:n}
@@ -958,37 +1091,62 @@
 %
 %
 %
-% \begin{function}{\hook_use:n}
+% \begin{function}{\hook_use:n,\hook_use:nnw}
 %   \begin{syntax}
 %     \cs{hook_use:n} \Arg{hook}
+%     \cs{hook_use:nnw} \Arg{hook} \Arg{number} \Arg{arg_1} \ldots \Arg{arg_n}
 %   \end{syntax}
 %    Executes the \Arg{hook} code followed (if set up) by the code for next
 %    invocation only, then empties that next invocation code.
+%    \cs{hook_use:nnw} should be used for hooks declared with arguments,
+%    and should be followed by as many brace groups as the declared
+%    number of arguments.
+%    The \meta{number} should be the number of arguments declared for
+%    the hook.  If the hook is not declared, this command does nothing
+%    and it will remove \meta{number} items from the input.
 %
 %    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{function}{\hook_use_once:n,\hook_use_once:nnw}
 %   \begin{syntax}
 %     \cs{hook_use_once:n} \Arg{hook}
+%     \cs{hook_use_once:nnw} \Arg{hook} \Arg{number} \Arg{arg_1} \ldots \Arg{arg_n}
 %   \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.
+%    \cs{hook_use_once:nnw} should be used for hooks declared with arguments,
+%    and should be followed by as many brace groups as the declared
+%    number of arguments.
+%    The \meta{number} should be the number of arguments declared for
+%    the hook.  If the hook is not declared, this command does nothing
+%    and it will remove \meta{number} items from the input.
 %
 %    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{function}{
+%     \hook_gput_code:nnn,
+%     \hook_gput_code_with_args:nnn
+%   }
 %   \begin{syntax}
 %     \cs{hook_gput_code:nnn} \Arg{hook} \Arg{label} \Arg{code}
+%     \cs{hook_gput_code_with_args: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 \cs{hook_gput_code_with_args:nnn} is used, the \meta{code}
+%    can access the arguments passed to \cs{hook_use:nnw}
+%    (or~\cs{hook_use_once:nnw}) with \verb|#1|, \verb|#2|, \ldots,
+%    \verb|#n| (up to the number of arguments declared for the hook).
+%    In that case, if an actual parameter token should be added to the
+%    code, it should be doubled.
+%
 %    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
@@ -999,18 +1157,26 @@
 %    See section~\ref{sec:default-label}.
 % \end{function}
 %
-% \begin{function}
-%   {\hook_gput_next_code:nn}
+% \begin{function}{
+%     \hook_gput_next_code:nn,
+%     \hook_gput_next_code_with_args: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.
 %
+%    If \cs{hook_gput_next_code_with_args:nn} is used, the \meta{code}
+%    can access the arguments passed to \cs{hook_use:nnw}
+%    (or~\cs{hook_use_once:nnw}) with \verb|#1|, \verb|#2|, \ldots,
+%    \verb|#n| (up to the number of arguments declared for the hook).
+%    In that case, if an actual parameter token should be added to the
+%    code, it should be doubled.
+%
 %    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}.
 %
@@ -1019,8 +1185,7 @@
 % \end{function}
 %
 %
-% \begin{function}
-%   {\hook_gclear_next_code:n}
+% \begin{function}{\hook_gclear_next_code:n}
 %   \begin{syntax}
 %     \cs{hook_gclear_next_code:n} \Arg{hook}
 %   \end{syntax}
@@ -1240,7 +1405,7 @@
 %    \begin{itshape}\begin{color}{blue} ...
 %    \end{itshape}\end{color}
 %\end{verbatim}
-%   and an error message that \verb=\begin{color}= ended by
+%    and an error message saying that \verb=\begin{color}= was 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
@@ -1377,9 +1542,9 @@
 %    you typically do not know what kind of variable parts may come up
 %    in real documents.
 %
-%    For example, \pkg{babel} may want to provide hooks such as
-%    \hook{babel/\meta{language}/afterextras}.  Language support in
-%    \pkg{babel} is often done through external language
+%    For example, \pkg{babel} provides hooks such as
+%    \hook{babel/\meta{language}/afterextras}.  However, language
+%    support in \pkg{babel} is often done through external language
 %    packages. Thus doing the activation for all languages inside the
 %    core \pkg{babel} code is not a viable approach. Instead it needs
 %    to be done by each language package (or by the user who wants to
@@ -1396,6 +1561,125 @@
 %    conciderably.
 %
 %
+% \subsection{Hooks with arguments}
+% \label{sec:hook-args}
+%
+%    Sometimes it is necessary to pass contextual information to a hook,
+%    and, for one reason or another, it is not feasible to store such
+%    information in macros.  To serve this purpose, hooks can be
+%    declared with arguments, so that the programmer can pass along the
+%    data necessary for the code in the hook to function properly.
+%
+%    A hook with arguments works mostly like a regular hook, and most
+%    commands that work for regular hooks, also work for hooks that take
+%    arguments.  The differences are when the hook is declared
+%    (\cs{NewHookWithArguments} is used instead of \cs{NewHook}), then
+%    code can be added with both \cs{AddToHook} and
+%    \cs{AddToHookWithArguments}, and when the hook is used
+%    (\cs{UseHookWithArguments} instead of \cs{UseHook}).
+%
+%    \medskip
+%
+%    A hook with arguments must be declared as such (before it is first
+%    used, as all regular hooks) using
+%    \cs{NewHookWithArguments}\Arg{hook}\Arg{number}.  All code added to
+%    that hook can then use \verb|#1| to access the first argument,
+%    \verb|#2| to access the second, and so forth up to the number of
+%    arguments declared.  However, it is still possible to add code with
+%    references to the arguments of a hook that was not yet declared
+%    (we will discuss that later).  At their core, hooks are macros, so
+%    \TeX's limit of 9~arguments applies, and a low-level \TeX{} error
+%    is raised if you try to reference an argument number that doesn't
+%    exist.
+%
+%    \medskip
+%
+%    To use a hook with arguments, just write
+%    \cs{UseHookWithArguments}\Arg{hook}\Arg{number} followed by a
+%    braced list of the arguments.  For example, if the hook \hook{test}
+%    takes three arguments, write:
+%\begin{verbatim}
+%    \UseHookWithArguments{test}{3}{arg-1}{arg-2}{arg-3}
+%\end{verbatim}
+%    then, in the \meta{code} of the hook, all instances of \verb|#1|
+%    will be replaced by \verb|arg-1|, \verb|#2| by \verb|arg-2| and so
+%    on.  If, at the point of usage, the programmer provides more
+%    arguments than the hook is declared to take, the excess arguments
+%    are simply ignored by the hook.  Behaviour is
+%    unpredictable\footnote
+%      {The hook \emph{will} take the declared number of arguments, and
+%       what will happen depends on what was grabbed, and what the hook
+%       code does with its arguments.}
+%    if too few arguments are provided.  If the hook isn't declared,
+%    \meta{number} arguments are removed from the input stream.
+%
+%    \medskip
+%
+%    Adding code to a hook with arguments can be done with
+%    \cs{AddToHookWithArguments} as well as with the regular
+%    \cs{AddToHook}, to achieve different outcomes.  The main difference
+%    when it comes to adding code to a hook, in this case, is firstly
+%    the possibility of accessing a hook's arguments, of course, and
+%    second, how parameter tokens (\verb|#|$_6$) are treated.
+%
+%    Using \cs{AddToHook} in a hook that takes arguments will work as it
+%    does for all other hooks. This allows a package developer to add
+%    arguments to a hook that otherwise had none without having to worry
+%    about compatibility.  This means that, for example:
+%\begin{verbatim}
+%    \AddToHook{test}{\def\foo#1{Hello, #1!}}
+%\end{verbatim}
+%    will define the same macro \cs[no-index]{foo} regardless if the
+%    hook \hook{test} takes arguments or not.
+%
+%    Using \cs{AddToHookWithArguments} allows the \meta{code} added to
+%    access the arguments of the hook with \verb|#1|, \verb|#2|, and so
+%    forth, up to the number of the arguments declared in the hook.
+%    This means that if one wants to add a \verb|#|$_6$ to the
+%    \meta{code} that token must be doubled in the input.  The same
+%    definition from above, using \cs{AddToHookWithArguments}, needs to
+%    be rewritten:
+%\begin{verbatim}
+%    \AddToHookWithArguments{test}{\def\foo##1{Hello, ##1!}}
+%\end{verbatim}
+%
+%    Extending the above example to use the hook arguments, we could
+%    rewrite something like (now from declaration to usage, to get the
+%    whole picture):
+%\begin{verbatim}
+%    \NewHookWithArguments{test}{1}
+%    \AddToHookWithArguments{test}{%
+%      \typeout{Defining foo with "#1"}
+%      \def\foo##1{Hello, ##1! Some text after: #1}%
+%    }
+%    \UseHook{test}{Howdy!}
+%    \ShowCommand\foo
+%\end{verbatim}
+%    Running the code above prints in the terminal:
+%\begin{verbatim}
+%    Defining foo with "Howdy!"
+%    > \foo=macro:
+%    #1->Hello, #1! Some text after: Howdy!.
+%\end{verbatim}
+%    Note how \verb|##1| in the call to \cs{AddToHookWithArguments}
+%    became \verb|#1|, and the \verb|#1| was replaced by the argument
+%    passed to the hook.  Should the hook be used again, with a
+%    different argument, the definition would naturally change.
+%
+%    \bigskip
+%
+%    It is possible to add code referencing a hook's arguments before
+%    such hook is declared and the number of hooks is fixed.  However,
+%    if some code is added to the hook, that references more arguments
+%    than will be declared for the hook, there will be a low-level
+%    \TeX{} error about an \enquote{Illegal parameter number} at the
+%    time the hook is declared, which will be hard to track down because
+%    at that point \TeX{} can't know whence the offending code came
+%    from.  Thus it is important that package writers explicitly
+%    document how many arguments (if any) each hook can take, so users
+%    of those packages know how many arguments can be referenced, and
+%    equally important, what each argument means.
+%
 % \subsection{Private \LaTeX{} kernel hooks}
 %
 %    There are a few places where it is absolutely essential for
@@ -1461,7 +1745,7 @@
 %    package/class name if called inside such a file
 %    (see section~\ref{sec:default-label}).
 %
-%    This way one can add further code to the hook using
+%    This way one can add 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 another package's code.  When using the
@@ -1746,10 +2030,10 @@
 %
 % \subsection{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
+%    \LaTeXe{} has always provided \cs{AtEndDocument} to add code to the
+%    \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
+%    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.
@@ -2095,13 +2379,37 @@
 %    \end{macrocode}
 % \end{macro}
 %
-% \begin{macro}[int]{\tl_gremove_once:Nx,\tl_show:x,\tl_log:x}
+% \begin{macro}{\c_@@_empty_tl}
+% \begin{macro}{\c_@@_nine_parameters_tl}
+%   An empty token list, and one containing nine parameters.
+% \changes{v1.1a}{2023/04/06}
+%         {Add auxiliary token lists (hook-args).}
+%    \begin{macrocode}
+\tl_const:Nn \c_@@_empty_tl { }
+\tl_const:Nn \c_@@_nine_parameters_tl { #1#2#3#4#5#6#7#8#9 }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[int]{
+%     \tl_gremove_once:Nx,
+%     \tl_show:x,
+%     \tl_log:x,
+%     \tl_set:Ne,
+%     \cs_replacement_spec:c,
+%     \prop_put:Nne,
+%     \str_count:e
+%   }
 %   Some variants of \pkg{expl3} functions.
 %   \fmiinline{should probably be moved to expl3}
 %    \begin{macrocode}
 \cs_generate_variant:Nn \tl_gremove_once:Nn { Nx }
 \cs_generate_variant:Nn \tl_show:n { x }
 \cs_generate_variant:Nn \tl_log:n { x }
+\cs_generate_variant:Nn \tl_set:Nn { Ne }
+\cs_generate_variant:Nn \cs_replacement_spec:N { c }
+\cs_generate_variant:Nn \prop_put:Nnn { Nne }
+\cs_generate_variant:Nn \str_count:n { e }
 %    \end{macrocode}
 % \end{macro}
 %
@@ -2112,15 +2420,20 @@
 %    \end{macrocode}
 % \end{macro}
 %
-% \begin{macro}{\@@_clean_to_scan:w}
+% \begin{macro}{
+%     \@@_use_none_delimit_by_s_mark:w,
+%     \@@_use_i_delimit_by_s_mark:nw
+%   }
 %   Removes tokens until the next \cs{s_@@_mark}.
+% \changes{v1.1a}{2023/04/06}
+%         {Use standard naming scheme (hook-args).}
 %    \begin{macrocode}
-\cs_new:Npn \@@_clean_to_scan:w #1 \s_@@_mark { }
+\cs_new:Npn \@@_use_none_delimit_by_s_mark:w #1 \s_@@_mark { }
+\cs_new:Npn \@@_use_i_delimit_by_s_mark:nw #1 #2 \s_@@_mark {#1}
 %    \end{macrocode}
 % \end{macro}
 %
-% \begin{macro}{\@@_tl_set:Nn,\@@_tl_set:Nx,
-%               \@@_tl_set:cn,\@@_tl_set:cx}
+% \begin{macro}{\@@_tl_set:cn}
 %   Private copies of a few \pkg{expl3} functions.  \pkg{l3debug} will
 %   only add debugging to the public names, not to these copies, so we
 %   don't have to use \cs{debug_suspend:} and \cs{debug_resume:}
@@ -2130,44 +2443,43 @@
 %   copied because in \pkg{expl3} they use
 %   \cs[no-index]{__kernel_tl_(g)set:Nx}, which is also patched by
 %   \pkg{l3debug}.
-%   \changes{v1.0h}{2021/01/07}{Manually define some \pkg{l3tl} commands
+% \changes{v1.0h}{2021/01/07}{Manually define some \pkg{l3tl} commands
 %     to work around \pkg{expl3} changes}
+% \changes{v1.1a}{2023/04/06}
+%         {Clean-up unused variants (hook-args).}
 %    \begin{macrocode}
-\cs_new_protected:Npn \@@_tl_set:Nn #1#2
-  { \cs_set_nopar:Npx #1 { \__kernel_exp_not:w {#2} } }
-\cs_new_protected:Npn \@@_tl_set:Nx #1#2
-  { \cs_set_nopar:Npx #1 {#2} }
-\cs_generate_variant:Nn \@@_tl_set:Nn { c }
-\cs_generate_variant:Nn \@@_tl_set:Nx { c }
+\cs_new_protected:Npn \@@_tl_set:cn #1#2
+  { \cs_set_nopar:cpx {#1} { \__kernel_exp_not:w {#2} } }
 %    \end{macrocode}
 % \end{macro}
 %
-% \begin{macro}{\@@_tl_gset:Nn,\@@_tl_gset:No,\@@_tl_gset:Nx,
+% \begin{macro}{\@@_tl_gset:Nn,\@@_tl_gset:Nx,
 %               \@@_tl_gset:cn,\@@_tl_gset:co,\@@_tl_gset:cx}
 %   Same as above.
 %    \begin{macrocode}
 \cs_new_protected:Npn \@@_tl_gset:Nn #1#2
   { \cs_gset_nopar:Npx #1 { \__kernel_exp_not:w {#2} } }
-\cs_new_protected:Npn \@@_tl_gset:No #1#2
-  { \cs_gset_nopar:Npx #1 { \__kernel_exp_not:w \exp_after:wN {#2} } }
 \cs_new_protected:Npn \@@_tl_gset:Nx #1#2
   { \cs_gset_nopar:Npx #1 {#2} }
-\cs_generate_variant:Nn \@@_tl_gset:Nn { c }
-\cs_generate_variant:Nn \@@_tl_gset:No { c }
+\cs_generate_variant:Nn \@@_tl_gset:Nn { c, co }
 \cs_generate_variant:Nn \@@_tl_gset:Nx { c }
 %    \end{macrocode}
 % \end{macro}
 %
-% \begin{macro}{\@@_tl_gput_right:Nn,\@@_tl_gput_right:No,\@@_tl_gput_right:cn}
+% \begin{macro}{
+%     \@@_tl_gput_right:Nn,
+%     \@@_tl_gput_right:Ne,
+%     \@@_tl_gput_right:cn,
+%   }
 %   Same as above.
 %    \begin{macrocode}
 \cs_new_protected:Npn \@@_tl_gput_right:Nn #1#2
   { \@@_tl_gset:Nx #1 { \__kernel_exp_not:w \exp_after:wN { #1 #2 } } }
-\cs_generate_variant:Nn \@@_tl_gput_right:Nn { No, cn }
+\cs_generate_variant:Nn \@@_tl_gput_right:Nn { Ne, cn }
 %    \end{macrocode}
 % \end{macro}
 %
-% \begin{macro}{\@@_tl_gput_left:Nn,\@@_tl_gput_left:No}
+% \begin{macro}{\@@_tl_gput_left:Nn}
 %   Same as above.
 %    \begin{macrocode}
 \cs_new_protected:Npn \@@_tl_gput_left:Nn #1#2
@@ -2175,7 +2487,6 @@
     \@@_tl_gset:Nx #1
       { \__kernel_exp_not:w {#2} \__kernel_exp_not:w \exp_after:wN {#1} }
   }
-\cs_generate_variant:Nn \@@_tl_gput_left:Nn { No }
 %    \end{macrocode}
 % \end{macro}
 %
@@ -2202,7 +2513,11 @@
 %
 % \DescribeMacro{\g_@@_\meta{hook}_code_prop}
 % \DescribeMacro{\@@\textvisiblespace\meta{hook}}
+% \DescribeMacro{\g_@@_\meta{hook}_reversed_tl}
+% \DescribeMacro{\g_@@_\meta{hook}_declared_tl}
+% \DescribeMacro{\g_@@_\meta{hook}_parameter_tl}
 % \DescribeMacro{\@@_next\textvisiblespace\meta{hook}}
+% \DescribeMacro{\@@_toplevel\textvisiblespace\meta{hook}}
 %    Hooks have a name (called \meta{hook} in the description below)
 %    and for each hook we have to
 %    provide a number of data structures. These are
@@ -2232,12 +2547,17 @@
 %    so that it can be identified.  The |-| character is used because
 %    $\meta{reversed}1$ is $+1$ for normal hooks and $-1$ for reversed
 %    ones.
-
+%
 %    \item[{\cs[no-index]{g_@@_\meta{hook}_declared_tl}}] This token
-%    list serves as marker for the hook being officially declared. Its
+%    list serves as a marker for the hook being officially declared. Its
 %    existence is tested to raise an error in case another declaration
 %    is attempted.
 %
+%    \item[{\cs[no-index]{c_@@_\meta{hook}_parameter_tl}}] This token
+%    list stores the parameter text for a declared hook (its existence
+%    almost completely intersects the token list above), which is used
+%    for managing hooks with arguments.
+%
 %    \item[\cs{@@_toplevel\textvisiblespace\meta{hook}}] This token list stores the code
 %    inserted in the hook from the user's document, in the |top-level|
 %    label.  This label is special, and doesn't participate in sorting.
@@ -2367,45 +2687,92 @@
 % \subsubsection{Setting hooks up}
 %
 %
-%  \begin{macro}{\hook_new:n,\@@_new:n}
+%  \begin{macro}{
+%    \hook_new:n,
+%    \hook_new_with_args:nn
+%  }
+%  \begin{macro}{@@_new:nn}
 %    The \cs{hook_new:n} declaration declares a new hook and expects
 %    the hook \meta{name} as its argument, e.g.,
 %    \hook{begindocument}.
+% \changes{v1.1a}{2023/04/06}
+%         {Add \cs{hook_new_with_args:nn} (hook-args).}
 %    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\hook_new_with_args:nn}
+%<latexrelease>                 {Hooks~with~args}
 \cs_new_protected:Npn \hook_new:n #1
-  { \@@_normalize_hook_args:Nn \@@_new:n {#1} }
+  { \@@_normalize_hook_args:Nn \@@_new:nn {#1} { 0 } }
+\cs_new_protected:Npn \hook_new_with_args:nn #1 #2
+  { \@@_normalize_hook_args:Nn \@@_new:nn {#1} {#2} }
 %    \end{macrocode}
 %
 %    \begin{macrocode}
-\cs_new_protected:Npn \@@_new:n #1
+\cs_new_protected:Npn \@@_new:nn #1 #2
   {
 %    \end{macrocode}
 %   We check if the hook was already \emph{explicitly} declared with
 %   \cs{hook_new:n}, and if it already exists we complain, otherwise set
 %   the \enquote{created} flag for the hook so that it errors next time
 %   \cs{hook_new:n} is used.
+% \changes{v1.1d}{2023/05/21}
+%         {Changes to allow support arguments in cmd hooks (cmd-args).}
 %    \begin{macrocode}
     \@@_if_declared:nTF {#1}
       { \msg_error:nnn { hooks } { exists } {#1} }
       {
         \tl_new:c { g_@@_#1_declared_tl }
-        \@@_make_usable:n {#1}
+        \cs_undefine:c { @@~#1 }
+        \cs_undefine:c { c_@@_#1_parameter_tl }
+        \@@_make_usable:nn {#1} {#2}
+%    \end{macrocode}
+%   In case there is already code in a hook, but it's undeclared, run
+%   \cs{@@_update_hook_code:n} to make it ready to be executed (see test
+%   \texttt{lthooks-034}).
+% \changes{v1.1a}{2023/04/06}
+%         {Update hook code after declaring.}
+%    \begin{macrocode}
+        \@@_update_hook_code:n {#1}
       }
   }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+% \end{macro}
+%
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\hook_new_with_args:nn}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_gset_protected:Npn \hook_new:n #1
+%<latexrelease>  { \@@_normalize_hook_args:Nn \@@_new:n {#1} }
+%<latexrelease>\cs_undefine:N \@@_new:nn
+%<latexrelease>\cs_gset_protected:Npn \@@_new:n #1
+%<latexrelease>  {
+%<latexrelease>    \@@_if_declared:nTF {#1}
+%<latexrelease>      { \msg_error:nnn { hooks } { exists } {#1} }
+%<latexrelease>      {
+%<latexrelease>        \tl_new:c { g_@@_#1_declared_tl }
+%<latexrelease>        \@@_make_usable:n {#1}
+%<latexrelease>      }
+%<latexrelease>  }
+%<latexrelease>\cs_gset_protected:Npn \hook_new_with_args:nn #1 { }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 % \end{macro}
 %
 %
 %
-%  \begin{macro}{\@@_make_usable:n}
+%  \begin{macro}{\@@_make_usable:nn}
 %
 %    This initializes all hook data structures for the hook but if
 %    used on its own doesn't mark the hook as declared (as
 %    \cs{hook_new:n} does, so a later \cs{hook_new:n} on that hook will
 %    not result in an error.  This command is internally used by
-%    \cs{hook_gput_code:n} when adding code to a generic hook.
+%    \cs{hook_gput_code:nnn} when adding code to a generic hook.
+% \changes{v1.1a}{2023/04/06}
+%         {Changes to add hook arguments (hook-args).}
 %    \begin{macrocode}
-\cs_new_protected:Npn \@@_make_usable:n #1
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_make_usable:nn}
+%<latexrelease>                 {Hooks~with~args}
+\cs_new_protected:Npn \@@_make_usable:nn #1 #2
   {
 %    \end{macrocode}
 %   Now we check if the hook's data structure can be safely created
@@ -2414,29 +2781,70 @@
 %   allocate the necessary data structures for the new hook,
 %   otherwise just do nothing.
 %    \begin{macrocode}
-    \tl_if_exist:cF { @@~#1 }
+    \@@_if_usable:nF {#1}
       {
         \seq_gput_right:Nn \g_@@_all_seq {#1}
 %    \end{macrocode}
+%    Here we'll define the \cs[no-index]{c_@@_\meta{hook}_parameter_tl}
+%    to hold a run of parameters up to the number of arguments of the
+%    hook (\verb|#2|).
+%    \begin{macrocode}
+        \__kernel_cs_parm_from_arg_count:nnF
+          { \tl_const:cn { c_@@_#1_parameter_tl } } {#2}
+          {
+            \msg_error:nnnn { hooks } { too-many-args } {#1} {#2}
+            \tl_const:cx { c_@@_#1_parameter_tl }
+              { \exp_not:V \c_@@_nine_parameters_tl }
+          }
+%    \end{macrocode}
+%    After that, use \cs{@@_normalise_cs_args:nn} to correct the number
+%    of parameters of the macros
+%    \cs[no-index]{@@_toplevel\textvisiblespace\meta{hook}} and
+%    \cs[no-index]{@@_next\textvisiblespace\meta{hook}}.  We need to be
+%    able to add
+%    code with arguments to a hook without prior knowledge of the number
+%    of arguments of that hook, so \pkg{lthooks} assumes~9 until the
+%    hook is properly declared and the number of arguments is known.
+%    \cs{@@_normalise_cs_args:nn} does the normalisation by using the
+%    \cs[no-index]{c_@@_\meta{hook}_parameter_tl} defined just above.
+%    \begin{macrocode}
+        \@@_normalise_cs_args:nn { _toplevel } {#1}
+        \@@_normalise_cs_args:nn { _next } {#1}
+%    \end{macrocode}
 %    This is only used by the actual code of the current hook, so
 %    declare it normally:
 %    \begin{macrocode}
-        \tl_new:c { @@~#1 }
+        \@@_code_gset:nn {#1} { }
 %    \end{macrocode}
 %    Now ensure that the base data structure for the hook exists:
 %    \begin{macrocode}
         \@@_init_structure:n {#1}
 %    \end{macrocode}
+%    The call to \cs{@@_normalise_code_pool:n} will correct any improper
+%    reference to arguments that don't exist in the hook, raising a
+%    low-level \TeX{} error and doubling the offending parameter tokens.
+%    It has to be done after \cs{@@_init_structure:n} because it
+%    operates on \cs[no-index]{g_@@_\meta{hook}_code_prop}.
+%    \begin{macrocode}
+        \@@_normalise_code_pool: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.
+%    These are defined conditionally, in case \cs{@@_make_usable:nn}
+%    is being used to redefine a hook.
+% \changes{v1.1d}{2023/05/21}
+%         {Changes to allow support arguments in cmd hooks (cmd-args).}
 %    \begin{macrocode}
-        \clist_new:c { g_@@_#1_labels_clist }
+        \clist_if_exist:cF { g_@@_#1_labels_clist }
+          {
+            \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 }
+            \tl_new:c { g_@@_#1_reversed_tl }
+          }
 %    \end{macrocode}
 %    The above is all in L3 convention, but we also provide an
 %    interface to legacy \LaTeXe{} hooks of the form \cs{@...hook},
@@ -2454,9 +2862,28 @@
         \@@_include_legacy_code_chunk:n {#1}
       }
   }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
-%  \end{macro}
 %
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_make_usable:nn}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_undefine:N \@@_make_usable:nn
+%<latexrelease>\cs_gset_protected:Npn \@@_make_usable:n #1
+%<latexrelease>  {
+%<latexrelease>    \tl_if_exist:cF { @@~#1 }
+%<latexrelease>      {
+%<latexrelease>        \seq_gput_right:Nn \g_@@_all_seq {#1}
+%<latexrelease>        \tl_new:c { @@~#1 }
+%<latexrelease>        \@@_init_structure:n {#1}
+%<latexrelease>        \clist_new:c { g_@@_#1_labels_clist }
+%<latexrelease>        \tl_new:c { g_@@_#1_reversed_tl }
+%<latexrelease>        \@@_include_legacy_code_chunk:n {#1}
+%<latexrelease>      }
+%<latexrelease>  }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%  \end{macro}
 %
 %
 % \begin{macro}{\@@_init_structure:n}
@@ -2472,49 +2899,124 @@
 %    \cs{@@_init_structure:n} the first time anything is added to it.
 %    Initializing a hook just with \cs{@@_init_structure:n} will not
 %    make it usable with \cs{hook_use:n}.
+% \changes{v1.1a}{2023/04/06}
+%         {Changes to add hook arguments (hook-args).}
 %    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_init_structure:n}
+%<latexrelease>                 {Hooks~with~args}
 \cs_new_protected:Npn \@@_init_structure:n #1
   {
     \@@_if_structure_exist:nF {#1}
       {
         \prop_new:c { g_@@_#1_code_prop }
-        \tl_new:c { @@_toplevel~#1 }
-        \tl_new:c { @@_next~#1 }
+        \@@_toplevel_gset:nn {#1} { }
+        \@@_next_gset:nn {#1} { }
       }
   }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
-%  \end{macro}
-%
 %
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_init_structure:n}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_gset_protected:Npn \@@_init_structure:n #1
+%<latexrelease>  {
+%<latexrelease>    \@@_if_structure_exist:nF {#1}
+%<latexrelease>      {
+%<latexrelease>        \prop_new:c { g_@@_#1_code_prop }
+%<latexrelease>        \tl_new:c { @@_toplevel~#1 }
+%<latexrelease>        \tl_new:c { @@_next~#1 }
+%<latexrelease>      }
+%<latexrelease>  }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%  \end{macro}
 %
-%  \begin{macro}{\hook_new_reversed:n,\@@_new_reversed:n}
+%  \begin{macro}{
+%    \hook_new_reversed:n,
+%    \hook_new_reversed_with_args:nn
+%  }
+%  \begin{macro}{\@@_new_reversed:nn}
 %
 %    Declare a new hook. The default ordering of code chunks is
 %    reversed, signaled by setting the token list to a minus sign.
+% \changes{v1.1a}{2023/04/06}
+%         {Add \cs{hook_new_reversed_with_args:nn} (hook-args).}
 %    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\hook_new_reversed_with_args:nn}
+%<latexrelease>                 {Hooks~with~args}
 \cs_new_protected:Npn \hook_new_reversed:n #1
-  { \@@_normalize_hook_args:Nn \@@_new_reversed:n {#1} }
-\cs_new_protected:Npn \@@_new_reversed:n #1
+  { \@@_normalize_hook_args:Nn \@@_new_reversed:nn {#1} { 0 } }
+\cs_new_protected:Npn \hook_new_reversed_with_args:nn #1 #2
+  { \@@_normalize_hook_args:Nn \@@_new_reversed:nn {#1} {#2} }
+\cs_new_protected:Npn \@@_new_reversed:nn #1 #2
   {
-    \@@_new:n {#1}
+    \@@_if_declared:nTF {#1}
+      { \msg_error:nnn { hooks } { exists } {#1} }
+      {
+        \@@_new:nn {#1} {#2}
+        \tl_gset:cn { g_@@_#1_reversed_tl } { - }
+      }
+  }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
-%    If the hook already exists the above will generate an error
-%    message, so the next line should be executed (but it is --- too
-%    bad).
+%
 %    \begin{macrocode}
-    \tl_gset:cn { g_@@_#1_reversed_tl } { - }
-  }
+%<latexrelease>\IncludeInRelease{2020/10/01}{\hook_new_reversed_with_args:nn}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_gset_protected:Npn \hook_new_reversed:n #1
+%<latexrelease>  { \@@_normalize_hook_args:Nn \@@_new_reversed:n {#1} }
+%<latexrelease>\cs_undefine:N \@@_new_reversed:nn
+%<latexrelease>\cs_gset_protected:Npn \@@_new_reversed:n #1
+%<latexrelease>  {
+%<latexrelease>    \@@_new:n {#1}
+%<latexrelease>    \tl_gset:cn { g_@@_#1_reversed_tl } { - }
+%<latexrelease>  }
+%<latexrelease>\cs_undefine:N \@@_new_reversed:nn
+%<latexrelease>\cs_gset_protected:Npn \hook_new_reversed_with_args:nn #1 #2 { }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 %  \end{macro}
+%  \end{macro}
 %
-%
-%
-%
-%  \begin{macro}{\hook_new_pair:nn}
+%  \begin{macro}{\hook_new_pair:nn,\hook_new_pair_with_args:nnn}
 %    A shorthand for declaring a normal and a (matching) reversed hook in one go.
+% \changes{v1.1a}{2023/04/06}
+%         {Add \cs{hook_new_pair_with_args:nnn} (hook-args).}
 %    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\hook_new_pair_with_args:nnn}
+%<latexrelease>                 {Hooks~with~args}
 \cs_new_protected:Npn \hook_new_pair:nn #1#2
-  { \hook_new:n {#1} \hook_new_reversed:n {#2} }
+  { \@@_normalize_hook_args:Nnn \@@_new_pair:nnn {#1} {#2} { 0 } }
+\cs_new_protected:Npn \hook_new_pair_with_args:nnn #1#2#3
+  { \@@_normalize_hook_args:Nnn \@@_new_pair:nnn {#1} {#2} {#3} }
+\cs_new_protected:Npn \@@_new_pair:nnn #1 #2 #3
+  {
+    \@@_if_declared:nTF {#1}
+      { \msg_error:nnn { hooks } { exists } {#1} }
+      {
+        \@@_if_declared:nTF {#2}
+          { \msg_error:nnn { hooks } { exists } {#2} }
+          {
+            \@@_new:nn {#1} {#3}
+            \@@_new_reversed:nn {#2} {#3}
+          }
+      }
+  }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\hook_new_pair_with_args:nnn}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_gset_protected:Npn \hook_new_pair:nn #1#2
+%<latexrelease>  {
+%<latexrelease>    \hook_new:n {#1}
+%<latexrelease>    \hook_new_reversed:n {#2}
+%<latexrelease>  }
+%<latexrelease>\cs_gset_protected:Npn \hook_new_pair_with_args:nnn #1#2#3
+%<latexrelease>  { }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 %  \end{macro}
 %
@@ -2531,6 +3033,8 @@
 %       \textbf{Warning: this support will vanish in future releases!}
 %    \end{quote}
 %    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_include_legacy_code_chunk:n}
+%<latexrelease>                 {Hooks~with~args}
 \cs_new_protected:Npn \@@_include_legacy_code_chunk:n #1
   {
 %    \end{macrocode}
@@ -2538,15 +3042,27 @@
 %    needs to be done.
 %    \begin{macrocode}
     \tl_if_exist:cT { @#1hook }
+      {
 %    \end{macrocode}
 %    Of course if the legacy hook exists but is empty, there is no need
 %    to add anything under \texttt{legacy} the legacy label.
 %    \begin{macrocode}
-      {
         \tl_if_empty:cF { @#1hook }
           {
-            \exp_args:Nnnv \@@_hook_gput_code_do:nnn {#1}
-                                  { legacy } { @#1hook }
+%    \end{macrocode}
+%    Here we set \cs{@@_replacing_args_false:} because no legacy code
+%    will reference hook arguments.
+% \changes{v1.1b}{2023/04/16}
+%         {\cs{@@_replacing_args_false:} in
+%          \cs{@@_include_legacy_code_chunk:n}.}
+%    \begin{macrocode}
+            \@@_replacing_args_false:
+            \use:e
+              {
+                \@@_hook_gput_code_do:nnn {#1} { legacy }
+                  { \exp_not:v { @#1hook } }
+              }
+            \@@_replacing_args_reset:
 %    \end{macrocode}
 %    Once added to the hook, we need to clear it otherwise it might
 %    get added again  later if the hook data gets updated.
@@ -2555,6 +3071,22 @@
           }
       }
   }
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_include_legacy_code_chunk:n}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_gset_protected:Npn \@@_include_legacy_code_chunk:n #1
+%<latexrelease>  {
+%<latexrelease>    \tl_if_exist:cT { @#1hook }
+%<latexrelease>      {
+%<latexrelease>        \tl_if_empty:cF { @#1hook }
+%<latexrelease>          {
+%<latexrelease>            \exp_args:Nnnv \@@_hook_gput_code_do:nnn
+%<latexrelease>              {#1} { legacy } { @#1hook }
+%<latexrelease>            \@@_tl_gclear:c { @#1hook }
+%<latexrelease>          }
+%<latexrelease>      }
+%<latexrelease>  }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 % \end{macro}
 %
@@ -2579,8 +3111,8 @@
 %    if the hook is disabled.
 %
 %    \begin{macrocode}
-%<latexrelease>\IncludeInRelease{2021/06/01}%
-%<latexrelease>         {\hook_disable_generic:n}{Disable~hooks}
+%<latexrelease>\IncludeInRelease{2021/06/01}{\hook_disable_generic:n}
+%<latexrelease>                 {Disable~hooks}
 %    \end{macrocode}
 %
 %    \begin{macrocode}
@@ -2595,7 +3127,7 @@
   {
     \bool_lazy_and:nnTF
         { \tl_if_exist_p:c { g_@@_#1_declared_tl } }
-        { ! \tl_if_exist_p:c { @@~#1 } }
+        { ! \cs_if_exist_p:c { @@~#1 } }
       { \prg_return_true: }
       { \prg_return_false: }
   }
@@ -2603,8 +3135,8 @@
 %    \end{macrocode}
 %
 %    \begin{macrocode}
-%<latexrelease>\IncludeInRelease{2020/10/01}
-%<latexrelease>                 {\hook_disable_generic:n}{Disable~hooks}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\hook_disable_generic:n}
+%<latexrelease>                 {Disable~hooks}
 %<latexrelease>
 %<latexrelease>\cs_new_protected:Npn \hook_disable_generic:n #1 {}
 %<latexrelease>
@@ -2619,9 +3151,11 @@
 %    The \cs{hook_activate_generic:n} declaration declares a new hook if it
 %    wasn't declared already, in which case it only checks that the
 %    already existing hook is not a reversed hook.
+% \changes{v1.1a}{2023/04/06}
+%         {Changes to add hook arguments (hook-args).}
 %    \begin{macrocode}
-%<latexrelease>\IncludeInRelease{2021/06/01}%
-%<latexrelease>         {\hook_activate_generic:n}{Providing~hooks}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\hook_activate_generic:n}
+%<latexrelease>                 {Providing~hooks}
 %    \end{macrocode}
 %
 %    \begin{macrocode}
@@ -2646,7 +3180,7 @@
         \@@_if_declared:nF {#1}
           {
             \tl_new:c { g_@@_#1_declared_tl }
-            \@@_make_usable:n {#1}
+            \@@_make_usable:nn {#1} { 0 }
             \tl_gset:cx { g_@@_#1_reversed_tl }
               { \@@_if_generic_reversed:nT {#1} { - } }
 %    \end{macrocode}
@@ -2660,21 +3194,40 @@
       }
   }
 %    \end{macrocode}
-% \end{macro}
-% \end{macro}
 %
 %    \begin{macrocode}
 %<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 %
 %    \begin{macrocode}
-%<latexrelease>\IncludeInRelease{2020/10/01}
-%<latexrelease>                 {\hook_activate_generic:n}{Providing~hooks}
-%<latexrelease>
-%<latexrelease>\cs_new_protected:Npn \hook_activate_generic:n #1 {}
-%<latexrelease>
+%<latexrelease>\IncludeInRelease{2021/06/01}{\hook_activate_generic:n}
+%<latexrelease>                 {Providing~hooks}
+%<latexrelease>\cs_gset_protected:Npn \@@_activate_generic:nn #1 #2
+%<latexrelease>  {
+%<latexrelease>    \@@_if_disabled:nTF {#1}
+%<latexrelease>      { \msg_warning:nnn { hooks } { activate-disabled } {#1} }
+%<latexrelease>      {
+%<latexrelease>        \@@_if_declared:nF {#1}
+%<latexrelease>          {
+%<latexrelease>            \tl_new:c { g_@@_#1_declared_tl }
+%<latexrelease>            \@@_make_usable:n {#1}
+%<latexrelease>            \tl_gset:cx { g_@@_#1_reversed_tl }
+%<latexrelease>              { \@@_if_generic_reversed:nT {#1} { - } }
+%<latexrelease>            \@@_update_hook_code:n {#1}
+%<latexrelease>          }
+%<latexrelease>      }
+%<latexrelease>  }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\hook_activate_generic:n}
+%<latexrelease>                 {Providing~hooks}
+%<latexrelease>\cs_gset_protected:Npn \hook_activate_generic:n #1 { }
 %<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
+% \end{macro}
+% \end{macro}
 %
 %
 % \subsection{Parsing a label}
@@ -2965,63 +3518,109 @@
 %
 % \subsection{Adding or removing hook code}
 %
-% \begin{macro}{\hook_gput_code:nnn}
-% \begin{macro}{\@@_gput_code:nnn,\@@_hook_gput_code_do:nnn}
+% \begin{macro}{\hook_gput_code:nnn,\hook_gput_code_with_args:nnn}
+% \begin{macro}{
+%     \@@_gput_code:nnn,
+%     \@@_gput_code_store:nnn,
+%     \@@_hook_gput_code_do:nnn,
+%     \@@_prop_gput_labeled_cleanup:nnn,
+%     \@@_prop_gput_labeled_do:Nnnn
+%    }
 %
 %    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}.
 % \changes{v1.0o}{2021/07/22}{Do not queue removals (gh/625)}
+% \changes{v1.1a}{2023/04/06}
+%         {Add \cs{hook_gput_code_with_args:nnn} (hook-args).}
 %    \begin{macrocode}
-\cs_new_protected:Npn \hook_gput_code:nnn #1 #2
-  { \@@_normalize_hook_args:Nnn \@@_gput_code:nnn {#1} {#2} }
+%<latexrelease>\IncludeInRelease{2023/06/01}{\hook_gput_code_with_args:nnn}
+%<latexrelease>                 {Hooks~with~args}
+\cs_new_protected:Npn \hook_gput_code:nnn #1 #2 #3
+  {
+    \@@_replacing_args_false:
+    \@@_normalize_hook_args:Nnn \@@_gput_code:nnn {#1} {#2} {#3}
+    \@@_replacing_args_reset:
+  }
+\cs_new_protected:Npn \hook_gput_code_with_args:nnn #1 #2 #3
+  {
+    \@@_replacing_args_true:
+    \@@_normalize_hook_args:Nnn \@@_gput_code:nnn {#1} {#2} {#3}
+    \@@_replacing_args_reset:
+  }
 %    \end{macrocode}
 %
+%   If \cs{AddToHookWithArguments} was used, do some sanity checking,
+%   and if it's not possible to use arguments at this point, fall back
+%   to regular \cs{AddToHook} by using \cs{@@_replacing_args_false:}.
+% \changes{v1.1a}{2023/04/06}
+%         {Changes to add hook arguments (hook-args).}
 %    \begin{macrocode}
 \cs_new_protected:Npn \@@_gput_code:nnn #1 #2 #3
   {
+    \@@_chk_args_allowed:nn {#1} { AddToHook }
 %    \end{macrocode}
-%   First check if the code should be executed immediately, rather than
+%   Then check if the code should be executed immediately, rather than
 %   stored:
 % \changes{v1.0r}{2021/09/06}{Use dedicated conditional (gh/606)}
 %    \begin{macrocode}
     \@@_if_execute_immediately:nTF {#1}
-      {#3}
       {
 %    \end{macrocode}
+%   \cs{AddToHookWithArguments} can't be used on one-time hooks (that
+%   were already used).
+%    \begin{macrocode}
+        \@@_if_replacing_args:TF
+          {
+            \msg_error:nnnn { hooks } { one-time-args }
+              {#1} { AddToHook }
+          }
+          { }
+        \use:n
+      }
+      { \@@_gput_code_store:nnn {#1} {#2} }
+          {#3}
+  }
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_gput_code_store:nnn #1 #2 #3
+  {
+%    \end{macrocode}
 %    Then check if the hook is usable.
 %    \begin{macrocode}
-        \@@_if_usable:nTF {#1}
+    \@@_if_usable: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}
+      {
+        \@@_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}
-          }
+        \@@_update_hook_code:n {#1}
+      }
 %    \end{macrocode}
 %
 %    If the hook is not usable, before giving up, check if it's not
 %    disabled and otherwise try to declare it as a generic hook, if its
 %    name matches one of the valid patterns.
 %    \begin{macrocode}
-          {
-            \@@_if_disabled:nTF {#1}
-              { \msg_error:nnn { hooks } { hook-disabled } {#1} }
-              { \@@_try_declaring_generic_hook:nnn {#1} {#2} {#3} }
-          }
+      {
+        \@@_if_disabled:nTF {#1}
+          { \msg_error:nnn { hooks } { hook-disabled } {#1} }
+          { \@@_try_declaring_generic_hook:nnn {#1} {#2} {#3} }
       }
   }
 %    \end{macrocode}
 %
 %    This macro will unconditionally add a chunk of code to the given hook.
+% \changes{v1.1a}{2023/04/06}
+%         {Changes to add hook arguments (hook-args).}
 %    \begin{macrocode}
 \cs_new_protected:Npn \@@_hook_gput_code_do:nnn #1 #2 #3
   {
@@ -3046,42 +3645,172 @@
         \str_if_eq:eeTF { top-level } { \@@_currname_or_default: }
           {
 %    \end{macrocode}
-%    If the hook's basic structure does not exist, we need to declare it
-%    with \cs{@@_init_structure:n}.
+%   If the hook's basic structure does not exist, we need to declare it
+%   with \cs{@@_init_structure:n}.
 %    \begin{macrocode}
             \@@_init_structure:n {#1}
-            \@@_tl_gput_right:cn { @@_toplevel~#1 } {#3}
+%    \end{macrocode}
+%   Then append to the \verb|_toplevel| container for the hook.
+%    \begin{macrocode}
+            \@@_cs_gput_right:nnn { _toplevel } {#1} {#3}
           }
           { \msg_error:nnn { hooks } { misused-top-level } {#1} }
       }
       {
-        \prop_get:cnNTF { g_@@_#1_code_prop } {#2} \l_@@_return_tl
+%    \end{macrocode}
+%   When adding to the code pool, we have to double hashes if
+%   \cs{AddToHook} was used (\verb|replacing_args| is false), so that
+%   later it is turned into a single parameter token, rather than a
+%   parameter to the hook macro.
+%    \begin{macrocode}
+        \exp_args:Nx \@@_prop_gput_labeled_cleanup:nnn
           {
-            \prop_gput:cno { g_@@_#1_code_prop } {#2}
-              { \l_@@_return_tl #3 }
+            \@@_if_replacing_args:TF
+              { \exp_not:n }
+              { \@@_double_hashes:n }
+                {#3}
           }
-          { \prop_gput:cnn { g_@@_#1_code_prop } {#2} {#3} }
+          {#1} {#2}
       }
   }
 %    \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.
+%   Adds code to a hook's code pool.
+% \changes{v1.1a}{2023/04/06}
+%         {Macro added (hook-args).}
 %    \begin{macrocode}
-\cs_new_protected:Npn \@@_gput_undeclared_hook:nnn #1 #2 #3
+\cs_new_protected:Npn \@@_prop_gput_labeled_cleanup:nnn #1 #2 #3
   {
-    \@@_init_structure:n {#1}
-    \@@_hook_gput_code_do:nnn {#1} {#2} {#3}
+    \tl_set:Nn \l_@@_return_tl {#1}
+    \@@_if_replacing_args:TF
+      {
+        \@@_if_usable:nT {#2}
+          {
+            \@@_set_normalise_fn:nn {#2}
+              { Invalid~code~added~\msg_line_context: }
+            \@@_normalise_fn:nn {#3} {#1}
+            \prop_get:NnN \l_@@_work_prop {#3} \l_@@_return_tl
+          }
+      }
+      { }
+    \exp_args:NcV \@@_prop_gput_labeled_do:Nnn
+      { g_@@_#2_code_prop } \l_@@_return_tl {#3}
+  }
+\cs_new_protected:Npn \@@_prop_gput_labeled_do:Nnn #1 #2 #3
+  {
+    \prop_get:NnNTF #1 {#3} \l_@@_return_tl
+      { \prop_gput:Nno #1 {#3} { \l_@@_return_tl #2 } }
+      { \prop_gput:Nnn #1 {#3} {#2} }
   }
 %    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\@@_try_declaring_generic_hook:nnn,
-%               \@@_try_declaring_generic_next_hook:nn}
+%
+%    \begin{macrocode}
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\hook_gput_code:nnn}
+%<latexrelease>                 {Providing~hooks}
+%<latexrelease>\cs_gset_protected:Npn \hook_gput_code:nnn #1 #2
+%<latexrelease>  { \@@_normalize_hook_args:Nnn \@@_gput_code:nnn {#1} {#2} }
+%<latexrelease>\cs_gset_protected:Npn \@@_gput_code:nnn #1 #2 #3
+%<latexrelease>  {
+%<latexrelease>    \@@_if_execute_immediately:nTF {#1}
+%<latexrelease>      {#3}
+%<latexrelease>      {
+%<latexrelease>        \@@_if_usable:nTF {#1}
+%<latexrelease>          {
+%<latexrelease>            \@@_hook_gput_code_do:nnn {#1} {#2} {#3}
+%<latexrelease>            \@@_update_hook_code:n {#1}
+%<latexrelease>          }
+%<latexrelease>          {
+%<latexrelease>            \@@_if_disabled:nTF {#1}
+%<latexrelease>              { \msg_error:nnn { hooks } { hook-disabled } {#1} }
+%<latexrelease>              { \@@_try_declaring_generic_hook:nnn {#1} {#2} {#3} }
+%<latexrelease>          }
+%<latexrelease>      }
+%<latexrelease>  }
+%<latexrelease>\cs_gset_protected:Npn \@@_hook_gput_code_do:nnn #1 #2 #3
+%<latexrelease>  {
+%<latexrelease>    \@@_debug:n{\iow_term:x{****~ Add~ to~
+%<latexrelease>                      \@@_if_usable:nF {#1} { undeclared~ }
+%<latexrelease>                      hook~ #1~ (#2)
+%<latexrelease>                      \on@line\space <-~ \tl_to_str:n{#3}} }
+%<latexrelease>    \str_if_eq:nnTF {#2} { top-level }
+%<latexrelease>      {
+%<latexrelease>        \str_if_eq:eeTF { top-level } { \@@_currname_or_default: }
+%<latexrelease>          {
+%<latexrelease>            \@@_init_structure:n {#1}
+%<latexrelease>            \@@_tl_gput_right:cn { @@_toplevel~#1 } {#3}
+%<latexrelease>          }
+%<latexrelease>          { \msg_error:nnn { hooks } { misused-top-level } {#1} }
+%<latexrelease>      }
+%<latexrelease>      {
+%<latexrelease>        \prop_get:cnNTF { g_@@_#1_code_prop } {#2} \l_@@_return_tl
+%<latexrelease>          {
+%<latexrelease>            \prop_gput:cno { g_@@_#1_code_prop } {#2}
+%<latexrelease>              { \l_@@_return_tl #3 }
+%<latexrelease>          }
+%<latexrelease>          { \prop_gput:cnn { g_@@_#1_code_prop } {#2} {#3} }
+%<latexrelease>      }
+%<latexrelease>  }
+%<latexrelease>\cs_gset_protected:Npn \hook_gput_code_with_args:nnn #1#2#3 { }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_chk_args_allowed:nn}
+%   This macro checks if it is possible to add code with references to
+%   a hook's arguments for hook \verb|#1|.  It only does something if
+%   the function being run is \verb|replacing_args|.  This macro will
+%   error if the hook is declared and takes no arguments, then it will
+%   set \cs{@@_replacing_args_false:} so that the macro which called it
+%   will add the code normally.
+% \changes{v1.1a}{2023/04/06}
+%         {Macro added (hook-args).}
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_chk_args_allowed:nn}
+%<latexrelease>                 {Hooks~with~args}
+\cs_new_protected:Npn \@@_chk_args_allowed:nn #1 #2
+  {
+    \@@_if_replacing_args:TF
+      {
+        \@@_if_declared:nT {#1}
+          { \tl_if_empty:cT { c_@@_#1_parameter_tl } { \use_ii:nn } }
+        \use_none:n
+          {
+            \msg_error:nnnn { hooks } { without-args } {#1} {#2}
+            \@@_replacing_args_false:
+          }
+      }
+      { }
+  }
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_chk_args_allowed:nn}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_undefine:N \@@_chk_args_allowed:nn
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+% \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.
+%   An implicitly declared hook doesn't have arguments (in principle),
+%   so use \cs{c_false_bool} here.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_gput_undeclared_hook:nnn #1 #2 #3
+  {
+    \@@_init_structure:n {#1}
+    \@@_hook_gput_code_do:nnn {#1} {#2} {#3}
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_try_declaring_generic_hook:nnn,
+%               \@@_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
@@ -3098,24 +3827,43 @@
 %   \cs{hook_gput_next_code:nn} if the generic hook was declared, or
 %   to \cs{@@_gput_next_do:nn} otherwise.
 % \changes{v1.0p}{2021/08/25}{Standardise generic hook names (gh/648)}
+% \changes{v1.1d}{2023/05/21}
+%         {Changes to allow support arguments in cmd hooks (cmd-args).}
 %    \begin{macrocode}
-%<latexrelease>\IncludeInRelease{2021/11/15}{\@@_try_declaring_generic_hook:nnn}
-%<latexrelease>                 {Standardise~generic~hook~names}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_try_declaring_generic_hook:nnn}
+%<latexrelease>                 {Hooks~with~args}
 \cs_new_protected:Npn \@@_try_declaring_generic_hook:nnn #1
   {
     \@@_try_declaring_generic_hook:wnTF #1 / / / \scan_stop: {#1}
-      \hook_gput_code:nnn
+      \@@_gput_code:nnn
       \@@_gput_undeclared_hook:nnn
         {#1}
   }
 \cs_new_protected:Npn \@@_try_declaring_generic_next_hook:nn #1
   {
     \@@_try_declaring_generic_hook:wnTF #1 / / / \scan_stop: {#1}
-      \hook_gput_next_code:nn
+      \@@_gput_next_code:nn
       \@@_gput_next_do:nn
         {#1}
   }
 %<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2021/11/15}{\@@_try_declaring_generic_hook:nnn}
+%<latexrelease>                 {Standardise~generic~hook~names}
+%<latexrelease>\cs_gset_protected:Npn \@@_try_declaring_generic_hook:nnn #1
+%<latexrelease>  {
+%<latexrelease>    \@@_try_declaring_generic_hook:wnTF #1 / / / \scan_stop: {#1}
+%<latexrelease>      \hook_gput_code:nnn
+%<latexrelease>      \@@_gput_undeclared_hook:nnn
+%<latexrelease>        {#1}
+%<latexrelease>  }
+%<latexrelease>\cs_gset_protected:Npn \@@_try_declaring_generic_next_hook:nn #1
+%<latexrelease>  {
+%<latexrelease>    \@@_try_declaring_generic_hook:wnTF #1 / / / \scan_stop: {#1}
+%<latexrelease>      \hook_gput_next_code:nn
+%<latexrelease>      \@@_gput_next_do:nn
+%<latexrelease>        {#1}
+%<latexrelease>  }
+%<latexrelease>\EndIncludeInRelease
 %<latexrelease>\IncludeInRelease{2020/10/01}{\@@_try_declaring_generic_hook:nnn}
 %<latexrelease>                 {Standardise~generic~hook~names}
 %<latexrelease>\cs_new_protected:Npn \@@_try_declaring_generic_hook:nnn #1
@@ -3170,9 +3918,11 @@
 % \end{macro}
 %
 % \begin{macro}[TF]{\@@_try_declaring_generic_hook:wn}
+% \changes{v1.1a}{2023/04/06}
+%         {Changes to add hook arguments (hook-args).}
 %    \begin{macrocode}
-%<latexrelease>\IncludeInRelease{2021/11/15}{\@@_try_declaring_generic_hook:wn}%
-%<latexrelease>                 {Standardise~generic~hook~names}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_try_declaring_generic_hook:wn}
+%<latexrelease>                 {Hooks~with~args}
 \prg_new_protected_conditional:Npnn \@@_try_declaring_generic_hook:wn
     #1 / #2 / #3 / #4 \scan_stop: #5 { TF }
   {
@@ -3188,18 +3938,25 @@
 %    For some commands this will not be possible, in which case
 %    \cs{@@_patch_cmd_or_delay:Nnn} (defined in \texttt{ltcmdhooks})
 %    will generate an appropriate error message.
+% \changes{v1.1d}{2023/05/21}
+%         {Changes to allow support arguments in cmd hooks (cmd-args).}
 %    \begin{macrocode}
             \str_if_eq:nnT {#1} { cmd }
-              { \@@_try_put_cmd_hook:n {#5} }
+              {
+                \@@_try_put_cmd_hook:n {#5}
+                \@@_make_usable:nn {#5} { 9 }
+                \use_none:nnn
+              }
 %    \end{macrocode}
 %
 %    Declare the hook always even if it can't really be used (error
 %    message generated elsewhere).
 %
-%    Here we use \cs{@@_make_usable:n}, so that a \cs{hook_new:n} is still
-%    possible later.
+%    Here we use \cs{@@_make_usable:nn}, so that a \cs{hook_new:n} is
+%    still possible later.  Generic hooks (except \hook{cmd} hooks) take
+%    no arguments, so use zero as the second argument.
 %    \begin{macrocode}
-            \@@_make_usable:n {#5}
+            \@@_make_usable:nn {#5} { 0 }
           }
         \@@_if_generic_reversed:nT {#5}
           { \tl_gset:cn { g_@@_#5_reversed_tl } { - } }
@@ -3285,6 +4042,37 @@
 % \end{macro}
 %
 %    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2021/11/15}{\@@_try_declaring_generic_hook:wn}
+%<latexrelease>                 {Standardise~generic~hook~names}
+%<latexrelease>\prg_new_protected_conditional:Npnn \@@_try_declaring_generic_hook:wn
+%<latexrelease>    #1 / #2 / #3 / #4 \scan_stop: #5 { TF }
+%<latexrelease>  {
+%<latexrelease>    \@@_if_generic:nTF {#5}
+%<latexrelease>      {
+%<latexrelease>        \@@_if_usable:nF {#5}
+%<latexrelease>          {
+%<latexrelease>            \str_if_eq:nnT {#1} { cmd }
+%<latexrelease>              { \@@_try_put_cmd_hook:n {#5} }
+%<latexrelease>            \@@_make_usable:n {#5}
+%<latexrelease>          }
+%<latexrelease>        \@@_if_generic_reversed:nT {#5}
+%<latexrelease>          { \tl_gset:cn { g_@@_#5_reversed_tl } { - } }
+%<latexrelease>        \prg_return_true:
+%<latexrelease>      }
+%<latexrelease>      {
+%<latexrelease>        \@@_if_deprecated_generic:nTF {#5}
+%<latexrelease>          {
+%<latexrelease>            \@@_deprecated_generic_warn:n {#5}
+%<latexrelease>            \exp_after:wN \@@_declare_deprecated_generic:NNn
+%<latexrelease>            \exp:w % \exp_end:
+%<latexrelease>          }
+%<latexrelease>          { \prg_return_false: }
+%<latexrelease>      }
+%<latexrelease>  }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%
+%    \begin{macrocode}
 %<latexrelease>\IncludeInRelease{2021/06/01}{\@@_try_declaring_generic_hook:wn}
 %<latexrelease>                 {Support~cmd~hooks}
 %<latexrelease>\prg_new_protected_conditional:Npnn \@@_try_declaring_generic_hook:wn
@@ -3316,7 +4104,7 @@
 %    \end{macrocode}
 %
 %    \begin{macrocode}
-%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_try_declaring_generic_hook:wn}%
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_try_declaring_generic_hook:wn}
 %<latexrelease>                 {Support~cmd~hooks}
 %<latexrelease>\prg_new_protected_conditional:Npnn \@@_try_declaring_generic_hook:wn
 %<latexrelease>    #1 / #2 / #3 / #4 \scan_stop: #5 { TF }
@@ -3338,7 +4126,6 @@
 %<latexrelease>          { \prg_return_false: }
 %<latexrelease>      }
 %<latexrelease>  }
-%<latexrelease>
 %<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 % \end{macro}
@@ -3350,24 +4137,10 @@
 %   |file/foo.tex/before|).  If it is a file-specific hook, then it
 %   executes the \meta{true} branch, otherwise \meta{false}.
 %    \begin{macrocode}
-%<latexrelease>\IncludeInRelease{2021/11/15}{\@@_if_file_hook:w}%
+%<latexrelease>\IncludeInRelease{2021/11/15}{\@@_if_file_hook:w}
 %<latexrelease>                 {Standardise~generic~hook~names}
-\prg_new_conditional:Npnn \@@_if_file_hook:w
-    #1 / #2 \s_@@_mark #3 { TF }
-  {
-    \@@_if_generic:nTF {#3}
-      {
-        \str_if_eq:nnTF {#1} { file }
-          { \prg_return_true: }
-          { \prg_return_false: }
-      }
-      { \prg_return_false: }
-  }
 %<latexrelease>\EndIncludeInRelease
-%    \end{macrocode}
-%
-%    \begin{macrocode}
-%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_if_file_hook:w}%
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_if_file_hook:w}
 %<latexrelease>                 {Standardise~generic~hook~names}
 %<latexrelease>\prg_new_conditional:Npnn \@@_if_file_hook:w
 %<latexrelease>    #1 / #2 / #3 \s_@@_mark { TF }
@@ -3394,7 +4167,7 @@
 % \begin{macro}[EXP]{\@@_strip_double_slash:n,\@@_strip_double_slash:w}
 %
 %    \begin{macrocode}
-%<latexrelease>\IncludeInRelease{2021/11/15}{\@@_file_hook_normalize:n}%
+%<latexrelease>\IncludeInRelease{2021/11/15}{\@@_file_hook_normalize:n}
 %<latexrelease>                 {Standardise~generic~hook~names}
 %<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
@@ -3406,7 +4179,7 @@
 %   like \verb|\def\input@path{{./mypath/}}|, in which case a hook would
 %   have to be \verb|\AddToHook{file/./mypath//file.tex/after}|.
 %    \begin{macrocode}
-%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_file_hook_normalize:n}%
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_file_hook_normalize:n}
 %<latexrelease>                 {Standardise~generic~hook~names}
 %<latexrelease>\cs_new:Npn \@@_file_hook_normalize:n #1
 %<latexrelease>  { \@@_strip_double_slash:n {#1} }
@@ -3453,7 +4226,7 @@
 %      The generic hooks used when loading a file
 %    \end{description}
 %    \begin{macrocode}
-%<latexrelease>\IncludeInRelease{2021/11/15}{\c_@@_generics_prop}%
+%<latexrelease>\IncludeInRelease{2021/11/15}{\c_@@_generics_prop}
 %<latexrelease>                 {Standardise~generic~hook~names}
 \clist_map_inline:nn { cmd , env , file , package , class , include }
   {
@@ -3482,7 +4255,7 @@
 %    \end{macrocode}
 %
 %    \begin{macrocode}
-%<latexrelease>\IncludeInRelease{2020/10/01}{\c_@@_generics_prop}%
+%<latexrelease>\IncludeInRelease{2020/10/01}{\c_@@_generics_prop}
 %<latexrelease>                 {Standardise~generic~hook~names}
 %<latexrelease>\prop_const_from_keyval:Nn \c_@@_generics_prop
 %<latexrelease>     {cmd=,env=,file=,package=,class=,include=}
@@ -3496,13 +4269,13 @@
 %    The following generic hooks are supposed to use reverse ordering
 %    (the |ii| and |iii| names are kept for the deprecation cycle):
 %    \begin{macrocode}
-%<latexrelease>\IncludeInRelease{2021/11/15}{\c_@@_generics_reversed_ii_prop}%
+%<latexrelease>\IncludeInRelease{2021/11/15}{\c_@@_generics_reversed_ii_prop}
 %<latexrelease>                 {Standardise~generic~hook~names}
 %<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 %
 %    \begin{macrocode}
-%<latexrelease>\IncludeInRelease{2020/10/01}{\c_@@_generics_reversed_ii_prop}%
+%<latexrelease>\IncludeInRelease{2020/10/01}{\c_@@_generics_reversed_ii_prop}
 %<latexrelease>                 {Standardise~generic~hook~names}
 %<latexrelease>\prop_const_from_keyval:Nn \c_@@_generics_reversed_ii_prop {after=,end=}
 %<latexrelease>\prop_const_from_keyval:Nn \c_@@_generics_reversed_iii_prop {after=}
@@ -3511,13 +4284,46 @@
 %    \end{macrocode}
 %  \end{macro}
 %
+%
+% \begin{macro}{
+%     \c_@@_parameter_cmd/./before_tl,\c_@@_parameter_cmd/./after_tl,
+%   }
+%   Token lists defining the number of arguments for a given type of
+%   generic hook.
+% \changes{v1.1d}{2023/05/21}
+%         {Token lists added (cmd-args).}
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\c_@@_parameter_cmd/./before_tl}
+%<latexrelease>                 {Hooks~with~args}
+%    \end{macrocode}
+%
+%   \hook{cmd} hooks are declared with 9 arguments because they have a
+%   variable number of arguments (depending on the command they are
+%   attached to), so we use the maximum here.
+%    \begin{macrocode}
+\tl_const:cn { c_@@_parameter_cmd/./before_tl } { #1#2#3#4#5#6#7#8#9 }
+\tl_const:cn { c_@@_parameter_cmd/./after_tl }  { #1#2#3#4#5#6#7#8#9 }
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2020/10/01}{\c_@@_parameter_cmd/./before_tl}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\EndIncludeInRelease
+%    \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.
 % \changes{v1.0o}{2021/07/22}{Do not queue removals (gh/625)}
+% \changes{v1.1a}{2023/04/06}
+%         {Changes to add hook arguments (hook-args).}
 %    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\hook_gremove_code:nn}
+%<latexrelease>                 {Hooks~with~args}
 \cs_new_protected:Npn \hook_gremove_code:nn #1 #2
   { \@@_normalize_hook_args:Nnn \@@_gremove_code:nn {#1} {#2} }
 \cs_new_protected:Npn \@@_gremove_code:nn #1 #2
@@ -3542,8 +4348,8 @@
         \str_if_eq:nnTF {#2} {*}
           {
             \prop_gclear:c { g_@@_#1_code_prop }
-            \@@_tl_gclear:c { @@_toplevel~#1 }
-            \@@_tl_gclear:c { @@_next~#1 }
+            \@@_toplevel_gset:nn {#1} { }
+            \@@_next_gset:nn {#1} { }
           }
           {
 %    \end{macrocode}
@@ -3551,7 +4357,7 @@
 %    there is under the same label.
 %    \begin{macrocode}
             \str_if_eq:nnTF {#2} { top-level }
-              { \@@_tl_gclear:c { @@_toplevel~#1 } }
+              { \@@_toplevel_gset:nn {#1} { } }
               {
                 \prop_gpop:cnNF { g_@@_#1_code_prop } {#2} \l_@@_return_tl
                   { \msg_warning:nnnn { hooks } { cannot-remove } {#1} {#2} }
@@ -3576,14 +4382,568 @@
           { \msg_warning:nnnn { hooks } { cannot-remove } {#1} {#2} }
       }
   }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\hook_gremove_code:nn}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_new_protected:Npn \@@_gremove_code:nn #1 #2
+%<latexrelease>  {
+%<latexrelease>    \@@_if_structure_exist:nTF {#1}
+%<latexrelease>      {
+%<latexrelease>        \str_if_eq:nnTF {#2} {*}
+%<latexrelease>          {
+%<latexrelease>            \prop_gclear:c { g_@@_#1_code_prop }
+%<latexrelease>            \@@_tl_gclear:c { @@_toplevel~#1 }
+%<latexrelease>            \@@_tl_gclear:c { @@_next~#1 }
+%<latexrelease>          }
+%<latexrelease>          {
+%<latexrelease>            \str_if_eq:nnTF {#2} { top-level }
+%<latexrelease>              { \@@_tl_gclear:c { @@_toplevel~#1 } }
+%<latexrelease>              {
+%<latexrelease>                \prop_gpop:cnNF { g_@@_#1_code_prop } {#2} \l_@@_return_tl
+%<latexrelease>                  { \msg_warning:nnnn { hooks } { cannot-remove } {#1} {#2} }
+%<latexrelease>              }
+%<latexrelease>          }
+%<latexrelease>        \@@_if_usable:nT {#1}
+%<latexrelease>          { \@@_update_hook_code:n {#1} }
+%<latexrelease>      }
+%<latexrelease>      {
+%<latexrelease>        \@@_if_deprecated_generic:nTF {#1}
+%<latexrelease>          {
+%<latexrelease>            \@@_deprecated_generic_warn:n {#1}
+%<latexrelease>            \@@_do_deprecated_generic:Nn \@@_gremove_code:nn {#1} {#2}
+%<latexrelease>          }
+%<latexrelease>          { \msg_warning:nnnn { hooks } { cannot-remove } {#1} {#2} }
+%<latexrelease>      }
+%<latexrelease>  }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_cs_gput_right:nnn}
+% \begin{macro}{\@@_cs_gput_right_fast:nnn,\@@_cs_gput_right_slow:nnn}
+% \begin{macro}{\@@_code_gset_auxi:nnnn,\@@_code_gset_auxi:eeen}
+%   This macro is used to append code to the \verb|toplevel| and
+%   \verb|next| token lists, trating them correctly depending on their
+%   number of arguments, and depending if the code being added should
+%   have parameter tokens understood as parameters, or doubled to be
+%   stored as parameter tokens.
+% \changes{v1.1a}{2023/04/06}
+%         {Macro added (hook-args).}
+% \changes{v1.1e}{2023/06/06}
+%         {Short-circuit when the hook is declared without args (gh1078).}
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_cs_gput_right:nnn}
+%<latexrelease>                 {Hooks~with~args}
+%    \end{macrocode}
+%
+%   Check if the current hook is declared and takes no arguments.  In
+%   this case, we short-circuit and use the simpler and much faster
+%   approach that doesn't require hash-doubling.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_cs_gput_right:nnn #1 #2
+  {
+    \if:w T
+        \@@_if_declared:nF {#2} { F }
+        \tl_if_empty:cF { c_@@_#2_parameter_tl } { F }
+          T
+      \exp_after:wN \@@_cs_gput_right_fast:nnn
+    \else:
+      \exp_after:wN \@@_cs_gput_right_slow:nnn
+    \fi:
+        {#1} {#2}
+  }
+\cs_new_protected:Npn \@@_cs_gput_right_fast:nnn #1 #2 #3
+  { \cs_gset:cpx { @@#1~#2 } { \exp_not:v { @@#1~#2 } \exp_not:n {#3} } }
+\cs_new_protected:Npn \@@_cs_gput_right_slow:nnn #1 #2 #3
+  {
+%    \end{macrocode}
+%   The auxiliary \cs{@@_code_gset_auxi:eeen} just does the assignment
+%   at the end.  Its first argument is the parameter text of the macro,
+%   which is chosen here depending if
+%   \cs[no-index]{c_@@_\meta{hook}_parameter_tl} exists, if the hook is
+%   declared, and if it's a generic hook.
+%    \begin{macrocode}
+    \cs_if_exist:cF { @@#1~#2 }
+      { \@@_code_gset_aux:nnn {#1} {#2} { } }
+    \@@_code_gset_auxi:eeen
+      {
+        \@@_if_declared:nTF {#2}
+          { \tl_use:c { c_@@_#2_parameter_tl } }
+          {
+            \@@_if_generic:nTF {#2}
+              { \@@_generic_parameter:n {#2} }
+              { \c_@@_nine_parameters_tl }
+          }
+      }
+%    \end{macrocode}
+%   Here we take the existing code in the macro, expand it with as many
+%   arguments as it takes, then double the hashes so the code can be
+%   reused. \pho{Maybe can be improved. The case of adding to an empty
+%     cs can be optimised by quickly checking \cs{cs_replacement_spec}.}
+%    \begin{macrocode}
+      {
+        \exp_args:NNo \exp_args:No \@@_double_hashes:n
+          {
+            \cs:w @@#1~#2 \exp_last_unbraced:Ne \cs_end:
+              { \@@_braced_cs_parameter:n { @@#1~#2 } }
+          }
+      }
+%    \end{macrocode}
+%   Now the new code:  if we are replacing arguments, then hashes are
+%   left untouched, otherwise they are doubled.
+%    \begin{macrocode}
+      {
+        \@@_if_replacing_args:TF
+          { \exp_not:n }
+          { \@@_double_hashes:n }
+            {#3}
+      }
+%    \end{macrocode}
+%   And finally, the csname which we'll define with all the above.
+%    \begin{macrocode}
+      { @@#1~#2 }
+  }
+%    \end{macrocode}
+%
+%   And as promised, the auxiliary that does the definition.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_code_gset_auxi:nnnn #1 #2 #3 #4
+  { \cs_gset:cpn {#4} #1 { #2 #3 } }
+\cs_generate_variant:Nn \@@_code_gset_auxi:nnnn { eeen }
 %    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_cs_gput_right:nnn}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_undefine:N \@@_cs_gput_right:nnn
+%<latexrelease>\cs_undefine:N \@@_cs_gput_right_fast:nnn
+%<latexrelease>\cs_undefine:N \@@_cs_gput_right_slow:nnn
+%<latexrelease>\cs_undefine:N \@@_code_gset_auxi:nnnn
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+% \end{macro}
 % \end{macro}
 % \end{macro}
 %
 % \begin{macro}{
+%     \@@_code_gset:nn,\@@_code_gset:ne,
+%     \@@_toplevel_gset:nn,
+%     \@@_next_gset:nn,
+%     \@@_code_gset_aux:nnn
+%   }
+%   These macros define
+%   \cs[no-index]{@@\meta{type}\textvisiblespace\meta{hook}} (with
+%   \meta{type} being \verb|_next|, \verb|_toplevel|, or empty) with the
+%   given code and the parameters stored in
+%   \cs[no-index]{c_@@_\meta{hook}_parameter_tl} (or none, if that
+%   doesn't exist).
+% \changes{v1.1a}{2023/04/06}
+%         {Macro added (hook-args).}
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_code_gset:nn}
+%<latexrelease>                 {Hooks~with~args}
+\cs_new_protected:Npn \@@_code_gset:nn
+  { \@@_code_gset_aux:nnn { } }
+\cs_new_protected:Npn \@@_toplevel_gset:nn
+  { \@@_code_gset_aux:nnn { _toplevel } }
+\cs_new_protected:Npn \@@_next_gset:nn
+  { \@@_code_gset_aux:nnn { _next } }
+\cs_new_protected:Npn \@@_code_gset_aux:nnn #1 #2 #3
+  {
+    \cs_gset:cpn { @@#1~#2 \exp_last_unbraced:Ne }
+      { \@@_parameter:n {#2} }
+      {#3}
+  }
+\cs_generate_variant:Nn \@@_code_gset:nn { ne }
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_code_gset:nn}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_undefine:N \@@_code_gset:nn
+%<latexrelease>\cs_undefine:N \@@_toplevel_gset:nn
+%<latexrelease>\cs_undefine:N \@@_next_gset:nn
+%<latexrelease>\cs_undefine:N \@@_code_gset_aux:nnn
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_normalise_cs_args:nn}
+%   This macro normalises the parameters of the macros
+%   \cs[no-index]{@@\meta{type}\textvisiblespace\meta{hook}} to take the
+%   right number of arguments after a hook is declared.  At this point
+%   we know \cs[no-index]{c_@@_\meta{hook}_parameter_tl} exists, so use
+%   that to count the arguments and use that as \meta{parameter text}
+%   for the newly (re)defined macro.
+% \changes{v1.1a}{2023/04/06}
+%         {Macro added (hook-args).}
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_normalise_cs_args:nn}
+%<latexrelease>                 {Hooks~with~args}
+\cs_new_protected:Npn \@@_normalise_cs_args:nn #1 #2
+  {
+    \cs_if_exist:cT { @@#1~#2 }
+      {
+        \@@_code_gset_auxi:eeen
+          { \tl_use:c { c_@@_#2_parameter_tl } }
+          {
+            \exp_args:NNo \exp_args:No \@@_double_hashes:n
+              {
+                \cs:w @@#1~#2 \exp_last_unbraced:Ne \cs_end:
+                  { \@@_braced_cs_parameter:n { @@#1~#2 } }
+              }
+          }
+          { }
+          { @@#1~#2 }
+      }
+  }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_normalise_cs_args:nn}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_undefine:N \@@_normalise_cs_args:nn
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_normalise_code_pool:n}
+% \begin{macro}{\@@_set_normalise_fn:nn}
+%   This one's a bit of a hack.  It takes a hook, and iterates over its
+%   code pool (\cs[no-index]{g_@@_\meta{hook}_code_prop}), redefining
+%   each code label to use only valid arguments.  This is used when, for
+%   example, a code is added referencing arguments \verb|#1| and
+%   \verb|#2|, but the hook has only \verb|#1|.  In this example, every
+%   reference to \verb|#2| is changed to \verb|##2|.  This is done
+%   because otherwise \TeX{} will throw a low-level error every time
+%   some change happens to the hook (code is added, a rule is set, etc),
+%   which can get quite repetitive for no good reason.
+% \changes{v1.1a}{2023/04/06}
+%         {Macro added (hook-args).}
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_normalise_code_pool:n}
+%<latexrelease>                 {Hooks~with~args}
+\cs_new_protected:Npn \@@_normalise_code_pool:n #1
+  {
+%    \end{macrocode}
+%   First, call \cs{@@_set_normalise_fn:nn} with the hook name to set
+%   everything up, then we'll loop over the
+%   hook's code pool applying the normalisation above.  After that's
+%   done, copy the temporary property list back to the hook's.
+%    \begin{macrocode}
+    \@@_set_normalise_fn:nn {#1} { Offending~label:~'##1' }
+    \prop_clear:N \l_@@_work_prop
+    \prop_map_function:cN { g_@@_#1_code_prop } \@@_normalise_fn:nn
+    \prop_gset_eq:cN { g_@@_#1_code_prop } \l_@@_work_prop
+  }
+%    \end{macrocode}
+%
+%   The sole purpose of this function is to define
+%   \cs{@@_normalise_fn:nn}, which will then do the correcting of the
+%   code being added to the hook.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_set_normalise_fn:nn #1 #2
+  {
+%    \end{macrocode}
+%   To start, we define two auxiliary token lists.
+%   \cs[no-index]{l_@@_tmpb_tl} contains:
+%\begin{verbatim}
+%   {\c__hook_hashes_tl 1}
+%   {\c__hook_hashes_tl 2}
+%   ...
+%   {\c__hook_hashes_tl 9}
+%\end{verbatim}
+%    \begin{macrocode}
+    \cs_set:Npn \@@_tmp:w ##1##2##3##4##5##6##7##8##9 { }
+    \tl_set:Ne \l_@@_tmpb_tl
+      { \@@_braced_cs_parameter:n { @@_tmp:w } }
+    \group_begin:
+      \@@_tl_set:cn { c_@@_hash_tl } { \exp_not:N \c_@@_hashes_tl }
+      \use:e
+        {
+    \group_end:
+    \tl_set:Nn \exp_not:N \l_@@_tmpb_tl { \l_@@_tmpb_tl }
+        }
+%    \end{macrocode}
+%   And \cs[no-index]{l_@@_tmpa_tl} contains:
+%\begin{verbatim}
+%   {\c__hook_hash_tl 1}
+%   {\c__hook_hash_tl 2}
+%   ...
+%   {\c__hook_hash_tl <n>}
+%\end{verbatim}
+%   with \meta{n} being the number of arguments declared for the hook.
+%    \begin{macrocode}
+    \exp_last_unbraced:NNf
+    \cs_set:Npn \@@_tmp:w { \@@_parameter:n {#1} } { }
+    \tl_set:Ne \l_@@_tmpa_tl { \@@_braced_cs_parameter:n { @@_tmp:w } }
+%    \end{macrocode}
+%   Now this function does the fun part.  It is meant to be used with
+%   \cs{prop_map_function:NN}, taking a label name in \verb|##1| and the
+%   code stored in that label in \verb|##2|.
+%    \begin{macrocode}
+    \cs_gset_protected:Npx \@@_normalise_fn:nn ##1 ##2
+      {
+%    \end{macrocode}
+%   Here we'll define two auxiliary macros:  the first one throws an
+%   error when it detects an invalid argument reference.  It piggybacks
+%   on \TeX's low-level \enquote{Illegal parameter number} error, but it
+%   defines a weirdly-named control sequence so that the error comes out
+%   nicely formatted.  For example, if the label \enquote{badpkg} adds
+%   some code that references argument \verb|#3| in the hook
+%   \enquote{foo}, which takes only two arguments, the error will be:
+%\begin{verbatim}
+%   ! Illegal parameter number in definition of hook 'foo'.
+%   (hooks)             Offending label: 'badpkg'.
+%   <to be read again> 
+%                      3
+%\end{verbatim}
+%   At the point of this definition, the error is raised if the code
+%   happens to reference an invalid argument.  If it was possible to
+%   detect that this definition raised no error, the next step would be
+%   unnecessary.  We'll do all this in a group so this weird definition
+%   doesn't leak out, and set \cs{tex_escapechar:D} to $-1$ so this hack
+%   shows up extra nice in the case of an error.
+%    \begin{macrocode}
+        \group_begin:
+          \int_set:Nn \tex_escapechar:D { -1 }
+          \cs_set:cpn
+              {
+                hook~'#1'. ^^J
+                (hooks) \prg_replicate:nn { 13 } { ~ }
+                #2 % more message text
+              }
+              \exp_not:v { c_@@_#1_parameter_tl }
+            {##2}
+        \group_end:
+%    \end{macrocode}
+%   This next macro, with a much less fabulous name, takes always nine
+%   arguments, and it just transfers the code \verb|##2| under the label
+%   \verb|##1| to the temporary property list.  The first \meta{n}
+%   arguments are taken from \cs[no-index]{l_@@_tmpa_tl}, and the other
+%   $9-\meta{n}$ taken from \cs[no-index]{l_@@_tmpb_tl} (which contains
+%   twice as many \verb|#| tokens as the former).  Then,
+%   \cs{@@_double_hashes:n} is used to double non-argument hashes, and
+%   expand the \cs{c_@@_hash_tl} and \cs{c_@@_hashes_tl} to the actual
+%   parameter tokens.
+%    \begin{macrocode}
+        \cs_set:Npn \exp_not:N \@@_tmp:w
+            \exp_not:V \c_@@_nine_parameters_tl
+          {
+            \prop_put:Nne \exp_not:N \l_@@_work_prop
+              {##1} { \exp_not:N \@@_double_hashes:n {##2} }
+          }
+%    \end{macrocode}
+%   This next macro, with a much less fabulous name, takes always nine
+%   arguments, and it just transfers the code \verb|##2| under the label
+%   \verb|##1| to the temporary property list.  The first \meta{n}
+%   arguments are taken from \cs[no-index]{l_@@_tmpa_tl}, and the other
+%   $9-\meta{n}$ taken from \cs[no-index]{l_@@_tmpb_tl} (which contains
+%   twice as many \verb|#| tokens as the former).  Then,
+%   \cs{@@_double_hashes:n} is used to double non-argument hashes, and
+%   expand the \cs{c_@@_hash_tl} and \cs{c_@@_hashes_tl} to the actual
+%   parameter tokens.
+%    \begin{macrocode}
+        \exp_not:N \@@_tmp:w
+          \exp_not:V \l_@@_tmpa_tl
+          \exp_args:No \exp_not:o
+            { \exp_after:wN \@@_tmp:w \l_@@_tmpb_tl }
+      }
+  }
+\cs_new_eq:NN \@@_normalise_fn:nn ?
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_normalise_code_pool:n}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_undefine:N \@@_normalise_code_pool:n
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[pTF]{\@@_cs_if_empty:c}
+%   Check if the expansion of a control sequence is empty by looking at
+%   its replacement text.
+% \changes{v1.1a}{2023/04/06}
+%         {Macro added (hook-args).}
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_cs_if_empty:c}
+%<latexrelease>                 {Hooks~with~args}
+\prg_new_conditional:Npnn \@@_cs_if_empty:c #1 { p, T, F, TF }
+  {
+    \if:w \scan_stop: \@@_replacement_spec:c {#1} \scan_stop:
+      \prg_return_true:
+    \else:
+      \prg_return_false:
+    \fi:
+  }
+\cs_new:Npn \@@_replacement_spec:c #1
+  {
+    \exp_args:Nc \token_if_macro:NT {#1}
+      { \cs_replacement_spec:c {#1} }
+  }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_cs_if_empty:c}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_undefine:N \@@_cs_if_empty:c
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_braced_cs_parameter:n}
+% \begin{macro}{\@@_braced_hidden_loop:w}
+% \begin{macro}{\@@_cs_parameter_count:N}
+% \begin{macro}{\@@_cs_parameter_count:w,\@@_cs_end:w}
+%   Looks at the \meta{parameter text} of a control sequence, and
+%   returns a run of \enquote{hidden} braced parameters for that macro.
+%   This works as long as the macros take a simple run of zero to nine
+%   arguments.  The parameters are \enquote{hidden} because the
+%   parameter tokens are returned inside \cs{c_@@_hash_tl} instead of
+%   explicitly, so that \cs{@@_double_hashes:n} won't touch these.
+% \changes{v1.1a}{2023/04/06}
+%         {Macro added (hook-args).}
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_braced_cs_parameter:n}
+%<latexrelease>                 {Hooks~with~args}
+\cs_new:Npn \@@_braced_cs_parameter:n #1
+  {
+    \exp_last_unbraced:Ne \@@_braced_hidden_loop:w
+      { \exp_args:Nc \@@_cs_parameter_count:N {#1} } ? \s_@@_mark
+  }
+\cs_new:Npn \@@_braced_hidden_loop:w #1
+  {
+    \if:w ? #1
+      \@@_use_i_delimit_by_s_mark:nw
+    \fi:
+    { \exp_not:N \c_@@_hash_tl #1 }
+    \@@_braced_hidden_loop:w
+  }
+\cs_new:Npn \@@_cs_parameter_count:N #1
+  {
+    \exp_last_unbraced:Nf \@@_cs_parameter_count:w
+      { \token_if_macro:NT #1 { \cs_parameter_spec:N #1 } }
+      ? \@@_cs_end:w ? \@@_cs_end:w ? \@@_cs_end:w
+      ? \@@_cs_end:w ? \@@_cs_end:w ? \@@_cs_end:w
+      ? \@@_cs_end:w ? \@@_cs_end:w ? \@@_cs_end:w
+      \s_@@_mark
+  }
+\cs_new:Npn \@@_cs_parameter_count:w #1#2 #3#4 #5#6 #7#8
+  { #2 #4 #6 #8 \@@_cs_parameter_count:w }
+\cs_new:Npn \@@_cs_end:w #1 \s_@@_mark { }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%
+%   This function can't be undefined when rolling back because it's used
+%   at the end of this module to adequate the hook data structures to
+%   previous versions.
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_braced_cs_parameter:n}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_braced_parameter:n}
+% \begin{macro}{\@@_braced_real_loop:w}
+%   This one is used in simpler cases, where no special handling of
+%   hashes is required.  This is used only inside
+%   \cs{@@_initialize_hook_code:n}, so it assumes
+%   \cs[no-index]{c_@@_\meta{hook}_parameter_tl} is defined, but should
+%   work otherwise.
+% \changes{v1.1a}{2023/04/06}
+%         {Macro added (hook-args).}
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_braced_parameter:n}
+%<latexrelease>                 {Hooks~with~args}
+\cs_new:Npn \@@_braced_parameter:n #1
+  {
+    \if_case:w
+      \int_eval:n
+        { \exp_args:Nv \str_count:n { c_@@_#1_parameter_tl } / 3 }
+      \exp_stop_f:
+    \or: {##1}
+    \or: {##1} {##2}
+    \or: {##1} {##2} {##3}
+    \or: {##1} {##2} {##3} {##4}
+    \or: {##1} {##2} {##3} {##4} {##5}
+    \or: {##1} {##2} {##3} {##4} {##5} {##6}
+    \or: {##1} {##2} {##3} {##4} {##5} {##6} {##7}
+    \or: {##1} {##2} {##3} {##4} {##5} {##6} {##7} {##8}
+    \or: {##1} {##2} {##3} {##4} {##5} {##6} {##7} {##8} {##9}
+    \else:
+      \msg_expandable_error:nnn { latex2e } { should-not-happen }
+        { Invalid~parameter~spec. }
+    \fi:
+  }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_braced_parameter:n}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_undefine:N \@@_braced_parameter:n
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_parameter:n}
+%   This is just a shortcut to \verb|e|- or \verb|f|-expand to the
+%   \meta{parameter text} of the hook.
+% \changes{v1.1a}{2023/04/06}
+%         {Macro added (hook-args).}
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_parameter:n}
+%<latexrelease>                 {Hooks~with~args}
+\cs_new:Npn \@@_parameter:n #1
+  {
+    \cs:w c_@@_
+    \tl_if_exist:cTF { c_@@_#1_parameter_tl }
+      { #1_parameter } { empty }
+    _tl \cs_end:
+  }
+\cs_new:Npn \@@_generic_parameter:n #1
+  { \@@_generic_parameter:w #1 / / / \s_@@_mark }
+\cs_new:Npn \@@_generic_parameter:w #1 / #2 / #3 / #4 \s_@@_mark
+  {
+    \cs_if_exist_use:cF { c_@@_parameter_#1/./#3_tl }
+      { \c_@@_empty_tl }
+  }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_parameter:n}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_undefine:N \@@_parameter:n
+%<latexrelease>\cs_undefine:N \@@_generic_parameter:n
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+% \end{macro}
+%
+%
+%  \subsection{Setting rules for hooks code}
+%
+% \begin{macro}{
 %     \g_@@_??_code_prop,
 %     \@@~??,
 %     \g_@@_??_reversed_tl,
+%     \c_@@_??_parameter_tl,
 %   }
 %
 %    Initially these variables simply used an empty ``label'' name (not
@@ -3597,11 +4957,11 @@
 %    lot, the code should be fast, so this is not done with \texttt{c}
 %    expansion in the code later on.
 %
-%    \cs{@@\textvisiblespace??} isn't used, but it has to be defined to trick
-%    the code into thinking that \texttt{??} is actually a hook.
+%    \cs{@@\textvisiblespace??} isn't used, but it has to be defined to
+%    trick the code into thinking that \texttt{??} is actually a hook.
 %    \begin{macrocode}
-\prop_new:c {g_@@_??_code_prop}
-\prop_new:c {@@~??}
+\prop_new:c { g_@@_??_code_prop }
+\prop_new:c { @@~?? }
 %    \end{macrocode}
 %
 %    Default rules are always given in normal ordering (never in
@@ -3610,11 +4970,23 @@
 %    \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}
+\tl_new:c { g_@@_??_reversed_tl }
 %    \end{macrocode}
-%  \end{macro}
 %
-%  \subsection{Setting rules for hooks code}
+%    The parameter text for the \enquote{default} hook is empty.
+% \changes{v1.1a}{2023/04/06}
+%         {Token list added (hook-args).}
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\c_@@_??_parameter_tl}
+%<latexrelease>                 {Hooks~with~args}
+\tl_const:cn { c_@@_??_parameter_tl } { }
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2020/10/01}{\c_@@_??_parameter_tl}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_undefine:c { c_@@_??_parameter_tl }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%  \end{macro}
 %
 %  \begin{macro}{\hook_gset_rule:nnnn}
 %  \begin{macro}{\@@_gset_rule:nnnn}
@@ -3645,13 +5017,13 @@
         \@@_deprecated_generic_warn:n {#1}
         \@@_do_deprecated_generic:Nn \@@_gset_rule:nnnn {#1}
           {#2} {#3} {#4}
-        \@@_clean_to_scan:w
+        \@@_use_none_delimit_by_s_mark:w
       }
     \@@_if_execute_immediately:nT {#1}
       {
         \msg_error:nnnnnn { hooks } { rule-too-late }
           {#1} {#2} {#3} {#4}
-        \@@_clean_to_scan:w
+        \@@_use_none_delimit_by_s_mark:w
       }
 %    \end{macrocode}
 %    First we ensure the basic data structure of the hook exists:
@@ -3840,44 +5212,82 @@
 %  \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.
+%    in the right order.
+% \changes{v1.1a}{2023/04/06}
+%         {Changes to add hook arguments (hook-args).}
 %    \begin{macrocode}
-\cs_new_protected:Npn \@@_initialize_all: {
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_initialize_all:}
+%<latexrelease>                 {Hooks~with~args}
+\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
+    \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.
+%    of them.  Here we have to determine if the hook has arguments so
+%    that auxiliaries know what to do with hashes.  We look at
+%    \cs[no-index]{c_@@_\meta{hook}_parameter_tl}, if it has any
+%    parameters, and set \verb|replacing_args| accordingly.
 %    \begin{macrocode}
-  \@@_debug:n { \prop_gclear:N \g_@@_used_prop }
-  \seq_map_inline:Nn \g_@@_all_seq
+    \@@_debug:n { \prop_gclear:N \g_@@_used_prop }
+    \seq_map_inline:Nn \g_@@_all_seq
       {
+        \tl_if_empty:cTF { c_@@_##1_parameter_tl }
+          { \@@_replacing_args_false: }
+          { \@@_replacing_args_true: }
         \@@_update_hook_code:n {##1}
+        \@@_replacing_args_reset:
       }
 %    \end{macrocode}
 %    If we are debugging we show results hook by hook for all hooks
 %    that have data.
 %    \begin{macrocode}
-  \@@_debug:n
-     { \iow_term:x{^^JAll~ initialized~ (non-empty)~ hooks:}
-       \prop_map_inline:Nn \g_@@_used_prop
-           { \iow_term:x{^^J~ ##1~ ->~
-               \exp_not:v {@@~##1}~ }
-           }
-     }
+    \@@_debug:n
+      {
+        \iow_term:x { ^^J All~initialized~(non-empty)~hooks: }
+        \prop_map_inline:Nn \g_@@_used_prop
+          {
+            \iow_term:x
+              { ^^J ~ ##1 ~ -> ~ \cs_replacement_spec:c { @@~##1 } ~ }
+          }
+      }
 %    \end{macrocode}
 %    After all hooks are initialized we change the ``use'' to just
 %    call the hook code and not initialize it (as it was done in the
 %    preamble.
 %    \begin{macrocode}
-  \cs_gset_eq:NN \hook_use:n \@@_use_initialized:n
-  \cs_gset_eq:NN \@@_preamble_hook:n \use_none:n
-}
+    \@@_post_initialization_defs:
+  }
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_initialize_all:}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_gset_protected:Npn \@@_initialize_all:
+%<latexrelease>  {
+%<latexrelease>    \cs_gset_eq:NN \@@_update_hook_code:n \@@_initialize_hook_code:n
+%<latexrelease>    \@@_debug:n { \prop_gclear:N \g_@@_used_prop }
+%<latexrelease>    \seq_map_inline:Nn \g_@@_all_seq
+%<latexrelease>      { \@@_update_hook_code:n {##1} }
+%<latexrelease>    \@@_debug:n
+%<latexrelease>      {
+%<latexrelease>        \iow_term:x{^^JAll~ initialized~ (non-empty)~ hooks:}
+%<latexrelease>        \prop_map_inline:Nn \g_@@_used_prop
+%<latexrelease>          {
+%<latexrelease>            \iow_term:x
+%<latexrelease>              { ^^J ~ ##1 ~ -> ~ \cs_replacement_spec:c { @@~##1 } ~ }
+%<latexrelease>          }
+%<latexrelease>      }
+%<latexrelease>    \cs_gset_eq:NN \hook_use:n \@@_use_initialized:n
+%<latexrelease>    \cs_gset_eq:NN \@@_preamble_hook:n \use_none:n
+%<latexrelease>  }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 %  \end{macro}
 %
@@ -3887,7 +5297,11 @@
 %    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.
+% \changes{v1.1a}{2023/04/06}
+%         {Changes to add hook arguments (hook-args).}
 %    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_initialize_hook_code:n}
+%<latexrelease>                 {Hooks~with~args}
 \cs_new_protected:Npn \@@_initialize_hook_code:n #1
   {
     \@@_debug:n
@@ -3915,10 +5329,15 @@
       {
         \prop_if_empty:cTF { g_@@_#1_code_prop }
           {
-            \@@_tl_gset:co { @@~#1 }
+            \@@_code_gset:ne {#1}
               {
-                \cs:w @@_toplevel~#1 \exp_after:wN \cs_end:
-                \cs:w @@_next~#1 \cs_end:
+%    \end{macrocode}
+%   The hook may take arguments, so we add a run of braced parameters
+%   after the \verb|_next| and \verb|_toplevel| macros, so that the
+%   arguments passed to the hook are forwarded to them.
+%    \begin{macrocode}
+                \exp_not:c { @@_toplevel~#1 } \@@_braced_parameter:n {#1}
+                \exp_not:c { @@_next~#1 } \@@_braced_parameter:n {#1}
               }
           }
           {
@@ -3958,6 +5377,42 @@
           }
       }
   }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_initialize_hook_code:n}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_gset_protected:Npn \@@_initialize_hook_code:n #1
+%<latexrelease>  {
+%<latexrelease>    \@@_debug:n
+%<latexrelease>      { \iow_term:x { ^^J Update~code~for~hook~'#1' \on@line :^^J } }
+%<latexrelease>    \@@_include_legacy_code_chunk:n {#1}
+%<latexrelease>    \@@_if_usable:nT {#1}
+%<latexrelease>      {
+%<latexrelease>        \prop_if_empty:cTF { g_@@_#1_code_prop }
+%<latexrelease>          {
+%<latexrelease>            \@@_tl_gset:co { @@~#1 }
+%<latexrelease>              {
+%<latexrelease>                \cs:w @@_toplevel~#1 \exp_after:wN \cs_end:
+%<latexrelease>                \cs:w @@_next~#1 \cs_end:
+%<latexrelease>              }
+%<latexrelease>          }
+%<latexrelease>          {
+%<latexrelease>            \@@_if_reversed:nTF {#1}
+%<latexrelease>              { \cs_set_eq:NN \@@_tl_gput:Nn    \@@_tl_gput_left:Nn
+%<latexrelease>                \cs_set_eq:NN \@@_clist_gput:NV \clist_gput_left:NV  }
+%<latexrelease>              { \cs_set_eq:NN \@@_tl_gput:Nn    \@@_tl_gput_right:Nn
+%<latexrelease>                \cs_set_eq:NN \@@_clist_gput:NV \clist_gput_right:NV }
+%<latexrelease>            \prop_set_eq:Nc \l_@@_work_prop { g_@@_#1_code_prop }
+%<latexrelease>            \@@_initialize_single:ccn
+%<latexrelease>              { @@~#1 } { g_@@_#1_labels_clist } {#1}
+%<latexrelease>            \@@_debug:n
+%<latexrelease>              { \exp_args:NNx \prop_gput:Nnn \g_@@_used_prop {#1} { } }
+%<latexrelease>          }
+%<latexrelease>      }
+%<latexrelease>  }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 %  \end{macro}
 %
@@ -4034,7 +5489,12 @@
 %    ^^A #1 <- \@@~#1
 %    ^^A #2 <- \g_@@_#1_labels_clist
 %    ^^A #3 <- #1
+%
+% \changes{v1.1a}{2023/04/06}
+%         {Changes to add hook arguments (hook-args).}
 %    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_initialize_single:NNn}
+%<latexrelease>                 {Hooks~with~args}
 \cs_new_protected:Npn \@@_initialize_single:NNn #1#2#3
   {
 %    \end{macrocode}
@@ -4181,15 +5641,103 @@
 %    time execution (\verb=#3=).  These should normally be empty.  The
 %    |top-level| code is added with \cs{@@_tl_gput:Nn} as that might
 %    change for a reversed hook (then |top-level| is the very first code
-%    chunk added).  The |next| code is always added last.
-%    \begin{macrocode}
-    \exp_args:NNo \@@_tl_gput:Nn #1 { \cs:w @@_toplevel~#3 \cs_end: }
-    \@@_tl_gput_right:No #1 { \cs:w @@_next~#3 \cs_end: }
+%    chunk added).  The |next| code is always added last (to the right).
+%   The hook may take arguments, so we add a run of braced parameters
+%   after the \verb|_next| and \verb|_toplevel| macros, so that the
+%   arguments passed to the hook are forwarded to them.
+%    \begin{macrocode}
+    \exp_args:NNe \@@_tl_gput:Nn #1
+      { \exp_not:c { @@_toplevel~#3 } \@@_braced_parameter:n {#3} }
+    \@@_tl_gput_right:Ne #1
+      { \exp_not:c { @@_next~#3 } \@@_braced_parameter:n {#3} }
+    \use:e
+      {
+        \cs_gset:cpn { @@~#3 } \use:c { c_@@_#3_parameter_tl }
+          { \exp_not:V #1 }
+      }
   }
 %    \end{macrocode}
 %
 %    \begin{macrocode}
 \cs_generate_variant:Nn \@@_initialize_single:NNn { cc }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_initialize_single:NNn}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_new_protected:Npn \@@_initialize_single:NNn #1#2#3
+%<latexrelease>  {
+%<latexrelease>    \seq_clear:N \l_@@_labels_seq
+%<latexrelease>    \int_zero:N  \l_@@_labels_int
+%<latexrelease>    \tl_set:Nn \l_@@_cur_hook_tl {#3}
+%<latexrelease>    \prop_map_inline:Nn \l_@@_work_prop
+%<latexrelease>       {
+%<latexrelease>         \int_incr:N \l_@@_labels_int
+%<latexrelease>         \seq_put_right:Nn \l_@@_labels_seq {##1}
+%<latexrelease>         \@@_tl_set:cn { \@@_tl_csname:n {##1} } { 0 }
+%<latexrelease>         \seq_clear_new:c { \@@_seq_csname:n {##1} }
+%<latexrelease>       }
+%<latexrelease>    \prop_map_inline:Nn \l_@@_work_prop
+%<latexrelease>      {
+%<latexrelease>        \prop_map_inline:Nn \l_@@_work_prop
+%<latexrelease>          {
+%<latexrelease>            \@@_if_label_case:nnnnn {##1} {####1}
+%<latexrelease>              { \prop_map_break: }
+%<latexrelease>              { \@@_apply_label_pair:nnn {##1} {####1} }
+%<latexrelease>              { \@@_apply_label_pair:nnn {####1} {##1} }
+%<latexrelease>                  {#3}
+%<latexrelease>          }
+%<latexrelease>      }
+%<latexrelease>    \@@_debug:n { \@@_debug_label_data:N \l_@@_work_prop }
+%<latexrelease>    \tl_set:Nn \l_@@_rear_tl { 0 }
+%<latexrelease>    \tl_set:cn { \@@_tl_csname:n { 0 } } { 0 }
+%<latexrelease>    \seq_map_inline:Nn \l_@@_labels_seq
+%<latexrelease>      {
+%<latexrelease>        \int_compare:nNnT { \cs:w \@@_tl_csname:n {##1} \cs_end: } = 0
+%<latexrelease>            {
+%<latexrelease>              \tl_set:cn { \@@_tl_csname:n { \l_@@_rear_tl } }{##1}
+%<latexrelease>              \tl_set:Nn \l_@@_rear_tl {##1}
+%<latexrelease>            }
+%<latexrelease>      }
+%<latexrelease>    \tl_set_eq:Nc \l_@@_front_tl { \@@_tl_csname:n { 0 } }
+%<latexrelease>    \@@_tl_gclear:N #1
+%<latexrelease>    \clist_gclear:N #2
+%<latexrelease>    \bool_while_do:nn { ! \str_if_eq_p:Vn \l_@@_front_tl { 0 } }
+%<latexrelease>      {
+%<latexrelease>        \int_decr:N \l_@@_labels_int
+%<latexrelease>        \prop_get:NVN \l_@@_work_prop \l_@@_front_tl \l_@@_return_tl
+%<latexrelease>        \exp_args:NNV \@@_tl_gput:Nn #1 \l_@@_return_tl
+%<latexrelease>        \@@_clist_gput:NV #2 \l_@@_front_tl
+%<latexrelease>        \@@_debug:n{ \iow_term:x{Handled~ code~ for~ \l_@@_front_tl} }
+%<latexrelease>        \seq_map_inline:cn { \@@_seq_csname:n { \l_@@_front_tl } }
+%<latexrelease>          {
+%<latexrelease>            \tl_set:cx { \@@_tl_csname:n {##1} }
+%<latexrelease>                       { \int_eval:n
+%<latexrelease>                           { \cs:w \@@_tl_csname:n {##1} \cs_end: - 1 }
+%<latexrelease>                       }
+%<latexrelease>            \int_compare:nNnT
+%<latexrelease>                { \cs:w \@@_tl_csname:n {##1} \cs_end: } = 0
+%<latexrelease>                {
+%<latexrelease>                  \tl_set:cn { \@@_tl_csname:n { \l_@@_rear_tl } } {##1}
+%<latexrelease>                  \tl_set:Nn \l_@@_rear_tl            {##1}
+%<latexrelease>                }
+%<latexrelease>          }
+%<latexrelease>        \tl_set_eq:Nc \l_@@_front_tl
+%<latexrelease>                      { \@@_tl_csname:n { \l_@@_front_tl } }
+%<latexrelease>      }
+%<latexrelease>    \int_compare:nNnF \l_@@_labels_int = 0
+%<latexrelease>      {
+%<latexrelease>        \iow_term:x{====================}
+%<latexrelease>        \iow_term:x{Error:~ label~ rules~ are~ incompatible:}
+%<latexrelease>        \@@_debug_label_data:N \l_@@_work_prop
+%<latexrelease>        \iow_term:x{====================}
+%<latexrelease>      }
+%<latexrelease>    \exp_args:NNo \@@_tl_gput:Nn #1 { \cs:w @@_toplevel~#3 \cs_end: }
+%<latexrelease>    \@@_tl_gput_right:No #1 { \cs:w @@_next~#3 \cs_end: }
+%<latexrelease>  }
+%<latexrelease>\cs_generate_variant:Nn \@@_tl_gput_right:Nn { No }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 %  \end{macro}
 %
@@ -4444,7 +5992,11 @@
   { \@@_log_cmd:x { >~\@spaces #1 } }
 %    \end{macrocode}
 %
+% \changes{v1.1a}{2023/04/06}
+%         {Changes to add hook arguments (hook-args).}
 %    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_log:nN}
+%<latexrelease>                 {Hooks~with~args}
 \cs_new_protected:Npn \@@_log:nN #1 #2
   {
     \@@_if_deprecated_generic:nT {#1}
@@ -4455,7 +6007,20 @@
       }
     \@@_preamble_hook:n {#1}
     \@@_log_cmd:x
-      { ^^J ->~The~ \@@_if_generic:nT {#1} { generic~ } hook~'#1': }
+      {
+        ^^J ->~The~
+        \@@_if_generic:nT {#1} { generic~ }
+        hook~'#1'
+        \@@_if_disabled:nF {#1}
+          {
+            \exp_args:Nf \@@_print_args:nn {#1}
+              {
+                \int_eval:n
+                  { \str_count:e { \@@_parameter:n {#1} } / 3 }
+              }
+          }
+        :
+      }
 %    \end{macrocode}
 %
 %    \begin{macrocode}
@@ -4471,7 +6036,12 @@
           { \@@_log_line_indent:x { --- } }
           {
             \prop_map_inline:cn { g_@@_#1_code_prop }
-              { \@@_log_line_indent:x { ##1~->~\tl_to_str:n {##2} } }
+              {
+                \exp_after:wN \cs_set:Npn \exp_after:wN \@@_tmp:w
+                  \c_@@_nine_parameters_tl {##2}
+                \@@_log_line_indent:x
+                  { ##1~->~\cs_replacement_spec:N \@@_tmp:w }
+              }
           }
 %    \end{macrocode}
 %
@@ -4485,9 +6055,9 @@
           }
         \@@_log_line_indent:x
           {
-            \tl_if_empty:cTF { @@_toplevel~#1 }
+            \@@_cs_if_empty:cTF { @@_toplevel~#1 }
               { --- }
-              { -> ~ \exp_args:Nv \tl_to_str:n { @@_toplevel~#1 } }
+              { -> ~ \cs_replacement_spec:c { @@_toplevel~#1 } }
           }
 %    \end{macrocode}
 %
@@ -4495,7 +6065,7 @@
         \@@_log_line:x { Extra~code~for~next~invocation: }
         \@@_log_line_indent:x
           {
-            \tl_if_empty:cTF { @@_next~#1 }
+            \@@_cs_if_empty:cTF { @@_next~#1 }
               { --- }
 %    \end{macrocode}
 %
@@ -4503,7 +6073,10 @@
 %    the first tokens (the code to clear itself) so we call a helper
 %    command to  get rid of them.
 %    \begin{macrocode}
-              { ->~ \exp_args:Nv \@@_log_next_code:n { @@_next~#1 } }
+              {
+                -> ~ \exp_last_unbraced:Nf \@@_log_next_code:w
+                  { \cs_replacement_spec:c { @@_next~#1 } }
+              }
           }
 %    \end{macrocode}
 %
@@ -4549,7 +6122,7 @@
                 \@spaces
                 \clist_if_empty:cTF { g_@@_#1_labels_clist }
                   { --- }
-                  { \clist_use:cn {g_@@_#1_labels_clist} { ,~ } }
+                  { \clist_use:cn { g_@@_#1_labels_clist } { ,~ } }
               }
           }
           {
@@ -4563,15 +6136,139 @@
           }
       }
   }
+%<latexrelease>\EndIncludeInRelease
+%
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_log:nN}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_new_protected:Npn \@@_log:nN #1 #2
+%<latexrelease>  {
+%<latexrelease>    \@@_if_deprecated_generic:nT {#1}
+%<latexrelease>      {
+%<latexrelease>        \@@_deprecated_generic_warn:n {#1}
+%<latexrelease>        \@@_do_deprecated_generic:Nn \@@_log:nN {#1} #2
+%<latexrelease>        \exp_after:wN \use_none:nnnnnnnnn \use_none:nnnnn
+%<latexrelease>      }
+%<latexrelease>    \@@_preamble_hook:n {#1}
+%<latexrelease>    \@@_log_cmd:x
+%<latexrelease>      { ^^J ->~The~ \@@_if_generic:nT {#1} { generic~ } hook~'#1': }
+%<latexrelease>    \@@_if_usable:nF {#1}
+%<latexrelease>      { \@@_log_line:x { The~hook~is~not~declared. } }
+%<latexrelease>    \@@_if_disabled:nT {#1}
+%<latexrelease>      { \@@_log_line:x { The~hook~is~disabled. } }
+%<latexrelease>    \hook_if_empty:nTF {#1}
+%<latexrelease>      { #2 { The~hook~is~empty } }
+%<latexrelease>      {
+%<latexrelease>        \@@_log_line:x { Code~chunks: }
+%<latexrelease>        \prop_if_empty:cTF { g_@@_#1_code_prop }
+%<latexrelease>          { \@@_log_line_indent:x { --- } }
+%<latexrelease>          {
+%<latexrelease>            \prop_map_inline:cn { g_@@_#1_code_prop }
+%<latexrelease>              { \@@_log_line_indent:x { ##1~->~\tl_to_str:n {##2} } }
+%<latexrelease>          }
+%<latexrelease>        \@@_log_line:x
+%<latexrelease>          {
+%<latexrelease>            Document-level~(top-level)~code
+%<latexrelease>            \@@_if_usable:nT {#1}
+%<latexrelease>              { ~(executed~\@@_if_reversed:nTF {#1} {first} {last} ) } :
+%<latexrelease>          }
+%<latexrelease>        \@@_log_line_indent:x
+%<latexrelease>          {
+%<latexrelease>            \tl_if_empty:cTF { @@_toplevel~#1 }
+%<latexrelease>              { --- }
+%<latexrelease>              { -> ~ \exp_args:Nv \tl_to_str:n { @@_toplevel~#1 } }
+%<latexrelease>          }
+%<latexrelease>        \@@_log_line:x { Extra~code~for~next~invocation: }
+%<latexrelease>        \@@_log_line_indent:x
+%<latexrelease>          {
+%<latexrelease>            \tl_if_empty:cTF { @@_next~#1 }
+%<latexrelease>              { --- }
+%<latexrelease>              { ->~ \exp_args:Nv \@@_log_next_code:n { @@_next~#1 } }
+%<latexrelease>          }
+%<latexrelease>        \@@_log_line:x { Rules: }
+%<latexrelease>        \bool_set_true:N \l_@@_tmpa_bool
+%<latexrelease>        \@@_list_rules:nn {#1}
+%<latexrelease>          {
+%<latexrelease>            \bool_set_false:N \l_@@_tmpa_bool
+%<latexrelease>            \@@_log_line_indent:x
+%<latexrelease>              {
+%<latexrelease>                ##2~ with~
+%<latexrelease>                \str_if_eq:nnT {##3} {??} { default~ }
+%<latexrelease>                relation~ ##1
+%<latexrelease>              }
+%<latexrelease>          }
+%<latexrelease>        \bool_if:NT \l_@@_tmpa_bool
+%<latexrelease>          { \@@_log_line_indent:x { --- } }
+%<latexrelease>        \bool_lazy_and:nnTF
+%<latexrelease>            { \@@_if_usable_p:n {#1} }
+%<latexrelease>            { ! \hook_if_empty_p:n {#1} }
+%<latexrelease>          {
+%<latexrelease>            \@@_log_line:x
+%<latexrelease>              {
+%<latexrelease>                Execution~order
+%<latexrelease>                \bool_if:NTF \l_@@_tmpa_bool
+%<latexrelease>                  { \@@_if_reversed:nT {#1} { ~(after~reversal) } }
+%<latexrelease>                  { ~(after~
+%<latexrelease>                    \@@_if_reversed:nT {#1} { reversal~and~ }
+%<latexrelease>                    applying~rules)
+%<latexrelease>                  } :
+%<latexrelease>              }
+%<latexrelease>            #2 % \tl_show:n
+%<latexrelease>              {
+%<latexrelease>                \@spaces
+%<latexrelease>                \clist_if_empty:cTF { g_@@_#1_labels_clist }
+%<latexrelease>                  { --- }
+%<latexrelease>                  { \clist_use:cn { g_@@_#1_labels_clist } { ,~ } }
+%<latexrelease>              }
+%<latexrelease>          }
+%<latexrelease>          {
+%<latexrelease>            \@@_log_line:x { Execution~order: }
+%<latexrelease>            #2
+%<latexrelease>              {
+%<latexrelease>                \@spaces Not~set~because~the~hook~ \@@_if_usable:nTF {#1}
+%<latexrelease>                  { code~pool~is~empty }
+%<latexrelease>                  { is~\@@_if_disabled:nTF {#1} {disabled} {undeclared} }
+%<latexrelease>              }
+%<latexrelease>          }
+%<latexrelease>      }
+%<latexrelease>  }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 %
 % \begin{macro}{\@@_log_next_code:n}
 %    To display the code for next invocation only (i.e., from
-%    \cs{AddToHookNext} we have to remove the first two tokens at the
-%    front which are \cs{tl_gclear:N} and the token list to clear.
+%    \cs{AddToHookNext} we have to remove the string
+%    \cs{@@_clear_next:n}\Arg{hook}, so the simplest is to use a macro
+%    delimited by a \verb|}|$_12$.
+% \changes{v1.1a}{2023/04/06}
+%         {Changes to add hook arguments (hook-args).}
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_log_next_code:n}
+%<latexrelease>                 {Hooks~with~args}
+\exp_last_unbraced:NNNNo
+\cs_new:Npn \@@_log_next_code:w #1 \c_right_brace_str { }
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_log_next_code:n}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_gset:Npn \@@_log_next_code:n #1
+%<latexrelease>  { \exp_args:No \tl_to_str:n { \use_none:nn #1 } }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_print_args:n}
+%    Pretty-prints the number of arguments of a hook.
 %    \begin{macrocode}
-\cs_new:Npn \@@_log_next_code:n #1
-  { \exp_args:No \tl_to_str:n { \use_none:nn #1 } }
+\cs_new:Npn \@@_print_args:nn #1 #2
+  {
+    \int_compare:nNnT {#2} > { 0 }
+      {
+        \@@_if_declared:nT {#1} { \use_none:nnn }
+        \@@_if_cmd_hook:nT {#1}
+          { \use_i:nnn { ~ (unknown ~ } }
+        \use:n { ~ (#2 ~ }
+        argument \int_compare:nNnT {#2} > { 1 } { s } )
+      }
+  }
 %    \end{macrocode}
 % \end{macro}
 %
@@ -4656,15 +6353,34 @@
 %  \subsection{Specifying code for next invocation}
 %
 % \begin{macro}{\hook_gput_next_code:nn}
+% \changes{v1.1a}{2023/04/06}
+%         {Add \cs{hook_gput_next_code_with_args:nn} (hook-args).}
 %    \begin{macrocode}
-\cs_new_protected:Npn \hook_gput_next_code:nn #1
-  { \@@_normalize_hook_args:Nn \@@_gput_next_code:nn {#1} }
+%<latexrelease>\IncludeInRelease{2023/06/01}{\hook_gput_next_code:nn}
+%<latexrelease>                 {Hooks~with~args}
+\cs_new_protected:Npn \hook_gput_next_code:nn #1 #2
+  {
+    \@@_replacing_args_false:
+    \@@_normalize_hook_args:Nn \@@_gput_next_code:nn {#1} {#2}
+    \@@_replacing_args_reset:
+  }
+\cs_new_protected:Npn \hook_gput_next_code_with_args:nn #1 #2
+  {
+    \@@_replacing_args_true:
+    \@@_normalize_hook_args:Nn \@@_gput_next_code:nn {#1} {#2}
+    \@@_replacing_args_reset:
+  }
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2020/10/01}{\hook_gput_next_code:nn}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_gset_protected:Npn \hook_gput_next_code:nn #1
+%<latexrelease>  { \@@_normalize_hook_args:Nn \@@_gput_next_code:nn {#1} }
+%<latexrelease>\cs_gset_protected:Npn \hook_gput_next_code_with_args:nn #1 #2 { }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 %  \end{macro}
 %
-% \begin{macro}{\@@_gput_next_code:nn,
-%               \@@_gput_next_do:nn,
-%               \@@_gput_next_do:Nnn}
+% \begin{macro}{\@@_gput_next_code:nn}
 %    \begin{macrocode}
 \cs_new_protected:Npn \@@_gput_next_code:nn #1 #2
   {
@@ -4672,20 +6388,17 @@
       { \msg_error:nnn { hooks } { hook-disabled } {#1} }
       {
         \@@_if_structure_exist:nTF {#1}
-          { \@@_gput_next_do:nn {#1} {#2} }
-          { \@@_try_declaring_generic_next_hook:nn {#1} {#2} }
+          { \@@_gput_next_do:nn }
+          { \@@_try_declaring_generic_next_hook:nn }
+              {#1} {#2}
       }
   }
 %    \end{macrocode}
+%  \end{macro}
 %
-%    \begin{macrocode}
-\cs_new_protected:Npn \@@_gput_next_do:nn #1
-  {
-    \exp_args:Nc \@@_gput_next_do:Nnn
-      { @@_next~#1 } {#1}
-  }
-%    \end{macrocode}
-%   First check if the ``next code'' token list is empty:  if so we need
+% \begin{macro}{\@@_gput_next_do:nn}
+%   Start by sanity-checking with \cs{@@_chk_args_allowed:nn}.
+%   Then check if the ``next code'' token list is empty:  if so we need
 %   to add a \cs{tl_gclear:c} to clear it, so the code lasts for one
 %   usage only.  The token list is cleared early so that nested usages
 %   don't get lost.  \cs{tl_gclear:c} is used instead of
@@ -4694,38 +6407,74 @@
 %   that would make an infinite loop.  Also in case the main code token
 %   list is empty, the hook code has to be updated to add the next
 %   execution token list.
+% \changes{v1.1a}{2023/04/06}
+%         {Changes to add hook arguments (hook-args).}
+% \changes{v1.1c}{2023/04/19}
+%         {Initialise hook structure when adding 'next' code (gh/1052).}
 %    \begin{macrocode}
-\cs_new_protected:Npn \@@_gput_next_do:Nnn #1 #2
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_gput_next_do:nn}
+%<latexrelease>                 {Hooks~with~args}
+\cs_new_protected:Npn \@@_gput_next_do:nn #1
   {
-    \tl_if_empty:cT { @@~#2 }
-      { \@@_update_hook_code:n {#2} }
-    \tl_if_empty:NT #1
-      { \@@_tl_gset:Nn #1 { \@@_clear_next:n {#2} } }
-    \@@_tl_gput_right:Nn #1
+    \@@_init_structure:n {#1}
+    \@@_chk_args_allowed:nn {#1} { AddToHookNext }
+    \@@_cs_if_empty:cT { @@~#1 }
+      { \@@_update_hook_code:n {#1} }
+    \@@_cs_if_empty:cT { @@_next~#1 }
+      { \@@_next_gset:nn {#1} { \@@_clear_next:n {#1} } }
+    \@@_cs_gput_right:nnn { _next } {#1}
   }
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_gput_next_do:nn}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_gset_protected:Npn \@@_gput_next_do:nn #1
+%<latexrelease>  {
+%<latexrelease>    \exp_args:Nc \@@_gput_next_do:Nnn
+%<latexrelease>      { @@_next~#1 } {#1}
+%<latexrelease>  }
+%<latexrelease>\cs_gset_protected:Npn \@@_gput_next_do:Nnn #1 #2
+%<latexrelease>  {
+%<latexrelease>    \tl_if_empty:cT { @@~#2 }
+%<latexrelease>      { \@@_update_hook_code:n {#2} }
+%<latexrelease>    \tl_if_empty:NT #1
+%<latexrelease>      { \@@_tl_gset:Nn #1 { \@@_clear_next:n {#2} } }
+%<latexrelease>    \@@_tl_gput_right:Nn #1
+%<latexrelease>  }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 %  \end{macro}
 %
-%
-%
-%  \begin{macro}{\hook_gclear_next_code:n,\@@_clear_next:n}
+%  \begin{macro}{\hook_gclear_next_code:n}
 %    Discard anything set up for next invocation of the hook.
 % \changes{v1.0o}{2021/07/27}{Macro made public}
 %    \begin{macrocode}
 \cs_new_protected:Npn \hook_gclear_next_code:n #1
   { \@@_normalize_hook_args:Nn \@@_clear_next:n {#1} }
-\cs_new_protected:Npn \@@_clear_next:n #1
-  { \cs_gset_eq:cN { @@_next~#1 } \c_empty_tl }
 %    \end{macrocode}
 %  \end{macro}
 %
+% \begin{macro}{\@@_clear_next:n}
+% \changes{v1.1a}{2023/04/06}
+%         {Changes to add hook arguments (hook-args).}
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_clear_next:n}
+%<latexrelease>                 {Hooks~with~args}
+\cs_new_protected:Npn \@@_clear_next:n #1
+  { \@@_next_gset:nn {#1} { } }
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_clear_next:n}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_gset_protected:Npn \@@_clear_next:n #1
+%<latexrelease>  { \cs_gset_eq:cN { @@_next~#1 } \c_empty_tl }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+% \end{macro}
 %
 %
 % \subsection{Using the hook}
 %
 % \begin{macro}{\hook_use:n}
 % \begin{macro}[EXP]{\@@_use_initialized:n}
-% \begin{macro}{\@@_use_undefined:w,\@@_use_end:}
 % \begin{macro}{\@@_preamble_hook:n}
 %   \cs{hook_use:n} as defined here is used in the preamble, where
 %   hooks aren't initialized by default.  \cs{@@_use_initialized:n} is
@@ -4750,24 +6499,50 @@
 %   \verb=\csname ...\endcsname= construct \cs{tl_if_exist:c} is
 %   careful to avoid this.
 %    \begin{macrocode}
-%<latexrelease>\IncludeInRelease{2021/11/15}{\hook_use:n}
-%<latexrelease>                 {Standardise~generic~hook~names}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\hook_use:n}
+%<latexrelease>                 {Hooks~with~args}
 \cs_new_protected:Npn \hook_use:n #1
   {
-    \tl_if_exist:cT { @@~#1 }
-      {
-        \@@_preamble_hook:n {#1}
-        \cs:w @@~#1 \cs_end:
-      }
+    \@@_preamble_hook:n {#1}
+    \@@_use_initialized:n {#1}
   }
 \cs_new:Npn \@@_use_initialized:n #1
   {
     \if_cs_exist:w @@~#1 \cs_end:
-      \cs:w @@~#1 \exp_after:wN \cs_end:
+      \cs:w @@~#1 \use_i:nn
     \fi:
+    \use_none:n
+    \cs_end:
   }
 \cs_new_protected:Npn \@@_preamble_hook:n #1
-  { \@@_initialize_hook_code:n {#1} }
+  {
+    \if_cs_exist:w @@~#1 \cs_end:
+      \@@_initialize_hook_code:n {#1}
+    \fi:
+  }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2021/11/15}{\hook_use:n}
+%<latexrelease>                 {Standardise~generic~hook~names}
+%<latexrelease>\cs_new_protected:Npn \hook_use:n #1
+%<latexrelease>  {
+%<latexrelease>    \tl_if_exist:cT { @@~#1 }
+%<latexrelease>      {
+%<latexrelease>        \@@_preamble_hook:n {#1}
+%<latexrelease>        \cs:w @@~#1 \cs_end:
+%<latexrelease>      }
+%<latexrelease>  }
+%<latexrelease>\cs_new:Npn \@@_use_initialized:n #1
+%<latexrelease>  {
+%<latexrelease>    \if_cs_exist:w @@~#1 \cs_end:
+%<latexrelease>      \cs:w @@~#1 \exp_after:wN \cs_end:
+%<latexrelease>    \fi:
+%<latexrelease>  }
+%<latexrelease>\cs_new_protected:Npn \@@_preamble_hook:n #1
+%<latexrelease>  { \@@_initialize_hook_code:n {#1} }
+%<latexrelease>\cs_new:Npn \hook_use:nnw #1 { }
 %<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 %
@@ -4799,13 +6574,69 @@
 %<latexrelease>\cs_new_protected:Npn \@@_preamble_hook:n #1
 %<latexrelease>  { \@@_initialize_hook_code:n {#1} }
 %<latexrelease>\cs_new_eq:NN \@@_use_end: \cs_end:
+%<latexrelease>\cs_new:Npn \hook_use:nnw #1 { }
 %<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 % \end{macro}
 % \end{macro}
 % \end{macro}
+%
+%
+% \begin{macro}{\hook_use:nnw}
+% \begin{macro}[EXP]{\@@_use_initialized:nnw}
+% \changes{v1.1a}{2023/04/06}
+%         {Add \cs{hook_use:nnw} (hook-args).}
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\hook_use:nnw}
+%<latexrelease>                 {Hooks~with~args}
+\cs_new_protected:Npn \hook_use:nnw #1
+  {
+    \@@_preamble_hook:n {#1}
+    \@@_use_initialized:nnw {#1}
+  }
+\cs_new:Npn \@@_use_initialized:nnw #1 #2
+  {
+    \cs:w
+      \if_cs_exist:w @@~#1 \cs_end:
+        @@~#1
+      \else:
+        use_none: \prg_replicate:nn {#2} { n }
+      \fi:
+    \cs_end:
+  }
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2020/10/01}{\hook_use:nnw}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_gset:Npn \hook_use:nnw #1 #2
+%<latexrelease>  { \use:c { use_none: \prg_replicate:nn {#2} { n } } }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+%
+% \begin{macro}{\@@_post_initialization_defs:}
+% \changes{v1.1a}{2023/04/06}
+%         {Macro added (hook-args).}
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_post_initialization_defs:}
+%<latexrelease>                 {Hooks~with~args}
+\cs_new_protected:Npn \@@_post_initialization_defs:
+  {
+    \cs_gset_eq:NN \hook_use:n \@@_use_initialized:n
+    \cs_gset_eq:NN \hook_use:nnw \@@_use_initialized:nnw
+    \cs_gset_eq:NN \@@_preamble_hook:n \use_none:n
+    \cs_gset_eq:NN \@@_post_initialization_defs: \prg_do_nothing:
+  }
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_post_initialization_defs:}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_undefine:N \@@_post_initialization_defs:
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
 % \end{macro}
 %
+%
 % \begin{macro}[EXP]{\@@_use:wn}
 % \begin{macro}{\@@_try_file_hook:n,\@@_if_usable_use:n}
 %   \cs{@@_use:wn} does a quick check to test if the current hook is a
@@ -4861,8 +6692,7 @@
 % \end{macro}
 % \end{macro}
 %
-% \begin{macro}{\hook_use_once:n}
-% \begin{macro}{\@@_use_once:n}
+% \begin{macro}{\hook_use_once:n,\hook_use_once:nnw}
 %   For hooks that can and should be used only once we have a special
 %   use command that further inhibits the hook from getting more code
 %   added to it.  This has the effect that any
@@ -4874,20 +6704,80 @@
 %   documented to not trim spaces.
 %
 % \changes{v1.0r}{2021/09/06}{Clean up after \cs{UseOneTimeHook} (gh/606)}
+% \changes{v1.1a}{2023/04/06}
+%         {Add \cs{hook_use_once:nnw} (hook-args).}
 %    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\hook_use_once:nnw}
+%<latexrelease>                 {Hooks~with~args}
 \cs_new_protected:Npn \hook_use_once:n #1
   {
     \@@_if_execute_immediately:nF {#1}
-      { \@@_normalize_hook_args:Nn \@@_use_once:n { \use:n {#1} } }
+      { \@@_normalize_hook_args:Nn \@@_use_once:nn { \use:n {#1} } { 0 } }
   }
-\cs_new_protected:Npn \@@_use_once:n #1
+\cs_new_protected:Npn \hook_use_once:nnw #1 #2
+  {
+    \@@_if_execute_immediately:nF {#1}
+      { \@@_normalize_hook_args:Nn \@@_use_once:nn { \use:n {#1} } {#2} }
+  }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+% \end{macro}
+%
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\hook_use_once:nnw}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_gset_protected:Npn \hook_use_once:n #1
+%<latexrelease>  {
+%<latexrelease>    \@@_if_execute_immediately:nF {#1}
+%<latexrelease>      { \@@_normalize_hook_args:Nn \@@_use_once:n { \use:n {#1} } }
+%<latexrelease>  }
+%<latexrelease>\cs_gset:Npn \hook_use_once:nnw #1 #2
+%<latexrelease>  { \use:c { use_none: \prg_replicate:nn {#2} { n } } }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%
+% \begin{macro}{\@@_use_once:nn}
+% \changes{v1.1a}{2023/04/06}
+%         {Changes to add hook arguments (hook-args).}
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_use_once:nn}
+%<latexrelease>                 {Hooks~with~args}
+\cs_new_protected:Npn \@@_use_once:nn #1 #2
   {
     \@@_preamble_hook:n {#1}
     \@@_use_once_set:n {#1}
-    \@@_use_initialized:n {#1}
-    \@@_use_once_clear:n {#1}
+%    \end{macrocode}
+%   When a hook has arguments, the call to \cs{@@_use_initialized:n},
+%   should be the very last thing to happen, otherwise the arguments
+%   grabbed will be wrong.  So, to clean up after the hook we need to
+%   cheat a bit and sneak the cleanup code at the end of the hook,
+%   along with the next execution code.
+%    \begin{macrocode}
+    \@@_replacing_args_false:
+    \@@_cs_gput_right:nnn { _next } {#1} { \@@_use_once_clear:n {#1} }
+    \@@_replacing_args_reset:
+    \@@_if_usable:nTF {#1}
+      { \@@_use_initialized:n {#1} }
+      {
+        \int_compare:nNnT {#2} > { 0 }
+          { \use:c { use_none: \prg_replicate:nn {#2} { n } } }
+      }
   }
+%<latexrelease>\EndIncludeInRelease
+%
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_use_once:nn}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_gset_protected:Npn \@@_use_once:n #1
+%<latexrelease>  {
+%<latexrelease>    \@@_preamble_hook:n {#1}
+%<latexrelease>    \@@_use_once_set:n {#1}
+%<latexrelease>    \@@_use_initialized:n {#1}
+%<latexrelease>    \@@_use_once_clear:n {#1}
+%<latexrelease>  }
+%<latexrelease>\cs_undefine:N \@@_use_once:nn
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
+% \end{macro}
 %
 % \begin{macro}{\@@_use_once_set:n}
 % \begin{macro}{\@@_use_once_clear:n}
@@ -4900,43 +6790,60 @@
 %   \cs{hook_use_once:n} will expand to nothing.
 % \changes{v1.0r}{2021/09/06}{Clean up after \cs{UseOneTimeHook} (gh/606)}
 % \changes{v1.0u}{2022/05/13}{Check if prop exists to avoid l3debug error}
+% \changes{v1.1a}{2023/04/06}
+%         {Changes to add hook arguments (hook-args).}
 %    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_use_once_clear:n}
+%<latexrelease>                 {Hooks~with~args}
 \cs_new_protected:Npn \@@_use_once_set:n #1
   { \@@_tl_gset:cn { g_@@_#1_reversed_tl } { I } }
 \cs_new_protected:Npn \@@_use_once_clear:n #1
   {
-    \@@_tl_gclear:c { @@~#1 }
-    \@@_tl_gclear:c { @@_next~#1 }
-    \@@_tl_gclear:c { @@_toplevel~#1 }
+    \@@_code_gset:nn {#1} { }
+    \@@_next_gset:nn {#1} { }
+    \@@_toplevel_gset:nn {#1} { }
     \prop_gclear_new:c { g_@@_#1_code_prop }
   }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_use_once_clear:n}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_new_protected:Npn \@@_use_once_clear:n #1
+%<latexrelease>  {
+%<latexrelease>    \@@_tl_gclear:c { @@~#1 }
+%<latexrelease>    \@@_tl_gclear:c { @@_next~#1 }
+%<latexrelease>    \@@_tl_gclear:c { @@_toplevel~#1 }
+%<latexrelease>    \prop_gclear_new:c { g_@@_#1_code_prop }
+%<latexrelease>  }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
-% \end{macro}
-% \end{macro}
 % \end{macro}
 % \end{macro}
 %
 % \begin{macro}[pTF]{\@@_if_execute_immediately:n}
-%   To check whether the code being added should be executed immediately (that
-%   is, if the hook is a one-time hook), we check if it's usable (it
-%   can't be one-time if it was not already usable), then we check that
+%   To check whether the code being added should be executed immediately
+%   (that is, if the hook is a one-time hook), we check if
 %   \cs[no-index]{g_@@_\meta{hook}_reversed_tl} is |I|.  The gymnastics
 %   around \cs{if:w} is there to allow the |reversed| token list to be
 %   empty.
 % \changes{v1.0r}{2021/09/06}{Macro added (gh/606)}
+% \changes{v1.1a}{2023/04/06}
+%         {Changes to add hook arguments (hook-args).}
 %    \begin{macrocode}
 \prg_new_conditional:Npnn \@@_if_execute_immediately:n #1 { T, F, TF }
   {
-    \@@_if_usable:nTF {#1}
-      {
-        \exp_after:wN \@@_clean_to_scan:w
-        \if:w I \cs:w g_@@_#1_reversed_tl \cs_end:
-          \s_@@_mark \prg_return_true:
-        \else:
-          \s_@@_mark \prg_return_false:
+    \exp_after:wN \@@_use_none_delimit_by_s_mark:w
+    \if:w I
+        \if_cs_exist:w g_@@_#1_reversed_tl \cs_end:
+          \cs:w g_@@_#1_reversed_tl \exp_after:wN \cs_end:
         \fi:
-      }
-      { \prg_return_false: }
+        X
+      \s_@@_mark \prg_return_true:
+    \else:
+      \s_@@_mark \prg_return_false:
+    \fi:
   }
 %    \end{macrocode}
 %  \end{macro}
@@ -4963,30 +6870,57 @@
 %
 %    All other states can only be queried with internal tests as the
 %    different states are irrelevant for package code.
-
+%
 % \begin{macro}[pTF]{\hook_if_empty:n}
 %   Test if a hook is empty (that is, no code was added to that hook).
 %   A \meta{hook} being empty means that all three of its
 %   \cs{g_@@_\meta{hook}_code_prop}, its
 %   \cs{@@_toplevel\textvisiblespace\meta{hook}} and its
 %   \cs{@@_next\textvisiblespace\meta{hook}} are empty.
+% \changes{v1.1a}{2023/04/06}
+%         {Changes to add hook arguments (hook-args).}
+% \changes{v1.1c}{2023/04/19}
+%         {Simpler and faster version (gh/1052).}
 %    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\hook_if_empty:n}
+%<latexrelease>                 {Hooks~with~args}
 \prg_new_conditional:Npnn \hook_if_empty:n #1 { p , T , F , TF }
   {
-    \@@_if_structure_exist:nTF {#1}
-      {
-        \bool_lazy_and:nnTF
-            { \prop_if_empty_p:c { g_@@_#1_code_prop } }
-            {
-              \bool_lazy_and_p:nn
-                { \tl_if_empty_p:c { @@_toplevel~#1 } }
-                { \tl_if_empty_p:c { @@_next~#1 } }
-            }
-          { \prg_return_true: }
-          { \prg_return_false: }
-      }
-      { \prg_return_true: }
+    \if:w
+        T
+        \prop_if_exist:cT { g_@@_#1_code_prop }
+          { \prop_if_empty:cF { g_@@_#1_code_prop } { F } }
+        \@@_cs_if_empty:cF { @@_toplevel~#1 } { F }
+        \@@_cs_if_empty:cF { @@_next~#1 } { F }
+        T
+      \prg_return_true:
+    \else:
+      \prg_return_false:
+    \fi:
   }
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\hook_if_empty:n}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\prg_new_conditional:Npnn \hook_if_empty:n #1 { p , T , F , TF }
+%<latexrelease>  {
+%<latexrelease>    \@@_if_structure_exist:nTF {#1}
+%<latexrelease>      {
+%<latexrelease>        \bool_lazy_and:nnTF
+%<latexrelease>            { \prop_if_empty_p:c { g_@@_#1_code_prop } }
+%<latexrelease>            {
+%<latexrelease>              \bool_lazy_and_p:nn
+%<latexrelease>                { \tl_if_empty_p:c { @@_toplevel~#1 } }
+%<latexrelease>                { \tl_if_empty_p:c { @@_next~#1 } }
+%<latexrelease>            }
+%<latexrelease>          { \prg_return_true: }
+%<latexrelease>          { \prg_return_false: }
+%<latexrelease>      }
+%<latexrelease>      { \prg_return_true: }
+%<latexrelease>  }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 % \end{macro}
 %
@@ -4994,15 +6928,15 @@
 % \begin{macro}[pTF]{\@@_if_usable:n}
 %   A hook is usable if the
 %   token list that stores the sorted code for that hook,
-%   \cs[no-index]{@@~\meta{hook}}, exists.  The property list
-%   \cs[no-index]{g_@@_\meta{hook}_code_prop} cannot be used here
+%   \cs[no-index]{@@\textvisiblespace\meta{hook}}, exists.  The property
+%   list \cs[no-index]{g_@@_\meta{hook}_code_prop} cannot be used here
 %   because often it is necessary to add code to a hook without knowing
 %   if such hook was already declared, or even if it will ever be
 %   (for example, in case the package that defines it isn't loaded).
 %    \begin{macrocode}
 \prg_new_conditional:Npnn \@@_if_usable:n #1 { p , T , F , TF }
   {
-    \tl_if_exist:cTF { @@~#1 }
+    \cs_if_exist:cTF { @@~#1 }
       { \prg_return_true: }
       { \prg_return_false: }
   }
@@ -5046,7 +6980,7 @@
 %    \begin{macrocode}
 \prg_new_conditional:Npnn \@@_if_reversed:n #1 { p , T , F , TF }
   {
-    \exp_after:wN \@@_clean_to_scan:w
+    \exp_after:wN \@@_use_none_delimit_by_s_mark:w
     \if:w - \cs:w g_@@_#1_reversed_tl \cs_end:
       \s_@@_mark \prg_return_true:
     \else:
@@ -5086,6 +7020,37 @@
 %  \end{macro}
 %  \end{macro}
 %
+% \begin{macro}[pTF]{\@@_if_cmd_hook:n}
+% \begin{macro}[pTF]{\@@_if_cmd_hook:w}
+%   An internal conditional that checks if a given hook is a valid
+%   generic \hook{cmd} hook.
+% \changes{v1.1d}{2023/05/21}
+%         {Changes to allow support arguments in cmd hooks (cmd-args).}
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\@@_if_cmd_hook:n}
+%<latexrelease>                 {Hooks~with~args}
+\prg_new_conditional:Npnn \@@_if_cmd_hook:n #1 { T }
+  { \@@_if_cmd_hook:w #1 / / / \s_@@_mark }
+\cs_new:Npn \@@_if_cmd_hook:w #1 / #2 / #3 / #4 \s_@@_mark
+  {
+    \if:w Y
+          \str_if_eq:nnF {#1} { cmd } { N }
+          \tl_if_exist:cF { c_@@_generic_#1/./#3_tl } { N }
+          Y
+      \prg_return_true:
+    \else:
+      \prg_return_false:
+    \fi:
+  }
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2020/10/01}{\@@_if_cmd_hook:n}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_undefine:N \@@_if_cmd_hook:nT
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%  \end{macro}
+%  \end{macro}
+%
 % \begin{macro}[pTF]{\@@_if_generic_reversed:n}
 %   An internal conditional that checks if a name belongs to a generic
 %   reversed hook.
@@ -5103,6 +7068,50 @@
 %    \end{macrocode}
 %  \end{macro}
 %
+% \begin{macro}[EXP]{\@@_if_replacing_args:TF}
+% \begin{macro}[EXP]{\@@_misused_if_replacing_args:nn}
+% \begin{macro}{\@@_replacing_args_true:}
+% \begin{macro}{\@@_replacing_args_false:}
+% \begin{macro}{\@@_replacing_args_reset:}
+% \begin{macro}{\g_@@_replacing_stack_seq}
+%   An internal conditional that checks if the code being added to the
+%   hook contains arguments.
+% \changes{v1.1a}{2023/04/06}
+%         {Macro added (hook-args).}
+%    \begin{macrocode}
+\seq_new:N \g_@@_replacing_stack_seq
+\cs_new:Npn \@@_misused_if_replacing_args:nn #1 #2
+  {
+    \msg_expandable_error:nnn { latex2e } { should-not-happen }
+      { Misused~\@@_if_replacing_args:. }
+  }
+\cs_new:Npn \@@_if_replacing_args:TF
+  { \@@_misused_if_replacing_args:nn }
+\cs_new_protected:Npn \@@_replacing_args_true:
+  {
+    \seq_gpush:No \g_@@_replacing_stack_seq
+      { \@@_if_replacing_args:TF }
+    \cs_set:Npn \@@_if_replacing_args:TF { \use_i:nn }
+  }
+\cs_new_protected:Npn \@@_replacing_args_false:
+  {
+    \seq_gpush:No \g_@@_replacing_stack_seq
+      { \@@_if_replacing_args:TF }
+    \cs_set:Npn \@@_if_replacing_args:TF { \use_ii:nn }
+  }
+\cs_new_protected:Npn \@@_replacing_args_reset:
+  {
+    \seq_gpop:NN \g_@@_replacing_stack_seq \l_@@_return_tl
+    \cs_gset_eq:NN \@@_if_replacing_args:TF \l_@@_return_tl
+  }
+%    \end{macrocode}
+%  \end{macro}
+%  \end{macro}
+%  \end{macro}
+%  \end{macro}
+%  \end{macro}
+%  \end{macro}
+%
 %  \subsection{Messages}
 %
 %    Hook errors are LaTeX kernel errors:
@@ -5137,6 +7146,55 @@
       Please~ use~ a~ different~ name~ for~ your~ hook.}
 %    \end{macrocode}
 %
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{too-many-args}
+%<latexrelease>                 {Hooks~with~args}
+%    \end{macrocode}
+%
+% \changes{v1.1a}{2023/04/06}
+%         {Messages 'too-many-args', 'without-args' and 'one-time-args' added (hook-args).}
+%    \begin{macrocode}
+\msg_new:nnnn { hooks } { too-many-args }
+  { Too~many~arguments~for~hook~'#1'. }
+  {
+    You~tried~to~declare~a~hook~with~#2~arguments,~but~a~
+    hook~can~only~have~up~to~nine.~LaTeX~will~define~this~
+    hook~with~nine~arguments.
+  }
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+\msg_new:nnnn { hooks } { without-args }
+  { Hook~'#1'~has~no~arguments. }
+  {
+    You~tried~to~use~\iow_char:N\\#2WithArguments~
+    on~a~hook~that~takes~no~arguments.\\
+    Check~the~usage~of~the~hook~or~use~\iow_char:N\\#2~instead.\\
+    \\
+    LaTeX~will~use~\iow_char:N\\#2.
+  }
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+\msg_new:nnnn { hooks } { one-time-args }
+  { You~can't~have~arguments~in~used~one-time~hook~'#1'. }
+  {
+    You~tried~to~use~\iow_char:N\\#2WithArguments~
+    on~a~one-time~hook~that~has~already~been~used.~
+    You~have~to~add~the~code~before~the~hook~is~used,~
+    or~add~the~code~without~arguments~using~\iow_char:N\\#2~instead.\\
+    \\
+    LaTeX~will~use~\iow_char:N\\#2.
+  }
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2020/10/01}{too-many-args}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\EndIncludeInRelease
+%    \end{macrocode}
+%
 %
 %    \begin{macrocode}
 \msg_new:nnnn { hooks } { hook-disabled }
@@ -5278,17 +7336,49 @@
 %  \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} }
+\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}{
+%    \NewHookWithArguments,
+%    \NewReversedHookWithArguments,
+%    \NewMirroredHookPairWithArguments
+%  }
+%    Declaring new hooks with arguments\ldots
+% \changes{v1.1a}{2023/04/06}
+%         {Add \cs{NewHookWithArguments} (hook-args).}
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\NewHookWithArguments}
+%<latexrelease>                 {Hooks~with~args}
+\NewDocumentCommand \NewHookWithArguments             { mm }
+  { \hook_new_with_args:nn {#1} {#2} }
+\NewDocumentCommand \NewReversedHookWithArguments     { mm }
+  { \hook_new_reversed_with_args:nn {#1} {#2} }
+\NewDocumentCommand \NewMirroredHookPairWithArguments { mmm }
+  { \hook_new_pair_with_args:nnn {#1} {#2} {#3} }
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2023/06/01}{\NewHookWithArguments}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_new_protected:Npn \NewHookWithArguments #1 #2 { }
+%<latexrelease>\cs_new_protected:Npn \NewReversedHookWithArguments #1 #2 { }
+%<latexrelease>\cs_new_protected:Npn \NewMirroredHookPairWithArguments #1 #2 #3 { }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 %  \end{macro}
 %
 %
 %
 %    \begin{macrocode}
-%<latexrelease>\IncludeInRelease{2021/06/01}%
-%<latexrelease>         {\hook_activate_generic:n}{Providing~hooks}
+%<latexrelease>\IncludeInRelease{2021/06/01}{\ActivateGenericHook}
+%<latexrelease>                 {Providing~hooks}
 %    \end{macrocode}
 %
 %  \begin{macro}{\ActivateGenericHook}
@@ -5296,7 +7386,8 @@
 % \changes{v1.0m}{2021/04/29}{Add \cs{ProvideHook} etc.}
 % \changes{v1.0o}{2021/08/02}{Change name}
 %    \begin{macrocode}
-\NewDocumentCommand \ActivateGenericHook { m }{ \hook_activate_generic:n {#1} }
+\NewDocumentCommand \ActivateGenericHook { m }
+  { \hook_activate_generic:n {#1} }
 %    \end{macrocode}
 %  \end{macro}
 %
@@ -5304,32 +7395,57 @@
 %    Disabling a generic hook.
 % \changes{v1.0o}{2021/08/02}{Change name}
 %    \begin{macrocode}
-\NewDocumentCommand \DisableGenericHook { m }{ \hook_disable_generic:n {#1} }
+\NewDocumentCommand \DisableGenericHook { m }
+  { \hook_disable_generic:n {#1} }
 %    \end{macrocode}
 %  \end{macro}
 %
 %    \begin{macrocode}
 %<latexrelease>\EndIncludeInRelease
-%<latexrelease>\IncludeInRelease{2020/10/01}
-%<latexrelease>                 {\hook_activate_generic:n}{Providing~hooks}
-%<latexrelease>
-%<latexrelease>\def \ActivateGenericHook#1{}
-%<latexrelease>
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2020/10/01}{\ActivateGenericHook}
+%<latexrelease>                 {Providing~hooks}
+%<latexrelease>\def \ActivateGenericHook #1 { }
+%<latexrelease>\def \DisableGenericHook #1 { }
 %<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 %
 %
-%  \begin{macro}{\AddToHook}
+%  \begin{macro}{\AddToHook,\AddToHookWithArguments}
+% \changes{v1.1a}{2023/04/06}
+%         {Add \cs{AddToHookWithArguments} (hook-args).}
 %    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\AddToHookWithArguments}
+%<latexrelease>                 {Hooks~with~args}
 \NewDocumentCommand \AddToHook { m o +m }
   { \hook_gput_code:nnn {#1} {#2} {#3} }
+\NewDocumentCommand \AddToHookWithArguments { m o +m }
+  { \hook_gput_code_with_args:nnn {#1} {#2} {#3} }
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2020/10/01}{\AddToHookWithArguments}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_new_protected:Npn \AddToHookWithArguments #1 #2 #3 { }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 %  \end{macro}
 %
-%  \begin{macro}{\AddToHookNext}
+%  \begin{macro}{\AddToHookNext,\AddToHookNextWithArguments}
+% \changes{v1.1a}{2023/04/06}
+%         {Add \cs{AddToHookNextWithArguments} (hook-args).}
 %    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\AddToHookNextWithArguments}
+%<latexrelease>                 {Hooks~with~args}
 \NewDocumentCommand \AddToHookNext { m +m }
   { \hook_gput_next_code:nn {#1} {#2} }
+\NewDocumentCommand \AddToHookNextWithArguments { m +m }
+  { \hook_gput_next_code_with_args:nn {#1} {#2} }
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2020/10/01}{\AddToHookNextWithArguments}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_new_protected:Npn \AddToHookNextWithArguments #1 #2 { }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 %  \end{macro}
 %
@@ -5396,12 +7512,29 @@
 %
 %
 %
-%  \begin{macro}{\UseHook,\UseOneTimeHook}
+%  \begin{macro}{
+%    \UseHook,
+%    \UseOneTimeHook,
+%    \UseHookWithArguments,
+%    \UseOneTimeHookWithArguments,
+%  }
 %    Avoid the overhead of \pkg{xparse} and its protection that we
 %    don't want here (since the hook should vanish without trace if empty)!
+% \changes{v1.1a}{2023/04/06}
+%         {Add \cs{UseHookWithArguments} (hook-args).}
 %    \begin{macrocode}
+%<latexrelease>\IncludeInRelease{2023/06/01}{\UseHookWithArguments}
+%<latexrelease>                 {Hooks~with~args}
 \cs_new:Npn \UseHook        { \hook_use:n }
 \cs_new:Npn \UseOneTimeHook { \hook_use_once:n }
+\cs_new:Npn \UseHookWithArguments        { \hook_use:nnw }
+\cs_new:Npn \UseOneTimeHookWithArguments { \hook_use_once:nnw }
+%<latexrelease>\EndIncludeInRelease
+%<latexrelease>\IncludeInRelease{2020/10/01}{\UseHookWithArguments}
+%<latexrelease>                 {Hooks~with~args}
+%<latexrelease>\cs_new:Npn \UseHookWithArguments #1 #2 { }
+%<latexrelease>\cs_new:Npn \UseOneTimeHookWithArguments #1 #2 { }
+%<latexrelease>\EndIncludeInRelease
 %    \end{macrocode}
 %  \end{macro}
 %
@@ -5462,7 +7595,7 @@
 %    \end{macrocode}
 % \end{macro}
 %
-% \begin{macro}[EXP]{\IfHookExistsTF}
+% \begin{macro}[EXP,int]{\IfHookExistsTF}
 %    Marked for removal and no longer documented in the doc section!
 % \phoinline{\cs{IfHookExistsTF} is used in \texttt{jlreq.cls},
 % \texttt{pxatbegshi.sty}, \texttt{pxeverysel.sty},
@@ -5484,7 +7617,7 @@
 % \changes{v1.0p}{2021/08/20}{Added deprecation warnings for
 %                             old generic hook commands (gh/638)}
 %
-% \begin{macro}{
+% \begin{macro}[int]{
 %     \hook_disable:n,
 %     \hook_provide:n,
 %     \hook_provide_reversed:n,
@@ -5529,7 +7662,7 @@
 %    \end{macrocode}
 % \end{macro}
 %
-% \begin{macro}{
+% \begin{macro}[int]{
 %     \DisableHook,
 %     \ProvideHook,
 %     \ProvideReversedHook,
@@ -5596,8 +7729,10 @@
 %<@@=>
 %    \end{macrocode}
 %
-%  \begin{macro}{\@expl@@@initialize@all@@,
-%                \@expl@@@hook@curr@name@pop@@}
+%  \begin{macro}[int]{
+%      \@expl@@@initialize@all@@,
+%      \@expl@@@hook@curr@name@pop@@
+%  }
 %
 % \InternalDetectionOff
 %    \begin{macrocode}
@@ -5620,8 +7755,8 @@
 %                             to be for \cs{AddToHook} vice versa (gh/401)}
 %    \begin{macrocode}
 %
-%<latexrelease>\IncludeInRelease{0000/00/00}%
-%<latexrelease>                 {lthooks}{The~hook~management}%
+%<latexrelease>\IncludeInRelease{0000/00/00}{lthooks}
+%<latexrelease>                 {The~hook~management}%
 %<latexrelease>
 %<latexrelease>\def \NewHook#1{}
 %<latexrelease>\def \NewReversedHook#1{}
@@ -5656,10 +7791,56 @@
 %<latexrelease>\long\def \IfHookEmptyTF #1#2#3{#2}
 %<latexrelease>
 %<latexrelease>\EndModuleRelease
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<@@=hook>
+%    \end{macrocode}
+%
+% \changes{v1.1a}{2023/04/06}
+%         {Add dedicated rollback code to revert data structures (hook-args).}
+%    \begin{macrocode}
+%<latexrelease>\cs:w @@_rollback_tidying: \cs_end:
+%<latexrelease>\bool_lazy_and:nnT
+%<latexrelease>    { \int_compare_p:nNn { \sourceLaTeXdate } > { 20230600 } }
+%<latexrelease>    { \int_compare_p:nNn { \requestedLaTeXdate } < { 20230601 } }
+%<latexrelease>  {
+%<latexrelease>    \cs_gset_protected:Npn \@@_rollback_tidying:
+%<latexrelease>      {
+%<latexrelease>        \@latex@error { Rollback~code~executed~twice }
+%<latexrelease>          {
+%<latexrelease>            Something~went~wrong~(unless~this~was~
+%<latexrelease>            done~on~purpose~in~a~testing~environment).
+%<latexrelease>          }
+%<latexrelease>        \use_none:nnnn
+%<latexrelease>      }
+%<latexrelease>    \cs_set:Npn \@@_tmp:w #1 #2
+%<latexrelease>      {
+%<latexrelease>        \@@_tl_gset:cx { @@#1~#2 }
+%<latexrelease>          {
+%<latexrelease>            \exp_args:No \exp_not:o
+%<latexrelease>              {
+%<latexrelease>                \cs:w @@#1~#2 \exp_last_unbraced:Ne \cs_end:
+%<latexrelease>                  { \@@_braced_cs_parameter:n { @@#1~#2 } }
+%<latexrelease>              }
+%<latexrelease>          }
+%<latexrelease>      }
+%<latexrelease>    \seq_map_inline:Nn \g_@@_all_seq
+%<latexrelease>      {
+%<latexrelease>        \exp_after:wN \cs_gset_nopar:Npn
+%<latexrelease>          \cs:w g_@@_#1_code_prop \exp_args:NNo \exp_args:No
+%<latexrelease>            \cs_end: { \cs:w g_@@_#1_code_prop \cs_end: }
+%<latexrelease>        \@@_tmp:w { _toplevel } {#1}
+%<latexrelease>        \@@_tmp:w { _next } {#1}
+%<latexrelease>      }
+%<latexrelease>  }
 \ExplSyntaxOff
 %</2ekernel|latexrelease>
 %    \end{macrocode}
 %
+%    \begin{macrocode}
+%<@@=>
+%    \end{macrocode}
 %
 % \Finale
 %
-- 
cgit v1.2.3

