Controle los mensajes de error realizados por comandos definidos con \NewDocumentCommand

Controle los mensajes de error realizados por comandos definidos con \NewDocumentCommand

El \NewDocumentCommandcomando utiliza un truco inteligente para mejorar los mensajes de error: si los argumentos de \mycommandestán confusos, no sería deseable que el error resultante fuera algo así como "El archivo finalizó mientras se escaneaba el uso de \__xparse_grab_D:w", por lo que cada vez que xparse vaya a capturar otro argumento, define \mycommand<space>hacer la captura de argumentos y luego lo usa para hacer el trabajo de modo que si algo sale mal, el error sea algo así como "El párrafo finalizó antes de \mycommand<space>completarse".

Quiero que un comando como \NewDocumentCommandAsese tenga dos argumentos: uno, el nombre de la macro que se definirá y el segundo, el nombre del comando que se utilizará en los mensajes de error.

Tengo dos casos de uso para esto. Una es que tengo un paquete que define comandos que solo deben usarse dentro de un entorno determinado. Por lo tanto, define los comandos en el espacio de nombres privado del paquete y \letes el nombre público del comando para la versión privada al inicio del entorno. Obviamente quiero basar todos los mensajes de error en torno al nombre público del comando, así que me gustaría decir algo como

\NewDocumentCommandAs \__my_pkg_cmd:args \cmd {<args>}{<body>}

El segundo caso de uso es que tengo un comando con un argumento opcional, un argumento que es todo hasta el primer paréntesis abierto y luego un argumento delimitado por paréntesis equilibrados que comienza con ese primer paréntesis abierto. Debido a que el tipo de argumento "hasta" absorbe los tokens que está escaneando, necesito volver a insertar el paréntesis abierto y usar un auxiliar:

\NewDocumentCommand \mycommand { o u( } { \mycommand_aux{#1}{#2}( }
\NewDocumentCommandAs \mycommand_aux \mycommand { m m r() } { 
    % do stuff here
}

Por supuesto, esto podría solucionarse creando una versión de u (¿quizás U?) que reinserte los tokens que elimina, pero ese podría ser un caso de uso inusual.

Para fines de formato, supongo que enviaré mis dos implementaciones como respuesta. ¿Alguien más se ha ocupado de este problema? ¿Hay algún enfoque mejor que me falta? ¿No debería hacer esto y resolver estos problemas de otra manera? ¿Es esta una característica que podría agregarse a una versión futura de xparse?

Respuesta1

Mi primer intento fue:

\cs_new_protected:Npn \NewDocumentCommandAs#1#2#3#4{
    \group_begin:
    % Store the original value of #2
    \cs_set_eq:cN { tempsave_ \cs_to_str:N #2 } #2
    \cs_set_eq:cc { tempsave_ \cs_to_str:N #2 \c_space_tl code } 
                  { \cs_to_str:N #2 \c_space_tl code }
    % Use \DeclareDocumentCommand with #2 so that error messages work
    \DeclareDocumentCommand#2{#3}{\group_end: #4}
    % Define #1 to be a wrapper command that sets #2<space>code equal to #1<space>code
    \cs_new:Npx #1{
        \group_begin:
        \exp_not:N \cs_set_eq:NN
            \exp_not:c { \cs_to_str:N #2 \c_space_tl code }
            \exp_not:c { \cs_to_str:N #1 \c_space_tl code }
        \exp_not:c { \cs_to_str:N #1 \c_space_tl inner }
    }
    % Save the value of #2 set by DeclareDocumentCommand
    \cs_gset_eq:cN { \cs_to_str:N #1 \c_space_tl inner } #2
    \cs_gset_eq:cc{ \cs_to_str:N #1 \c_space_tl code } { \cs_to_str:N #2 \c_space_tl code }
    % Restore the original value of #2
    \cs_gset_eq:Nc #2 { tempsave_ \cs_to_str:N #2 }
    \cs_gset_eq:cc { \cs_to_str:N #2 \c_space_tl code } { tempsave_ \cs_to_str:N #2 \c_space_tl code }
    \group_end:
}

Esto es un poco complicado, pero funciona bien. El gran beneficio de esto es que no depende de la implementación \NewDocumentCommandmás allá del detalle en el que se almacenan las entrañas de la macro #1<space>codey no se utilizan otros comandos auxiliares. Una limitación es que no funcionará si todos los argumentos son del tipo "m", pero esto no me molesta. Este enfoque tampoco puede funcionar para macros expandibles.

Otro enfoque posible es crear una versión parcheada \__xparse_declare_cmd_mixed_aux:Nnque use un nombre diferente para el comando de error (almacenado en \l__xparse_fn_tl) e instalar esa definición temporalmente para poder usar \NewDocumentCommand. Esto tiene la ventaja de que puedes hacer algo similar \__xparse_declare_cmd_mixed_expandable:Nny conseguir que funcione una versión ampliable. También funciona incluso si todos los argumentos son obligatorios. La desventaja es que depende de la implementación exacta de \__xparse_declare_cmd_mixed_aux:Nn.

% Make two copies of \__xparse_declare_cmd_aux:Nn, one to patch, one as a save
% If \l_..._environment_bool is true, it forces xparse to use argument grabbers even when
% all arguments are mandatory. So we'll set it to be true for \NewDocumentCommandAs
\cs_new_eq:NN \__my_xparse_declare_cmd_aux_as:Nnn \__xparse_declare_cmd_aux:Nnn
\cs_new_eq:NN \__my_xparse_declare_cmd_aux_save:Nnn \__xparse_declare_cmd_aux:Nnn
\patchcmd \__my_xparse_declare_cmd_aux_as:Nnn { \bool_set_false:N \l__xparse_environment_bool } { \bool_set_true:N \l__xparse_environment_bool }{}{\oops}

\cs_new_eq:NN \__my_xparse_declare_cmd_mixed_aux_as:Nn   \__xparse_declare_cmd_mixed_aux:Nn
\cs_new_eq:NN \__my_xparse_declare_cmd_mixed_aux_save:Nn \__xparse_declare_cmd_mixed_aux:Nn

% Replace \l__xparse_function_tl with my own token list that I can set independently
\tl_new:N \l__my_xparse_function_as_tl
\patchcmd \__my_xparse_declare_as_cmd_mixed_aux:Nn 
    {{ \l__xparse_function_tl \c_space_tl }} 
    {{ \l__my_xparse_function_as_tl \c_space_tl }}{}{\oops}

\cs_new:Npn \NewDocumentCommandAs #1#2#3#4 {
    % Patch in my modified version of \__xparse_declare_cmd_mixed_aux
    \cs_set_eq:NN \__xparse_declare_cmd_mixed_aux:Nn \__my_xparse_declare_cmd_mixed_aux_as:Nn
    \tl_set:Nn \l__my_xparse_function_as_tl { \cs_to_str:N #2 } % Use #2 as the error name
    \NewDocumentCommand#1{#3}{#4}
    \cs_set_eq:NN \__xparse_declare_cmd_mixed_aux:Nn \__my_xparse_declare_as_cmd_mixed_aux_save:Nn
}

información relacionada