较新的新答案

较新的新答案

如何在 LaTeX 2(或 2epsilon)中将宏展开两次。我知道这个问题在 expl3 中两次扩展的首选方法是什么?,但我很好奇你如何在 LaTeX 中做到这一点。

我想指出的是,我怀疑如果你真的需要在代码中执行此操作,你可能需要重新制定代码,而不是找到执行此操作的方法。但是,出于教育目的,我仍然很好奇。

以下是我的(失败的)尝试

\documentclass{article}
\makeatletter
% Command for printing stuff to the error log
\def\inspect#1{\@latex@warning{\string#1:\meaning#1}}
\makeatother
\def\a{3}
\def\b{2\a}
\def\c{1\b}
% I want to obtain a macro containing "12\a" from using only \c
% Expand once
\edef\expandedOnce{\unexpanded\expandafter{\c}}
\inspect\expandedOnce
%% ^ produces 1\b in the error log
\expandafter\expandafter\edef\expandafter\expandedTwice{\unexpanded\expandafter{\expandedOnce}}
\inspect\expandedTwice
%% ^ Also produces 1\b in the error log, but I'd want it to
%% produce 12\a
\begin{document}
~
\end{document}

答案1

较新的新答案

下面的代码会将每个可扩展标记展开一次,如果该标记需要参数,则会提供参数(不适用于带分隔符的参数,如\def\foo#1.{#1})。请注意,这不一定是 TeX 展开事物的方式。我创建它主要是因为我很好奇如何做到这一点。

\documentclass[]{article}

\newcommand\foo{\bazA}
\newcommand\bazA{\bazB}
\newcommand\bazB{bar}

\def\a{3}
\def\b{2\a}
\makeatletter
\def\c{1\b 9}
\makeatother

\def\afterfi#1\fi{\fi#1}
\def\afterelsefi#1\else#2\fi{\fi#1}
\def\afterorfi#1\or#2\fi{\fi#1}
\def\afterfiB\fi#1#2{\fi#2}
\def\afterelsefiA\else#1\fi#2#3{\fi#2}
\makeatletter
\newcommand\ifempty[1]%>>>
  {%
    \if\relax\detokenize{#1}\relax
      \afterelsefiA
    \else
      \afterfiB
    \fi
  }%<<<
\newcommand\ifdigit[1]%>>>
  {%
    \ifx1#1\afterelsefiA
    \else\ifdigit@b2#1%
    \else\ifdigit@b3#1%
    \else\ifdigit@b4#1%
    \else\ifdigit@b5#1%
    \else\ifdigit@b6#1%
    \else\ifdigit@b7#1%
    \else\ifdigit@b8#1%
    \else\ifdigit@b9#1%
    \else\ifdigit@b0#1%
    \else\afterfiB
    \fi
  }%<<<
\newcommand\ifdigit@b[2]%>>>
  {%
    \fi\ifx#1#2\afterelsefiA
  }%<<<
\def\q@stop{\q@stopError}
\def\q@mark{\q@markError}
\long\def\expandingloop@a#1#%>>>
  {%
    \expandingloop@b#1\q@stop
  }%<<<
\long\def\expandingloop@b#1%>>>
  {%
    \ifx\q@stop#1%
      \afterelsefi\expandingloop@c
    \else
      \afterfi\expandingloop@d#1%
    \fi
  }%<<<
\newcommand\expandingloop@c[1]%>>>
  {%
    \ifempty{#1}
      {{}\expandingloop@a}
      {%
        \ifx\q@stop#1%
        \else
          \afterfi{\expandingloop@a#1{\q@stop}}\expandingloop@a%
        \fi
      }%
  }%<<<
\newcommand\expandingloop@d[1]%>>>
  {%
    \ifcase\testargs{#1}
      \afterorfi\unexpanded\expandafter{#1}\expandingloop@b%
    \or\afterorfi\expandingloop@d@i{#1}%
    \or\afterorfi\expandingloop@d@ii{#1}%
    \or\afterorfi\expandingloop@d@iii{#1}%
    \or\afterorfi\expandingloop@d@iv{#1}%
    \or\afterorfi\expandingloop@d@v{#1}%
    \or\afterorfi\expandingloop@d@vi{#1}%
    \or\afterorfi\expandingloop@d@vii{#1}%
    \or\afterorfi\expandingloop@d@viii{#1}%
    \or\afterfi\expandingloop@d@ix{#1}%
    \or\DelimitedArgumentError
    \fi
  }%<<<
\newcommand\expandingloop@d@group[3]%>>>
  {%
    \ifx\q@stop#3%
      \OutOfArgumentsError
    \else
      \afterfi\expandingloop@d@group@a{#1}{#2{#3}}%
    \fi
  }%<<<
\def\expandingloop@d@group@a#1#2#3#%>>>
  {%
    #1{#2}#3\q@stop
  }%<<<
\newcommand\expandingloop@d@exec[1]%>>>
  {%
    \unexpanded\expandafter{#1}\expandingloop@b
  }%<<<
\newcommand\expandingloop@d@i[2]%>>>
  {%
    \ifx\q@stop#2%
      \afterelsefi\expandingloop@d@group{\expandingloop@d@exec}{#1}%
    \else
      \afterfi\expandingloop@d@exec{#1#2}%
    \fi
  }%<<<
\newcommand\def@expandingloop@d@[2]%>>>
  {%
    \expandafter\edef\csname expandingloop@d@#1\endcsname##1##2%
      {%
        \unexpanded{\ifx\q@stop}##2%
          \unexpanded{\afterelsefi\expandingloop@d@group}%
          \expandafter\noexpand\csname expandingloop@d@#2\endcsname{##1}%
        \unexpanded{\else
          \afterfi}%
          \expandafter\noexpand\csname expandingloop@d@#2\endcsname{##1##2}%
        \noexpand\fi
      }%
  }%<<<
\def@expandingloop@d@{ix}{viii}
\def@expandingloop@d@{viii}{vii}
\def@expandingloop@d@{vii}{vi}
\def@expandingloop@d@{vi}{v}
\def@expandingloop@d@{v}{iv}
\def@expandingloop@d@{iv}{iii}
\def@expandingloop@d@{iii}{ii}
\def@expandingloop@d@{ii}{i}
\newcommand\singleallexpand[1]%>>>
  {%
    \edef#1{\expandafter\expandingloop@a#1{\q@stop}}%
  }%<<<
\newcommand\testargs[1]%>>>
  {%
    \expandafter\testargs@a\meaning#1->\q@mark\q@stop%
  }%<<<
\long\def\testargs@a#1->#2#3\q@stop%>>>
  {%
    \ifx\q@mark#2%
      \afterelsefi0%
    \else
      \afterfi\testargs@b#1\q@stop
    \fi
  }%<<<
\long\def\testargs@b#1:#2\q@stop%>>>
  {%
    \ifempty{#2}
      {0}
      {\testargs@c#2\q@stop}%
  }%<<<
\begingroup
\catcode`\#=12
\def\zz{\endgroup\def\myhashtag{#}}
\zz
\long\edef\testargs@c#1#2#3\q@stop%>>>
  {%
    \noexpand\ifx\myhashtag#1%
      \noexpand\ifdigit{#2}
        {%
          \noexpand\ifempty{#3}
            {\noexpand\afterelsefi#2}
            {\noexpand\afterelsefi\noexpand\testargs@c#3\noexpand\q@stop}%
        }
        {10}% macros don't take >9 arguments so this is a great error flag
    \noexpand\else
      10% macros don't take >9 arguments so this is a great error flag
    \noexpand\fi
  }%<<<
\makeatother

\newcommand\Meaning[1]{\texttt{\meaning#1}}

\begin{document}
\noindent
For \verb|\foo|:

\let\tmp\foo
Unexpanded:
\Meaning\tmp

\singleallexpand\tmp
Expanded once:
\Meaning\tmp

\singleallexpand\tmp
Expanded twice:
\Meaning\tmp

\noindent
For \verb|\c|:

\let\tmp\c
Unexpanded:
\Meaning\tmp

\singleallexpand\tmp
Expanded once:
\Meaning\tmp

\singleallexpand\tmp
Expanded twice:
\Meaning\tmp
\end{document}

新答案

下面\tmp以更可靠的方式扩展了每个标记(我希望这是您想要的)。不过,我没有彻底测试过。它只适用于不带参数的内容。

\documentclass[]{article}

\newcommand\foo{\bazA}
\newcommand\bazA{\bazB}
\newcommand\bazB{bar}

\def\a{3}
\def\b{2\a}
\def\c{1{\b}}

\def\afterfi#1\fi{\fi#1}
\makeatletter
\def\q@stop{\q@stop}
\def\expandingloop@a#1#%
  {%
    \expandingloop@b#1\q@stop
    \expandingloop@c
  }
\def\expandingloop@b#1%
  {%
    \ifx\q@stop#1%
    \else
      \afterfi\unexpanded\expandafter{#1}\expandingloop@b
    \fi
  }
\newcommand\expandingloop@c[1]
  {%
    \ifx\q@stop#1%
    \else
      \afterfi{\expandingloop@a#1{\q@stop}}\expandingloop@a%
    \fi
  }
\newcommand\singleallexpand[1]
  {%
    \edef#1{\expandafter\expandingloop@a#1{\q@stop}}%
  }
\makeatother

\newcommand\Meaning[1]{\texttt{\meaning#1}}


\begin{document}
\noindent
For \verb|\foo|:

\let\tmp\foo
Unexpanded:
\Meaning\tmp

\singleallexpand\tmp
Expanded once:
\Meaning\tmp

\singleallexpand\tmp
Expanded twice:
\Meaning\tmp

\noindent
For \verb|\c|:

\let\tmp\c
Unexpanded:
\Meaning\tmp

\singleallexpand\tmp
Expanded once:
\Meaning\tmp

\singleallexpand\tmp
Expanded twice:
\Meaning\tmp
\end{document}

在此处输入图片描述


旧答案

以下两个都仅扩展第一个标记。

您可以使用\edef\fooA{\unexpanded\expandafter\expandafter\expandafter{\fooB}}定义\fooA为与\fooB扩展两次的 a 相同。

使用临时宏可以做类似下面的事情。

\documentclass[]{article}

\newcommand\foo{\bazA}
\newcommand\bazA{\bazB}
\newcommand\bazB{bar}

\newcommand\singleexpand[1]
  {%
    \edef#1{\unexpanded\expandafter\expandafter\expandafter{#1}}%
  }

\newcommand\Meaning[1]{\texttt{\meaning#1}}


\begin{document}
\let\tmp\foo
Unexpanded:
\Meaning\tmp

\singleexpand\tmp
Expanded once:
\Meaning\tmp

\singleexpand\tmp
Expanded twice:
\Meaning\tmp
\end{document}

当参数包含一个组时是邪恶的(但它在最小的例子中起作用,其他一切都是添加无数个测试的问题):

\def\afterfi#1\fi{\fi#1}
\def\expandingloop#1#2\endexpandingloop
  {%
    \unexpanded\expandafter{#1}%
    \if\relax\detokenize{#2}\relax
    \else
      \afterfi\expandingloop#2\endexpandingloop
    \fi
  }
\newcommand\singleallexpand[1]
  {%
    \edef#1{\expandafter\expandingloop#1\endexpandingloop}%
  }

答案2

我明白了

> \zz=macro:
->12\a 12\a .

在 etex (或 pdftex)的终端上

\def\a{3}
\def\b{2\a}
\def\c{1\b1\b}
\def\afterfi#1\fi{\fi#1}
\def\foo#1{\ifx\relax#1\else\afterfi\expandafter\unexpanded\expandafter{#1}\foo\fi}
\edef\zz{\expandafter\foo\c\relax}

\show\zz

\bye

请注意,据我所知,这是您要求的扩展顺序,但不是 TeX 通常使用的顺序,因此它实际上并不是“扩展两次”

考虑

\def\a{\b} \def\b#1{}  \def\c{zzzzz}
\def\z{\a\c}

根据您的定义,我认为您想要在第一步进行扩展\a,然后获得“第一次扩展”,然后在第二步进行扩展以获得。\c\z\b zzzzz\bzzzz

然而 TeX 会在每个阶段完全扩展第一个标记,因此在第一步中得到,\b\c然后在第二步中得到一个空列表。TeX\c根本不会扩展它。

答案3

我认为一个可靠的算法可以扩展从随意的最多 k 次的标记集是不可能的:

最多扩展一次就已经有问题了:

你需要一个递归迭代的算法⟨尚未扩展的标记列表⟩并保持⟨已扩展令牌列表⟩如下:


步骤1:

检查⟨尚未扩展的标记列表⟩是空的。

如果是的话:交付⟨已扩展令牌列表⟩

如果不是这样:

  • 如果第一个元素/第一个标记⟨尚未扩展的标记列表⟩不可扩展,请将其从⟨尚未扩展的标记列表⟩并将其添加到⟨已扩展令牌列表⟩

    如果第一个元素/第一个标记 ⟨尚未扩展的标记列表⟩是可扩展的,则对其进行扩展,并从中删除扩展的结果 ⟨尚未扩展的标记列表⟩并将扩展的结果添加到⟨已扩展令牌列表⟩

  • 重复步骤1。

这里的一个关键点是“从⟨尚未扩展的标记列表⟩并将扩展的结果添加到⟨已扩展令牌列表⟩“。

这一点至关重要,因为它意味着扩展后你需要检测哪些 token 属于扩展的结果,哪些 token 已经在扩展中了。⟨尚未扩展的标记列表⟩在扩张发生之前。

假设,例如,一个宏定义如下:\def\macro#1#2#3{\A\B\C}
,而⟨尚未扩展的标记列表⟩包含以下内容:
\macro \A\B\C Whatsoever

现在你需要\macro扩展一次。之后,⟨尚未扩展的标记列表⟩包含以下内容:
\A\B\C Whatsoever

现在你需要将结果从扩展\macro移向⟨已扩展令牌列表⟩

在这种情况下,扩展的结果\macro是由标记形成的\A\B\C

如何检测这些\A\B\C是替换文本/扩展的扩展结果,因此与扩展之前的文本\macro不同\A\B\C⟨尚未扩展的标记列表⟩

(解析每个可扩展标记的结果\meaning以查明它是否是处理参数的宏并不能提供有关宏的参数文本的可靠信息,因为\meaning您没有关于标记类别代码的可靠信息/关于一组传递的字符是否表示一组字符标记(无论类别代码如何)或控制序列......)

另一个关键点是您需要“按标记”进行迭代,而宏通常“按参数”工作。


在特殊情况下,如果您已经了解扩展提供的定义和标记,则可以执行以下操作:

\def\a{3}
\def\b{2\a}
\def\c{1\b}
% You wish to obtain a macro containing "12\a" from using only \c


% \expandaf-% \expandaf-%
% ter-      % ter-      %
% chain 1   % chain 2   %
%    |      %    |      %
\expandafter\expandafter
\expandafter            \def
\expandafter\expandafter
\expandafter            \expandedTwice
\expandafter\expandafter
\expandafter            {%
\expandafter\expandafter
\c}

\expandafter-chain 1 交付:

% \expandaf-%
% ter-      %
% chain 2   %
%    |      %
\expandafter
            \def
\expandafter
            \expandedTwice
\expandafter
            {%
\expandafter
1\b}

\expandafter-chain 2 交付:

\def
\expandedTwice
{%
12\a}

(如果您不熟悉\expandafter

\expandafter是一个可扩展的原语,它对下一个和下一个倒数第二个标记起作用:

如果下一个标记是可扩展的,则的替换文本 将为。
\expandafter⟨next token⟩⟨next but one token⟩

⟨next token⟩⟨top-level-expansion of next but one token⟩

如果下一个标记不可扩展,则 的替换文本 将为。
\expandafter⟨next token⟩⟨next but one token⟩

⟨next token⟩⟨next but one token⟩

换句话说:(La)TeX 认为-work 已完成,并在顶层扩展下一个标记完成后从标记流中\expandafter删除。 这就是为什么你可以使用-chains 来“跳过”不应扩展的标记。\expandafter
\expandafter

例如,如果你有\def\foo{bar},并且执行
\expandafter 1\expandafter 2\expandafter 3\foo
,你将获得
123bar

执行第一项\expandafter会导致执行第二项\expandafter。因此,“执行第二项\expandafter”被视为执行第一项的一个方面\expandafter
执行第二项\expandafter又会导致执行第三项\expandafter。因此,“执行第三项\expandafter”被视为执行第二项的一个方面\expandafter。第三项\expandafter导致\foo顶层扩展。
当的顶层扩展\foo被传递时,(La)TeX 将认为第三项的扩展工作\expandafter已完成。
此扩展工作由第二个发起\expndafter。由于第二个发起的扩展工作\expandafter已经完成,因此现在第二个的扩展工作也\expandafter完成了。第二个的扩展工作\expandafter由第一个发起\expandafter
由于第一个发起的扩展工作\expandafter已经完成,因此现在第一个的扩展工作也\expandafter完成了。)


但如果你有,例如,

\catcode`\(=1 %
\catcode`\)=2 %
\def\a{3}
\def\b{2\a}
\def\c{11111(1)11111\b}

,并希望获得11111(1)111112\a— 括号仍然是 catcode 1 / 2 — 从 传出\c,这可能是一个有趣的任务。


顺便说一句 1:获得扩展结果的方法选择高度依赖于上下文:

在“纯扩展上下文”中,即,在\csname..\endcsname或在\write{...}或在内,\edef{..}您不能让 LaTeX 定义临时宏/您不能让 LaTeX 执行任何分配(在构造的控制序列标记未定义的情况下,\csname..\endcsname本地分配 -primitive 的含义的结果除外\relax)。

顺便说一句 2: \edef/\xdef并非在所有情况下都可靠。
例如,看看:

\edef\test{%
  Where does the assignment end? Here? \iffalse{\fi}%
  {\iffalse}\fi Or here?%
}%
\par
\meaning\test

答案4

我相当确定以下内容将对原始问题中描述的宏进行扩展:(事后看来,使用分号作为终止字符可能不是最佳设计选择)。也许我错过了为什么这不起作用的原因?

\documentclass{article}
\makeatletter
\def\a{3}
\def\b{2\a}
\def\c{1\b}
\def\@iterator{%
    % Expects to be followed by a list of tokens terminated
    % by a semicolon. If the next character is not a semicolon
    % then consider it a token to expand.
    % If the next character is a semicolon then we're done iterating
    % and can finalising procedures
    \@ifnextchar;\@finishIter\@processnext%
}
\newcommand\tmp@exptok{}
\def\@processnext#1{%
    % Proces one token and add it to our macro containing
    % \tmp@exptok contains all the tokens expanded once.
    % So we're now adding this next token.
    \xdef\tmp@exptok{\unexpanded\expandafter{\tmp@exptok}\unexpanded\expandafter{#1}}\@iterator%
}
\def\@finishIter;{\global\let\expandedResult\tmp@exptok\gdef\tmp@exptok{}}
\newcommand\expandtwice[2]{%
    % First do one expansion
    \@iterator#1;%
    % Now all tokens are expanded once and contained in \expandedResult
    % Now we reapply it to re-expand every token once again
    \expandafter\@iterator\expandedResult;%
    % Now store the result in the macro given by the user
    \let#2\expandedResult%
}
\makeatother
\expandtwice{\c\c}{\cTwiceExpanded}
% \c\c -> {1\b}{1\b} -> 1{2\a}1{2\a}
\begin{document}
    \texttt{\meaning\cTwiceExpanded}%<- now contains 12\a12\a
\end{document}

编辑:当然,也存在一些特殊情况,例如无法扩展的宏,\textbf这将失败(但可以通过\protect在扩展后查找来修复)。此外,如果您使用组括号({}),则会出现问题。后者可能有一个解决方案,即检查下一个字符\bgroup或类似的东西。

编辑2:解决分组问题:

\def\@itergroup{%
    \expandafter\@iterator\@firstofone
}
\def\@iterator{%
    \@ifnextchar\bgroup{\@itergroup}{\@ifnextchar;\@finishIter\@processnext}%
}

此外,作为 OP,我会诚实地说我还没有花时间去了解其他提供的大多数答案是如何工作的,但我会在不久的将来这样做并选择一个答案。

相关内容