我认为以下问题毫无意义。
尽管如此,我自己却没有看到一个好的、强大的解决方案,这让我感到很难过。;-)
假设这样的事情:
\def\unfold{\umbrella My bald head always dries so slowly.}
\outer\def\umbrella{No raindrops on my head, please! }
顶层扩展\unfold
产生以下标记列表:
\umbrella My bald head always dries so slowly.
\umbrella
是根据 定义的\outer
。因此,将 的顶层扩展作为参数传递给另一个宏
并不是一个好主意。\unfold
如何仅使用以下方法进行检查
- 也可以应用于仅扩展的上下文中,例如,使用
\csname..\endcsname
-、\edef
- 或\write
-expansion, - 不需要某种禁止的哨兵令牌,
- 当 -parameter 具有不寻常的值时也可以工作
\escapechar
,
任意的 token 序列是否也包含\outer
-tokens?
我对纯 TeX 和 LaTeX 的解决方案都很满意。
对于使用 Lua 和/或 expl3 的解决方案,我可能需要一些时间来追溯每一步。;-)
我很感激能给我指明正确的方向。
答案1
我到底陷入了什么境地……
有三种可能的方法:
\def\outer{}
继续你的生活。毫无疑问最好的选择(在全部方面)。- 我敢打赌,用 TeX 实现
\suppressoutererror
这一点比编写 TeX 代码要容易得多。 - 现在是 2020 年,Knuth 将在这一年解决已报告的 TeX 错误。提交
\outer
以供考虑。 - 我说三!
- 我想如果你还在读的话,你希望我展示一些代码。那就继续阅读吧 :-)
宏的问题\outer
在于,它们本来就是用过的仅。你不应该做事用它们。任何你想做的事情,TeX 都会对你大喊大叫。这排除了 TeX 最强大的功能之一:宏。你根本无法在宏上使用它们\outer
。
然后你想要可扩展性,所以你也排除了大多数 TeX 的原语(包括最有用的竞争者,,\let
它不仅可以查看\outer
宏,还可以删除它)。即使是 ε-TeX 在这里也没用,因为\detokenize
和朋友不能有\outer
控制序列(所以是的,下面的代码在 Knuth TeX 中有效)。
这让你的资源变得非常有限。现在只有一个原语可能对你有帮助:\meaning
。下面的代码错误用于\meaning
尝试找出\outer
参数中是否存在控制序列。。。
什么它可以做:
有两个宏,\ifoutertl
和\ifouterarg
。第一个宏将参数标记列表展开一次,以显示其内容,第二个宏需要用括号(catcode 1 和 2)括住参数。在您的示例中,您可以像这样使用它们:
\ifoutertl\unfold{with}{without} outer
\ifouterarg{\umbrella My bald head always dries so slowly.}{with}{without} outer
\ifouterarg{My bald head always dries so slowly.}{with}{without} outer
它会打印:with outer with outer without outer
。
如何它作品:
进入此处的人,请放弃一切希望。
基本
当你这样做时:
\ifouterarg{This is \outer: \umbrella}{T}{F}
代码将首先删除前导字符{
,然后使用以下命令找到第一个标记\meaning
:
\some@macro the letter This is \outer: \umbrella}{T}{F}
然后会对\some@macro
进行一些检查\escapechar
(在过程中基本上被忽略),然后查看紧接着的下一个t
并尝试找出如何处理它。At
可以是字母(the letter <something>
)或字符(the character <something>
)。然后代码处理它并继续处理其余部分。然后代码到达\outer
,并用 命中它\meaning
:
\some@macro \outer: \umbrella}{T}{F}
(\outer
是原语,也是\meaning\outer
)。在这种情况下,代码将看到 ,并将使用[注 1]执行其操作。稍后,它到达并使用 命中它:\12o12u12t12e12r12
\
\outer
\umbrella
\meaning
\some@macro \outer macro:->No raindrops on my head, please! }{T}{F}
这次它将看到字符标记序列\outer macro:->
,在这种情况下,它将理解\outer
参数中有一个控制序列,然后在一系列扩展步骤之后,它将保留结果。如果没有找到控制序列,它将保留T
结果。这个“一系列扩展步骤”意味着用[注 2]匹配所有标记,然后如前所述将其删除。\outer
F
No raindrops on my head, please!
\meaning
分析每个 token(\meaning
解析之后)
在用 命中一个标记之后\meaning
(记住,在用 命中标记之前,我们不能查看该标记\meaning
),代码会继续分析它以找出要做什么。我们对控制序列的前缀感兴趣,因此代码首先要查找的是\protected
\long
和\outer
。幸运的是,\outer
始终是列表中出现的最后一个前缀,因此代码会跳过其他两个并继续查找更多。如果找到\outer
,我们会查找\outer macro:->
,它保证在那里[注 3]。
如果标记不是宏(因此没有macro:->
文本),则我们继续查找它是否是 TeX 的 10 个字符标记之一(类别代码为 1、2、3、4、6、7、8、10、11 或 12)。如果标记是其中之一,则代码会将其删除并继续扫描。
如果不是(例如,一个原语,其含义是它自己,一个\count
寄存器,其含义是\count<number>
,还有其他几个),那么代码就会放弃并重新开始[注 4](反正它也不是一个\outer
控制序列,所以我们不在乎)。这一次,它将从\meaning
再次执行开始(例如,在 中\count123
),然后第一个标记将是the character \
,然后它将找到出路。
嵌套
我不确定您是否注意到,但在我们告诉宏何时结束的过程中从未注意到。
它正在做\meaning
一些事情并观察它。因此,上面过程中出现了两个例外:如果标记是begin-group character
,则代码将开始(某种)新的扩展级别,该扩展级别将在下一个结束end-group character
。\outer
在那里找到的任何控制序列都将报告给上层扩展级别,因此您可以随心所欲地嵌套括号 :-)
如果括号匹配并且end-group character
找到了,则到达标记列表的末尾,并且它会停止,选择正确的条件分支。
为了跟踪嵌套级别以及是否\outer
找到控制序列,命令扫描过程的宏会执行以下操作(伪代码):
\romannumeral
\expandafter \after_egroup_action
\expandafter \outer_found_boolean
\romannumeral \scan
当\scan
找到另一个 时begin-group character
,会放置一个新层(\action\boolean\scan
),代码会继续扫描。当end-group character
找到 时,会插入 停止,控制权将传递给宏,宏将:继续使用 扫描标记列表,或结束该过程并使用来选择正确的条件分支。\exp_end:
\ud@exp@end
\romannumeral
\after_egroup_action
\outer_found_boolean
\outer_found_boolean
笔记:
- 1:由于代码无法查看实际的控制序列,该过程并不完全稳健。假设:
如果你设法将标记的 token 的 catcode 更改为 12,那么是的,你成功地欺骗了代码,让它认为你有一个% V------V \ifouterarg{\outer macro:->}{T}{F}
\outer
控制序列。我怀疑是否有可能克服这个问题:我们无法检查\outer
(并发现它是原始\outer
),而不会冒着抓取控制序列的风险\outer
。一旦我们用 击中它\meaning
,它就与\outer
宏没有区别了。所以是的,它不是万无一失的,抱歉。
2:是的,代码很慢。非常慢。它必须用 击中每个标记
\meaning
,因此如果你有\def\a{\b\b\b\b
。 。 很多 。 。\b\b\b\b\b}
和\def\b{<something awfully long>}
,那么是的,它会\meaning
在每个 中执行\b
,然后\meaning
在每个标记中执行在\b
,这可能会迅速升级。同样,我怀疑这是否可以优化。但不要误会:代码不会扩展永远。参数中的任何宏都会用 进行扩展\meaning
,但其他所有内容都是字符,最终将被扫描并删除。3:也就是说,如果
\outer macro:->
来自控制序列\meaning
的\outer
。如果您采用注释 1 中的示例并将其\ifouterarg{\outer macro:>}{T}{F}
改为读取,则代码将扩展为F
。4:是的,代码可以优化,以便了解字符标记以外的更多原始标记,这样(以 为例
\count123
)它就不会逐个使用\
、c
、o
、 . . (使用\meaning
和大循环),而是会看到它是\count<something>
并走捷径。实现这一点留给读者练习 ;-)
代码可能不算健壮,但可以完成任务。目前我不确定是否有更好的方法。虽然大部分代码都是在我改变想法时编写的,所以可能存在冗余,可以稍微提高速度。但我认为改进不多。谨慎行事!
如果你能读到这里,恭喜你!代码如下:
\catcode`\@=11
% Utilities
\def\@empty{}
\long\def\@gobble#1{}
\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}
\long\def\ud@usetwo#1#2{#1#2}
\def\ud@zap@space#1{\ud@@zap@space#1 \@empty}
\def\ud@@zap@space#1 #2{#1%
\ifx#2\@empty\else\expandafter\ud@@zap@space\fi#2}
\ud@usetwo{\let\ud@sptoken= }{ }
\chardef\ud@exp@end=0
\chardef\ud@false=0
\chardef\ud@true=1
% User-level macros
\def\ifoutertl#1{%
\romannumeral
\iffalse{\fi\ud@ifouter\ud@false\ud@ifouter@TF#1}}
\def\ifouterarg{%
\romannumeral
\expandafter\expandafter\expandafter\ud@ifouterarg@aux
\expandafter\@gobble\string}
% Internal macros
\def\ud@ifouter#1#2#3{%
\expandafter\ud@if@outer@scan\expandafter#1\expandafter#2#3}
\def\ud@ifouterarg@aux{%
\ud@ifouter\ud@false\ud@ifouter@TF\@empty}
\def\ud@ifouter@TF#1{%
\ifodd#1%
\expandafter\expandafter\expandafter
\ud@exp@end\expandafter\@firstoftwo
\else
\expandafter\expandafter\expandafter
\ud@exp@end\expandafter\@secondoftwo
\fi}
\def\ud@if@outer@scan#1#2{%
\expandafter\ud@if@outer@decypher
\expandafter#1\expandafter#2\meaning}
\def\ud@if@outer@decypher#1#2{%
\expandafter\ud@rearrange
\expandafter#1\expandafter#2%
\romannumeral\ud@if@outer@escapechar}
\def\ud@if@outer@escapechar#1{%
\ifnum\ifnum\escapechar<0 0\else 1\fi
\ifnum\escapechar>255 0\else 1\fi=0
\ud@decypher@noescape
\else
\ifnum`#1=\escapechar
\ud@decypher@escape
\else
\ud@decypher@noescape
\fi
\fi#1}
\def\ud@rearrange#1#2#3{#3#1#2}
\def\ud@decypher@noescape#1\fi\fi{\fi\fi\ud@decypher@cs@prefix}
\def\ud@decypher@escape\else#1\fi\fi#2{\fi\fi
\ud@decypher@cs@prefix}
\def\ud@decypher@cs@prefix#1{%
\ifcase0\if #1p1\fi \if #1l2\fi
\if #1o3\fi \if #1m4\fi \ud@sptoken
\expandafter\ud@scan@token@keyword%
\or \expandafter\ud@scan@string@p % \protected
\or \expandafter\ud@scan@string@l % \long
\or \expandafter\ud@scan@string@o % \outer
\or \expandafter\ud@scan@string@m % macro :->
\fi#1}
\def\ud@return@same@scanner{\ud@exp@end\ud@if@outer@scan}
\def\ud@return@true@scanner{\ud@exp@end\ud@return@true@outer}
\def\ud@return@true@outer#1{\ud@if@outer@scan\ud@true}
\def\ud@newstring#1#2{%
\ifx\relax#2%
\expandafter\@gobble
\else
\edef\ud@tmp@tl{\ud@tmp@tl#1}%
\edef\ud@test@tokn{\string#1}%
\expandafter\edef\csname ud@scan@string@\ud@tmp@tl\endcsname##1{%
\noexpand\ifx ##1\ud@test@tokn
\noexpand\expandafter\expandafter\noexpand
\csname ud@scan@string@\ud@tmp@tl#2\endcsname
\noexpand\else
\noexpand\expandafter\noexpand\ud@return@same@scanner
\noexpand\fi}%
\expandafter\ud@newstring
\fi{#2}}
\def\ud@new@scan@string#1{%
\def\ud@tmp@tl{}%
\ud@newstring #1{end}\relax
\expandafter\def\csname ud@scan@string@\ud@zap@space{#1}end\endcsname}
\ud@new@scan@string{protected}{\ud@if@outer@escapechar}
\ud@new@scan@string{long}{\ud@if@outer@escapechar}
\ud@new@scan@string{macro:->}{\ud@return@same@scanner}
\ud@new@scan@string{outer macro:->}{\ud@return@true@scanner}
\def\ud@scan@bgroup{\ud@exp@end\ud@scan@bgroup@aux}
\def\ud@scan@bgroup@aux#1#2{%
\expandafter\ud@after@group@continue
\expandafter#1\expandafter#2%
\romannumeral\ud@if@outer@scan#1\ud@after@egroup}
\def\ud@after@egroup{\ud@exp@end}
\def\ud@after@group@continue#1#2#3{%
\ud@if@outer@scan#3#2}
\def\ud@scan@egroup{\ud@exp@end\ud@scan@egroup@aux}
\def\ud@scan@egroup@aux#1#2{#2#1}
\def\ud@gobble@char@return#1{\ud@return@same@scanner}
\def\ud@gobble@char@do#1#2{#1}
\ud@usetwo{\def\ud@gobble@two@spaces}{ } {}
\ud@new@scan@string{begin-group character}{\ud@gobble@char@do\ud@scan@bgroup}
\ud@new@scan@string{end-group character}{\ud@gobble@char@do\ud@scan@egroup}
\ud@new@scan@string{math shift character}{\ud@gobble@char@return}
\ud@new@scan@string{alignment tab character}{\ud@gobble@char@return}
\ud@new@scan@string{macro parameter character}{\ud@gobble@char@return}
\ud@new@scan@string{superscript character}{\ud@gobble@char@return}
\ud@new@scan@string{subscript character}{\ud@gobble@char@return}
\ud@new@scan@string{blank space}{\expandafter\ud@return@same@scanner\ud@gobble@two@spaces}
\ud@new@scan@string{the letter}{\ud@gobble@char@return}
\ud@new@scan@string{thec haracter}{\ud@gobble@char@return}
\def\ud@scan@token@keyword#1{%
\expandafter\ifx\csname ud@scan@string@#1\endcsname\relax
\expandafter\ud@return@same@scanner
\else
\csname ud@scan@string@#1\expandafter\endcsname
\fi#1}
% keyword forks
\def\ud@strip@prefix#1>{}
\def\ud@detokenize#1#2{\def#1{#2}\edef#1{\expandafter\ud@strip@prefix\meaning#1}}
\def\ud@tl@head{\expandafter\@firstoftwo}
\def\ud@set@fork@string#1#2#3{%
\begingroup \escapechar-1
\def\x{ud@scan@string@#1}%
\expandafter\ud@set@fork@string@aux
\csname\x#2\expandafter\endcsname
\csname\x#2_\expandafter\endcsname
\csname\x#3\expandafter\expandafter\expandafter\endcsname
\expandafter\string\csname#2\expandafter\expandafter\expandafter\endcsname
\expandafter\string\csname#3\endcsname}
\def\ud@set@fork@string@aux#1#2#3#4#5{%
\endgroup
\let#2#1%
\def#1##1{%
\ifx ##1#4\expandafter#2%
\else \ifx ##1#5\expandafter\expandafter\expandafter#3%
\else \expandafter\expandafter\expandafter\ud@return@same@scanner
\fi
\fi##1}}
\ud@set@fork@string{ma}{c}{t} % macro / math
\ud@set@fork@string{macro}{p}{:} % macro parameter / macro:->
\ud@set@fork@string{the}{c}{l} % the character / the letter
\ud@set@fork@string{b}{l}{e} % blank / begin
\catcode`\@=12
% -----
% Tests
% -----
\def\rain{My bald head is still wet.}
\def\unfold{\umbrella My bald {head always} dries so slowly.}
\outer\def\umbrella{No raindrops on my head, please! }
\newcount\abc
\tt
0\ifouterarg{\newcount}{T}{F} (T)\par
1\ifouterarg{\abc}{T}{F} (F)\par
2\ifouterarg{\zzz}{T}{F} (F)\par
3\ifoutertl\unfold{T}{F} (T)\par
4\ifoutertl\rain{T}{F} (F)\par
5\ifouterarg{No raindrops on my head, please! }{T}{F} (F)\par
6\ifouterarg{\umbrella My bald {head always} dries so slowly.}{T}{F} (T)\par
7\edef\tmpa{\ifoutertl\unfold{T}{F}}\meaning\tmpa (T)\par
8\edef\tmpa{\ifouterarg{\umbrella corp.}{T}{F}}\meaning\tmpa (T)\par
9\edef\tmpa{\ifouterarg{\zombies!}{T}{F}}\meaning\tmpa (F)\par
\bye
答案2
让我们尝试一下 Lua 解决方案。我们可以使用\suppressoutererror
来简化扫描,但那不太有趣,所以我们改为扫描单个标记,并手动尝试跟踪嵌套括号。
对于每个扫描到的标记,Lua 都可以访问“命令 ID”。这有点像 catcodes 的泛化。特别是,每个 catcode 1 ( {
) 标记都有 ID 1,每个 catcode 2
( }
) 标记都有 ID 2,每个调用宏的标记都有由或\outer
返回的 ID 。因此,对于每个标记,我们只需检查它是否具有这些命令 ID 中的任何一个。对于 id,我们增加嵌套级别,对于 ,我们减少它,如果找到其他两个 ID 之一,我们记得在最后返回 true:token.command_id'outer_call'
token.command_id'long_outer_call'
1
2
\documentclass{article}
\begin{document}
\directlua{
local i = luatexbase.new_luafunction'hasouter'
% The following creates a table outer_cmd, such that
% outer_cmds[i] is true iff i is a id corresponding to
% a call to an \outer macro
local outer_cmds = {
[token.command_id'outer_call'] = true,
[token.command_id'long_outer_call'] = true,
}
lua.get_functions_table()[i] = function() % This function will be executed if we use `\hasouter`
local tok = token.scan_token() % scan_token applies full expansion until the first non-expandable token is found. This allows e.g. \hasouter\expandafter{...}
local cmd = tok.command % Look at the command code
if cmd \csstring\~= 1 then % \csstring\ must makes sure that TeX does not expand ~.
token.put_next(firsttok) % If we read a wrong character, putting it back ensures that TeX gets less confused if the user decides to continue after the error.
error[[Argument must start with \csstring\{]]
end
local nesting = 0
local result = false % This will become true if we find an \outer call
while true do % An endless loop. This will still terminate because we return early if nesing becomes 0 again
if cmd == 1 then % tok is equivalent to `{`. Increase the nesting level.
nesting = nesting + 1
elseif cmd == 2 then % tok is equivalent to `}`. Decrease the nesting level.
nesting = nesting - 1
if nesting == 0 then
% We want to expand to the first or second parameter depending on result, so we insert @first/secondoftwo
token.put_next(token.create(result and '@firstoftwo' or '@secondoftwo'))
return
end
else
result = result or outer_cmds[cmd] % If result is already true, don't change anything. Otherwise make it true if cmd corresponds to an outer call
end
tok = token.get_next() % Continue with the next token. get_next applies no expansion.
cmd = tok.command
end
end
token.set_lua('hasouter', i) % Define \hasouter to execute the function above
}
\def\unfold{\umbrella My bald head always dries so slowly.}
\def\unfoldX{My bald head always dries so slowly.}
\outer\def\umbrella{No raindrops on my head, please! }
\hasouter\expandafter{\unfold}{with}{without} outer
\hasouter\expandafter{\unfoldX}{with}{without} outer
\hasouter{\umbrella My bald head always dries so slowly.}{with}{without} outer
\hasouter{My bald head always dries so slowly.}{with}{without} outer
\end{document}
正如 Phelype Oleinik 在评论中提到的那样,这实际上在内部不起作用,\edef
因为get_next
强制执行了宏中不应包含任何外部宏的限制。
在现代 LuaTeX 版本中(例如从 TeXLive 2020 开始或lualatex-dev
在 TeXLive 2019 中编译时),可以使用以下方法解决这个问题tex.runtoks
:执行\lowercase{}
或类似操作不会执行任何操作,但它会使 TeX 的扫描仪恢复到接受宏的正常状态\outer
。当然\ŀowercase
不可扩展,但runtoks
允许在可扩展上下文中使用不可扩展的东西。
\documentclass{article}
\begin{document}
\directlua{
local i = luatexbase.new_luafunction'realhasouter'
local j = luatexbase.new_luafunction'hasouter'
local outer_cmds = {
[token.command_id'outer_call'] = true,
[token.command_id'long_outer_call'] = true,
}
lua.get_functions_table()[i] = function()
local delayed_tok = token.get_next()
local tok = token.scan_token()
local cmd = tok.command
if cmd \csstring\~= 1 then
token.put_next(firsttok)
error[[Argument must start with \csstring\{]]
end
local nesting = 0
local result = false
while true do
if cmd == 1 then
nesting = nesting + 1
elseif cmd == 2 then
nesting = nesting - 1
if nesting == 0 then
token.put_next(token.create(result and '@firstoftwo' or '@secondoftwo'))
token.put_next(delayed_tok)
return
end
else
result = result or outer_cmds[cmd]
end
tok = token.get_next()
cmd = tok.command
end
end
local call_realhasouter_toks = {
token.new(0, token.command_id'case_shift'), token.new(0, 1), token.new(0, 2), % This is similar to \lowercase{}. It doesn't do anything, but it changes the status of TeX's scanner to allow outer tokens
token.new(i, token.command_id'lua_expandable_call')
}
lua.get_functions_table()[j] = function()
tex.runtoks(function()
token.put_next(call_realhasouter_toks)
end)
end
token.set_lua('hasouter', j)
}
\def\unfold{\umbrella My bald head always dries so slowly.}
\def\unfoldX{My bald head always dries so slowly.}
\outer\def\umbrella{No raindrops on my head, please! }
\hasouter\expandafter{\unfold}{with}{without} outer
\hasouter\expandafter{\unfoldX}{with}{without} outer
\hasouter{\umbrella My bald head always dries so slowly.}{with}{without} outer
\hasouter{My bald head always dries so slowly.}{with}{without} outer
\edef\xxx{\hasouter{My bald head always dries so slowly.}{with}{without} outer}
\show\xxx
\edef\xxx{\hasouter{\umbrella My bald head always dries so slowly.}{with}{without} outer}
\show\xxx
\end{document}
答案3
这引入了\countouters{<macro>}
,它将计算参数中包含的宏的出现次数\outer
。结果存储在计数器中outercnt
。为了实现这一点,我不得不对 token-digesting 包的逻辑进行调整tokcycle
(插入一个额外的捕获级别来查找预消化的\outer
宏)。
要测试的宏本身可以包含宏和组...然而、\outer
宏不允许在定义的宏内的组内。因此,例如在下面的 MWE 中,如果后来将宏定义为外部宏,则\unfold
不能将其定义为。\def\unfold{\textit{\umbrella}}
\umbrella
\documentclass{article}
\usepackage{listofitems,tokcycle}
\newcounter{outercnt}
\makeatletter
\let\detect@CANTabsorbA\detect@CANTabsorb
\long\def\detect@CANTabsorb{%
\expandafter\def\expandafter\mytmp\expandafter{\meaning\tc@next}%
\expandafter\setsepchar\expandafter{\detokenize{\outer macro:->}}%
\readlist\mylist{\mytmp}%
\tctestifnum{\listlen\mylist[]>1}%
{\stepcounter{outercnt}\expandafter\@tokcycle\string}%
{\detect@CANTabsorbA}%
}
\makeatother
\tokcycleenvironment\countouterenv{}{\processtoks{##1}}{}{}
\newcommand\countouters[1]{\setcounter{outercnt}{0}%
\expandafter\countouterenv#1\endcountouterenv}
\begin{document}
\def\unfold{1. \umbrella My bald head \textit{always} dries so slowly.
My \umbrella}
\outer\def\umbrella{No raindrops on my head, please! }
\countouters{\unfold}
Outer occurences = \theoutercnt
\end{document}
答案4
我注意到 Phelype Oleinik 的回答存在一些问题,因此我尝试找出更好的解决方案。
下面是一个实现。然而绝对不要尝试对代码进行逆向工程。请阅读英文说明,或参阅下面的其他选项。
阶段 0. 准备
如何从输入流中获取去标记化的标记
(标记等于其自身\string
,即与其他 catcode 显式空间或非空间)
(由函数处理remove_next_stringified_character
。)
应用于\meaning
该令牌,则以下内容为blank space
或the character ⟨non-space token⟩
。
如果有 e-TeX 可用,另一种方法(应该更快)是先扩展\unless \if \relax ⟨the character to be removed⟩
一次,然后扩展\fi
。
在保存数据的同时扩展输入流中的以下标记
(实现到编译引擎中)
这部分只是标准技巧。如果我们保留一个数字,则有两种方法
\number ⟨some number⟩ \expandafter ⟨explicit space token⟩ ⟨some other tokens⟩
请注意,只需 1 步即可扩展为⟨some number⟩ ⟨once expansion of these other tokens⟩
。- 将数字包装成
\csname
,进行必要的扩展,然后\cs_to_str:N
返回。(问题:污染哈希表) - 编写一些代码在每个标记前插入一个
\expandafter
标记(这很容易,因为它只是纯数字),然后进行扩展。(好的,这是第三种方法。)
第 1 阶段。处理输入大小
(在函数中实现count_string_len
)
背景
该算法是多遍的,它需要将容器宏作为输入。
首先,有一点问题\meaning
。
假设我们知道\escapechar=-1
,并且以下标记是...
- 炭火
c
猫其他 - 标记
\the
,后跟c
某个外部标记 - 一些外部标记
- 字符
e
猫 } - 原始的
\end
在这种情况下继续进行的唯一方法(*)\meaning
是到下一个标记,但接下来的标记可能是......
end-group character e
来自上述情况 4 或 5the character c
来自上述情况 1 或 2the ⟨some outer token⟩
来自上述案例 2end-⟨some outer token⟩
来自上述情况 4 或 5
前两点已经让人很难区分了,然后仍有可能存在一些后续令牌,outer
因此我们需要\meaning
再次执行它,等等。
(*)(我相信可以\noexpand
在申请之前申请\meaning
。这种方法也可以起作用,但似乎更复杂。)
附注:LuaTeX 似乎存在一些错误,其中标记\meaning
的\noexpand
是[unknown command code! (0, 1)]
而不是通常的\relax
。因此,以下描述假设 noexpand 标记的含义是⟨escape character⟩relax
。
我们在这里要使用\string
,因为反复应用于\string
某事物不会使其大小无限制地增加,即\string
幂等的。
方法
我们将计算所有标记的字符串表示的总长度。
首先放入⟨token list⟩A
输入流,然后重复
- 将下一个标记字符串化。字符串化必须非空。
- 吸收一个字符串化的标记。
这最终将吸收整个令牌列表。
假设在 50 个字符之后,我们命中了A
第 51 个字符。有两种可能性
- 要么我们到达了结尾
⟨token list⟩
,在这种情况下我们报告结果是 50, A
或者在⟨token list⟩中某些token的字符串化中存在一些。
为了仔细检查,我们将其放入⟨token list⟩B
输入流中(请注意,如果第二种情况为真,则输入流中会残留一些垃圾,我们稍后会清理这些垃圾),然后字符串化抓取恰好 50 个标记,将下一个标记字符串化,然后检查它是否为A
或B
。
如果是A
,我们还没有到达标记列表的末尾,所以我们递归调用该函数,知道长度至少为 51 个字符。
内部函数最初会吸收 51 个字符,然后检查是否存在任何A
或B
;经过一些步骤后,它最终应该返回结果。
假设确定实际长度为 100,那么外部函数在将它们字符串化后将需要清理大约 50 个剩余的标记。(确切的数字留给实现者作为练习。)
第二阶段。应用于\meaning
一个代币
(也可以count_string_len
通过传递合适的值来处理)preprocess
假设此时我们知道 ⟨token list⟩ 中所有标记的连接字符串化长度为 100。
对于每个数字 X,我们从 0 迭代到 99:
我们将字符串化/删除前 X 个标记,应用于
\meaning
以下标记(不\string
对其进行验证),然后使用上面的 A/B 翻转想法来获取剩余的标记。这样,我们可以计算所有标记的字符串化的总长度,减去第一次字符串化/删除后生成的标记的长度,再加上它的长度
\meaning
。操作与上述相同,但是
\meaning
我们应用而不是\noexpand
,然后应用\meaning
。请注意,这种方式有两个重要的属性
{
只有 N 类型的 token 的长度会发生变化(在 下、}
、的含义保持不变
\noexpand
)- 所有外部标记的长度都会改变(因为
outer macro
严格长于\relax
,无论 的值如何\escapechar
)
第 3 阶段。计算结果
鉴于上述情况,在迭代值 X 时,在字符串化/删除前 X 个标记后,我们可以简单地\noexpand
获取下一个标记,将其作为参数,然后不难处理它以确定它是否是\outer
。
当我在这里说“简单”时,我的意思是您需要执行以下操作......
- 首先检查字符串“macro:”是否出现在含义中。
- 如果是,则检查“mark”是否出现在第一次出现“macro”之前。
- 如果不是,则检查“outer”是否出现在第一次出现“macro”之前。
我们还知道字符串化的总长度,因此如果我们“复制”而不是吸收标记,我们可以N-X
在检查之后继续字符串化/抓取剩余的标记。
笔记
- 此代码不使用
\numexpr
,而是以一元形式存储“数字”。总体而言,时间复杂度是一样的(由于各种原因,这种方法甚至可能更快)。 - 据我所知,这种方法非常可靠。下面只有最小的测试套件,但请参阅下面链接的源文件中的测试。
替代方法
如果有的\unexpanded
话,只需执行正常的“尾部递归”操作来处理\unexpanded
每一步之后的标记列表和参数。
这要求参数(如果以标记列表而不是容器宏的形式给出)最初被包装在二括号层(一个用于\unexpanded
,另一个用于吸收标记列表)。
实际上,替代方法不起作用,\unexpanded
也不接受输入中的外部标记。不过主要方法确实有效。
实际实施在这里。
%! TEX program = pdflatex
\documentclass{article}
\usepackage{filecontentsdef}
\begin{document}
\ExplSyntaxOn
% ======== some auxiliary macros ========
\def\__process_char #1 #2 {
%\prettye:n{\expandafter \expandafter \expandafter \noexpand \char_generate:nn {`#2} {"#1}}
\expandafter \expandafter \expandafter \noexpand \char_generate:nn {`#2} {"#1}
\__process_s
}
\def\__process_space_other_cat #1 {
\expandafter \expandafter \expandafter \noexpand \char_generate:nn {32} {"#1}
\__process_s
}
\def\__process_cs #1 / {
\expandafter \noexpand \csname #1 \endcsname
\__process_s
}
\def\__process_s#1{
\token_if_eq_charcode:NNTF #1 0 { % 0 <name> / → the control sequence
\__process_cs
} {
\token_if_eq_charcode:NNTF #1 s { ~ \__process_s
} {
\token_if_eq_charcode:NNTF #1 S { % S <cat> → a space
\__process_space_other_cat
} {
\token_if_eq_charcode:NNF #1 . { % . → end
\__process_char #1
}
}
}
}
}
% main handler function, will exec the resulting token list.
\def\__process_all#1{
\begingroup \exp_last_unbraced:Nx \endgroup {\__process_s #1}
}
\ExplSyntaxOff
\begin{filecontentsdefmacro}{\data}
0def/0ifescapenull/1{0texconditional/1{0ifnum/C0C>0escapechar/2}2}0def/0remove_next_stringified_character/1{0expandafter/0stzz31/0meaning/2}0def/0stzz31/6#6#C11{0if/Bb6#6#C10use_ii_to_i:w/0fi/0use_ii:nn/0stzz33/0stzz35/2}0def/0stzz35/ChCesCcChCaCrCaCcCtCeCrs6#6#C11{0exp_end:/2}0def/0stzz33/ClCaCnCksCsCpCaCcCess1{0exp_end:/2}0def/0stringify_until_A/1{0expandafter/0stzz56/0string/2}0def/0stzz56/1{0expandafter/0stzz57/0meaning/2}0def/0stzz57/6#6#C11{0ifx/6#6#C1Cb0use_ii_to_i:w/0fi/0use_ii:nn/0stzz59/0stzz61/2}0def/0stzz61/ChCesCcChCaCrCaCcCtCeCrs6#6#C11{0ifx/6#6#C1CA0use_ii_to_i:w/0fi/0use_ii:nn/0stzz63/0stzz67/2}0def/0stzz63/1{0exp_end:/BA2}0def/0stzz59/ClCaCnCksCsCpCaCcCess1{0expandafter/0exp_end:/0expandafter/C.0romannumeral/0stringify_until_A/2}0def/0stzz67/1{0expandafter/0exp_end:/0expandafter/C.0romannumeral/0stringify_until_A/2}0def/0replicate_helper/6#6#C11{0stzz80/6#6#C12}0def/0stzz80/6#6#C16#6#C21{0stzz83/6#6#C26#6#C12}0def/0stzz83/6#6#C16#6#C21{0ifx/6#6#C10relax/0use_ii_to_i:w/0fi/0use_ii:nn/0stzz85/1{0stzz88/6#6#C22}2}0def/0stzz88/6#6#C11{0expandafter/0stzz103/0expandafter/6#6#C10exp:w/0replicate_helper/6#6#C12}0def/0stzz85/1{0exp_end:/0relax/2}0def/0replicate/6#6#C16#6#C21{0expandafter/0stzz102/0exp:w/0replicate_helper/6#6#C26#6#C10relax/2}0def/0stzz102/6#6#C10relax/1{0exp_end:/6#6#C12}0def/0stzz103/6#6#C11{0exp_end:/6#6#C12}0def/0stringify_remove_next_repeated/1{0expandafter/0stzz125/0exp:w/2}0def/0stzz125/1{0expandafter/0remove_next_stringified_character/0string/2}0def/0stringify_remove_next_repeated_count/6#6#C11{0expandafter/0stzz149/0expandafter/1{0exp:w/0replicate/1{6#6#C12}0stringify_remove_next_repeated/2}2}0def/0remove_next_repeated/1{0expandafter/0stzz143/0exp:w/2}0def/0stzz143/6#6#C11{0exp_end:/2}0def/0remove_next_repeated_count/6#6#C11{0expandafter/0stzz149/0expandafter/1{0exp:w/0replicate/1{6#6#C12}0remove_next_repeated/2}2}0def/0stzz149/6#6#C11{0expandafter/0exp_end:/0romannumeral/6#6#C10exp_end:/2}0def/0exp_forward/1{0expandafter/0stzz157/0exp:w/2}0def/0stzz157/1{0exp_end:/0exp_forward/2}0def/0exp_forward_end/1{0expandafter/0stzz164/2}0def/0stzz164/1{0exp_end:/0exp_forward_end/2}0def/0r_function_do_nothing/1{0exp_end:/2}0def/0r_function_meaning_noexpand/1{0expandafter/0r_function_meaning/0noexpand/2}0def/0r_function_meaning/1{0expandafter/0exp_end:/0meaning/2}0def/0stzz191/6#6#C1CmCaCcCrCoC:6#6#C20relax/1{1{6#6#C12}1{6#6#C22}2}0def/0stzz205/6#6#C1CmCaCrCk1{2}0def/0stzz209/6#6#C1CoCuCtCeCr1{2}0def/0check_outer_aux/1{0expandafter/0stzz184/0noexpand/2}0def/0stzz184/6#6#C11{0expandafter/0stzz187/0expandafter/1{0meaning/6#6#C12}2}0def/0stzz187/6#6#C11{0expandafter/0stzz195/0expandafter/1{0stzz191/6#6#C1CmCaCcCrCoC:0relax/2}6#6#C12}0def/0stzz195/6#6#C11{0stzz195a/6#6#C12}0def/0stzz195a/6#6#C16#6#C21{0ifempty_simple/1{6#6#C22}0exp_end:/1{0stzz205b/1{6#6#C12}2}2}0def/0stzz205b/6#6#C11{0expandafter/0stzz206/0expandafter/1{0stzz205/6#6#C1CmCaCrCk2}1{6#6#C12}2}0def/0stzz206/6#6#C16#6#C21{0ifempty_simple/1{6#6#C12}1{0stzz206b/1{6#6#C22}2}0exp_end:/2}0def/0stzz206b/6#6#C11{0expandafter/0stzz212/0expandafter/1{0stzz209/6#6#C1CoCuCtCeCr2}2}0def/0stzz212/6#6#C11{0ifempty_simple/1{6#6#C12}0exp_end:/0stzz215/2}0def/0stzz215/1{0exp_end:/C12}0def/0count_string_len/6#6#C16#6#C26#6#C36#6#C41{0stzz232/6#6#C11{6#6#C22}1{6#6#C42}1{6#6#C32}2}0def/0stzz232/6#6#C16#6#C26#6#C36#6#C41{0stzz233/6#6#C41{6#6#C22}1{6#6#C32}6#6#C12}0def/0stzz233/6#6#C16#6#C26#6#C36#6#C41{0stzz234/6#6#C3C*1{6#6#C22}6#6#C16#6#C42}0def/0stzz234/6#6#C1C*6#6#C26#6#C36#6#C41{0stzz236/6#6#C1C*6#6#C46#6#C36#6#C2C*2}0def/0stzz236/6#6#C1C*6#6#C26#6#C36#6#C4C*1{0stzz237/6#6#C26#6#C4C*6#6#C36#6#C1C*6#6#C2BA2}0def/0stzz237/6#6#C16#6#C2C*6#6#C36#6#C4C*1{0expandafter/0stzz240/0expandafter/6#6#C10exp:w/6#6#C20expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C30exp:w/6#6#C40expandafter/0exp_end:/0expandafter/C*2}0def/0stzz240/6#6#C16#6#C2C*6#6#C36#6#C4C*1{0stzz240a/6#6#C2C*6#6#C16#6#C4C*6#6#C30exp:w/0stringify_remove_next_repeated_count/1{6#6#C22}2}0def/0stzz240a/6#6#C1C*6#6#C26#6#C3C*6#6#C41{0expandafter/0stzz241/0exp:w/6#6#C10expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C20exp:w/6#6#C30expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C42}0def/0stzz241/6#6#C1C*6#6#C26#6#C3C*6#6#C41{0stzz241a/6#6#C46#6#C26#6#C3C*6#6#C1C*0exp:w/6#6#C42}0def/0stzz241a/6#6#C16#6#C26#6#C3C*6#6#C4C*1{0expandafter/0stzz242/0expandafter/6#6#C10expandafter/6#6#C20exp:w/6#6#C30expandafter/0exp_end:/0expandafter/C*0exp:w/6#6#C40expandafter/0exp_end:/0expandafter/C*2}0def/0stzz242/6#6#C16#6#C26#6#C3C*6#6#C4C*1{0stzz242a/6#6#C3C*6#6#C26#6#C16#6#C4C*0exp:w/0stringify_remove_next_repeated_count/1{6#6#C32}2}0def/0stzz242a/6#6#C1C*6#6#C26#6#C36#6#C4C*1{0expandafter/0stzz246/0exp:w/6#6#C10expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C20expandafter/6#6#C30exp:w/6#6#C40expandafter/0exp_end:/0expandafter/C*2}0def/0stzz246/6#6#C1C*6#6#C26#6#C36#6#C4C*1{0stzz246a/6#6#C1C*6#6#C26#6#C36#6#C4C*0exp:w/0stringify_until_A/2}0def/0stzz246a/6#6#C1C*6#6#C26#6#C36#6#C4C*1{0expandafter/0stzz249/0exp:w/6#6#C10expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C20expandafter/6#6#C30exp:w/6#6#C40expandafter/0exp_end:/0expandafter/C*2}0def/0stzz249/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5BA1{0stzz252/6#6#C4C*6#6#C1C*6#6#C26#6#C31{6#6#C52}2}0def/0stzz252/6#6#C1C*6#6#C2C*6#6#C36#6#C46#6#C51{0expandafter/0stzz253/0expandafter/1{0exp:w/0replicate/1{6#6#C52}0exp_forward/2}6#6#C2C*6#6#C36#6#C46#6#C1C*2}0def/0stzz253/6#6#C16#6#C2C*6#6#C36#6#C46#6#C5C*1{0stzz256/6#6#C5C*6#6#C2C*6#6#C36#6#C46#6#C1C*2}0def/0stzz256/6#6#C1C*6#6#C2C*6#6#C36#6#C46#6#C5C*1{0stzz257/6#6#C1C*6#6#C2C*6#6#C36#6#C46#6#C5C*6#6#C3BB2}0def/0stzz257/6#6#C1C*6#6#C2C*6#6#C36#6#C46#6#C5C*1{0expandafter/0stzz261/0exp:w/6#6#C10expandafter/0exp_end:/0expandafter/C*0exp:w/6#6#C20expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C30expandafter/6#6#C40exp:w/6#6#C50expandafter/0exp_end:/0expandafter/C*2}0def/0stzz261/6#6#C1C*6#6#C2C*6#6#C36#6#C46#6#C5C*1{0stzz261a/6#6#C1C*6#6#C46#6#C36#6#C2C*6#6#C5C*0exp:w/0stringify_remove_next_repeated_count/1{6#6#C12}2}0def/0stzz261a/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*1{0expandafter/0stzz262/0exp:w/6#6#C10expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C20expandafter/6#6#C30exp:w/6#6#C40expandafter/0exp_end:/0expandafter/C*0exp:w/6#6#C50expandafter/0exp_end:/0expandafter/C*2}0def/0stzz262/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*1{0stzz262a/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*0exp:w/6#6#C22}0def/0stzz262a/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*1{0expandafter/0stzz263/0exp:w/6#6#C10expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C20expandafter/6#6#C30exp:w/6#6#C40expandafter/0exp_end:/0expandafter/C*0exp:w/6#6#C50expandafter/0exp_end:/0expandafter/C*2}0def/0stzz263/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*1{0stzz263a/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*0exp:w/0stringify_remove_next_repeated_count/1{6#6#C46#6#C52}2}0def/0stzz263a/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*1{0expandafter/0stzz265/0exp:w/6#6#C10expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C20expandafter/6#6#C30exp:w/6#6#C40expandafter/0exp_end:/0expandafter/C*0exp:w/6#6#C50expandafter/0exp_end:/0expandafter/C*2}0def/0stzz265/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*1{0stzz265a/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*0string/2}0def/0stzz265a/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*1{0expandafter/0stzz271/0exp:w/6#6#C10expandafter/0exp_end:/0expandafter/C*0expandafter/6#6#C20expandafter/6#6#C30exp:w/6#6#C40expandafter/0exp_end:/0expandafter/C*0exp:w/6#6#C50expandafter/0exp_end:/0expandafter/C*2}0def/0stzz271/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*6#6#C61{0stzz272/1{6#6#C62}6#6#C26#6#C1C*6#6#C36#6#C4C*6#6#C5C*2}0def/0stzz272/6#6#C16#6#C26#6#C3C*6#6#C46#6#C5C*6#6#C6C*1{0ifx/6#6#C1CA0use_ii_to_i:w/0fi/0use_ii:nn/1{0stzz284c/6#6#C3C*6#6#C26#6#C46#6#C5C*2}0stzz284/6#6#C6C*2}0def/0stzz284/6#6#C1C*1{0exp_end:/6#6#C12}0def/0stzz284c/6#6#C1C*6#6#C26#6#C36#6#C4C*6#6#C5C*1{0stzz274/6#6#C1C*6#6#C4C*6#6#C36#6#C26#6#C5C*2}0def/0stzz274/6#6#C1C*6#6#C2C*6#6#C36#6#C46#6#C5C*1{0expandafter/0stzz275/0expandafter/1{0exp:w/0count_string_len/6#6#C31{6#6#C12}6#6#C41{6#6#C26#6#C50exp_forward/2}2}6#6#C5C*2}0def/0stzz275/6#6#C16#6#C2C*1{0stzz280/6#6#C1C*6#6#C2C*2}0def/0stzz280/6#6#C1C*6#6#C2C*1{0stzz280a/6#6#C1C*6#6#C2C*0exp:w/0stringify_remove_next_repeated_count/1{6#6#C16#6#C10exp_forward/0exp_forward/2}2}0def/0stzz280a/6#6#C1C*6#6#C2C*1{0expandafter/0stzz281/0exp:w/6#6#C10expandafter/0exp_end:/0expandafter/C*0exp:w/6#6#C20expandafter/0exp_end:/0expandafter/C*2}0def/0stzz281/6#6#C1C*6#6#C2C*1{0exp_end:/6#6#C20exp_forward/6#6#C12}0def/0ifempty_simple/6#6#C11{0ifx/0relax/6#6#C10relax/0expandafter/0use_i:nn/0else/0expandafter/0use_ii:nn/0fi/2}0def/0ifnotempty_simple/6#6#C11{0ifx/0relax/6#6#C10relax/0expandafter/0use_ii:nn/0else/0expandafter/0use_i:nn/0fi/2}0def/0iflenequal_simple/6#6#C16#6#C21{0expandafter/0stzz312/0exp:w/0remove_next_repeated_count/1{6#6#C22}6#6#C1C?6#6#C2C+2}0def/0stzz312/6#6#C16#6#C2C+1{0texconditional/1{0ifx/6#6#C1C?2}2}0def/0if_outer/6#6#C11{0stzz318/6#6#C12}0def/0stzz318/6#6#C11{0expandafter/0stzz321/0expandafter/1{0exp:w/0count_string_len/6#6#C11{2}0r_function_do_nothing/1{2}2}6#6#C12}0def/0stzz321/6#6#C16#6#C21{0expandafter/0stzz326/0expandafter/1{0use_none:n/6#6#C12}1{6#6#C22}2}0def/0stzz322a/6#6#C16#6#C21{0expandafter/0stzz326/0expandafter/1{0use_none:n/6#6#C22}1{6#6#C12}2}0def/0stzz326/6#6#C16#6#C21{0expandafter/0stzz327/0expandafter/1{0exp:w/0count_string_len/6#6#C21{6#6#C12}0r_function_meaning_noexpand/1{2}2}1{6#6#C22}1{6#6#C12}2}0def/0stzz327/6#6#C16#6#C26#6#C31{0expandafter/0stzz328/0expandafter/1{0exp:w/0count_string_len/6#6#C21{6#6#C32}0r_function_meaning/1{2}2}1{6#6#C22}1{6#6#C32}1{6#6#C12}2}0def/0stzz328/6#6#C16#6#C26#6#C36#6#C41{0iflenequal_simple/1{6#6#C42}1{6#6#C12}1{0stzz336f/1{6#6#C32}1{6#6#C22}2}1{0stzz332/1{6#6#C22}1{6#6#C32}1{6#6#C12}2}2}0def/0stzz332/6#6#C16#6#C26#6#C31{0expandafter/0stzz333/0expandafter/1{0exp:w/0count_string_len/6#6#C11{6#6#C22}0check_outer_aux/1{2}2}1{6#6#C12}1{6#6#C22}1{6#6#C32}2}0def/0stzz333/6#6#C16#6#C26#6#C36#6#C41{0iflenequal_simple/1{6#6#C12}1{6#6#C42}1{0stzz336f/1{6#6#C32}1{6#6#C22}2}0use_i:nn/2}0def/0stzz336f/6#6#C16#6#C21{0ifnotempty_simple/1{6#6#C12}1{0stzz322a/1{6#6#C22}1{6#6#C12}2}0use_ii:nn/2}0def/0debug/C1C21{0stzz352/2}0def/0stzz352/C3C41{0exp_end:/2}.
\end{filecontentsdefmacro}
\ExplSyntaxOn
\exp_args:NV \__process_all \data
\def \use_ii_to_i:w \fi \use_ii:nn { \fi \use_i:nn }
\def \texconditional #1 {#1 \use_ii_to_i:w \fi \use_ii:nn}
\ExplSyntaxOff
% -----
% Tests
% -----
\def\rain{My bald head is still wet.}
\def\unfold{\umbrella My bald {head always} dries so slowly.}
\outer\def\umbrella{No raindrops on my head, please! }
\newcount\abc
\tt
3\csname if_outer\endcsname\unfold{T}{F} (T)\par
4\csname if_outer\endcsname\rain{T}{F} (F)\par
7\edef\tmpa{\csname if_outer\endcsname\unfold{T}{F}}\meaning\tmpa (T)\par
\end{document}
我认为它与所有引擎兼容。
正如刚才提到的。绝对不要尝试对代码进行逆向工程。
我从源代码编译了该代码https://github.com/user202729/TeXlib/blob/main/test_imperative2.tex使用我的一个未发布的库。
这个( ,名称可能会根据 CTAN 维护者的要求稍后更改)是为了使可扩展编程/输入流解析宏更容易编写,并减少无意义的、等宏imperative
的数量。(或更糟的是,、、、)_aux
_auxi
_auxii
\macro@
\macro@@
\m@cro
m@cr@
不幸的是,目前还没有文件……