\NewDocumentCommand 构造了 csname

\NewDocumentCommand 构造了 csname

本着我之前的问题定义 \xthinspace:仅当后面没有特定字符时才使用细空格省略号和正确的空间因子,我正在尝试定义一个通用\xspace的宏系统。

我倾向于的语法如下:

\makexfollow{xfollow}{(always)}{(followed)}{(not followed)}
\addtoxfollowexceptions{x}
\newcommand*{\foo}{foo\xfollow}

\foo \foo x

屈服

foo(总是)(未关注)foo(总是)(关注)x

通过编辑和包装egreg 的代码,我已经完成了大部分工作;请参阅下面的代码。我甚至弄清楚了如何使用\cs:w … \cs_end::c说明符来参数化大多数内部 csname。

问题在于命令\addtoxfollowexceptions& \removefromxfollowexceptions;我无法弄清楚如何声明它们以xfollow用通用内容替换名称的一部分。

我怎样才能实现这个目标?

\documentclass{article}

\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\makexfollow}{m m m m}
  {
    % Token list for “exceptions”
    \tl_new:c { g_#1_exceptions_tl }

    % Add to list
    % Should be `\cs:w addto#1exceptions \cs_end:` or something similar
    \NewDocumentCommand{\addtoxfollowexceptions}{m}
      {
        \tl_gput_right:cn { g_#1_exceptions_tl } { ##1 }
      }

    % Remove from list
    % Should be `\cs:w removefrom#1exceptions \cs_end:` or something similar
    \NewDocumentCommand{\removefromxfollowexceptions}{m}
      {
        \tl_gremove_all:cn { g_#1_exceptions_tl } { ##1 }
      }

    \cs_new_protected:cpn { #1 }
      {
        % Unconditional part
        #2

        % Is next token one of the “exceptions”?
        \bool_set_false:c { l_#1_followed_bool }

        % Peek ahead, ignoring spaces, then hand-off to `\xfollow_check:`
        % with `\l_peek_token` set.
        %
        % This is a clever hack: We’re actually checking whether the next
        % non-space token is a space token; if not, call `\xfollow_check:`.
        % Of course, by definition this will always test false, achieving
        % the result described above.
        \peek_catcode_ignore_spaces:NF \c_space_token
          { \cs:w #1_check: \cs_end: }
      }

      % Compare `\l_peek_token` against `\g_xfollow_exceptions_tl`
      \cs_new_protected:cpn { #1_check: }
        {
          % Loop over exceptions list
          \tl_map_inline:cn { g_#1_exceptions_tl }
            {
              \token_if_eq_charcode:NNT ####1 \l_peek_token
                {
                  % Tokens match; set flag and break from loop
                  \bool_set_true:c { l_#1_followed_bool }
                  \prg_map_break:
                }
            }
          % Conditional part
          \bool_if:cTF { l_#1_followed_bool }
            {#3}
            {#4}
        }

  }

\ExplSyntaxOff

\makexfollow{xfollow}{(always)}{(followed)}{(not followed)}
\addtoxfollowexceptions{x}
\newcommand*{\foo}{foo\xfollow}

\begin{document}

\foo \foo x

\end{document}

答案1

目前,xparse仅包含记录的函数以控制序列 ( \foo) 生成命令,而不是通过控制序列名称 ( foo)。这是故意的,因为目的是保持语法相对清晰:LaTeX-L 列表中对此进行了一些讨论。话虽如此,我们还没有详细探讨以您尝试的方式创建“用户定义”名称的必要性。因此,目前您需要使用

\exp_args:Nc \NewDocumentCommand { addto #1 exceptions  }

来实现你想要的。按名称生成是一个非常专业的事情(在整个文档命令集中),所以我们可能仍然只坚持使用这种方法。

但是,我认为对于你来说,这实际上是错误的方法。从你想要实现的目标的描述来看,你无法明确地生成文档命令。在我看来,你想要的是属于序言而不是文档的命令。同样,这是一个目前尚未完全决定的领域,但到目前为止,这些design level函数通常应该采用强制参数,因此可以使用\cs_new:Npn或类似方法直接生成。因此,我倾向于使用大小写混合的名称,并\cs_new_protected:cpn在这里使用

\cs_new_protected:cpn { AddTo \char_uppercase:N #1 Exceptions } ...

(我用来\char_uppercase:N改变首字母的大小写#1)。

正如我所说,这两个领域都需要进一步讨论。最好在 LaTeX-L 列表中进行讨论。


附注:我\use:c { ... }更倾向于\cs:w ... \cs_end:。一般来说,我们建议在可用的情况下使用“更高级别”的接口,并且任何 都:w通常只在必要时使用。

答案2

我强烈建议不要addto #1 exceptions按照你描述的方式或AddTo #1 ExceptionsJoseph 提倡的方式进行定义。最好将其#1作为参数传递给单个\AddToPeekExceptions命令。也就是说,我更喜欢语法

\documentclass{article}

\NewPeekCommand {\xfollow} {(always)} {(followed)} {(not followed)}
\AddToPeekExceptions {\xfollow} {x}
\newcommand*{\foo}{foo\xfollow}

\begin{document}

\foo \foo x

\end{document}

其中\NewPeekCommand仅定义一个控制序列,用于文档中(或定义其他命令)。

我尝试尽可能清晰地写出下面的实现。

  1. \NewPeekCommand\AddToPeekExceptions和的第一个参数\RemoveFromPeekExceptions以控制序列而不是字符的形式给出,因为它将在文档中以这种方式使用。这可以防止出现诸如\NewPeekCommand{123}尝试使用 之类的意外情况\123

  2. 因此,我对输入很挑剔:检查这些用户函数的第一个参数是否确实是一个单一的控制序列,否则就会引发错误。

  3. 对于三个用户级命令,我提供了一个代码级命令,它不测试其参数,供想要与此包交互的包编写者使用。这些命令的名称以 开头,\NPC_并已记录在案。

  4. \NPC_new_peek_command:Nnnn命令仅定义异常标记列表和命令本身。它不像您那样定义一堆帮助程序;相反,命令名称(以及(always)(followed)(not followed)标记)作为参数提供给单个辅助程序,\__NPC_check:NnTF负责查看下一个标记。

  5. 我保留了你使用 的诀窍\peek_catcode_ignore_spaces:NTF \c_space_token

  6. \__NPC_check:NnTF和辅助对象\__NPC_check_ii:NnTF以及内部变量\l__NPC_bool的名称包含__NPC,表明它们不能在该包之外使用。

  7. 您可以随意使用此代码进行任何操作(尤其是更改命令名称)。用它制作软件包应该不会太难。

    % \begin{documentation}
    %
    % This package provides \cs{NewPeekCommand}, \cs{AddToPeekExceptions},
    % and \cs{RemoveFromPeekExceptions}.
    %
    % ^^A Todo: add example of use.
    %
    % \section{Design-level commands}
    %
    % \begin{function}{\NewPeekCommand}
    %   \begin{syntax}
    %     \cs{NewPeekCommand} \marg{function} \marg{always} \marg{followed} \marg{not followed}
    %   \end{syntax}
    %   Defines the \meta{function} as a new 'peek' command, which looks at
    %   the following token.  If this token is part of the exception list
    %   (see \cs{AddToPeekExceptions}), use the \meta{always} and
    %   \meta{followed} tokens; otherwise use the \meta{always} and
    %   \meta{not followed} tokens.
    % \end{function}
    %
    % \begin{function}{\AddToPeekExceptions}
    %   \begin{syntax}
    %     \cs{AddToPeekExceptions} \marg{function} \marg{exceptions}
    %   \end{syntax}
    %   Adds every token in the \meta{exceptions} to the exception list for
    %   the \meta{function}.
    % \end{function}
    %
    % \begin{function}{\RemoveFromPeekExceptions}
    %   \begin{syntax}
    %     \cs{RemoveFromPeekExceptions} \marg{function} \marg{exceptions}
    %   \end{syntax}
    %   Removes every token in the \meta{exceptions} from the exception list
    %   for the \meta{function}.
    % \end{function}
    %
    % \section{Low-level analogs}
    %
    % \begin{function}{\NPC_new_peek_command:Nnnn}
    %   This command is used to implement \cs{NewPeekCommand}, without
    %   checking that the arguments take the correct form.
    % \end{function}
    %
    % \begin{function}{\NPC_add_to_peek_exceptions:Nn}
    %   This command is used to implement \cs{AddToPeekExceptions}, without
    %   checking that the arguments take the correct form.
    % \end{function}
    %
    % \begin{function}{\NPC_remove_from_peek_exceptions:Nn}
    %   This command is used to implement \cs{RemoveFromPeekExceptions},
    %   without checking that the arguments take the correct form.
    % \end{function}
    %
    % \end{documentation}
    %
    % \begin{implementation}
    %
    % First load \pkg{xparse} and turn on the \texttt{expl} syntax.  There
    % should probably be a \cs{ProvideExplPackage} in there.  See
    % \emph{e.g.}, \pkg{siunitx} for good practice.
    %    \begin{macrocode}
    \RequirePackage{xparse}
    \ExplSyntaxOn
    %    \end{macrocode}
    %
    % \section{User commands}
    %
    % \begin{macro}{\NewPeekCommand}
    %   We check that |#1| is a single control sequence.  First check that
    %   |#1| is a token list with exactly one token.  Then check that this
    %   token is a control sequence.  That test will fail if |#1| is a
    %   single space (but who cares).  If the input is correct (T branch),
    %   call \cs{NPC_new_peek_command:Nnnn}, otherwise raise an error and do
    %   nothing.
    %    \begin{macrocode}
    \NewDocumentCommand { \NewPeekCommand } {m m m m}
      {
        \bool_if:nTF { \tl_if_single_token_p:n {#1} && \token_if_cs_p:N {#1} }
          { \NPC_new_peek_command:Nnnn #1 {#2} {#3} {#4} }
          { \msg_error:nnnn { NPC } { cs-expected } { \NewPeekCommand } {#1} }
      }
    %    \end{macrocode}
    % \end{macro}
    %
    % \begin{macro}{\AddToPeekExceptions}
    %   We check that |#1| is a single control sequence.  If it isn't, raise
    %   an error.  Otherwise, there is still a need to check that the
    %   exception list exists, in other words that the command |#1| was
    %   declared with \cs{NewPeekCommand}.  If the input was correct, call
    %   \cs{NPC_add_to_peek_exceptions:Nn}.
    %    \begin{macrocode}
    \NewDocumentCommand { \AddToPeekExceptions } { m m }
      {
        \bool_if:nTF { \tl_if_single_token_p:n {#1} && \token_if_cs_p:N {#1} }
          {
            \tl_if_exist:cF { g_ \cs_to_str:N #1 _exceptions_tl }
              {
                \msg_error:nnnn { NPC } { undeclared-peek-command }
                  { \AddToPeekExceptions } {#1}
                \tl_new:c { g_ \cs_to_str:N #1 _exceptions_tl }
              }
            \NPC_add_to_peek_exceptions:Nn #1 {#2}
          }
          {
            \msg_error:nnnn { NPC } { cs-expected }
              { \AddToPeekExceptions } {#1}
          }
      }
    %    \end{macrocode}
    % \end{macro}
    %
    % \begin{macro}{\RemoveFromPeekExceptions}
    %   The error checking is the same as for \cs{AddToPeekExceptions}.
    %   Then call \cs{NPC_remove_from_peek_exceptions:Nn}.
    %    \begin{macrocode}
    \NewDocumentCommand { \RemoveFromPeekExceptions } { m m }
      {
        \bool_if:nTF { \tl_if_single_token_p:n {#1} && \token_if_cs_p:N {#1} }
          {
            \tl_if_exist:cF { g_ \cs_to_str:N #1 _exceptions_tl }
              {
                \msg_error:nnnn { NPC } { undeclared-peek-command }
                  { \RemoveFromPeekExceptions } {#1}
                \tl_new:c { g_ \cs_to_str:N #1 _exceptions_tl }
              }
            \NPC_remove_from_peek_exceptions:Nn #1 {#2}
          }
          {
            \msg_error:nnnn { NPC } { cs-expected }
              { \RemoveFromPeekExceptions } {#1}
          }
      }
    %    \end{macrocode}
    % \end{macro}
    %
    % \section{Messages}
    %
    % If the first argument of one of the user functions is not a single
    % control sequence, complain.
    %    \begin{macrocode}
    \msg_new:nnnn { NPC } { cs-expected }
      { The~first~argument~of~#1 must~be~a~control~sequence. }
      {
        The~command~#1 received~'#2'~as~its~first~argument,~instead~
        of~a~single~control~sequence~such~as~\token_to_str:N \xspace.
      }
    %    \end{macrocode}
    %
    % If the first argument of \cs{AddToPeekExceptions} or
    % \cs{RemoveFromPeekExceptions} is an unknown control sequence,
    % complain.
    %    \begin{macrocode}
    \msg_new:nnn { NPC } { undeclared-peek-command }
      {
        The~first~argument~of~#1 must~be~declared~with~
        \token_to_str:N \NewPeekCommand.
      }
    %    \end{macrocode}
    %
    % \section{Low-level}
    %
    % \begin{macro}{\NPC_add_to_peek_exceptions:Nn}
    %   Adds all the tokens in |#2| to the list of exceptions for the peek
    %   command |#1|.
    %    \begin{macrocode}
    \cs_new_protected:Npn \NPC_add_to_peek_exceptions:Nn #1#2
      { \tl_gput_right:cn { g_ \cs_to_str:N #1 _exceptions_tl } { #2 } }
    %    \end{macrocode}
    % \end{macro}
    %
    % \begin{macro}{\NPC_remove_from_peek_exceptions:Nn}
    %   Removes each the tokens in |#2| from the list of exceptions for the
    %   peek command |#1|.  Allowing more than one token in |#2| requires
    %   putting a loop there.
    %    \begin{macrocode}
    \cs_new_protected:Npn \NPC_remove_from_peek_exceptions:Nn #1#2
      {
        \tl_map_inline:nn {#2}
          { \tl_gremove_all:cn { g_ \cs_to_str:N #1 _exceptions_tl } { ##1 } }
      }
    %    \end{macrocode}
    % \end{macro}
    %
    % \begin{macro}{\NPC_new_peek_command:Nnnn}
    %   Declare a token list to hold the exceptions for the command |#1|,
    %   then declare |#1| which simply calls the internal
    %   \cs{__NPC_check:NnTF} with the appropriate arguments.
    %    \begin{macrocode}
    \cs_new_protected:Npn \NPC_new_peek_command:Nnnn #1#2#3#4
      {
        \tl_new:c { g_ \cs_to_str:N #1 _exceptions_tl }
        \cs_new_protected_nopar:Npn #1
          { \__NPC_check:NnTF #1 {#2} {#3} {#4} }
      }
    %    \end{macrocode}
    % \end{macro}
    %
    % \begin{macro}[aux]{\__NPC_check:NnTF}
    %   Peek ahead, ignoring spaces.  This is a clever hack: we’re actually
    %   checking whether the next non-space token is a space token; if not,
    %   call \cs{__NPC_check_ii:NnTF}.  Of course, by definition this will
    %   always test false.
    %    \begin{macrocode}
    \cs_new_protected:Npn \__NPC_check:NnTF #1#2#3#4
      {
        \peek_catcode_ignore_spaces:NF \c_space_token
          { \__NPC_check_ii:NnTF #1 {#2} {#3} {#4} }
      }
    %    \end{macrocode}
    % \end{macro}
    %
    % \begin{macro}[aux]{\__NPC_check_ii:NnTF}
    %   Now \cs{l_peek_token} is set to the following token, and we wish to
    %   compare it with the exceptions for |#1|.  Loop over the exception
    %   list, comparing the character code of each exception with
    %   \cs{l_peek_token}, and if there is a match, turn on the boolean and
    %   break the map.  After the loop, leave either the \meta{always} and
    %   \meta{followed} tokens, or the \meta{always} and \meta{not followed}
    %   tokens in the input stream.
    %    \begin{macrocode}
    \cs_new_protected:Npn \__NPC_check_ii:NnTF #1#2#3#4
      {
        \bool_set_false:N \l__NPC_bool
        \tl_map_inline:cn { g_\cs_to_str:N #1 _exceptions_tl }
          {
            \token_if_eq_charcode:NNT ##1 \l_peek_token
              {
                \bool_set_true:N \l__NPC_bool
                \tl_map_break:
              }
          }
        \bool_if:NTF \l__NPC_bool { #2 #3 } { #2 #4 }
      }
    %    \end{macrocode}
    % \end{macro}
    %
    % \begin{variable}{\l__NPC_bool}
    %   This boolean is used to keep track of whether the \cs{l_peek_token}
    %   appears in the exception list or not.
    %    \begin{macrocode}
    \bool_new:N \l__NPC_bool
    %    \end{macrocode}
    % \end{variable}
    %
    % \end{implementation}
    %
    
    
    
    
    
    \ExplSyntaxOff
    
    \documentclass{article}
    
    \NewPeekCommand {\xfollow} {(always)} {(followed)} {(not followed)}
    \AddToPeekExceptions {\xfollow} {x}
    \newcommand*{\foo}{foo\xfollow}
    
    \begin{document}
    
    \foo \foo x
    
    \end{document}
    

答案3

只需使用\cs_new_protected:cpn

\documentclass{article}

\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\makexfollow}{m m m m}
  {
    % Token list for exceptions
    \tl_new:c { g_#1_exceptions_tl }

    % Add to list
    \cs_new_protected:cpn { addto #1 exceptions } ##1
      {
        \tl_gput_right:cn { g_#1_exceptions_tl } { ##1 }
      }

    % Remove from list
    \cs_new_protected:cpn { removefrom #1 exceptions} ##1
      {
        \tl_gremove_all:cn { g_#1_exceptions_tl } { ##1 }
      }

    \cs_new_protected:cpn { #1 }
      {
        % Unconditional part
        #2

        % Is next token one of the exceptions
        \bool_set_false:c { l_#1_followed_bool }

        % Peek ahead, ignoring spaces, then hand-off to `\xfollow_check:`
        % with `\l_peek_token` set.
        %
        % This is a clever hack: We're actually checking whether the next
        % non-space token is a space token; if not, call `\xfollow_check:`.
        % Of course, by definition this will always test false, achieving
        % the result described above.
        \peek_catcode_ignore_spaces:NF \c_space_token
          { \cs:w #1_check: \cs_end: }
      }

      % Compare `\l_peek_token` against `\g_xfollow_exceptions_tl`
      \cs_new_protected:cpn { #1_check: }
        {
          % Loop over exceptions list
          \tl_map_inline:cn { g_#1_exceptions_tl }
            {
              \token_if_eq_charcode:NNT ####1 \l_peek_token
                {
                  % Tokens match; set flag and break from loop
                  \bool_set_true:c { l_#1_followed_bool }
                  \prg_map_break:
                }
            }
          % Conditional part
          \bool_if:cTF { l_#1_followed_bool }
            {#3}
            {#4}
        }

  }

\ExplSyntaxOff

\makexfollow{xfollow}{(always)}{(followed)}{(not followed)}
\addtoxfollowexceptions{x}

\show\xfollow
\show\removefromxfollowexceptions

\newcommand*{\foo}{foo\xfollow}

\begin{document}

\foo \foo x

\end{document}

相关内容