TeXbook 宏 \stest 如何工作?

TeXbook 宏 \stest 如何工作?

我们将\stestTeXbook 中的宏放入文件中,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 行的测试为真。

相关内容