Controlar mensagens de erro feitas por comandos definidos com \NewDocumentCommand

Controlar mensagens de erro feitas por comandos definidos com \NewDocumentCommand

O \NewDocumentCommandcomando usa um truque inteligente para criar mensagens de erro melhores - se os argumentos de \mycommandestiverem confusos, seria indesejável que o erro resultante fosse algo como "Arquivo finalizado durante a verificação do uso de \__xparse_grab_D:w", então sempre que o xparse for pegar outro argumento, ele define \mycommand<space>como capturar o argumento e depois usa isso para fazer o trabalho, de modo que, se algo der errado, o erro seja algo como "O parágrafo terminou antes de \mycommand<space>ser concluído".

Quero que um comando como \NewDocumentCommandAsesse receba dois argumentos - um é o nome da macro a ser definida e o segundo é o nome do comando a ser usado em mensagens de erro.

Eu tenho dois casos de uso para isso. Uma é que tenho um pacote que define comandos que devem ser usados ​​apenas dentro de um determinado ambiente. Portanto, ele define os comandos no namespace privado do pacote e \leté o nome público do comando para a versão privada no início do ambiente. Obviamente, quero basear todas as mensagens de erro no nome público do comando, então gostaria de dizer algo como

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

O segundo caso de uso é que eu tenho um comando com um argumento opcional, um argumento que é tudo até o primeiro parêntese aberto e, em seguida, um argumento delimitado por parênteses balanceados começando com o primeiro parêntese aberto. Como o tipo de argumento "até" absorve os tokens que está digitalizando, preciso reinserir o parêntese aberto e usar um auxiliar:

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

É claro que isso poderia ser corrigido criando uma versão de u (talvez U?) Que reinsira os tokens removidos, mas esse pode ser um caso de uso incomum.

Para fins de formatação, acho que enviarei minhas duas implementações como resposta. Alguém mais lidou com esse problema? Existe alguma abordagem melhor que estou perdendo? Não deveria eu fazer isso e resolver esses problemas de uma maneira diferente? Este é um recurso que pode ser adicionado a uma versão futura do xparse?

Responder1

Minha primeira tentativa foi:

\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:
}

Isso é um pouco confuso, mas funciona bem. O grande benefício disso é que não depende da implementação \NewDocumentCommandalém dos detalhes em que as entranhas da macro estão armazenadas #1<space>codee nenhum outro comando auxiliar é usado. Uma limitação é que não funcionará se todos os argumentos forem do tipo "m", mas isso não me incomoda. Essa abordagem também não funciona para macros expansíveis.

Outra abordagem possível é criar uma versão corrigida \__xparse_declare_cmd_mixed_aux:Nnque use um nome diferente para o comando de erro (armazenado em \l__xparse_fn_tl) e instalar essa definição temporariamente para uso \NewDocumentCommand. Isso tem a vantagem de que você pode fazer algo semelhante \__xparse_declare_cmd_mixed_expandable:Nne fazer com que uma versão expansível funcione. Também funciona mesmo que todos os argumentos sejam obrigatórios. A desvantagem é que depende da implementação exata do \__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
}

informação relacionada