控制由 \NewDocumentCommand 定义的命令产生的错误消息

控制由 \NewDocumentCommand 定义的命令产生的错误消息

\NewDocumentCommand命令使用了一个巧妙的技巧来制作更好的错误消息——如果的参数\mycommand混乱,则产生的错误可能类似于“扫描使用时文件结束\__xparse_grab_D:w”,因此每当 xparse 要抓取另一个参数时,它都会定义\mycommand<space>执行参数抓取,然后使用它来完成工作,这样如果出现问题,错误就会变成类似“段落在完成之前结束”之类的内容\mycommand<space>

我想要这样的命令\NewDocumentCommandAs接受两个参数——一个是需要定义的宏的名称,另一个是错误消息中要使用的命令的名称。

我有两个用例。一个是我有一个包,它定义了只能在特定环境中使用的命令。因此,它在包的私有命名空间中定义命令,并将\let命令的公共名称定义为环境开始时的私有版本。显然,我希望所有错误消息都基于命令的公共名称,所以我想说类似

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

第二个用例是,我有一个带有可选参数的命令,一个包含直到第一个开括号的所有内容的参数,然后是一个以第一个开括号开始的由匹配括号分隔的参数。由于“u​​ntil”参数类型吸收了它扫描到的标记,因此我需要重新插入开括号并使用辅助函数:

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

相关内容