\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
技巧要方便得多。