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