问题 1:

问题 1:

我想定义一个命令,我们称之为\commander,用于定义新命令的带星号和不带星号的版本。\commander将按如下方式调用:

\commander[<options>]{\thenewcommand}

这将创建命令\thenewcommand\thenewcommand*,但指令略有不同。(这些命令的具体定义对于这个问题并不重要。我已经解决了。)我尝试定义的方式\commander取决于一个名为 的单参数命令\commandname,它返回命令名称的字符串,但不返回反斜杠。例如:

  1. \commandname{\empty}返回字符串empty
  2. \commandname{\color}返回字符串color
  3. \commandname{\thenewcommand}返回字符串thenewcommand
  4. 等等...

我不认为 有什么问题,\commandname因为在我尝试定义 之前它一直运行良好\commander。但为了以防万一,这里是它的定义:

\newcommand{\commandname}[1]{%
  \ifcat\relax\noexpand#1%
  \expandafter\expandafter\expandafter%
  \@gobble\expandafter\string\fi#1}

(这是由阿肖克

\commandname欢迎任何改进或简化此问题的建议。

\commander定义如下:

\newcommand{\commander}[2][]{%
  \expandafter\newcommand{#2}{%
    \@ifstar%
      \csname star\commandname{#2}\endcsname%
      \csname nostar\commandname{#2}\endcsname}%
  %
  %DEFINE THE #2 COMMAND
  \expandafter\newcommand{\csname nostar\commandname{#2}\endcsname}%
    {<code for #2>}
  %
  %DEFINE THE #2* COMMAND
  \expandafter\newcommand{\csname star\commandname{#2}\endcsname}%
    {<code for #2*>}%
}

但每当我尝试调用时 \commander,它都会返回以下错误消息:

Command \csname already defined. \commander{\thenewcommand}
Extra \endcsname. \commander{\thenewcommand}

我以前曾使用过类似的结构来递归定义命令,所以我想问题在于同时使用\@ifstar\csname

我强烈希望解决方案尽可能地原始 TeX,因为我想在多个项目中使用它。不过,在我当前的项目中,我使用的是软件包everypagepgf/tikz。因此,依赖于这些软件包的解决方案也是受欢迎的。

在此先感谢您的帮助。

答案1

您不需要提取不带反斜杠的名称\csname。您可以使用类似以下内容:

\documentclass[]{article}

\makeatletter
\newcommand\commander[2][]
  {%
    \@ifdefinable#2%
      {%
        \protected\edef#2%
          {%
            \noexpand\@ifstar
              \expandafter\noexpand\csname\string#2star\endcsname
              \expandafter\noexpand\csname\string#2nostar\endcsname
          }%
        \expandafter\newcommand\csname\string#2star\endcsname{.star.}%
        \expandafter\newcommand\csname\string#2nostar\endcsname{no.star.}%
      }%
  }
\makeatother

\commander\thecommand

\begin{document}
\thecommand

\thecommand*
\end{document}

答案2

  \expandafter\newcommand{\csname nostar\commandname{#2}\endcsname}%

适用\expandafter{不可扩展的令牌。

你的意图

  \expandafter\newcommand\csname nostar\commandname{#2}\endcsname

适用\expandafter\csname

答案3

\newcommand{\commandname}[1]{%
  \ifcat\relax\noexpand#1%
  \expandafter\expandafter\expandafter%
  \@gobble\expandafter\string\fi#1}

\commandname欢迎任何改进或简化此问题的建议。

你问了,那我们就挑剔一下吧:;-)

您的日常工作\commandname似乎旨在做以下事情:

检测参数是否由单个 组成⟨control sequence token⟩
如果是,则应用\string并从结果中删除前导反斜杠,以获得可以“喂给” \csname..的东西\endcsname
如果不是,则获取形成可以“喂给” \csname..的东西的参数的标记序列\endcsname

问题 1:

有一些边缘情况:

如何处理这样的情况:例如,有人希望\SantaClaus从宏\Santa和宏中定义一个宏\CLaus,而\Santa被定义为\def\Santa{Santa}并且\Claus被定义为\def\Claus{Claus}

在这种情况下,人们可以这样做\expandafter\edef\csname\Santa\Claus\endcsname{\Santa\noexpand\nobreak\ \Claus}

这里的关键点是:你可以将宏“喂给” \csname。当 (La)TeX 搜索匹配的 时,可扩展标记将被扩展\endcsname

让我们看一下\commandname{\Santa\Claus}——结果如下:

\ifcat\relax\noexpand\Santa\Claus
\expandafter\expandafter\expandafter
\@gobble\expandafter\string\fi\Santa\Claus

\Claus Santa\Claus

我想,这不是有意的。

问题 2:

\commandname控制序列如果是隐式字符标记,则可能会失败:

例如,尝试

\let\CmdCharA=A
\commandname{\CmdCharA}

问题是:

这里表示类别代码为11(字母)、字符代码为65的\CmdCharA字符标记的含义。A

因此\ifcat-check 将得出\CmdCharA的 catcode(即 11(字母))与\ifcat内部对 -primitive 的 catcode 所采取的 catcode不同\relax

因此,串化和吞并现象就不会发生。

问题 3:

在 TeX 中,有一个整数参数\escapechar,它表示 TeX 引擎内部编码中字符的代码点编号,如果\string提供的名称为 ,则应将其添加到前面⟨control sequence token⟩。通常的值为\escapechar92。92 是反斜杠字符的代码点编号,既包括 ASCII 编码,也包括 Unicode 编码,而传统 TeX 引擎的内部编码为 ASCII,而基于 XeTeX 或 LuaTeX 的 TeX 引擎的内部编码为 Unicode。如果 的值为负数\escapechar,则根本不会添加任何字符。如果 的值\escapechar大于 TeX 引擎内部编码中可能的最大代码点编号,则情况如下:使用传统 TeX 引擎时,不会添加任何字符。使用最新的基于 XeTeX 或 LuaTeX 的引擎时,不会添加任何字符。使用 luatex 1.10.0 之前的基于 LuaTeX 的引擎,您可能会得到意想不到的结果。(例如,请参阅我的问题\string-primitive 在 LuaTeX 中以意想不到的方式工作 — 在哪里可以找到有关 LuaTeX 中 \string-primitive 的确切文档? 以及大卫卡莱尔对该问题的回答。)

尝试例如,

\newlinechar=`^^J
\escapechar=92 %
\def\stringifyrelaxwithescapechar#1{%
  \message{^^J^^J}%
  \message{When \string\escapechar=\number#1, then (\string\string\string\relax) yields:}%
  \escapechar=#1\relax
  \message{(\string\relax)}%
  \escapechar=92 %
}%
\message{^^J^^J}%
\message{The great \string\string\space and \string\escapechar\space test:}%
\message{^^J}%
\message{=======================================}%
\stringifyrelaxwithescapechar{92}%
\stringifyrelaxwithescapechar{`\A}%
\stringifyrelaxwithescapechar{`\B}%
\stringifyrelaxwithescapechar{`\C}%
\stringifyrelaxwithescapechar{`\Z}%
\stringifyrelaxwithescapechar{`\7}%
\stringifyrelaxwithescapechar{`\/}%
\stringifyrelaxwithescapechar{`\?}%
\stringifyrelaxwithescapechar{`\:}%
\stringifyrelaxwithescapechar{-1}%
\stringifyrelaxwithescapechar{32}%
\message{^^J^^J}%
\csname stop\endcsname
\bye

顺便说一句:由于 而传递的字符标记\string通常具有类别代码 12(其他)。空格字符是个例外。如果\string传递了一个空格字符标记,该标记将具有类别代码 10(空格),即它将是一个空格标记。当 的值为\escapechar32 时,应用于\string会产生一个以空格标记为首的标记序列。您无法通过因为将处理/删除以下内容而删除⟨control sequence token⟩该空格标记\@gobble\@gobble非限定参数而 (La)TeX 在搜索属于非分隔参数的第一个标记时会默默丢弃空格标记。因此,在这种极端情况下,前导空格和随后的非空格字符将被删除。

总结:在应用时,\string您可能需要考虑的值来\escapechar决定是否需要删除前导反斜杠(或其他内容)。

我建议采取一种不同的方法:

由于该机制旨在定义宏,因此它也不需要在纯扩展上下文中工作。

因此,您可以临时将值 92(92 是 (La)TeX 内部编码中反斜杠字符的代码点编号)分配给\escapechar

除此之外,不要让机制尝试找出用户是否指定了⟨control sequence token⟩需要“输入”的标记序列\csname\endcsname而是让用户自己指定。

我有时会使用一个宏\name来处理由左花括号 ( ) 分隔的参数{和嵌套在括号中的参数。嵌套在括号中的参数被视为将通过..构造的
的名称。在宏定义的 中,您可以使用-notation 来表示宏,其最后一个参数将由 分隔,这将(与其他参数分隔符不同)保留在原处/重新插入:⟨control sequence token⟩\csname\endcsname⟨parameter text⟩#{{

\newcommand\exchange[2]{#2#1}%
\@ifdefinable\name{\long\def\name#1#{\romannumeral0\innername{#1}}}%
\newcommand\innername[2]{\expandafter\exchange\expandafter{\csname#2\endcsname}{ #1}}%

一些使用示例:

\name{foo}\foo
\name\string{foo}\string\foo
\name\meaing{foo}\meaning\foo
\name\global\long\def{foo}...\global\long\def\foo...
\name\newcommand*{foo}...\newcommand*\foo...
\name\name\global\let{foo}={bar}\name\global\let\foo={bar}\global\let\foo=\bar

把这些部分放在一起,假设命令的“星号”形式始终处理同一组参数/(除了星号之外)应具有⟨parameter text⟩与“非星号”形式相同的参数,我可能会做这样的事情:

\makeatletter

\newcommand\exchange[2]{#2#1}%
\@ifdefinable\name{\long\def\name#1#{\romannumeral0\innername{#1}}}%
\newcommand\innername[2]{\expandafter\exchange\expandafter{\csname#2\endcsname}{ #1}}%

% \myUnexpandableStringifier{<tokens to prepend after stringification>}%
%                           {<control sequence token to stringify>}%
% ->
% <tokens to prepend after stringification>{<stringified control sequence token without leading escapechar>}
\newcommand\myUnexpandableStringifier[2]{%
  \begingroup
  \escapechar=92 %
  \expandafter\expandafter\expandafter\exchange
  \expandafter\expandafter\expandafter{%
  \expandafter\expandafter\expandafter{%
  \expandafter\@gobble\string#2}}{\endgroup#1}%
}%
\@ifdefinable\commander{%
  \DeclareRobustCommand\commander[1]{%
     \myUnexpandableStringifier{\innercommander}{#1}%
  }%
}%
\@ifdefinable\innercommander{%
  \long\def\innercommander#1#2#{%
    % #1 = <name of control sequence>
    % #2 = <(same) parameter-text both for starred variant and non-starred variant>
    \name\name\name\innerinnercommander{#1}{#1atnostar}{#1atstar}{#2}%
  }%
}%
\newcommand\innerinnercommander[6]{%
   % #1 = <control sequence>
   % #2 = <control sequence at nostar>
   % #3 = <control sequence at star>
   % #4 = <(same) parameter-text both for starred variant and non-starred variant>
   % #5 = <<balanced text> of the <definition text> of the unstarred definition>
   % #6 = <<balanced text> of the <definition text> of the starred definition>
  \@ifdefinable{#1}{%
    \DeclareRobustCommand*#1{\@ifstar{#3}{#2}}%
    \newcommand#2#4{#5}%
    \newcommand#3#4{#6}%
  }%
}%

\commander\Foo[3][optional]{%
  This is Foo-without-star's optional argument: #1%
  This is Foo-without-star's first non-optional argument: #2%
  This is Foo-without-star's second non-optional argument: #3%
}{%
  This is Foo-with-star's optional argument: #1%
  This is Foo-with-star's first non-optional argument: #2%
  This is Foo-with-star's second non-optional argument: #3%
}%
\typeout{Meanings of macros related to \string\Foo}
\typeout{==================================}
\typeout{\string\Foo: \meaning\Foo}
\typeout{\name\string{Foo }: \name\meaning{Foo }}
\typeout{\string\Fooatnostar: \meaning\Fooatnostar}
\typeout{\name\string{\string\Fooatnostar}: \name\meaning{\string\Fooatnostar}}
\typeout{\string\Fooatstar: \meaning\Fooatstar}
\typeout{\name\string{\string\Fooatstar}: \name\meaning{\string\Fooatstar}}
\typeout{^^J}

\name\commander{Bar}[2]{%
  This is Bar-without-star's first non-optional argument: #1%
  This is Bar-without-star's second non-optional argument: #2%
}{%
  This is Bar-with-star's first non-optional argument: #1%
  This is Bar-with-star's second non-optional argument: #2%
}%

\typeout{Meanings of macros related to \string\Bar}
\typeout{==================================}
\typeout{\string\Bar: \meaning\Bar}
\typeout{\name\string{Bar }: \name\meaning{Bar }}
\typeout{\string\Baratnostar: \meaning\Baratnostar}
\typeout{\string\Baratstar: \meaning\Baratstar}
\typeout{^^J}

\stop

答案4

错误在于\csname...\endcsname之后的支撑\newcommand

但是,我认为最好还是\commander完全避免。如果你坚持这样做,你可以使用xparse

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\commander}{mmo}
 {% #1 = command to define, #2 = code for the main, #3 = code for *-version
  \IfNoValueTF{#3}
   {
    \NewDocumentCommand{#1}{}{#2}
   }
   {
    \NewDocumentCommand{#1}{s}
     {
      \IfBooleanTF{##1}{#3}{#2}
     }
   }
 }
\ExplSyntaxOff

\commander{\foo}{This is foo, it has no *-variant}
\commander{\baz}{This is baz, normal variant}[This is baz, *-variant]

\begin{document}

\foo

\baz

\baz*

\end{document}

在此处输入图片描述

相关内容