发生了什么\amacro{bla}

发生了什么\amacro{bla}

expl3我确信这个问题已经得到解答了,但是我似乎找不到任何能够在核心 TeX 原语中解释这个问题的答案,而且我也在标记级别上理解这个问题,例如,在我掌握 TeX 本身之前,我绝对不想进入这个话题。

我想要做的基本上只是能够编写能够以嵌套方式使用括号的宏,例如\amacro{\bmacro{bla}},首先计算结果\bmacro{bla}为例如la(没有第一个标记的参数 = \@cdr),然后将其插入\amacro,计算结果为l(仅参数的第一个标记 = \@car)。

\catcode`\@=11 %
\def\@car#1#2\@nil{#1}
\def\@cdr#1#2\@nil{#2}
\def\amacro#1{%
    \edef\tempa{#1}%
    \@car\tempa\@nil%
}
\def\bmacro#1{%
    \edef\tempa{#1}%
    \@cdr\tempa\@nil%
}
\catcode`\@=12 %

    a: \amacro{bla}\par
    b: \bmacro{bla}\par
    a(b): \amacro{\bmacro{bla}}

\bye

我尝试\edef确保在操作之前参数已经完全展开,但是虽然单个宏调用工作正常,但使用嵌套方式编译会失败

! Missing control sequence inserted.
<inserted text> 
                \inaccessible 
l.16 a(b): \amacro{\bmacro{bla}}

? 

这并不出乎意料,因为它有点像是嵌套\edef在同一个名字上,所以我可以很好地看到它以这种方式中断,但我并没有真正弄清楚\inaccessible在这种情况下它的含义以及它为什么会出现。

当然,重点是必须在 TeX 核心基元中,这是正确的做法,对吧?在对参数执行操作之前,必须有某种方法可以完全展开参数。

答案1

首先,你缺少的是\expandafter

\documentclass{article}

\makeatletter
\def\amacro#1{%
  \edef\tempa{#1}%
  \expandafter\@car\tempa\@nil 
}
\def\bmacro#1{%
  \edef\tempa{#1}%
  \expandafter\@cdr\tempa\@nil 
}
\makeatother

\begin{document}

a: \amacro{bla}

b: \bmacro{bla}

%    a(b): \amacro{\bmacro{bla}}

\end{document}

否则\@car就只会生产\temp\@cdr什么也不会产生。

不能嵌套\bmacro\amacro因为\edef不可扩展。

另一方面,LuaTeX 有这个功能\expanded(并且在不久的将来其他引擎也会有)。

% compile with lualatex
\documentclass{article}

\makeatletter
\def\amacro#1{%
  \expandafter\@car\expanded{#1}\@nil
}
\def\bmacro#1{%
  \expandafter\@cdr\expanded{#1}\@nil
}
\makeatother

\begin{document}

a: \amacro{bla}

b: \bmacro{bla}

a(b): \amacro{\bmacro{bla}}

\end{document}

在此处输入图片描述

答案2

基本上,我发布这个答案是因为它对于评论来说太长了。

照常,@egreg 的帖子 已经从本质上回答了你的问题。但是,我认为你很难理解扩张的确切含义,而且你倾向于将扩张与消化混淆(IE(在胃中执行)是两个截然不同的概念。这完全可以理解,因为 TeX 在这方面的行为确实很难理解,而且答案David Carlisle 链接的这个答案对解释这个微妙的话题很有帮助;不过,我认为逐步向您展示执行代码时究竟会发生什么也会有所帮助。这正是这个答案所做的。


发生了什么\amacro{bla}

代币

\amacro{bla}

被替换为

\edef\tempa{bla} \@car\tempa\@nil

然后 TeX 处理\edef:这是一个不可扩展的标记,因此它被转发到“胃”,在那里执行。由于替换文本中的标记都是不可扩展的(它们只是字符标记),这意味着 被 \tempa定义为扩展为bla。接下来,TeX 找到\@car,其定义为

\def\@car#1#2\@nil{#1}

具体来说,作为宏,\@car是可扩展的。 的第一个参数\@car是无界的,第二个参数由 界定\@nil;在我们的例子中, \tempa (!) 被视为第一个参数,第二个参数为空。请注意,\@nil也是扩展过程的一部分。扩展的结果是第一个参数,即\tempa。总之,

\@car\tempa\@nil

被替换为

\tempa

这又是一个可扩展的标记,因此它被扩展:的当前定义\tempabla,因此

bla

是扩展后标记流中剩余的内容。这些都是不可扩展的标记,因此它们被发送到胃中,在那里执行。最终结果是 TeX 排版“abc”(请注意,它已经处于水平模式)并且已进行临时定义。


发生了什么\bmacro{bla}

这与上一个案例非常相似,因此我将其留给你“练习”。最终结果是

\bmacro{bla}

被替换为空IE\tempa,它将从标记流中移除,但总是具有将定义扩展为的副作用bla


发生了什么\amacro{\bmacro{bla}}

当然,这是最有趣的情况。最初,token 流包含

\amacro{\bmacro{bla}}

根据 的定义\amacro,这被替换为

\edef\tempa{\bmacro{bla}} \@car\tempa\@nil

同样,TeX 处理的下一个东西是\edef,我再说一遍,它是不可膨胀的,因此它会进入胃部,在那里执行(未展开!):\tempa被定义为将展开为目前已满的扩展\bmacro{bla}。因此 TeX 开始充分展开\bmacro{bla};根据​​ 的当前定义\bmacro,第一步展开产生\edef\tempa{bla} \@cdr\tempa\@nil,TeX 依次查看这十个标记中的每一个,看它是否恰好可以展开。现在:

  • \edef无法扩展,因此保持不变;

  • \tempa, 另一方面,可扩展,所以(啊哈!)它根据其当前定义进行扩展,此时,它仍然是通过执行该行设置的定义,作为副作用

    b: \bmacro{bla}\\
    

    在源文件中找到,即bla;这三个标记是不可扩展的,因此它们保持原样;

  • 后面跟着一个{标记,但它不可扩展,因此被保留下来;

  • 类似地,接下来的四个标记,,,,b和被l“逐字”复制到替换文本中;a}

  • \@cdr是可扩展的,因此它被扩展:\tempa成为它的第一个参数,而的第二个参数为\@cdr空;\@nil也被吞噬作为参数分隔符;根据其定义, \@cdr被扩展为它的第二个参数,也就是说,扩展为空;

  • 此时,我们正在执行的定义的替换文本(回想一下,它是

    \edef\tempa{\bmacro{bla}}
    

    参见上文我们所说的“它是执行(未展开!)”)已完全展开,得到\edef bla{bla}

总之,\tempa被定义为一个宏,其替换文本是

\edef bla{bla}

(九个标记)。这就完成了执行(不是扩展)代码

\edef\tempa{\bmacro{bla}}

现在令牌流中剩下的是

\@car\tempa\@nil

\@car是可扩展的,因此它被扩展了(显然……):它的第一个参数是\tempa,第二个参数为空;扩展是第一个参数,即\tempa。总而言之,标记流中剩余的是

\tempa

(当然,在获取 where\@nil的参数时已被吞噬\@car )。这又是一个可扩展的标记,其当前扩展为

\edef bla{bla}

因为我们刚刚执行了定义。因此 TeX 处理\edef,它是不可扩展的,因此执行在胃里……嗯,我认为现在错误的原因已经非常清楚了。

(深呼吸。)

相关内容