Команда \NewDocumentCommand
использует хитрый трюк для создания более качественных сообщений об ошибках — если аргументы \mycommand
перепутаны, было бы нежелательно, чтобы результирующая ошибка была чем-то вроде «Файл закончился при сканировании с использованием \__xparse_grab_D:w
», поэтому всякий раз, когда xparse собирается захватить другой аргумент, он определяет, что нужно \mycommand<space>
выполнить захват аргумента, а затем использует его для выполнения работы, так что если что-то пойдет не так, ошибка будет чем-то вроде «Абзац закончился до того, как \mycommand<space>
был завершен».
Мне нужно, чтобы такая команда \NewDocumentCommandAs
принимала два аргумента: один — имя определяемого макроса, а второй — имя команды, которая будет использоваться в сообщениях об ошибках.
У меня есть два варианта использования этого. Один из них заключается в том, что у меня есть пакет, который определяет команды, которые должны использоваться только внутри определенной среды. Поэтому он определяет команды в частном пространстве имен пакета и \let
публичное имя команды для частной версии в начале среды. Очевидно, что я хочу основывать все сообщения об ошибках вокруг публичного имени команды, поэтому я хотел бы сказать что-то вроде
\NewDocumentCommandAs \__my_pkg_cmd:args \cmd {<args>}{<body>}
Второй вариант использования заключается в том, что у меня есть команда с необязательным аргументом, аргументом, который представляет собой все до первой открывающей скобки, а затем аргумент, разделенный сбалансированными скобками, начиная с этой первой открывающей скобки. Поскольку тип аргумента "until" поглощает токены, до которых он сканирует, мне нужно повторно вставить открывающую скобку и использовать вспомогательный:
\NewDocumentCommand \mycommand { o u( } { \mycommand_aux{#1}{#2}( }
\NewDocumentCommandAs \mycommand_aux \mycommand { m m r() } {
% do stuff here
}
Конечно, это можно исправить, создав версию u (может быть, U?), которая повторно вставляет удаленные ею токены, но это может оказаться необычным вариантом использования.
Для форматирования, я думаю, я отправлю свои две реализации в качестве ответа. Кто-нибудь еще справлялся с этой проблемой? Есть ли какой-то лучший подход, который я упускаю? Не следует ли мне сделать это и решить эти проблемы другим способом? Это функция, которая может быть добавлена в будущую версию xparse?
решение1
Моя первая попытка была:
\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:
}
Это немного запутанно, но работает нормально. Большое преимущество этого в том, что это не зависит от реализации \NewDocumentCommand
за пределами деталей, в которых хранятся внутренности макроса, #1<space>code
и не используются никакие другие вспомогательные команды. Одно ограничение в том, что это не будет работать, если все аргументы имеют тип "m", но меня это не беспокоит. Этот подход также не может работать для расширяемых макросов.
Другой возможный подход — сделать исправленную версию , \__xparse_declare_cmd_mixed_aux:Nn
которая использует другое имя для команды ошибки (хранящейся в \l__xparse_fn_tl
), и временно установить это определение ради использования \NewDocumentCommand
. Это имеет то преимущество, что вы можете сделать то же самое \__xparse_declare_cmd_mixed_expandable:Nn
и получить работающую расширяемую версию. Это также работает, даже если все аргументы являются обязательными. Недостаток в том, что это зависит от точной реализации \__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
}