在定义中定义活动字符

在定义中定义活动字符
\def\test{\catcode`!=\active \def!{test}}

当然,上面的代码不会起作用,因为 TeX 在读取宏定义的替换文本时什么都不执行。 !无论如何都会得到 catcode 12。我尝试在这个网站上搜索,有人建议使用\scantokens。据我所知,\scantokens是一个可扩展的命令,它读取以下标记列表,将其存储在伪文件中,然后输入它,因此标记会根据新的 catcode 重新标记。但是,以下代码仍然失败,我不明白为什么。

\def\test{\catcode`!=\active \scantokens{\def!}{test}}
\test

我收到“失控定义”的错误。

答案1

e-TeX 参考定义\scantokens如下:

\scantokens,后面跟着一个<一般文本>,分解<平衡文本><一般文本>转换为相应的字符序列,就像<平衡文本>被未扩展地写入文件;然后它使用 TeX 的\input机制在当前机制下重新处理这些字符\catcode

因此,\def!宏的一部分被处理,就好像它是在一个额外的文件中定义的,然后\input进入你的主文件。这包括在整个<平衡文本>已处理。

TeXbook,第 206 页,Knuth 对文件读取和宏做了注释\outer(感谢 GuM 指出这一点):

\outer不能出现在参数中(即使\par允许也不行),也不能出现在参数文本或定义的替换文本中[...]输入文件或对齐模板的结束也被认为是\outer这种意义上的;例如,文件不应该以定义的中间结束。

这正是您在此处遇到的问题。TeX 仍在扫描!文件结尾处的定义\scantokens。然后它会抱怨。如果您用实际替换输入文件仅包含的内容,File ended while scanning definition of !则会看到相同的行为和错误消息。\scantokens{\def!}\input\def!

Bruno Le Floch 找到了一个解决这个问题的好方法,即使用\let而不是\def来定义!

\def\bangdef{test}
\def\test{\catcode`!=\active \scantokens{\let!}\bangdef}
\test

为了完整性,这里有两个不使用 的标准技巧\scantokens。获得所需结果的一种方法是在\test已经应用了正确 catcode 的机制中定义:

\begingroup
\catcode`!=\active
\gdef\test{\catcode`!=\active \def!{test}}
\endgroup
\test
\show!

\gdef还可以保存当前的 catcode!并在定义之后恢复它\test

另一个常见技巧是将活动字符的小写代码(此处)更改~为要激活的字符的字符代码,然后使用\lowercase应用此更改:

\def\test{%
  \begingroup
  \lccode`\~=`\!
  \lowercase{\endgroup \catcode`\!=\active \def~}{test}%
}
\test
\show!

答案2

OpTeX\adef使用以下方式定义其宏\directlua

   \_doc ------------------------------
   \`\adef` `<char>{<body>}` defines active `<char>` as <body> and then
   puts the <char> as active character. I.e. the `<body>` can include the
   <char> as non-active charter (if it is non-active before `\adef`).
   For example `\adef ?{\,?}`.
   If the character is special, you can escape it, for example `\adef\%{...}`.
   The space can be declared by `\adef{ }{<body>}`.
   You can declare a macro with parameters too, for example
   `\adef @#1{...#1...}`. You can use prefixes `\protected`, `\global`,
   `\long` before `\adef`, they behave like prefixes before `\def`.
   \_cod ------------------------------

\_def\_adef#1#2#{\_adefA{#1}{#2}}
\_def\_adefA#1#2#3{\_ea\_def\_directlua{tex.cprint(13,"\_luaescapestring{\_csstring#1}")}#2{#3}%
   \_catcode`#1=13 }
\_public \adef ;

答案3

使用 etex 或 pdftex。

\def\test{\catcode`!=\active 
          \begingroup\endlinechar-1
                     \everyeof{{test}\endgroup}\scantokens{\gdef!}}

\test

\tt
\meaning!

\bye

在此处输入图片描述


不太原创,并且放弃在替换文本之前结束 scantokens 参数的想法:

