什么它可以做:

什么它可以做:

我认为以下问题毫无意义。

尽管如此,我自己却没有看到一个好的、强大的解决方案,这让我感到很难过。;-)


假设这样的事情:

\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

我到底陷入了什么境地……

有三种可能的方法:

  1. \def\outer{}继续你的生活。毫无疑问最好的选择(在全部方面)。
  2. 我敢打赌,用 TeX 实现\suppressoutererror这一点比编写 TeX 代码要容易得多。
  3. 现在是 2020 年,Knuth 将在这一年解决已报告的 TeX 错误。提交\outer以供考虑。
  4. 我说
  5. 我想如果你还在读的话,你希望我展示一些代码。那就继续阅读吧 :-)

宏的问题\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]匹配所有标记,然后如前所述将其删除。\outerFNo 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:由于代码无法查看实际的控制序列,该过程并不完全稳健。假设:
    %                  V------V
    \ifouterarg{\outer macro:->}{T}{F}
    
    如果你设法将标记的 token 的 catcode 更改为 12,那么是的,你成功地欺骗了代码,让它认为你有一个\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)它就不会逐个使用\co、 . . (使用\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'12

\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 spacethe 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,并且以下标记是...

  1. 炭火c猫其他
  2. 标记\the,后跟c某个外部标记
  3. 一些外部标记
  4. 字符e猫 }
  5. 原始的\end

在这种情况下继续进行的唯一方法(*)\meaning是到下一个标记,但接下来的标记可能是......

  • end-group character e来自上述情况 4 或 5
  • the character c来自上述情况 1 或 2
  • the ⟨some outer token⟩来自上述案例 2
  • end-⟨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 个标记,将下一个标记字符串化,然后检查它是否为AB

如果是A,我们还没有到达标记列表的末尾,所以我们递归调用该函数,知道长度至少为 51 个字符。

内部函数最初会吸收 51 个字符,然后检查是否存在任何AB;经过一些步骤后,它最终应该返回结果。

假设确定实际长度为 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@crom@cr@

不幸的是,目前还没有文件……

相关内容