% \iffalse meta-comment % %% File: l3debug.dtx % % Copyright (C) 2019-2024 The LaTeX Project % % It may be distributed and/or modified under the conditions of the % LaTeX Project Public License (LPPL), either version 1.3c of this % license or (at your option) any later version. The latest version % of this license is in the file % % https://www.latex-project.org/lppl.txt % % This file is part of the "l3kernel bundle" (The Work in LPPL) % and all files in that bundle must be distributed together. % % ----------------------------------------------------------------------- % % The development version of the bundle can be found at % % https://github.com/latex3/latex3 % % for those people who are interested. % %<*driver> \documentclass[full,kernel]{l3doc} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \title{^^A % The \pkg{l3debug} module\\ Debugging support^^A % } % % \author{^^A % The \LaTeX{} Project\thanks % {^^A % E-mail: % \href{mailto:latex-team@latex-project.org} % {latex-team@latex-project.org}^^A % }^^A % } % % \date{Released 2024-12-25} % % \maketitle % % \begin{documentation} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3debug} implementation} % % Internal kernel functions that are only defined here are listed in % \pkg{l3kernel-functions}, % see~\ref{sec:l3kernel-functions:l3debug-internals}. % % \begin{macrocode} %<*package> % \end{macrocode} % % \begin{macrocode} %<@@=debug> % \end{macrocode} % % Standard file identification. % \begin{macrocode} \ProvidesExplFile{l3debug.def}{2024-12-25}{}{L3 Debugging support} % \end{macrocode} % % \begin{variable}{\s_@@_stop} % Internal scan marks. % \begin{macrocode} \scan_new:N \s_@@_stop % \end{macrocode} % \end{variable} % % \begin{macro}[EXP]{\@@_use_i_delimit_by_s_stop:nw} % Functions to gobble up to a scan mark. % \begin{macrocode} \cs_new:Npn \@@_use_i_delimit_by_s_stop:nw #1 #2 \s_@@_stop {#1} % \end{macrocode} % \end{macro} % % \begin{variable}{\q_@@_recursion_tail,\q_@@_recursion_stop} % Internal quarks. % \begin{macrocode} \quark_new:N \q_@@_recursion_tail \quark_new:N \q_@@_recursion_stop % \end{macrocode} % \end{variable} % % \begin{macro}[EXP]{\@@_if_recursion_tail_stop:N} % Functions to query recursion quarks. % \begin{macrocode} \cs_new:Npn \@@_use_none_delimit_by_q_recursion_stop:w #1 \q_@@_recursion_stop { } \__kernel_quark_new_test:N \@@_if_recursion_tail_stop:N % \end{macrocode} % \end{macro} % % \begin{macro}{\debug_on:n, \debug_off:n, \@@_all_on:, \@@_all_off:} % \begin{macrocode} \cs_gset_protected:Npn \debug_on:n #1 { \exp_args:No \clist_map_inline:nn { \tl_to_str:n {#1} } { \cs_if_exist_use:cF { @@_ ##1 _on: } { \msg_error:nnn { debug } { debug } {##1} } } } \cs_gset_protected:Npn \debug_off:n #1 { \exp_args:No \clist_map_inline:nn { \tl_to_str:n {#1} } { \cs_if_exist_use:cF { @@_ ##1 _off: } { \msg_error:nnn { debug } { debug } {##1} } } } \cs_new_protected:Npn \@@_all_on: { \debug_on:n { check-declarations , check-expressions , deprecation , log-functions , } } \cs_new_protected:Npn \@@_all_off: { \debug_off:n { check-declarations , check-expressions , deprecation , log-functions , } } % \end{macrocode} % \end{macro} % % \begin{macro}{\debug_suspend:, \debug_resume:} % \begin{macro}{\@@_suspended:T} % \begin{macro}{\l_@@_suspended_tl} % Suspend and resume locally all debug-related errors and logging % except deprecation errors. The \cs{debug_suspend:} and \cs{debug_resume:} % pairs can be nested. We keep track of nesting in a token list % containing a number of periods. At first begin with the % \enquote{non-suspended} version of \cs{@@_suspended:T}. % \begin{macrocode} \tl_new:N \l_@@_suspended_tl { } \cs_gset_protected:Npn \debug_suspend: { \tl_put_right:Nn \l_@@_suspended_tl { . } \cs_set_eq:NN \@@_suspended:T \use:n } \cs_gset_protected:Npn \debug_resume: { \__kernel_tl_set:Nx \l_@@_suspended_tl { \tl_tail:N \l_@@_suspended_tl } \tl_if_empty:NT \l_@@_suspended_tl { \cs_set_eq:NN \@@_suspended:T \use_none:n } } \cs_new_eq:NN \@@_suspended:T \use_none:n % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro} % {\@@_check-declarations_on:, \@@_check-declarations_off:} % \begin{macro} % { % \__kernel_chk_var_exist:N, % \__kernel_chk_cs_exist:N, % \__kernel_chk_cs_exist:c % } % \begin{macro}[EXP]{\__kernel_chk_flag_exist:NN} % \begin{macro}{\__kernel_chk_var_local:N, \__kernel_chk_var_global:N} % \begin{macro}{\__kernel_chk_var_scope:NN} % When debugging is enabled these two functions set up functions that % test their argument (when \texttt{check-declarations} is active) % \begin{itemize} % \item \cs{__kernel_chk_var_exist:N} and \cs{__kernel_chk_cs_exist:N}, two % functions that test that their argument is defined; % \item \cs{__kernel_chk_var_scope:NN} that checks that its argument |#2| % has scope |#1|. % \item \cs{__kernel_chk_var_local:N} and \cs{__kernel_chk_var_global:N} that % perform both checks. % \end{itemize} % \begin{macrocode} \cs_new_protected:Npn \__kernel_chk_var_exist:N #1 { } \cs_new_protected:Npn \__kernel_chk_cs_exist:N #1 { } \cs_generate_variant:Nn \__kernel_chk_cs_exist:N { c } \cs_new:Npn \__kernel_chk_flag_exist:NN { } \cs_new_protected:Npn \__kernel_chk_var_local:N #1 { } \cs_new_protected:Npn \__kernel_chk_var_global:N #1 { } \cs_new_protected:Npn \__kernel_chk_var_scope:NN #1#2 { } \cs_new_protected:cpn { @@_check-declarations_on: } { \cs_set_protected:Npn \__kernel_chk_var_exist:N ##1 { \@@_suspended:T \use_none:nnn \cs_if_exist:NF ##1 { \msg_error:nne { debug } { non-declared-variable } { \token_to_str:N ##1 } } } \cs_set_protected:Npn \__kernel_chk_cs_exist:N ##1 { \@@_suspended:T \use_none:nnn \cs_if_exist:NF ##1 { \msg_error:nne { kernel } { command-not-defined } { \token_to_str:N ##1 } } } \cs_set:Npn \__kernel_chk_flag_exist:NN ##1##2 { \@@_suspended:T \use_iii:nnnn \flag_if_exist:NTF ##2 { ##1 ##2 } { \msg_expandable_error:nnn { kernel } { bad-variable } {##2} ##1 \l_tmpa_flag } } \cs_set_protected:Npn \__kernel_chk_var_scope:NN { \@@_suspended:T \use_none:nnn \@@_chk_var_scope_aux:NN } \cs_set_protected:Npn \__kernel_chk_var_local:N ##1 { \@@_suspended:T \use_none:nnnnn \__kernel_chk_var_exist:N ##1 \@@_chk_var_scope_aux:NN l ##1 } \cs_set_protected:Npn \__kernel_chk_var_global:N ##1 { \@@_suspended:T \use_none:nnnnn \__kernel_chk_var_exist:N ##1 \@@_chk_var_scope_aux:NN g ##1 } } \cs_new_protected:cpn { @@_check-declarations_off: } { \cs_set_protected:Npn \__kernel_chk_var_exist:N ##1 { } \cs_set_protected:Npn \__kernel_chk_cs_exist:N ##1 { } \cs_set:Npn \__kernel_chk_flag_exist:NN { } \cs_set_protected:Npn \__kernel_chk_var_local:N ##1 { } \cs_set_protected:Npn \__kernel_chk_var_global:N ##1 { } \cs_set_protected:Npn \__kernel_chk_var_scope:NN ##1##2 { } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_chk_var_scope_aux:NN} % \begin{macro}{\@@_chk_var_scope_aux:Nn} % \begin{macro}{\@@_chk_var_scope_aux:NNn} % First check whether the name of the variable |#2| starts with % \meta{letter}|_|. If it does then pass that letter, the % \meta{scope}, and the variable name to % \cs{@@_chk_var_scope_aux:NNn}. That function compares the two % letters and triggers an error if they differ (the \cs{scan_stop:} % case is not reachable here). If the second character was not |_| % then pass the same data to the same auxiliary, except for its first % argument which is now a control sequence. That control sequence is % actually a token list (but to avoid triggering the checking code we % manipulate it using \cs{cs_set_nopar:Npn}) containing a single % letter \meta{scope} according to what the first assignment to the % given variable was. % \begin{macrocode} \cs_new_protected:Npn \@@_chk_var_scope_aux:NN #1#2 { \exp_args:NNf \@@_chk_var_scope_aux:Nn #1 { \cs_to_str:N #2 } } \cs_new_protected:Npn \@@_chk_var_scope_aux:Nn #1#2 { \if:w _ \use_i:nn \@@_use_i_delimit_by_s_stop:nw #2 ? ? \s_@@_stop \exp_after:wN \@@_chk_var_scope_aux:NNn \@@_use_i_delimit_by_s_stop:nw #2 ? \s_@@_stop #1 {#2} \else: \exp_args:Nc \@@_chk_var_scope_aux:NNn { @@_chk_/ #2 } #1 {#2} \fi: } \cs_new_protected:Npn \@@_chk_var_scope_aux:NNn #1#2#3 { \if:w #1 #2 \else: \if:w #1 \scan_stop: \cs_gset_nopar:Npn #1 {#2} \else: \msg_error:nneee { debug } { local-global } {#1} {#2} { \iow_char:N \\ #3 } \fi: \fi: } \use:c { @@_check-declarations_off: } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_log-functions_on:, \@@_log-functions_off:} % \begin{macro}{\__kernel_debug_log:e} % These two functions (corresponding to the \pkg{expl3} option % \texttt{log-functions}) control whether \cs{__kernel_debug_log:e} % writes to the log file or not. By default, logging is off. % \begin{macrocode} \cs_new_protected:cpn { @@_log-functions_on: } { \cs_set_protected:Npn \__kernel_debug_log:e { \@@_suspended:T \use_none:nn \iow_log:e } } \cs_new_protected:cpn { @@_log-functions_off: } { \cs_set_protected:Npn \__kernel_debug_log:e { \use_none:n } } \cs_new_protected:Npn \__kernel_debug_log:e { \use_none:n } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro} % {\@@_check-expressions_on:, \@@_check-expressions_off:} % \begin{macro}{\__kernel_chk_expr:nNnN} % \begin{macro}{\@@_chk_expr_aux:nNnN} % When debugging is enabled these two functions set % \cs{__kernel_chk_expr:nNnN} to test or not whether the given % expression is valid. The idea is to evaluate the expression within % a brace group (to catch trailing \cs{use_none:nn} or similar), then % test that the result is what we expect. This is done by turning it % to an integer and hitting that with \cs{tex_romannumeral:D} after % replacing the first character by |-0|. If all goes well, that % primitive finds a non-positive integer and gives an empty output. % If the original expression evaluation stopped early it leaves a % trailing \cs{tex_relax:D}, which stops the second evaluation (used % to convert to integer) before it encounters the final % \cs{tex_relax:D}. Since \cs{tex_romannumeral:D} does not absorb % \cs{tex_relax:D} the output will be nonempty. Note that |#3| is % empty except for mu expressions for which it is \cs{tex_mutoglue:D} % to avoid an \enquote{incompatible glue units} error. Note also that % if we had omitted the first \cs{tex_relax:D} then for instance % |1+2\relax+3| would incorrectly be accepted as a valid integer % expression. % \begin{macrocode} \cs_new_protected:cpn { @@_check-expressions_on: } { \cs_set:Npn \__kernel_chk_expr:nNnN ##1##2 { \@@_suspended:T { ##1 \use_none:nnnnnnn } \exp_after:wN \@@_chk_expr_aux:nNnN \exp_after:wN { \tex_the:D ##2 ##1 \scan_stop: } ##2 } } \cs_new_protected:cpn { @@_check-expressions_off: } { \cs_set:Npn \__kernel_chk_expr:nNnN ##1##2##3##4 {##1} } \cs_new:Npn \__kernel_chk_expr:nNnN #1#2#3#4 {#1} \cs_new:Npn \@@_chk_expr_aux:nNnN #1#2#3#4 { \tl_if_empty:oF { \tex_romannumeral:D - 0 \exp_after:wN \use_none:n \int_value:w #3 #2 #1 \scan_stop: } { \msg_expandable_error:nnnn { debug } { expr } {#4} {#1} } #1 } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_deprecation_on:, \@@_deprecation_off:} % Make deprecated commands throw errors if the user requests it. % This relies on two token lists, filled up in \pkg{l3deprecation} by % calls to \cs{__kernel_deprecation_code:nn}. % \begin{macrocode} \cs_new_protected:Npn \@@_deprecation_on: { \g_@@_deprecation_on_tl } \cs_new_protected:Npn \@@_deprecation_off: { \g_@@_deprecation_off_tl } % \end{macrocode} % \end{macro} % % \begin{variable}{ % \l_@@_internal_tl, % \l_@@_tmpa_tl, % \l_@@_tmpb_tl, % } % For patching. % \begin{macrocode} \tl_new:N \l_@@_internal_tl \tl_new:N \l_@@_tmpa_tl \tl_new:N \l_@@_tmpb_tl % \end{macrocode} % \end{variable} % % \begin{macro}{ % \@@_generate_parameter_list:NNN, % \@@_build_parm_text:n, % \@@_build_arg_list:n, % \@@_arg_list_from_signature:nNN, % \@@_arg_check_invalid:N, % \@@_parm_terminate:w, % \@@_arg_if_braced:n, % \@@_get_base_form:N, % \@@_arg_return:N, % } % \begin{macro}[TF]{\@@_arg_if_braced:N} % Some functions don't take the arguments their signature indicates. % For instance, \cs{clist_concat:NNN} doesn't take (directly) any % argument, so patching it with something that uses |#1|, |#2|, or % |#3| results in ``Illegal parameter number in definition of % \cs{clist_concat:NNN}''. % % Instead of changing \emph{the} definition of the macros, we'll % create a copy of such macros, say, \cs[no-index]{@@_clist_concat:NNN} which % will be defined as % |\clist_concat:NNN#1#2#3|. For that % we need to identify the signature of every function and build the % appropriate parameter list. % % \cs{@@_generate_parameter_list:NNN} takes a function in |#1| and % returns teo parameter lists: |#2| contains the simple |#1#2#3| as % would be used in the \meta{parameter~text} of the definition and % |#3| contains the same parameters but with braces where necessary. % % With the current implementation the resulting |#3| is, for example % for |\some_function:NnNn|, |#1{#2}#3{#4}|. While this is correct, % it might be unnecessary. Bracing everything will usually have the % same outcome (unless the function was misused in the first place). % What should be done? % \begin{macrocode} \cs_new_protected:Npn \@@_generate_parameter_list:NNN #1#2#3 { \__kernel_tl_set:Nx \l_@@_internal_tl { \exp_last_unbraced:Nf \use_ii:nnn \cs_split_function:N #1 } \__kernel_tl_set:Nx #2 { \exp_args:NV \@@_build_parm_text:n \l_@@_internal_tl } \__kernel_tl_set:Nx #3 { \exp_args:NV \@@_build_arg_list:n \l_@@_internal_tl } } \cs_new:Npn \@@_build_parm_text:n #1 { \@@_arg_list_from_signature:nNN { 1 } \c_false_bool #1 \q_@@_recursion_tail \q_@@_recursion_stop } \cs_new:Npn \@@_build_arg_list:n #1 { \@@_arg_list_from_signature:nNN { 1 } \c_true_bool #1 \q_@@_recursion_tail \q_@@_recursion_stop } \cs_new:Npn \@@_arg_list_from_signature:nNN #1 #2 #3 { \@@_if_recursion_tail_stop:N #3 \@@_arg_check_invalid:N #3 \bool_if:NT #2 { \@@_arg_if_braced:NT #3 { \use_none:n } } \use:n { \c_hash_str \int_eval:n {#1} } \exp_args:Nf \@@_arg_list_from_signature:nNN { \int_eval:n {#1+1} } #2 } % \end{macrocode} % Argument types |w|, |p|, |T|, and |F| shouldn't be included in the % parameter lists, so we abort the loop if either is found. % \begin{macrocode} \cs_new:Npn \@@_arg_check_invalid:N #1 { \if:w w #1 \@@_parm_terminate:w \else: \if:w p #1 \@@_parm_terminate:w \else: \if:w T #1 \@@_parm_terminate:w \else: \if:w F #1 \@@_parm_terminate:w \else: \exp:w \fi: \fi: \fi: \fi: \exp_end: } \cs_new:Npn \@@_parm_terminate:w { \exp_after:wN \@@_use_none_delimit_by_q_recursion_stop:w \exp:w } \prg_new_conditional:Npnn \@@_arg_if_braced:N #1 { T } { \exp_args:Nf \@@_arg_if_braced:n { \@@_get_base_form:N #1 } } \cs_new:Npn \@@_arg_if_braced:n #1 { \if:w n #1 \prg_return_true: \else: \if:w N #1 \prg_return_false: \else: \msg_expandable_error:nnn { debug } { bad-arg-type } {#1} \fi: \fi: } \msg_new:nnn { debug } { bad-arg-type } { Wrong~argument~type~#1. } % \end{macrocode} % The macro below gets the base form of an % argument type given a variant. It serves only to differentiate % arguments which should be braced from ones which shouldn't. If all % were to be braced this would be unnecessary. I moved the |n| and |N| % variants to the beginning of the test as the are much more common % here. % \begin{macrocode} \cs_new:Npn \@@_get_base_form:N #1 { \if:w n #1 \@@_arg_return:N n \else: \if:w N #1 \@@_arg_return:N N \else: \if:w c #1 \@@_arg_return:N N \else: \if:w o #1 \@@_arg_return:N n \else: \if:w V #1 \@@_arg_return:N n \else: \if:w v #1 \@@_arg_return:N n \else: \if:w f #1 \@@_arg_return:N n \else: \if:w e #1 \@@_arg_return:N n \else: \if:w x #1 \@@_arg_return:N n \else: \@@_arg_return:N \scan_stop: \fi: \fi: \fi: \fi: \fi: \fi: \fi: \fi: \fi: \exp_stop_f: } \cs_new:Npn \@@_arg_return:N #1 { \exp_after:wN #1 \exp:w \exp_end_continue_f:w } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{ % \__kernel_patch:nnn, % \__kernel_patch_aux:nnn, % \@@_setup_debug_code:Nnn, % \@@_add_to_debug_code:Nnn, % \@@_insert_debug_code:Nnn, % \__kernel_patch_weird:nnn, % \__kernel_patch_weird_aux:nnn, % \@@_patch_weird:Nnn, % } % Simple patching by adding material at the start and end of (a % collection of) functions is straight-forward as we know the catcode % set up. The approach is essentially that in \pkg{etoolbox}. Notice % the need to worry about spaces: those are otherwise lost as normally % in \pkg{expl3} code they would be~|~|. % % As discussed above, some functions don't take arguments, so we can't % patch something that uses an argument in them. For these functions % \cs{__kernel_patch:nnn} is used. It starts by creating a copy of the % function (say, \cs{clist_concat:NNN}) with a |__debug_| prefix in % the name. This copy won't be changed. The code redefines the % original function to take the exact same arguments as advertised in % its signature (see \cs{@@_generate_parameter_list:NNN} above). % The redefined function also contains the debug code in the proper % position. If a function with the same name and the |__debug_| prefix % was already defined, then the macro patches that definition by % adding more debug code to it. % \begin{macrocode} \group_begin: \cs_set_protected:Npn \__kernel_patch:nnn { \group_begin: \char_set_catcode_other:N \# \__kernel_patch_aux:nnn } \cs_set_protected:Npn \__kernel_patch_aux:nnn #1#2#3 { \char_set_catcode_parameter:N \# \char_set_catcode_space:N \ % \tex_endlinechar:D -1 \scan_stop: \tl_map_inline:nn {#3} { \cs_if_exist:cTF { @@_ \cs_to_str:N ##1 } { \@@_add_to_debug_code:Nnn } { \@@_setup_debug_code:Nnn } ##1 {#1} {#2} } \group_end: } \cs_set_protected:Npn \@@_setup_debug_code:Nnn #1#2#3 { \cs_gset_eq:cN { @@_ \cs_to_str:N #1 } #1 \@@_generate_parameter_list:NNN #1 \l_@@_tmpa_tl \l_@@_tmpb_tl \exp_args:Ne \tex_scantokens:D { \tex_global:D \cs_prefix_spec:N #1 \tex_def:D \exp_not:N #1 \tl_use:N \l_@@_tmpa_tl { \tl_to_str:n {#2} \exp_not:c { @@_ \cs_to_str:N #1 } \tl_use:N \l_@@_tmpb_tl \tl_to_str:n {#3} } } } \cs_set_protected:Npn \@@_add_to_debug_code:Nnn #1#2#3 { \use:e { \cs_set:Npn \exp_not:N \@@_tmp:w ##1 \tl_to_str:n { macro: } ##2 \tl_to_str:n { -> } ##3 \c_backslash_str \tl_to_str:n { @@_ } \cs_to_str:N #1 ##4 \s_@@_stop { \exp_not:N \exp_args:Ne \exp_not:N \tex_scantokens:D { \tex_global:D ##1 \tex_def:D \exp_not:N #1 ##2 { ##3 \tl_to_str:n {#2} \c_backslash_str @@_ \cs_to_str:N #1 ##4 \tl_to_str:n {#3} } } } } \exp_after:wN \@@_tmp:w \cs_meaning:N #1 \s_@@_stop } % \end{macrocode} % Some functions, however, won't work with the signature reading setup % above because their signature contains |w|eird arguments. These % functions need to be patched using \cs{__kernel_patch_weird:nnn}, % which won't make a copy of the function, rather it will patch the % debug code directly into it. This means that whatever argument the % debug code uses must be actually used by the patched function. % \begin{macrocode} \cs_set_protected:Npn \__kernel_patch_weird:nnn { \group_begin: \char_set_catcode_other:N \# \__kernel_patch_weird_aux:nnn } \cs_set_protected:Npn \__kernel_patch_weird_aux:nnn #1#2#3 { \char_set_catcode_parameter:N \# \char_set_catcode_space:N \ % \tex_endlinechar:D -1 \scan_stop: \tl_map_inline:nn {#3} { \@@_patch_weird:Nnn ##1 {#1} {#2} } \group_end: } \cs_set_protected:Npn \@@_patch_weird:Nnn #1#2#3 { \use:e { \tex_endlinechar:D -1 \scan_stop: \exp_not:N \tex_scantokens:D { \tex_global:D \cs_prefix_spec:N #1 \tex_def:D \exp_not:N #1 \cs_parameter_spec:N #1 { \tl_to_str:n {#2} \cs_replacement_spec:N #1 \tl_to_str:n {#3} } } } } % \end{macrocode} % \end{macro} % % Patching the second argument to ensure it exists. This happens before % we alter |#1| so the ordering is correct. For many variable types % such as \texttt{int} a low-level error occurs when |#2| is unknown, so % adding a check is not needed. % \begin{macrocode} \__kernel_patch:nnn { \__kernel_chk_var_exist:N #2 } { } { \bool_set_eq:NN \bool_gset_eq:NN \clist_set_eq:NN \clist_gset_eq:NN \fp_set_eq:NN \fp_gset_eq:NN \prop_set_eq:NN \prop_gset_eq:NN \seq_set_eq:NN \seq_gset_eq:NN \str_set_eq:NN \str_gset_eq:NN \tl_set_eq:NN \tl_gset_eq:NN } % \end{macrocode} % % Patching both second and third arguments. % \begin{macrocode} \__kernel_patch:nnn { \__kernel_chk_var_exist:N #2 \__kernel_chk_var_exist:N #3 } { } { \clist_concat:NNN \clist_gconcat:NNN \prop_concat:NNN \prop_gconcat:NNN \seq_concat:NNN \seq_gconcat:NNN \str_concat:NNN \str_gconcat:NNN \tl_concat:NNN \tl_gconcat:NNN } % \end{macrocode} % % % \begin{macrocode} \cs_gset_protected:Npn \__kernel_tl_set:Nx { \cs_set_nopar:Npe } \cs_gset_protected:Npn \__kernel_tl_gset:Nx { \cs_gset_nopar:Npe } % \end{macrocode} % % Patching where the first argument to a function needs scope-checking: % either local or global (so two lists). % \begin{macrocode} \__kernel_patch:nnn { \__kernel_chk_var_local:N #1 } { } { \bool_set:Nn \bool_set_eq:NN \bool_set_true:N \bool_set_false:N \box_set_eq:NN \box_set_eq_drop:NN \box_set_to_last:N \clist_clear:N \clist_set_eq:NN \dim_zero:N \dim_set:Nn \dim_set_eq:NN \dim_add:Nn \dim_sub:Nn \fp_set_eq:NN \int_zero:N \int_set_eq:NN \int_add:Nn \int_sub:Nn \int_incr:N \int_decr:N \int_set:Nn \hbox_set:Nn \hbox_set_to_wd:Nnn \hbox_set:Nw \hbox_set_to_wd:Nnw \muskip_zero:N \muskip_set:Nn \muskip_add:Nn \muskip_sub:Nn \muskip_set_eq:NN \prop_clear:N \prop_concat:NNN \prop_pop:NnN \prop_pop:NnNT \prop_pop:NnNF \prop_pop:NnNTF \prop_put:Nnn \prop_put_if_not_in:Nnn \prop_put_from_keyval:Nn \prop_remove:Nn \prop_set_eq:NN \prop_set_from_keyval:Nn \seq_set_eq:NN \skip_zero:N \skip_set:Nn \skip_set_eq:NN \skip_add:Nn \skip_sub:Nn \str_clear:N \str_set_eq:NN \str_put_left:Nn \str_put_right:Nn \__kernel_tl_set:Nx \tl_clear:N \tl_set_eq:NN \tl_put_left:Nn \tl_put_left:NV \tl_put_left:Nv \tl_put_left:Ne \tl_put_left:No \tl_put_right:Nn \tl_put_right:NV \tl_put_right:Nv \tl_put_right:Ne \tl_put_right:No \tl_build_begin:N \tl_build_put_right:Nn \tl_build_put_left:Nn \vbox_set:Nn \vbox_set_top:Nn \vbox_set_to_ht:Nnn \vbox_set:Nw \vbox_set_to_ht:Nnw \vbox_set_split_to_ht:NNn } \__kernel_patch:nnn { \__kernel_chk_var_global:N #1 } { } { \bool_gset:Nn \bool_gset_eq:NN \bool_gset_true:N \bool_gset_false:N \box_gset_eq:NN \box_gset_eq_drop:NN \box_gset_to_last:N \cctab_gset:Nn \clist_gclear:N \clist_gset_eq:NN \dim_gset_eq:NN \dim_gzero:N \dim_gset:Nn \dim_gadd:Nn \dim_gsub:Nn \fp_gset_eq:NN \int_gzero:N \int_gset_eq:NN \int_gadd:Nn \int_gsub:Nn \int_gincr:N \int_gdecr:N \int_gset:Nn \hbox_gset:Nn \hbox_gset_to_wd:Nnn \hbox_gset:Nw \hbox_gset_to_wd:Nnw \muskip_gzero:N \muskip_gset:Nn \muskip_gadd:Nn \muskip_gsub:Nn \muskip_gset_eq:NN \prop_gclear:N \prop_gconcat:NNN \prop_gpop:NnN \prop_gpop:NnNT \prop_gpop:NnNF \prop_gpop:NnNTF \prop_gput:Nnn \prop_gput_if_not_in:Nnn \prop_gput_from_keyval:Nn \prop_gremove:Nn \prop_gset_eq:NN \prop_gset_from_keyval:Nn \seq_gset_eq:NN \skip_gzero:N \skip_gset:Nn \skip_gset_eq:NN \skip_gadd:Nn \skip_gsub:Nn \str_gclear:N \str_gset_eq:NN \str_gput_left:Nn \str_gput_right:Nn \__kernel_tl_gset:Nx \tl_gclear:N \tl_gset_eq:NN \tl_gput_left:Nn \tl_gput_left:NV \tl_gput_left:Nv \tl_gput_left:Ne \tl_gput_left:No \tl_gput_right:Nn \tl_gput_right:NV \tl_gput_right:Nv \tl_gput_right:Ne \tl_gput_right:No \tl_build_gbegin:N \tl_build_gput_right:Nn \tl_build_gput_left:Nn \vbox_gset:Nn \vbox_gset_top:Nn \vbox_gset_to_ht:Nnn \vbox_gset:Nw \vbox_gset_to_ht:Nnw \vbox_gset_split_to_ht:NNn } % \end{macrocode} % % Scoping for constants. % \begin{macrocode} \__kernel_patch:nnn { \__kernel_chk_var_scope:NN c #1 } { } { \bool_const:Nn \cctab_const:Nn \dim_const:Nn \int_const:Nn \intarray_const_from_clist:Nn \muskip_const:Nn \prop_const_from_keyval:Nn \prop_const_linked_from_keyval:Nn \skip_const:Nn \str_const:Nn \tl_const:Nn } % \end{macrocode} % % Flag functions. % \begin{macrocode} \__kernel_patch:nnn { \__kernel_chk_flag_exist:NN } { } { \flag_ensure_raised:N \flag_height:N \flag_if_raised:NT \flag_if_raised:NF \flag_if_raised:NTF \flag_if_raised_p:N \flag_raise:N } % \end{macrocode} % % Various one-offs. % \begin{macrocode} \__kernel_patch:nnn { \__kernel_chk_cs_exist:N #1 } { } { \cs_generate_variant:Nn } \__kernel_patch:nnn { \__kernel_chk_var_scope:NN g #1 } { } { \cctab_new:N } \__kernel_patch:nnn { \__kernel_chk_var_scope:NN l #1 } { } { \flag_new:N } \__kernel_patch:nnn { \__kernel_chk_var_scope:NN l #1 \__kernel_chk_flag_exist:NN } { } { \flag_clear:N } \__kernel_patch:nnn { \__kernel_chk_var_scope:NN g #1 } { } { \intarray_new:Nn } \__kernel_patch:nnn { \__kernel_chk_var_scope:NN q #1 } { } { \quark_new:N } \__kernel_patch:nnn { \__kernel_chk_var_scope:NN s #1 } { } { \scan_new:N } % \end{macrocode} % % Patch various internal commands to log definitions of functions. % First, a kernel internal. Then internals from the \pkg{cs}, % \pkg{keys} and \pkg{msg} modules. % \begin{macrocode} \__kernel_patch:nnn { } { \__kernel_debug_log:e { Defining~\token_to_str:N #1~ \msg_line_context: } } { \__kernel_chk_if_free_cs:N } %<@@=cs> \__kernel_patch_weird:nnn { \cs_if_free:NF #4 { \__kernel_debug_log:e { Variant~\token_to_str:N #4~% already~defined;~ not~ changing~ it~ \msg_line_context: } } } { } { \@@_generate_variant:wwNN } %<@@=keys> \__kernel_patch:nnn { \cs_if_exist:cF { \c_@@_code_root_str #1 } { \__kernel_debug_log:e { Defining~key~#1~\msg_line_context: } } } { } { \@@_cmd_set_direct:nn } %<@@=msg> \__kernel_patch:nnn { } { \__kernel_debug_log:e { Defining~message~ #1 / #2 ~\msg_line_context: } } { \@@_chk_free:nn } % \end{macrocode} % % \begin{macrocode} %<@@=prg> % \end{macrocode} % Internal functions from \pkg{prg} module. % \begin{macrocode} \__kernel_patch_weird:nnn { \__kernel_chk_cs_exist:c { #5 _p : #6 } } { } { \@@_set_eq_conditional_p_form:wNnnnn } \__kernel_patch_weird:nnn { \__kernel_chk_cs_exist:c { #5 : #6 TF } } { } { \@@_set_eq_conditional_TF_form:wNnnnn } \__kernel_patch_weird:nnn { \__kernel_chk_cs_exist:c { #5 : #6 T } } { } { \@@_set_eq_conditional_T_form:wNnnnn } \__kernel_patch_weird:nnn { \__kernel_chk_cs_exist:c { #5 : #6 F } } { } { \@@_set_eq_conditional_F_form:wNnnnn } % \end{macrocode} % % \begin{macrocode} %<@@=regex> % \end{macrocode} % Internal functions from \pkg{regex} module. % \begin{macrocode} \__kernel_patch:nnn { \@@_trace_push:nnN { regex } { 1 } \@@_escape_use:nnnn \group_begin: \__kernel_tl_set:Nx \l_@@_internal_a_tl { \@@_trace_pop:nnN { regex } { 1 } \@@_escape_use:nnnn } \use_none:nnn } { } { \@@_escape_use:nnn } \__kernel_patch:nnn { \@@_trace_push:nnN { regex } { 1 } \@@_build:N } { \@@_trace_states:n { 2 } \@@_trace_pop:nnN { regex } { 1 } \@@_build:N } { \@@_build:N } \__kernel_patch:nnn { \@@_trace_push:nnN { regex } { 1 } \@@_build_for_cs:n } { \@@_trace_states:n { 2 } \@@_trace_pop:nnN { regex } { 1 } \@@_build_for_cs:n } { \@@_build_for_cs:n } \__kernel_patch:nnn { \@@_trace:nne { regex } { 2 } { regex~new~state~ L=\int_use:N \l_@@_left_state_int ~ -> ~ R=\int_use:N \l_@@_right_state_int ~ -> ~ M=\int_use:N \l_@@_max_state_int ~ -> ~ \int_eval:n { \l_@@_max_state_int + 1 } } } { } { \@@_build_new_state: } \__kernel_patch:nnn { \@@_trace_push:nnN { regex } { 1 } \@@_group_aux:nnnnN } { \@@_trace_pop:nnN { regex } { 1 } \@@_group_aux:nnnnN } { \@@_group_aux:nnnnN } \__kernel_patch:nnn { \@@_trace_push:nnN { regex } { 1 } \@@_branch:n } { \@@_trace_pop:nnN { regex } { 1 } \@@_branch:n } { \@@_branch:n } \__kernel_patch:nnn { \@@_trace_push:nnN { regex } { 1 } \@@_match:n \@@_trace:nne { regex } { 1 } { analyzing~query~token~list } } { \@@_trace_pop:nnN { regex } { 1 } \@@_match:n } { \@@_match:n } \__kernel_patch:nnn { \@@_trace_push:nnN { regex } { 1 } \@@_match_cs:n \@@_trace:nne { regex } { 1 } { analyzing~query~token~list } } { \@@_trace_pop:nnN { regex } { 1 } \@@_match_cs:n } { \@@_match_cs:n } \__kernel_patch:nnn { \@@_trace:nne { regex } { 1 } { initializing } } { } { \@@_match_init: } \__kernel_patch:nnn { \@@_trace:nne { regex } { 2 } { state~\int_use:N \l_@@_curr_state_int } } { } { \@@_use_state: } \__kernel_patch:nnn { \@@_trace_push:nnN { regex } { 1 } \@@_replacement:n } { \@@_trace_pop:nnN { regex } { 1 } \@@_replacement:n } { \@@_replacement:n } % \end{macrocode} % % \begin{macrocode} \group_end: % \end{macrocode} % % \begin{macrocode} %<@@=debug> % \end{macrocode} % % Patching arguments is a bit more involved: we do these one at a time. % The basic idea is the same, using a |#| token that is a string. % \begin{macrocode} \group_begin: \cs_set_protected:Npn \__kernel_patch:Nn #1 { \group_begin: \char_set_catcode_other:N \# \__kernel_patch_aux:Nn #1 } \cs_set_protected:Npn \__kernel_patch_aux:Nn #1#2 { \char_set_catcode_parameter:N \# \tex_endlinechar:D -1 \scan_stop: \exp_args:Ne \tex_scantokens:D { \tex_global:D \cs_prefix_spec:N #1 \tex_def:D \exp_not:N #1 \cs_parameter_spec:N #1 { \exp_args:No \tl_to_str:n { #1 #2 } } } \group_end: } % \end{macrocode} % % The functions here can get a bit repetitive, so we define a helper % which can reuse the same patch code repeatedly. The main part of the % patch is the same, so we just have to deal with the part which varies % depending on the type of expression. % \begin{macrocode} \cs_set_protected:Npn \__kernel_patch_eval:nn #1#2 { \tl_map_inline:nn {#1} { \exp_args:NNe \__kernel_patch:Nn ##1 { { \c_hash_str 1 } { \exp_not:N \__kernel_chk_expr:nNnN { \c_hash_str 2 } \exp_not:n {#2} \exp_not:N ##1 } } } } %<@@=dim> \__kernel_patch_eval:nn { \dim_set:Nn \dim_gset:Nn \dim_add:Nn \dim_gadd:Nn \dim_sub:Nn \dim_gsub:Nn \dim_const:Nn } { \@@_eval:w { } } %<@@=int> \__kernel_patch_eval:nn { \int_set:Nn \int_gset:Nn \int_add:Nn \int_gadd:Nn \int_sub:Nn \int_gsub:Nn \int_const:Nn } { \@@_eval:w { } } \__kernel_patch_eval:nn { \muskip_set:Nn \muskip_gset:Nn \muskip_add:Nn \muskip_gadd:Nn \muskip_sub:Nn \muskip_gsub:Nn \muskip_const:Nn } { \tex_muexpr:D { \tex_mutoglue:D } } \__kernel_patch_eval:nn { \skip_set:Nn \skip_gset:Nn \skip_add:Nn \skip_gadd:Nn \skip_sub:Nn \skip_gsub:Nn \skip_const:Nn } { \tex_glueexpr:D { } } % \end{macrocode} % % Patching expandable expressions, first the one-argument versions, % then the two-argument ones. % \begin{macrocode} \cs_set_protected:Npn \__kernel_patch_eval:nn #1#2 { \tl_map_inline:nn {#1} { \exp_args:NNe \__kernel_patch:Nn ##1 { { \exp_not:N \__kernel_chk_expr:nNnN { \c_hash_str 1 } \exp_not:n {#2} \exp_not:N ##1 } } } } %<@@=box> \__kernel_patch_eval:nn { \@@_dim_eval:n } { \@@_dim_eval:w { } } %<@@=dim> \__kernel_patch_eval:nn { \dim_eval:n \dim_to_decimal:n \dim_to_decimal_in_sp:n \dim_abs:n \dim_sign:n } { \@@_eval:w { } } %<@@=int> \__kernel_patch_eval:nn { \int_eval:n \int_abs:n \int_sign:n } { \@@_eval:w { } } \__kernel_patch_eval:nn { \skip_eval:n \skip_horizontal:n \skip_vertical:n } { \tex_glueexpr:D { } } \__kernel_patch_eval:nn { \muskip_eval:n } { \tex_muexpr:D { \tex_mutoglue:D } } \cs_set_protected:Npn \__kernel_patch_eval:nn #1#2 { \tl_map_inline:nn {#1} { \exp_args:NNe \__kernel_patch:Nn ##1 { { \exp_not:N \__kernel_chk_expr:nNnN { \c_hash_str 1 } \exp_not:n {#2} \exp_not:N ##1 } { \exp_not:N \__kernel_chk_expr:nNnN { \c_hash_str 2 } \exp_not:n {#2} \exp_not:N ##1 } } } } %<@@=dim> \__kernel_patch_eval:nn { \dim_max:nn \dim_min:nn } { \@@_eval:w { } } %<@@=int> \__kernel_patch_eval:nn { \int_max:nn \int_min:nn \int_div_truncate:nn \int_mod:nn } { \@@_eval:w { } } % \end{macrocode} % % Conditionals: three argument ones then one argument ones % \begin{macrocode} \cs_set_protected:Npn \__kernel_patch_cond:nn #1#2 { \clist_map_inline:nn { :nNnT , :nNnF , :nNnTF , _p:nNn } { \exp_args:Nce \__kernel_patch:Nn { #1 ##1 } { { \exp_not:N \__kernel_chk_expr:nNnN { \c_hash_str 1 } \exp_not:n {#2} \exp_not:c { #1 ##1 } } { \c_hash_str 2 } { \exp_not:N \__kernel_chk_expr:nNnN { \c_hash_str 3 } \exp_not:n {#2} \exp_not:c { #1 ##1 } } } } } %<@@=dim> \__kernel_patch_cond:nn { dim_compare } { \@@_eval:w { } } %<@@=int> \__kernel_patch_cond:nn { int_compare } { \@@_eval:w { } } \cs_set_protected:Npn \__kernel_patch_cond:nn #1#2 { \clist_map_inline:nn { :nT , :nF , :nTF , _p:n } { \exp_args:Nce \__kernel_patch:Nn { #1 ##1 } { { \exp_not:N \__kernel_chk_expr:nNnN { \c_hash_str 1 } \exp_not:n {#2} \exp_not:c { #1 ##1 } } } } } %<@@=int> \__kernel_patch_cond:nn { int_if_even } { \@@_eval:w { } } \__kernel_patch_cond:nn { int_if_odd } { \@@_eval:w { } } % \end{macrocode} % % Step functions. % \begin{macrocode} %<@@=dim> \__kernel_patch:Nn \dim_step_function:nnnN { { \__kernel_chk_expr:nNnN {#1} \@@_eval:w { } \dim_step_function:nnnN } { \__kernel_chk_expr:nNnN {#2} \@@_eval:w { } \dim_step_function:nnnN } { \__kernel_chk_expr:nNnN {#3} \@@_eval:w { } \dim_step_function:nnnN } } %<@@=int> \__kernel_patch:Nn \int_step_function:nnnN { { \__kernel_chk_expr:nNnN {#1} \@@_eval:w { } \int_step_function:nnnN } { \__kernel_chk_expr:nNnN {#2} \@@_eval:w { } \int_step_function:nnnN } { \__kernel_chk_expr:nNnN {#3} \@@_eval:w { } \int_step_function:nnnN } } % \end{macrocode} % % Odds and ends % \begin{macrocode} \__kernel_patch:Nn \dim_to_fp:n { { (#1) } } % \end{macrocode} % % \begin{macrocode} \group_end: % \end{macrocode} % % \begin{macrocode} %<@@=skip> % \end{macrocode} % This one has catcode changes so must be done by hand. % \begin{macrocode} \cs_set_protected:Npn \@@_tmp:w #1 { \prg_set_conditional:Npnn \skip_if_finite:n ##1 { p , T , F , TF } { \exp_after:wN \@@_if_finite:wwNw \skip_use:N \tex_glueexpr:D \__kernel_chk_expr:nNnN {##1} \tex_glueexpr:D { } \skip_if_finite:n ; \prg_return_false: #1 ; \prg_return_true: \s_@@_stop } } \exp_args:No \@@_tmp:w { \tl_to_str:n { fil } } % \end{macrocode} % % \begin{macrocode} %<@@=msg> % \end{macrocode} % % Messages. % \begin{macrocode} \msg_new:nnnn { debug } { debug } { The~debugging~option~'#1'~does~not~exist~\msg_line_context:. } { The~functions~'\iow_char:N\\debug_on:n'~and~ '\iow_char:N\\debug_off:n'~only~accept~the~arguments~ 'all',~'check-declarations',~'check-expressions',~ 'deprecation',~'log-functions',~not~'#1'. } \msg_new:nnn { debug } { expr } { '#2'~in~#1 } \msg_new:nnnn { debug } { local-global } { Inconsistent~local/global~assignment } { \c_@@_coding_error_text_tl \if:w l #2 Local \else: \if:w g #2 Global \else: Constant \fi: \fi: \ % assignment~to~a~ \if:w l #1 local \else: \if:w g #1 global \else: constant \fi: \fi: \ % variable~'#3'. } \msg_new:nnnn { debug } { non-declared-variable } { The~variable~#1~has~not~been~declared~\msg_line_context:. } { \c_@@_coding_error_text_tl Checking~is~active,~and~you~have~tried~do~so~something~like: \\ \ \ \tl_set:Nn ~ #1 ~ \{ ~ ... ~ \} \\ without~first~having: \\ \ \ \tl_new:N ~ #1 \\ \\ LaTeX~will~continue,~creating~the~variable~where~it~is~the~one~being~set. } % \end{macrocode} % % \begin{macro}{\__kernel_if_debug:TF} % Flip the switch for deprecated code. % \begin{macrocode} \cs_set_protected:Npn \__kernel_if_debug:TF #1#2 {#1} % \end{macrocode} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % % \PrintIndex