我想定义一个命令,我们称之为\commander
,用于定义新命令的带星号和不带星号的版本。\commander
将按如下方式调用:
\commander[<options>]{\thenewcommand}
这将创建命令\thenewcommand
和\thenewcommand*
,但指令略有不同。(这些命令的具体定义对于这个问题并不重要。我已经解决了。)我尝试定义的方式\commander
取决于一个名为 的单参数命令\commandname
,它返回命令名称的字符串,但不返回反斜杠。例如:
\commandname{\empty}
返回字符串empty
\commandname{\color}
返回字符串color
\commandname{\thenewcommand}
返回字符串thenewcommand
- 等等...
我不认为 有什么问题,\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,因为我想在多个项目中使用它。不过,在我当前的项目中,我使用的是软件包everypage
和pgf/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⟩
。通常的值为\escapechar
92。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(空格),即它将是一个空格标记。当 的值为\escapechar
32 时,应用于\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}