\def\test{\catcode`!=\active 
          \scantokens{\def!{test}\relax}}% \relax to absorb an EOL space token

\tt

+++\test+++

\show!

\bye

生产

> !=macro:
->test.
l.10 \show!

答案4

“学术”信息:

你可以(滥用)使用\futurelet来摆脱由于-primitive\scantokens而产生的文件结束标记\input——在 LaTeX 中\input-primitive 被重命名为\@@input并且控制序列\input被重新定义:

% Characters of category 0, 5, 9, 14 and 15 don't make it into explicit 
% character tokens, thus allow syntax both with explicit character tokens
% and control symbol tokens.
%
% E.g., under normal circumstances you can't do
%    \DefineActiveChar{%}{<prefixes like \global>}<parameter text>{<replacement text}%
% , but you can do
%    \DefineActiveChar{\%}{<prefixes like \global>}<parameter text>{<replacement text}%
% .

% Reading a single space from a line of .tex-input, be it a real text file
% or be it with \scantokens' faking of unexpanded-writing and then inputting
% of a text file, requires special attention because at the time of
% pre-processing a line of .tex-input all space-characters at the end of the
% line get stripped off.
% With some TeX-implementations this is also the case with the horizontal-tab-
% character.
% Thus writing and reading a space-character to/from external text file
% requires appending an additional comment-character.
% At least with writing and reading linefed and carriage return
% special attention might be required, too.

\newtoks\scratchtoks
\def\thescratchtoks{\the\scratchtoks}%
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%

\def\GatherActiveCharacterAsRelax#1{%
  % #1 = active character token to (re)define
  \let#1=\relax\NeutraLizeEOFMarker{#1}%
}%
\def\NeutraLizeEOFMarker#1#2{%
  % #1 = active character token to (re)define
  % #2 = letter X or - in case X is the active character to redefine - active X
  %      ; ensures that in case #1 is space that space is not removed 
  %        during TeX's pre-processing of lines of input
  \endgroup
  \begingroup
  \let#1=\relax
  \scratchtoks={\GatherDefinitionPrefixAndDefine{#1}}%
  \everyeof={}%
  \futurelet\scratchmacro\thescratchtoks
}%
\long\def\GatherDefinitionPrefixAndDefine#1#2{%
  % #1 = active character token to (re)define
  % #2 = prefixes for `\def` like `\global` or `\long`
  \endgroup#2\def#1%
}%

\def\DefineActiveChar#1{%
  % #1 = character or one-letter-control-sequence for alphabetic constant
  \begingroup
  \escapechar=-1\relax
  \newlinechar=-1\relax
  \endlinechar=-1\relax
  \catcode`\^=7\relax
  \catcode`\X=11\relax
  \catcode`#1=\active
  \expandafter\expandafter\expandafter\GatherActiveCharacterAsRelax\expandafter\noexpand
  \scantokens
  \csname \ifnum`#1=10 first\else second\fi oftwo\endcsname
  {%
    \expandafter{\expandafter^\expandafter^JX}%
  }{%
    \csname \ifnum`#1=13 first\else second\fi oftwo\endcsname
    {%
      \expandafter{\expandafter^\expandafter^MX}%
    }{%
      \expandafter{\string#1X}%
    }%
  }%
}%

{\catcode`!=\active \outer\gdef!{Huh?}}%

%-------------------------------------------------------------------------
\DefineActiveChar{!}{\long}#1#2{ACTIVE EXCLAMATION MARK Arg1: #1 test Arg2: #2}
\begingroup \catcode`!=\active \show!\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{A}{\long}#1#2{ACTIVE A Arg1: #1 test Arg2: #2}
\begingroup \catcode`A=\active \showA\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\!}{\long}#1#2{ACTIVE EXCLAMATION MARK AGAIN Arg1: #1 TEST Arg2: #2}
\begingroup \catcode`!=\active \show!\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\%}{\long}#1#2{ACTIVE PERCENT Arg1: #1 Arg2: #2}
\begingroup \catcode`\%=\active \show%\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\{}{\long}#1#2{ACTIVE CURLY LEFT BRACE Arg1: #1 Arg2: #2}
\begingroup \catcode`\{=\active \show{\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\}}{\long}#1#2{ACTIVE CURLY RIGHT BRACE Arg1: #1 Arg2: #2}
\begingroup \catcode`\}=\active \show}\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\#}{\long}#1#2{ACTIVE HASH Arg1: #1 Arg2: #2}
\begingroup \catcode`\#=\active \show#\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\ }{\long}#1#2{ACTIVE SPACE Arg1: #1 Arg2: #2}
\begingroup \catcode`\ =\active\show \endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{ }{\long}#1#2{ACTIVE SPACE AGAIN Arg1: #1 Arg2: #2}
\begingroup \catcode`\ =\active\show \endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\^^I}{\long}#1#2{ACTIVE HORIZONTAL TAB Arg1: #1 Arg2: #2}
\begingroup \catcode`\^^I=\active\show^^I\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\\}{\long}#1#2{ACTIVE BACKSLASH Arg1: #1 Arg2: #2}
\begingroup \catcode`\/=0 \catcode`\\=\active/show\/endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\$}{\long}#1#2{ACTIVE DOLLAR Arg1: #1 Arg2: #2}
\begingroup \catcode`\$=\active\show$\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\&}{\long}#1#2{ACTIVE AMPERSAND Arg1: #1 Arg2: #2}
\begingroup \catcode`\&=\active\show&\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\^}{\long}#1#2{ACTIVE CARET Arg1: #1 Arg2: #2}
\begingroup \catcode`\^=\active\show^\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\_}{\long}#1#2{ACTIVE UNDERSCORE Arg1: #1 Arg2: #2}
\begingroup \catcode`\_=\active\show_\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\^^A}{\long}#1#2{ACTIVE START OF HEADING Arg1: #1 Arg2: #2}
\begingroup \catcode`\^^A=\active\show^^A\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\^^J}{\long}#1#2{ACTIVE LINE FEED Arg1: #1 Arg2: #2}
\begingroup \catcode`\^^J=\active\show^^J\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\^^M}{\long}#1#2{ACTIVE CARRIAGE RETURN Arg1: #1 Arg2: #2}
\begingroup \catcode`\^^M=\active\show^^M\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\~}{\long}#1#2{ACTIVE TILDE Arg1: #1 Arg2: #2}
\begingroup \catcode`\~=\active\show~\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{!}{\long}{!}
\begingroup
\catcode`!=\active\show!
\expandafter\show!%
\endgroup
%-------------------------------------------------------------------------

