![TeXbook 宏 \stest 如何工作?](https://linux22.com/image/470207/TeXbook%20%E5%AE%8F%20%5Cstest%20%E5%A6%82%E4%BD%95%E5%B7%A5%E4%BD%9C%EF%BC%9F.png)
我们将\stest
TeXbook 中的宏放入文件中,stest.tex
并进行一些格式化并添加行号以供参考。
1 \newif\ifspace \newif\iffunny \newif\ifexplicit \newif\ifactive
2 \def\stest#1{
3 \funnyfalse
4 \expandafter\s\the#1! \stest}
5 \def\s{
6 \global\explicitfalse
7 \global\activefalse
8 \futurelet\next\ss}
9 \def\ss{
10 \ifcat\noexpand\next\stoken
11 \let\nxt\sx
12 \else
13 \let\nxt\ns
14 \fi
15 \nxt}
16 \def\sx{
17 \spacetrue
18 \ifx\next\stoken
19 \let\nxt\sss
20 \else
21 \let\nxt=\ssss
22 \fi
23 \nxt}
24 \long\def\sss#1 #2\stest{
25 \def\next{#1}
26 \ifx\next\empty
27 \global\explicittrue
28 \else
29 \testactive#1\s
30 \fi}
31 \long\def\ssss#1#2\stest{
32 \funnytrue
33 \begingroup
34 \escapechar=\if*#1`?\else`*\fi\relax
35 \if#1\string#1
36 \uccode`#1=`~ % we assume that ~ is an active character
37 \uppercase{\ifcat\noexpand#1}\noexpand~
38 \global\activetrue
39 \else
40 \global\explicittrue
41 \fi
42 \else
43 \testactive#1\s
44 \fi
45 \endgroup}
46 \long\def\ns#1\stest{
47 \spacefalse}
48 \long\def\testactive#1#2\s{
49 \expandafter\tact\string#1\s\tact}
50 \long\def\tact#1#2\tact{
51 \def\next{#2}
52 \ifx\next\xs
53 \global\activetrue
54 \else
55 \ifx\next\empty
56 \global\activetrue
57 \fi
58 \fi}
59 \def\xs{\s}
\def\\{\let\stoken= } \\
\def\report{
\ifspace\message{SPACE}\fi
\iffunny\message{FUNNY}\fi
\ifexplicit\message{EXPLICIT}\fi
\ifactive\message{ACTIVE}\fi}
答案1
建议的替代方案存在一些问题
将
\t={}
其报告SPACE EXPLICIT
为空格,而不是!
用作保护标记。(在评论中,您建议可以将其记录下来,但修复它会更好)报告
\t{\par}
! Paragraph ended before \next was complete. <to be read again> \par
尽管你声称“
\long
没有必要”后
\catcode`\+\active \let+= \stoken \t{+}
您的版本报告
SPACE
但教科书上说
SPACE ACTIVE
从
\ifspace\message{SPACE}\fi \iffunny\message{FUNNY}\fi \ifexplicit\message{EXPLICIT}\fi \ifactive\message{ACTIVE}\fi
% texbook
\catcode`\8=9
\catcode`\9=14
9\message{Igor L}
9\newif\ifactive % extra always false here
9\newif\ifspace \newif\iffunny \newif\ifexplicit
9\def\stest#1{\spacefalse \funnyfalse \explicitfalse \expandafter\s\the#1 \stest}
9\def\s{\futurelet\next\ss}
9\def\ss{\ifcat\noexpand\next\stoken\spacetrue
9 \ifx\next\stoken\let\next=\sss\else\let\next=\ssss\fi
9 \else\let\next=\sssss\fi \next}
9\def\sss#1 #2\stest{\def\next{#1}\ifx\next\empty\explicittrue\fi}
9\def\ssss#1#2\stest{\funnytrue
9 {\escapechar=\if*#1`\?\else`\*\fi \if#1\string#1\global\explicittrue\fi}}
9\def\sssss#1\stest{}
8\message{TeXBook}
8\newif\ifspace \newif\iffunny \newif\ifexplicit \newif\ifactive
8\def\stest#1{\funnyfalse \expandafter\s\the#1! \stest}
8\def\s{\global\explicitfalse \global\activefalse \futurelet\next\ss}
8\def\ss{\ifcat\noexpand\next\stoken\let\nxt\sx\else\let\nxt\ns\fi\nxt}
8\def\sx{\spacetrue\ifx\next\stoken\let\nxt\sss\else\let\nxt=\ssss\fi\nxt}
8\long\def\sss#1 #2\stest{\def\next{#1}%
8 \ifx\next\empty \global\explicittrue \else\testactive#1\s\fi}
8\long\def\ssss#1#2\stest{\funnytrue{\escapechar=\if*#1`?\else`*\fi\relax
8 \if#1\string#1\uccode`#1=`~ % we assume that ~ is an active character
8 \uppercase{\ifcat\noexpand#1}\noexpand~\global\activetrue
8 \else\global\explicittrue\fi
8 \else\testactive#1\s\fi}}
8\long\def\ns#1\stest{\spacefalse}
8\long\def\testactive#1#2\s{\expandafter\tact\string#1\s\tact}
8\long\def\tact#1#2\tact{\def\next{#2}\ifx\next\xs\global\activetrue
8 \else\ifx\next\empty \global\activetrue\fi\fi} \def\xs{\s}
\def\\{\let\stoken= } \\ % now \stoken is a space token
\uccode`\ =`\* \uppercase{\uppercase{\def\fspace{ }\let\ftoken= } }
\catcode`\+\active
\let+= \stoken
\newtoks\t
\immediate\write200{empty}
\t={}
\stest\t
\ifspace\message{SPACE}\fi
\iffunny\message{FUNNY}\fi
\ifexplicit\message{EXPLICIT}\fi
\ifactive\message{ACTIVE}\fi
\immediate\write200{par}
\t={\par}
\stest\t
\ifspace\message{SPACE}\fi
\iffunny\message{FUNNY}\fi
\ifexplicit\message{EXPLICIT}\fi
\ifactive\message{ACTIVE}\fi
\immediate\write200{active plus}
\t={+}
\stest\t
\ifspace\message{SPACE}\fi
\iffunny\message{FUNNY}\fi
\ifexplicit\message{EXPLICIT}\fi
\ifactive\message{ACTIVE}\fi
\bye
生产
empty
par
active plus
SPACE ACTIVE
使用 TeXBook 代码,更改为
\catcode`\8=14
\catcode`\9=9
因此它使用问题中的代码并生成
empty
SPACE EXPLICIT
par
Runaway argument?
! Paragraph ended before \next was complete.
<to be read again>
\par
\stest ...e \explicitfalse \expandafter \s \the #1
\stest
l.61 \stest\t
?
! You can't use `\par' after \the.
l.66
?
Runaway argument?
0
! Paragraph ended before \next was complete.
<to be read again>
\par
l.67
?
active plus
SPACE )
答案2
这里提到的 Knuth 的宏代码有些晦涩难懂。我认为,应该使用更清晰的宏来解决给定的问题。
我创建了\isfoo <token>?\iftrue
宏(<token>
这里必须只有一个)。它们执行所有需要的测试。您可以适当组合它们以获得 Knuth 宏的结果。
\long\def\isspacetoken #1?\iftrue {\ifcat\space\noexpand#1}
\long\def\iseqspace #1?\iftrue {\expandafter\ifx\space#1}
\long\def\isspacechar #1?\iftrue {\if\space\string#1}
\long\def\isactivechar #1?\iftrue {\getccode{#1}\ifnum\ccode=13 }
\long\def\isimplicit #1#2\iftrue {\ifx?#2\empty}
\def\getccode#1{\expandafter\getccodeA\string#1\\\end}
\def\getccodeA#1#2\end{\expandafter\getccodeB\string\\#1}
\def\getccodeB#1#2#3{\ifx#1#3\chardef\ccode=0 \else\chardef\ccode=\catcode`#3 \fi}
\def\test#1{%
space token:\isspacetoken #1?\iftrue YES\else no\fi,
equal space:\iseqspace #1?\iftrue YES\else no\fi,
space character:\isspacechar #1?\iftrue YES\else no\fi,
active character:\isactivechar #1?\iftrue YES\else no\fi,
implicit space:\iseqspace #1?\iftrue
\isimplicit #1?\iftrue YES\else no\fi\else no\fi.
}
%%% testing:
\def\useit#1{#1}
\useit{\let\stoken= } % \let\stoken= <space> (code 32/10)
\bgroup\lccode`\ =`\* \lowercase{\egroup
\def\fspace{ }
\useit{\let\ftoken= } %
}
\catcode`\+\active
\let+= \stoken
\parindent=0pt \hsize=17cm
nomal space : \test{ }
space token : \test\stoken
space macro : \test\space
space token * : \test\ftoken
space macro * : \test\fspace
active space : \test+
par : \test\par
\bye
测试结果:
nomal space : space token:YES, equal space:YES, space character:no, active character:no, implicit space:no.
space token : space token:YES, equal space:YES, space character:no, active character:no, implicit space:YES.
space macro : space token:no, equal space:no, space character:no, active character:no, implicit space:no.
space token * : space token:YES, equal space:no, space character:no, active character:no, implicit space:no.
space macro * : space token:no, equal space:no, space character:no, active character:no, implicit space:no.
active space : space token:YES, equal space:YES, space character:no, active character:YES, implicit space:YES.
par : space token:no, equal space:no, space character:no, active character:no, implicit space:no.
答案3
概述:
• 隐式空格可以是活动字符(这里称为“活动”;我们必须将“活动空格”与“活动空格标记”分开)或控制序列。
• 在 \ss 中我们分支 catcode 10 或者不分支。
• 在 \sx 中,我们在 normal 和 funny 之间进行分支。
• 在 \sss 中,我们在显式和主动/隐式之间进行分支。
• 在 \ssss 中,我们在主动/显式和主动/隐式之间进行分支。
• 在 \testactive 中,我们在主动和隐式之间分支。
• \sss 的参数文本是 #1 #2,因为:如果标记列表中的第一个标记是显式空格,则它与参数文本中的空格匹配,因此 #1 将为空。另一方面,如果标记列表中的第一个标记是隐式空格或活动空格,则标记列表中的显式空格(如果有 — 不在第一个位置)或 ! 之后的空格(如果标记列表中没有显式空格)与参数文本中的空格匹配,并且 #1 将包括流中显式空格之前的所有标记。
• 每个空格标记(如果它是标记列表中的第一个)通过两个单独的路由处理。在第一个路由中(通过 \futurelet,无论空格标记是显式/隐式/活动的,它都会使 \next 成为隐式空格标记),我们在第 10 行检测到 catcode 10。在第二个路由中(通过宏 \sss 和 \ssss 的参数 #1,它使每个空格标记保持完整),我们检测显式/隐式/活动的。
• 在第 37 行中,我们在活动和显式之间进行分支。这里有一个微妙之处:我们使用 \uppercase 将活动字符从隐式空间转换为宏,以防止在 \ifcat 测试期间用其中的内容替换活动字符(默认情况下 ~ 是宏)。
• 对于 ACTIVE 有两种情况(假设活动字符是“+”):
[a] + → 正常
[b] + → 有趣
还有两种特殊情况:
[c] 活动字符的 charcode 为 32(这种情况可以分为两种子情况:当活动字符内的空格具有 charcode 32 时;以及当 charcode 不是 32 时)。
[d] 活动字符的 charcode 和活动字符内部的 charcode 相同(它们不能等于 32 — 这是 [c] 的子情况;并且它们不能等于 126 — 它在第 37 行使用)。
• \tact 的参数列表由对标记列表中的第一个标记运行 \string 形成(假设 \escapechar 介于 0 和 255 之间)。如果此标记不活跃,则参数列表中将有两个或更多标记。如果此标记是字符代码不为 32 的活跃字符,则参数列表中将有一个标记;如果此标记是字符代码为 32 的活跃字符,则参数列表将为空。\tact 检测这两种情况以确定标记是否活跃。
• 我们更改第 34 行中的 \escapechar,以使其不能等于空间的字符代码(显式或在主动和隐式的内部)——以确保第 35 行中的测试对于控制序列不能为真。
• 如果标记列表包含 \par,则使用 \long。
例子:
0.输出为空。
\input stest
\newtoks\t
\t={}
\stest\t \report \bye
\next 等同于 !。第 10 行的测试结果为 false。第 46 行的 #1 为 !。
1. 输出是 SPACE EXPLICIT
\input stest
\newtoks\t
\t={ }
\stest\t \report \bye
我们进入 \sss。#1 为空,#2 为 !。第 26 行的测试结果为真。
2. 输出是 SPACE FUNNY EXPLICIT
\input stest
{\uccode` =`*\uppercase{\gdef\fspace{ }}}
\newtoks\t
\t=\expandafter{\fspace}
\stest\t \report \bye
我们进入 \ssss。#1 是 *,#2 是 !。第 35 行的测试为真。第 37 行的测试为假。
3. 输出是 SPACE
\input stest
\newtoks\t
\t={\stoken}
\stest\t \report \bye
我们进入 \sss。#1 是 \stoken!,#2 为空。第 26 行的测试为假。我们进入 \testactive。#1 是 \stoken,#2 是 !。我们进入 \tact。#1 是 ,#2 是 stoken\s。
4. 输出是 SPACE FUNNY
\input stest
{\def\\{\global\let\ftoken= } \uccode` =`*\uppercase{\\ }}
\newtoks\t
\t={\ftoken}
\stest\t \report \bye
我们进入 \ssss。#1 是 \ftoken,#2 是 !。第 35 行的测试为假。我们进入 \testactive。#1 是 \ftoken,#2 为空。我们进入 \tact。#1 是 ,#2 是 ftoken\s。
5. 输出为 SPACE ACTIVE
\input stest
{\uccode`~=` \uppercase{\def\\{\global\let~= }} \\ }
\newtoks\t
{\catcode` =\active \global\t={ }}
\stest\t \report \bye
我们进入 \sss。#1 为 !,#2 为空。第 26 行的测试为假。我们进入 \testactive。#1 为 ,#2 为 !。我们进入 \tact。#1 为 \s,#2 为空。第 52 行的测试为假。第 55 行的测试为真。
6. 输出为 SPACE ACTIVE
\input stest
{\uccode`~=` \uppercase{\def\\{\global\let~= }} \uccode` =`*\uppercase{\\ }}
\newtoks\t
{\catcode` =\active \global\t={ }}
\stest\t \report \bye
我们进入 \ssss。#1 是 ,#2 是 ! 。第 35 行的测试为假。我们进入 \testactive。#1 是 ,#2 为空。我们进入 \tact。#1 是 \s,#2 为空。第 52 行的测试为假。第 55 行的测试为真。
7. 输出为 SPACE ACTIVE
\input stest
{\catcode`+=\active \def\\{\global\let+= } \\ }
\newtoks\t
{\catcode`+=\active \global\t={+}}
\stest\t \report \bye
我们进入 \sss。#1 为 +!,#2 为空。第 26 行的测试为假。我们进入 \testactive。#1 为 +,#2 为 !。我们进入 \tact。#1 为 +,#2 为 \s。第 52 行的测试为真。
8. 输出是 SPACE FUNNY ACTIVE
\input stest
{\catcode`+=\active \def\\{\global\let+= } \uccode` =`*\uppercase{\\ }}
\newtoks\t
{\catcode`+=\active \global\t={+}}
\stest\t \report \bye
我们进入 \ssss。#1 为 +,#2 为 !。第 35 行的测试为假。我们进入 \testactive。#1 为 +,#2 为空。我们进入 \tact。#1 为 +,#2 为 \s。第 52 行的测试为真。
9. 输出是 SPACE FUNNY ACTIVE
\input stest
{\catcode`*=\active \def\\{\global\let*= } \uccode` =`*\uppercase{\\ }}
\newtoks\t
{\catcode`*=\active \global\t={*}}
\stest\t \report \bye
我们进入 \ssss。#1 是 *,#2 是 !。第 35 行的测试为真。第 37 行的测试为真。