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
这又是一个可扩展的标记,因此它被扩展:的当前定义\tempa
是bla
,因此
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
,它是不可扩展的,因此执行在胃里……嗯,我认为现在错误的原因已经非常清楚了。
(深呼吸。)