Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

In TeX, various kinds of groups must match properly, for instance, the following is wrong:

$ \begingroup $ \endgroup

I would like macros which allow such mismatched grouping. They would keep certain marked assignments "local" to their grouping. For instance,

% 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} % ","

I am open to variations on the precise syntax, and to a solution in any engine with any package. Try be as close as possible to supporting all assignments.

EDIT: What I really want is a way of keeping some assignments local to groups which are independent of TeX's own. It just seemed easier to phrase the question in this way, and I think that solutions to both questions are identical.

To make things concrete, imagine that I want to convert (*-abc*+def(*?gh(i*-jk)lm)no) into a-b-c-d+e+f+g?h?i?j-k-l?m?n+o+. The rule would be that * defines the next character as the "current character", locally to parenthesized groups, and that the "current character" should be inserted after each other normal character. The easiest way to do that is to use TeX's grouping to keep the value of the "current character" local. For instance,

\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
}

The problem with that approach is that \storage must be modified globally, since the assignments happen within TeX's groups. In that particular application, it is not a problem. In my application (to the construction of an automaton for regular expression matching), I would end up doing global assignments to arbitrary token registers, which is very bad. Two solutions: either carry the value of \storage (all token registers) past each \endgroup, or restore the \currentchar at each \endgroup. In my true application, that second option is probably better, hence my question.

share|improve this question
    
Is it possible to give an example of usage i.e., application to understand better as to what you are looking for? –  Yiannis Lazarides Nov 21 '11 at 8:22
    
@YiannisLazarides I added an example (sorry it's so long, any help in making the example simpler is welcome). –  Bruno Le Floch Nov 21 '11 at 9:11
add comment

1 Answer

up vote 10 down vote accepted

ConTeXt provides \pushmacro and \popmacro for getting local variables without using groups. For example:

\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} % ","

The output on STDOUT is

Hello,Hello ,Hello ,

These macros do not introduce a group (hence are independent of the TeX grouping). Instead, \pushmacro saves the current value of the macro on a stack and \popmacro restores the value from the stack---effectively giving you local variables.

Below is the implementation of these macros from syst-aux.mkiv. This should also work with plain tex and latex (although I have not tested it)

\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
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.