模拟 TeX 的分组

模拟 TeX 的分组

在 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

相关内容