% end in case of running LaTeX:
\csname stop\endcsname
% end in case of running eTeX with plain TeX format:
\bye

如果使用 Knuthian TeX,而 ε-TeX 扩展类似\scantokens\everyeof不可用,则可以执行以下操作:

% Characters of category 0, 5, 9, 14 and 15 don't make it into explicit 
% character tokens, thus allow syntax both with explicit character tokens
% and control symbol tokens.
%
% E.g., under normal circumstances you can't do
%    \DefineActiveChar{%}{<prefixes like \global>}<parameter text>{<replacement text}%
% , but you can do
%    \DefineActiveChar{\%}{<prefixes like \global><parameter text>{<replacement text}%
% .

% Reading a single space from a line of .tex-input, be it a real text file
% or be it with \scantokens' faking of unexpanded-writing and then inputting
% of a text file, requires special attention because at the time of
% pre-processing a line of .tex-input all space-characters at the end of the
% line get stripped off.
% With some TeX-implementations this is also the case with the horizontal-tab-
% character.
% Thus writing and reading a space-character to/from external text file
% requires appending an additional comment-character.
% At least with writing and reading linefed and carriage return
% special attention might be required, too.

% In LaTeX the \input-primitive is renamed to \@@input and the control sequence
% \input is redefined not to be a macro:
\begingroup
\expandafter\ifx\csname @@input\endcsname\relax
  \endgroup
  \let\myinput=\input
\else
  \endgroup
  \expandafter\let\expandafter\myinput\expandafter=\csname @@input\endcsname
\fi
% In some TeX engines the `\everyeof`-primitive  is available, in others it is not:
\expandafter\ifx\csname everyeof\endcsname\relax
  \def\myeveryeof=#1{}%
\else
  \let\myeveryeof=\everyeof
\fi



\newwrite\scratchwrite
\newtoks\scratchtoks
\def\thescratchtoks{\the\scratchtoks}%

\def\GatherActiveCharacterAsRelax#1{%
  % #1 = active character token to (re)define
  \let#1=\relax\NeutraLizeEOFMarker{#1}%
}%
\def\NeutraLizeEOFMarker#1#2{%
  % #1 = active character token to (re)define
  % #2 = letter X or - in case X is the active character to redefine - active X
  %      ; ensures that in case #1 is space that space is not removed 
  %        during TeX's pre-processing of lines of input
  \endgroup
  \begingroup
  \let#1=\relax
  \scratchtoks={\GatherDefinitionPrefixAndDefine{#1}}%
  \myeveryeof={}%
  \futurelet\scratchmacro\thescratchtoks
}%
\long\def\GatherDefinitionPrefixAndDefine#1#2{%
  % #1 = active character token to (re)define
  % #2 = prefixes for `\def` like `\global` or `\long`
  \endgroup#2\def#1%
}%

