我想学习一些 TeX 编程技术,以便可以随时应用它来处理相同类型的情况:
代码
\newtoks\mytokenregister
\def\five{\four5}
\def\four{\three4}
\def\three{\two3}
\def\two{\one2}
\def\one{1}
\def\tokenstomacro#1{%
\mytokenregister{#1}%
\edef\macro{\the\mytokenregister}%
}%
\tokenstomacro{\one#1\two#2\three#3\four#4\five}%
\show\macro
\csname stop\endcsname
\bye
加倍#
并定义:
> \macro=macro:
->\one ##1\two ##2\three ##3\four ##4\five .
l.12 \show\macro
问题是:
我怎样才能得到\tokenstomacro
一个#
并扩大事物因此定义
> \macro=macro:
->1##112##2123##31234##412345.
?
我知道如果我将参数中的哈希值加倍\tokenstomacro
并直接应用\edef
,即没有通过\the
令牌寄存器的扩展的中间步骤,那么我就会得到以下结果:
\def\five{\four5}
\def\four{\three4}
\def\three{\two3}
\def\two{\one2}
\def\one{1}
\def\tokenstomacro#1{%
\edef\macro{#1}%
}%
\tokenstomacro{\one##1\two##2\three##3\four##4\five}%
\show\macro
\csname stop\endcsname
\bye
但我希望获得这个结果,而不需要“手动”将参数中的哈希值加倍\tokenstomacro
。
我希望它能够在没有 Lua 扩展的情况下工作。
我还希望它能够在没有(未扩展的)写入(伪)文件和\input
读回该(伪)文件的情况下工作,因为在创建形成参数的标记和读回(伪)文件之间,\tokenstomacro
类别代码机制可能会发生变化……
答案1
使用“扩展”的 TeX 引擎(不是 Knuthian TeX),
\newtoks\mytokenregister
\def\five{\four5}
\def\four{\three4}
\def\three{\two3}
\def\two{\one2}
\def\one{1}
\def\tokenstomacro#1{%
\mytokenregister\expandafter{\expanded{#1}}%
\edef\macro{\the\mytokenregister}%
}
\tokenstomacro{\one#1\two#2\three#3\four#4\five}
\show\macro
\csname stop\endcsname
\bye
你得到
> \macro=macro:
->1##112##2123##31234##412345.
正如 Joseph Wright 所建议的,甚至不需要令牌寄存器,因为它可以\unexpanded
作为“未命名令牌寄存器”工作:
\def\five{\four5}
\def\four{\three4}
\def\three{\two3}
\def\two{\one2}
\def\one{1}
\def\tokenstomacro#1{%
\edef\macro{\unexpanded\expandafter{\expanded{#1}}}%
}
\tokenstomacro{\one#1\two#2\three#3\four#4\five}
\show\macro
\csname stop\endcsname
\bye
答案2
作为示例,您给出了:
\tokenstomacro{\one#1\two#2\three#3\four#4\five}%
→
\macro=macro:
->1##112##2123##31234##412345.
关于什么
\tokenstomacro{\string#}%
?
扩展/字符串化是否应在哈希值加倍之前进行?
扩展/字符串化是否应在哈希值加倍之后进行?
这很不一样。
在进一步的情况下,\macro
将扩展为类别代码 12(其他)的单个哈希。
在后一种情况下\macro
,将扩展为类别代码 12(其他)的单个哈希,后面跟着类别代码 6(参数)的单个哈希。
是因为它可能。
\edef
仅使用 Knuthian TeX无法实现自动扩展和哈希加倍的通用方法。
做出此声明的理由:
David Carlisle 提醒我注意这样一个事实:在各种特殊情况下,\edef
可以通过 Knuthian TeX 的板载资源进行 -expansion 和 hash-doubling:
对于那些特殊情况,其中的参数\tokenstomacro
仅包含显式哈希值,作为单个哈希值,后面跟着 1..9 范围内的数字,您可以通过\edef
定义一个临时宏,其中在的参数中出现的所有序列#1
, ...,都变成参数,..., : 由于事物将扩展到该临时宏的定义中。然后,您可以使用序列, ...,作为参数,将该临时宏扩展为令牌寄存器的赋值,然后——为了加倍显式哈希值——通过- -expansion从该的内容中定义:#9
\tokenstomacro
#1
#9
\edef
⟨balanced text⟩
{#1}
{#9}
⟨balanced text⟩
\edef
\the⟨token register⟩
\macro
⟨token register⟩
\def\five{\four5}
\def\four{\three4}
\def\three{\two3}
\def\two{\one2}
\def\one{1}
\def\tokenstomacro#1{%
\edef\macro##1##2##3##4##5##6##7##8##9{#1}%
\toks0\expandafter{\macro{##1}{##2}{##3}{##4}{##5}{##6}{##7}{##8}{##9}}%
\edef\macro{\the\toks0}%
}%
\tokenstomacro{\one#1\two#2\three#3\four#4\five}%
\show\macro
\csname stop\endcsname
\bye
(触发了 -assignment中\expandafter
临时定义的扩展。在临时定义的扩展过程中会发生两件事:\macro
\toks0
\macro
- 单个显式类别代码 6 字符标记后面跟着数字 1..9,因此表示
⟨balanced text⟩
临时定义中的参数,将被单个显式类别代码 6 哈希字符标记序列替换,这些标记后面跟着数字 1..9 之一,这些数字被收集为参数。这样,序列、、...
的位置就保留了下来。#1
#2
#9
- 如果的参数
\tokenstomacro
包含具有偶数个相邻哈希值的序列,则这些序列将进入⟨平衡文本⟩临时定义的,而不被视为表示参数的东西。因此,在扩展临时定义时,具有这些序列的哈希值数量将减半。
因此,扩展临时定义的结果是,人们无法区分作为临时定义的参数的序列#1
,... ,用于保留,...的位置,与由于序列的哈希值减半而产生的序列,... ,这使其成为的临时定义。#9
#1
#9
#1
#9
##1
##9
⟨balanced text⟩
\macro
因此,该方法不适合在最终定义中保留\macro
具有偶数个相邻哈希值的序列的位置,例如在 中####1
或在 中##1
。
该方法也不适用于保留序列的位置(例如#A
在的最终定义中)\macro
。
由于我已经在这里吹毛求疵了,我们还要提一下,使用此方法,任何单个显式 catcode-6(参数)字符标记后面跟着数字 1..9 之一,都将在定义范围内\macro
被类别代码 6(参数)的显式哈希字符标记替换。在通常的类别代码制度下,这并不重要,因为在通常的类别代码制度下,哈希是唯一一个类别代码为 6 的字符。)
但上述方法可以适用的特殊情况并不代表所有可以想到的情况。
我认为,例如
\tokenstomacro{\one##1\two##2\three#1\four#2\five}%
还应考虑并得出:
\macro=macro:
->1####112####2123##11234##212345.
使用 Knuthian TeX 时,哈希值加倍的特殊情况是:
将哈希标记写入屏幕或外部文件时,将写入两个哈希:
\message{This is my message: #}
屏幕上显示:This is my message: ##
。
(请注意,使用\show
和时\message{\meaning\...}
不会出现此哈希加倍。
原因是:在写入屏幕时\show
,\message
实际上不处理类别代码 6(参数)的哈希标记,而是处理其“字符串化”变体,即类别代码 12(其他)的哈希标记。)在/扩展上下文 中
\the
传递 的标记⟨token variable⟩
(例如,标记寄存器的标记或类似 的标记参数的标记)\everypar
时,哈希值将加倍。 此哈希值加倍是为了补偿在扩展相关宏后哈希值会减少的情况。\everyeof
\edef
\xdef
补充 1:写入屏幕的任务与定义宏的任务不同。您自己已经在问题中明确排除了写入外部文件和“读回”发挥作用的方法。
补充 2:在/扩展期间会导致哈希值加倍,但同时会抑制内容的进一步扩展。因此,在/中,哈希值加倍的材料将始终不扩展至所讨论的宏定义中。为了扩展哈希值加倍的仍未扩展的材料,无论如何也需要对其进行扩展。但在扩展期间,无论如何,的两个连续哈希值将合并为一个。这将抵消哈希值加倍。\the⟨token variable⟩
\edef
\xdef
⟨token variable⟩
\edef
\xdef\macro{\the⟨token variable⟩}
\macro
\macro
⟨balanced text⟩
\macro
⟨definition text⟩
为了好玩,让我提一下以下内容:
特别是在 4 月 1 日使用 Knuthian TeX 时,在某些情况下(并非所有!)\double
在每个哈希前面添加而不是在“内部\edef
”将每个哈希加倍可能是一种可行但非常低效的解决方法:
\def\five{\four5}
\def\four{\three4}
\def\three{\two3}
\def\two{\one2}
\def\one{1}
\def\double#1{#1#1}
\def\tokenstomacro#1{%
\edef\macro{#1}%
}
\tokenstomacro{\one\double#1\two\double#2\three\double#3\four\double#4\five}
\show\macro
\csname stop\endcsname
\bye
此示例的控制台输出为:
This is pdfTeX, Version 3.14159265-2.6-1.40.19 (TeX Live 2019/dev/Debian) (preloaded format=pdftex)
entering extended mode
(./test.tex
> \macro=macro:
->1##112##2123##31234##412345.
l.11 \show\macro
?
)
No pages of output.
Transcript written on test.log.
这种解决方法很有趣,因此适合 4 月 1 日,因为它效率低下且毫无意义,因为打字##
通常比打字工作量少\double#
,而打字####
通常比打字工作量少\double{\double#}
……
对于某些(但不是全部!)情况,另一种解决方法是使用活动字符代替哈希值,这样不会显著增加打字工作量,从而提供两个哈希值:
\def\five{\four5}
\def\four{\three4}
\def\three{\two3}
\def\two{\one2}
\def\one{1}
% Let's use / instead of #
\catcode`\/=13
\def/{####}
\def\tokenstomacro#1{%
\edef\macro{#1}%
}
\tokenstomacro{\one/1\two/2\three/3\four/4\five}
\show\macro
\csname stop\endcsname
\bye
此示例的控制台输出为:
This is pdfTeX, Version 3.14159265-2.6-1.40.19 (TeX Live 2019/dev/Debian) (preloaded format=pdftex)
entering extended mode
(./test.tex
> \macro=macro:
->1##112##2123##31234##412345.
l.13 \show\macro
?
)
No pages of output.
Transcript written on test.log.
如果有 ε-TeX 扩展可用,则会出现更多特殊情况,其中哈希值加倍:
ε-TeX 基元
\detokenize
就像将未扩展的立即写入标记写入外部文件(由此发生哈希复制!),并在类别代码制度下读回该外部文件,其中除空间(其类别代码为 10(空间))之外的所有内容都具有类别代码 12(其他)。ε
-TeX 基元\scantokens
就像将未扩展的立即写入标记写入外部文件(由此发生哈希复制!),并在当前类别代码制度下读回该外部文件。当 ε-TeX 基元在/扩展上下文中
\unexpanded
传递时,哈希值将加倍。此哈希值加倍是为了补偿在扩展相关宏后哈希值会减少的情况。⟨balanced text⟩
\edef
\xdef
广告 3:您自己已经在问题中明确排除了写入外部伪文件和“读回”所起的作用的方法。
\edef
广告 4:仅将/\xdef
和组合\unexpanded
不足以实现您的期望:与情况 2 一样,哈希值加倍的材料未展开到宏定义的中。与情况 2 一样,进一步扩展该材料需要扩展所讨论的宏,从而将的⟨balanced text⟩
两个连续哈希值合并为一个,从而抵消哈希值加倍。⟨balanced text⟩
\macro
⟨definition text⟩
但是对于更新的 TeX 引擎来说也有这样的功能\expanded
,这就是为什么你在这里很幸运:
如果\expanded
可用,您可以使用\expanded
(用于扩展事物)和在扩展期间\unexpanded
将扩展结果的哈希值加倍的组合——不需要分配给临时令牌寄存器等:\expanded
\edef
\def\five{\four5}
\def\four{\three4}
\def\three{\two3}
\def\two{\one2}
\def\one{1}
\def\tokenstomacro#1{%
\edef\macro{\unexpanded\expandafter{\expanded{#1}}}%
}
\tokenstomacro{\one#1\two#2\three#3\four#4\five}
\show\macro
\csname stop\endcsname
\bye
以上面的例子(保存为 test.tex)来说,pdfTeX 的控制台输出是:
This is pdfTeX, Version 3.14159265-2.6-1.40.19 (TeX Live 2019/dev/Debian) (preloaded format=pdftex)
entering extended mode
(./test.tex
> \macro=macro:
->1##112##2123##31234##412345.
l.10 \show\macro
?
)
No pages of output.
Transcript written on test.log.
(当我还在写上一段的时候,约瑟夫·赖特向egreg 的回答-在egreg 的回答 \the⟨token register⟩
用来代替\unexpanded
——并指出了同样的道理。所以我肯定不是第一个有这个想法的人。)
对于不太新的 TeX 引擎,\detokenize
其中 ε-TeX 扩展可用但\expanded
不可用,我可以提供一个例程\DoubleEveryHash
,可在某些情况下作为解决方法:
该例程\DoubleEveryHash
处理一个参数,并通过\romannumeral0
-expansion 递归地将该参数中包含的每个显式 catcode-6(参数)字符标记加倍。
检查哈希的要点是:\string#
提供类别代码为 12(其他)的单个哈希字符标记,同时(如上所述)\detokenize
发生哈希加倍,因此\detokenize{#}
提供类别代码为 12(其他)的两个哈希字符标记。
如果的参数\DoubleEveryHash
包含匹配的 catcode 1 和 2 的显式字符标记对,则这些对中的每一对都会触发另一级\romannumeral0
-expansion。因此参数中括号的过度嵌套\DoubleEveryHash
将对语义嵌套造成影响。
此外,这\DoubleEveryHash
确实通过匹配类别代码 1 的左花括号和类别代码 2 的右花括号来替换类别代码 1(开始分组)和 2(结束分组)的显式字符标记的匹配对。
我想在大多数情况下这不会是个问题,因为通常花括号是类别代码 1/2 的唯一字符。
但必须提到这一点,因为这意味着它只\DoubleEveryHash
适用于用相同类型的显式花括号标记替换显式开始分组字符标记和显式结束分组字符标记的情况。
如果放入\DoubleEveryHash{...}
,\edef
由于\romannumeral0
-扩展,哈希值加倍将(与\edef\macro{\unexpanded\expandafter{\expanded{#1}}}
-方法相反)发生前扩展构成 参数的标记\DoubleEveryHash
。因此\edef\macro{\DoubleEveryHash{\string#1}}
(或者\DoubleEveryHash{\edef\macro{\string#1}}
如果您愿意)将产生有关非法参数数字的错误消息,因为在哈希加倍步骤中,您将获得两个哈希,后面跟着数字1
。第一个哈希将被字符串化。第二个哈希,后面跟着数字,不会被字符串化,因此将在为空时1
用作参数。#1
⟨parameter text⟩
\macro
\catcode`\@=11
%%=============================================================================
%% Paraphernalia:
%% \UD@firstoftwo, \UD@secondoftwo,
%% \UD@PassFirstToSecond, \UD@Exchange, \UD@removespace
%% \UD@CheckWhetherNull, \UD@CheckWhetherBrace,
%% \UD@CheckWhetherLeadingSpace, \UD@ExtractFirstArg
%%=============================================================================
\long\def\UD@firstoftwo#1#2{#1}%
\long\def\UD@secondoftwo#1#2{#2}%
\long\def\UD@PassFirstToSecond#1#2{#2{#1}}%
\long\def\UD@Exchange#1#2{#2#1}%
\UD@firstoftwo{\def\UD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\long\def\UD@CheckWhetherNull#1{%
\romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\UD@firstoftwo\expandafter{} \UD@secondoftwo}%
{\UD@firstoftwo\expandafter{} \UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has leading
%% catcode-1-token>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has no leading
%% catcode-1-token>}%
\long\def\UD@CheckWhetherBrace#1{%
\romannumeral0\expandafter\UD@secondoftwo\expandafter{\expandafter{%
\string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\UD@firstoftwo\expandafter{} \UD@firstoftwo}%
{\UD@firstoftwo\expandafter{} \UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument's first token is an explicit
%% space token
%%.............................................................................
%% \UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is a
%% space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is not
%% a space-token>}%
\long\def\UD@CheckWhetherLeadingSpace#1{%
\romannumeral0\UD@CheckWhetherNull{#1}%
{\UD@firstoftwo\expandafter{} \UD@secondoftwo}%
{\expandafter\UD@secondoftwo\string{\UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\long\def\UD@CheckWhetherLeadingSpaceB#1 {%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@secondoftwo#1{}}%
{\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
{\UD@Exchange{ }{\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter}\expandafter\expandafter
\expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%% \UD@ExtractFirstArg{ABCDE} yields {A}
%%
%% \UD@ExtractFirstArg{{AB}CDE} yields {AB}
%%.............................................................................
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\long\def\UD@ExtractFirstArg#1{%
\romannumeral0%
\UD@ExtractFirstArgLoop{#1\UD@SelDOm}%
}%
\long\def\UD@ExtractFirstArgLoop#1{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{ #1}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%=============================================================================
%% \DoubleEveryHash{<argument>}%
%%
%% Each explicit catcode-6(parameter)-character-token of the <argument>
%% will be doubled.
%%
%% You obtain the result after two expansion-steps, i.e.,
%% in expansion-contexts you get the result after "hitting"
%% \DoubleEveryHash by two \expandafter.
%%
%% As a side-effect, the routine does replace matching pairs of explicit
%% character tokens of catcode 1 and 2 by matching pairs of curly braces
%% of catcode 1 and 2.
%% I suppose this won't be a problem in most situations as usually the
%% curly braces are the only characters of category code 1 / 2...
%%
%% This routine needs \detokenize from the eTeX extensions.
%%-----------------------------------------------------------------------------
\long\def\DoubleEveryHash#1{%
\romannumeral0\UD@DoubleEveryHashLoop{#1}{}%
}%
\long\def\UD@DoubleEveryHashLoop#1#2{%
\UD@CheckWhetherNull{#1}{ #2}{%
\UD@CheckWhetherLeadingSpace{#1}{%
\expandafter\UD@DoubleEveryHashLoop
\expandafter{\UD@removespace#1}{#2 }%
}{%
\UD@CheckWhetherBrace{#1}{%
\expandafter\expandafter\expandafter\UD@PassFirstToSecond
\expandafter\expandafter\expandafter{%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral0%
\expandafter\UD@DoubleEveryHashLoop
\romannumeral0%
\UD@ExtractFirstArgLoop{#1\UD@SelDOm}{}%
}{#2}}%
{\expandafter\UD@DoubleEveryHashLoop
\expandafter{\UD@firstoftwo{}#1}}%
}{%
\expandafter\UD@CheckWhetherHash
\romannumeral0\UD@ExtractFirstArgLoop{#1\UD@SelDOm}{#1}{#2}%
}%
}%
}%
}%
\long\def\UD@CheckWhetherHash#1#2#3{%
\expandafter\UD@CheckWhetherLeadingSpace\expandafter{\string#1}{%
\expandafter\expandafter\expandafter\UD@CheckWhetherNull
\expandafter\expandafter\expandafter{%
\expandafter\UD@removespace\string#1}{%
\expandafter\expandafter\expandafter\UD@CheckWhetherNull
\expandafter\expandafter\expandafter{%
\expandafter\UD@removespace\detokenize{#1}}{%
% something whose stringification yields a single space
\UD@secondoftwo
}{% explicit space of catcode 6
\UD@firstoftwo
}%
}{% something whose stringification has a leading space
\UD@secondoftwo
}%
}{%
\expandafter\expandafter\expandafter\UD@CheckWhetherNull
\expandafter\expandafter\expandafter{%
\expandafter\UD@firstoftwo
\expandafter{\expandafter}\string#1}{%
\expandafter\expandafter\expandafter\UD@CheckWhetherNull
\expandafter\expandafter\expandafter{%
\expandafter\UD@firstoftwo
\expandafter{\expandafter}\detokenize{#1}}{%
% no hash
\UD@secondoftwo
}{% hash
\UD@firstoftwo
}%
}{% no hash
\UD@secondoftwo
}%
}%
{% hash
\expandafter\UD@DoubleEveryHashLoop
\expandafter{\UD@firstoftwo{}#2}{#3#1#1}%
}{% no hash
\expandafter\UD@DoubleEveryHashLoop
\expandafter{\UD@firstoftwo{}#2}{#3#1}%
}%
}%
\catcode`\@=12
%%=============================================================================
\def\five{\four5}
\def\four{\three4}
\def\three{\two3}
\def\two{\one2}
\def\one{1}
\def\tokenstomacro#1{%
\edef\macro{\DoubleEveryHash{#1}}%
}%
% Or, if you prefer:
% \def\tokenstomacro#1{%
% \DoubleEveryHash{\edef\macro{#1}}%
% }%
\tokenstomacro{\one#1\two#2\three#3\four#4\five}
\show\macro
\tokenstomacro{\one##1\two##2\three#1\four#2\five}
\show\macro
\tokenstomacro{\one##1{{{ \two##2 }\three#1}\four#2}\five}
\show\macro
\csname stop\endcsname
\bye
这个(相当大的)最小示例的控制台输出是:
This is pdfTeX, Version 3.14159265-2.6-1.40.19 (TeX Live 2019/dev/Debian) (preloaded format=pdflatex)
entering extended mode
(./test.tex
LaTeX2e <2018-12-01>
> \macro=macro:
->1##112##2123##31234##412345.
l.174 \show\macro
?
> \macro=macro:
->1####112####2123##11234##212345.
l.176 \show\macro
?
> \macro=macro:
->1####1{{{ 12####2 }123##1}1234##2}12345.
l.178 \show\macro
?
)
No pages of output.
Transcript written on test.log.