在 TeX 中,各种组必须正确匹配,例如以下内容是错误的:
$ \begingroup $ \endgroup
我希望宏能够允许这种不匹配的分组。它们会将某些标记的作业保留在其分组的“本地”。例如,
% Define \openA, \openB, \localA, \localB.
\def\foo{}
\def\baz{}
\openA
\localA\def\foo{Hello}
\openB
\localB\def\baz{Hello}
\message{\foo,\baz} % "Hello,Hello"
\closeA
\message{\foo,\baz} % ",Hello"
\closeB
\message{\foo,\baz} % ","
我愿意接受精确语法的变化,也愿意接受任何引擎和任何软件包的解决方案。尽量支持所有任务。
编辑:我真正想要的是一种将一些任务保留在与 TeX 自身无关的组中的方法。用这种方式表述问题似乎更容易,而且我认为这两个问题的答案是相同的。
为了使事情更加具体,假设我想将其转换(*-abc*+def(*?gh(i*-jk)lm)no)
为a-b-c-d+e+f+g?h?i?j-k-l?m?n+o+
。规则是将*
下一个字符定义为“当前字符”,局部地放在括号组中,并且“当前字符”应该插入到其他每个正常字符之后。最简单的方法是使用 TeX 的分组来保持“当前字符”的值是局部的。例如,
\def\convert#1{%
\def\storage{}%
\def\currentchar{}%
\readone#1\relax
}
\def\readone#1{%
\global\let\next\readone
\ifx#1\relax \global\let\next\relax
\else
\ifx#1(\begingroup
\else
\ifx#1)\endgroup
\else
\ifx#1*\let\next\star
\else \xdef\storage{\storage #1\currentchar}%
\fi
\fi
\fi
\fi
\next
}
该方法的问题在于\storage
必须全局修改,因为分配发生在 TeX 的组中。在那个特定的应用程序中,这不是问题。在我的应用程序中(用于构建用于正则表达式匹配的自动机),我最终会对任意标记寄存器进行全局分配,这是非常\storage
不好。有两种解决方案:要么将(所有令牌寄存器)的值移到每个 之后,要么在每个 之后\endgroup
恢复。在我的真实应用中,第二种选择可能更好,因此我提出了这个问题。\currentchar
\endgroup
答案1
ConTeXt 提供\pushmacro
和\popmacro
来获取不使用组的局部变量。例如:
\def\foo{}
\def\baz{}
\pushmacro\foo
\def\foo{Hello}
\pushmacro\baz
\def\baz{Hello}
\message{\foo,\baz} % "Hello,Hello"
\popmacro\foo
\message{\foo,\baz} % ",Hello"
\popmacro\baz
\message{\foo,\baz} % ","
STDOUT 上的输出是
Hello,Hello ,Hello ,
这些宏不引入组(因此与 TeX 分组无关)。相反,它们\pushmacro
将宏的当前值保存在堆栈中,并\popmacro
从堆栈中恢复该值 — — 有效地为您提供局部变量。
以下是 syst-aux.mkiv 中这些宏的实现。这也应该适用于纯 tex 和 latex(尽管我还没有测试过)
\def\@sl@{@sl@}
\def\@sg@{@sg@}
\let\@@pushedmacro\empty
\def\globalpushmacro#1%
{\xdef\@@pushedmacro{\string#1}%
\ifcsname\@sg@\@@pushedmacro\endcsname \else
\expandafter\newcount\csname\@sg@\@@pushedmacro\endcsname
\fi
\global\advance\csname\@sg@\@@pushedmacro\endcsname \plusone
\global\expandafter\let\csname\the\csname\@sg@\@@pushedmacro\endcsname\@@pushedmacro\endcsname#1}
\def\globalpopmacro#1%
{\xdef\@@pushedmacro{\string#1}%
\global\expandafter\let\expandafter#1\csname\the\csname\@sg@\@@pushedmacro\endcsname\@@pushedmacro\endcsname
\global\advance\csname\@sg@\@@pushedmacro\endcsname \minusone}
\def\localpushmacro#1% this one can be used to push a value over an \egroup
{\xdef\@@pushedmacro{\string#1}%
\ifcsname\@sl@\@@pushedmacro\endcsname \else
\expandafter\newcount\csname\@sl@\@@pushedmacro\endcsname
\fi
\global\advance\csname\@sl@\@@pushedmacro\endcsname \plusone
\global\expandafter\let\csname\the\csname\@sl@\@@pushedmacro\endcsname\@@pushedmacro\endcsname#1}
\def\localpopmacro#1%
{\xdef\@@pushedmacro{\string#1}%
\expandafter\let\expandafter#1\csname\the\csname\@sl@\@@pushedmacro\endcsname\@@pushedmacro\endcsname
\global\advance\csname\@sl@\@@pushedmacro\endcsname \minusone }
\let\pushmacro\localpushmacro
\let\popmacro \localpopmacro