\def\DefineActiveChar#1{%
  % #1 = character or one-letter-control-sequence for alphabetic constant
  \begingroup
  \escapechar=-1\relax
  \newlinechar=-1\relax
  \immediate\openout\scratchwrite scratchfile.tex
  \immediate\write\scratchwrite{%
    \ifnum`#1=10 \string^\string^J\else
      \ifnum`#1=13 \string^\string^M\else
       \string#1\fi\fi
     X%
  }%
  \immediate\closeout\scratchwrite
  \endgroup
  \begingroup
  \endlinechar=-1\relax
  \catcode`\^=7\relax
  \catcode`\X=11\relax
  \catcode`#1=\active
  \expandafter\expandafter\expandafter\GatherActiveCharacterAsRelax\expandafter\noexpand
  \myinput "scratchfile.tex" %
}%

{\catcode`!=\active \outer\gdef!{Huh?}}%

%-------------------------------------------------------------------------
\DefineActiveChar{!}{\long}#1#2{ACTIVE EXCLAMATION MARK Arg1: #1 test Arg2: #2}
\begingroup \catcode`!=\active \show!\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{A}{\long}#1#2{ACTIVE A Arg1: #1 test Arg2: #2}
\begingroup \catcode`A=\active \showA\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\!}{\long}#1#2{ACTIVE EXCLAMATION MARK AGAIN Arg1: #1 TEST Arg2: #2}
\begingroup \catcode`!=\active \show!\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\%}{\long}#1#2{ACTIVE PERCENT Arg1: #1 Arg2: #2}
\begingroup \catcode`\%=\active \show%\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\{}{\long}#1#2{ACTIVE CURLY LEFT BRACE Arg1: #1 Arg2: #2}
\begingroup \catcode`\{=\active \show{\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\}}{\long}#1#2{ACTIVE CURLY RIGHT BRACE Arg1: #1 Arg2: #2}
\begingroup \catcode`\}=\active \show}\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\#}{\long}#1#2{ACTIVE HASH Arg1: #1 Arg2: #2}
\begingroup \catcode`\#=\active \show#\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\ }{\long}#1#2{ACTIVE SPACE Arg1: #1 Arg2: #2}
\begingroup \catcode`\ =\active\show \endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{ }{\long}#1#2{ACTIVE SPACE AGAIN Arg1: #1 Arg2: #2}
\begingroup \catcode`\ =\active\show \endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\^^I}{\long}#1#2{ACTIVE HORIZONTAL TAB Arg1: #1 Arg2: #2}
\begingroup \catcode`\^^I=\active\show^^I\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\\}{\long}#1#2{ACTIVE BACKSLASH Arg1: #1 Arg2: #2}
\begingroup \catcode`\/=0 \catcode`\\=\active/show\/endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\$}{\long}#1#2{ACTIVE DOLLAR Arg1: #1 Arg2: #2}
\begingroup \catcode`\$=\active\show$\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\&}{\long}#1#2{ACTIVE AMPERSAND Arg1: #1 Arg2: #2}
\begingroup \catcode`\&=\active\show&\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\^}{\long}#1#2{ACTIVE CARET Arg1: #1 Arg2: #2}
\begingroup \catcode`\^=\active\show^\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\_}{\long}#1#2{ACTIVE UNDERSCORE Arg1: #1 Arg2: #2}
\begingroup \catcode`\_=\active\show_\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\^^A}{\long}#1#2{ACTIVE START OF HEADING Arg1: #1 Arg2: #2}
\begingroup \catcode`\^^A=\active\show^^A\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\^^J}{\long}#1#2{ACTIVE LINE FEED Arg1: #1 Arg2: #2}
\begingroup \catcode`\^^J=\active\show^^J\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\^^M}{\long}#1#2{ACTIVE CARRIAGE RETURN Arg1: #1 Arg2: #2}
\begingroup \catcode`\^^M=\active\show^^M\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{\~}{\long}#1#2{ACTIVE TILDE Arg1: #1 Arg2: #2}
\begingroup \catcode`\~=\active\show~\endgroup
%-------------------------------------------------------------------------
\DefineActiveChar{!}{\long}{!}
\begingroup
\catcode`!=\active\show!
\expandafter\show!%
\endgroup
%-------------------------------------------------------------------------

% end in case of running LaTeX:
\csname stop\endcsname
% end in case of running eTeX with plain TeX format:
\bye

我们再强调一下:

这段代码是“学术性的东西”。在实践中,使用\uccode+ \uppercase/ \lccode+ \lowercase- 或\scantokens+ -\everyeof技巧要方便得多。

相关内容