Kontrollieren Sie Fehlermeldungen von Befehlen, die mit \NewDocumentCommand definiert wurden

Kontrollieren Sie Fehlermeldungen von Befehlen, die mit \NewDocumentCommand definiert wurden

Der \NewDocumentCommandBefehl bedient sich eines cleveren Tricks, um bessere Fehlermeldungen zu erzeugen – wenn die Argumente \mycommanddurcheinander geraten, wäre es unerwünscht, wenn die resultierende Fehlermeldung etwa „Datei wurde beim Scannen der Verwendung von beendet \__xparse_grab_D:w“ wäre. Wenn xparse also ein anderes Argument erfassen soll, definiert es, \mycommand<space>dass das Argument erfasst werden soll, und verwendet dieses dann für die Arbeit, sodass die Fehlermeldung, wenn etwas schief geht, stattdessen etwa „Absatz wurde beendet, bevor \mycommand<space>er abgeschlossen war“ lautet.

Ich möchte, dass ein solcher Befehl \NewDocumentCommandAszwei Argumente annimmt – eines den Namen des zu definierenden Makros und das zweite den Namen des Befehls, der in Fehlermeldungen verwendet werden soll.

Ich habe hierfür zwei Anwendungsfälle. Einer ist, dass ich ein Paket habe, das Befehle definiert, die nur in einer bestimmten Umgebung verwendet werden dürfen. Es definiert also die Befehle im privaten Namespace des Pakets und \letgibt den öffentlichen Namen des Befehls an die private Version am Anfang der Umgebung weiter. Natürlich möchte ich alle Fehlermeldungen auf dem öffentlichen Namen des Befehls basieren lassen, also würde ich gerne etwas sagen wie

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

Der zweite Anwendungsfall ist, dass ich einen Befehl mit einem optionalen Argument habe, ein Argument, das alles bis zur ersten öffnenden Klammer ist, und dann ein Argument, das durch ausgeglichene Klammern begrenzt ist, beginnend mit dieser ersten öffnenden Klammer. Da der Argumenttyp „until“ die Token absorbiert, bis zu denen er scannt, muss ich die öffnende Klammer erneut einfügen und ein Hilfsargument verwenden:

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

Dies könnte natürlich behoben werden, indem eine Version von u (vielleicht U?) erstellt wird, die die entfernten Token wieder einfügt, aber das wäre möglicherweise ein ungewöhnlicher Anwendungsfall.

Aus Formatierungsgründen werde ich meine beiden Implementierungen wohl als Antwort einreichen. Hat sich sonst noch jemand mit diesem Problem befasst? Gibt es einen besseren Ansatz, den ich übersehe? Sollte ich das nicht tun und diese Probleme auf andere Weise lösen? Ist dies eine Funktion, die möglicherweise einer zukünftigen Version von xparse hinzugefügt wird?

Antwort1

Mein erster Versuch war:

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

Das ist ein bisschen chaotisch, funktioniert aber ganz gut. Der große Vorteil dabei ist, dass es nicht von der Implementierung \NewDocumentCommandüber das Detail hinaus abhängt, in dem die Kerne des Makros gespeichert sind, #1<space>codeund dass keine anderen Hilfsbefehle verwendet werden. Eine Einschränkung ist, dass es nicht funktioniert, wenn alle Argumente vom Typ „m“ sind, aber das stört mich nicht. Dieser Ansatz funktioniert auch nicht für erweiterbare Makros.

Ein anderer möglicher Ansatz besteht darin, eine gepatchte Version von zu erstellen, \__xparse_declare_cmd_mixed_aux:Nndie einen anderen Namen für den Fehlerbefehl verwendet (gespeichert in \l__xparse_fn_tl), und diese Definition vorübergehend zu installieren, um verwenden zu können \NewDocumentCommand. Dies hat den Vorteil, dass Sie etwas Ähnliches wie tun \__xparse_declare_cmd_mixed_expandable:Nnund eine erweiterbare Version zum Laufen bringen können. Es funktioniert auch, wenn alle Argumente obligatorisch sind. Der Nachteil besteht darin, dass es von der genauen Implementierung von abhängt \__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
}

verwandte Informationen