28

I am seeking to find something that allows me to "broadcast" macros outside a group. Concrete examples include paths and scopes in tizpictures. Here is an M(N)WE.

\documentclass[tikz,border=3.14mm]{standalone}
\usetikzlibrary{calc}
\makeatletter
\let\smuggleoutone\pgfmath@smuggleone
\makeatother
\begin{document}
\begin{tikzpicture}[globalize/.code n args={2}{\xdef#2{#1}},
localize/.code n args={2}{\pgfmathsetmacro{#2}{#1}\typeout{#2}
%\smuggleoutone#1
}]
\begin{scope}[local bounding box=extra]
\path let \p1=($(2,1)-(0,0)$),\n1={atan2(\y1,\x1)} in 
\pgfextra{\xdef\myangle{\n1}};
\node at (1,0) {\myangle};
\end{scope}
\node[anchor=south] at (extra.north) {using \verb|\pgfextra|};
%
\begin{scope}[local bounding box=globalize,xshift=3cm]
\path let \p1=($(2,1)-(0,0)$),\n1={atan2(\y1,\x1)} in 
[globalize={\n1}{\myangle}];
\node at (1,0) {\myangle};
\end{scope}
\node[anchor=south] at (globalize.north) {using \texttt{globalize}};
%
\xdef\myangle{7}
\begin{scope}[local bounding box=localize,xshift=6cm]
\path let \p1=($(2,1)-(0,0)$),\n1={atan2(\y1,\x1)} in 
[localize={\n1}{\myangle}];
\node at (1,0) {\myangle};
\end{scope}
\node[anchor=south] at (localize.north) {attempt to smuggle};
%
\end{tikzpicture}
\end{document}

enter image description here

The two options on the left do partly what I am seeking to do, namely broadcast the macro \myangle outside the path. However, they do it at the expense of making \myangle global. TikZ has some internal commands that may allow one to avoid this, and to just smuggle the macro outside the path. Specifically, @DavidCarlisle suggested in the chat to use pgfmath@smuggleone. However, my above attempts failed, i.e. if I uncomment

%\smuggleoutone#1

the code produces errors.

QUESTION: Can one smuggle the macro outside the group without making it global?

"BONUS": Of course it would be great if there was an explanation what all the smuggle commands do.

"BONUUUUS": Conceivably these methods may be useful independently of TikZ, so if there is a way not to make them depend on TikZ being loaded, this would be great, but is certainly not a requirement.

0

4 Answers 4

34
+500

You can "smuggle" definitions out of their group with the TeX primitive \aftergroup. I'll first explain what \aftergroup does, then give a possible definition of \smuggleone using \aftergroup and finally apply it to your MWE.

The short answer is that you could define \smuggleone (I've removed "out" from the name) as

\newcounter{smuggle}
\DeclareRobustCommand\smuggleone[1]{%
  \stepcounter{smuggle}%
  \expandafter\global\expandafter\let\csname smuggle@\arabic{smuggle}\endcsname#1%
  \aftergroup\let\aftergroup#1\expandafter\aftergroup\csname smuggle@\arabic{smuggle}\endcsname
}

If you paste in this definition and replace \smuggleoutone#1 by \smuggleone#2 in your MWE it should work. (Note that you were passing the wrong argument to \smuggleoutone, it should have been #2 instead of #1.)


About \aftergroup:

It is possible to insert a single token right after the end of the current group using \aftergroup<token>. You can only smuggle out one token at a time, so if you want to move out something consisting of multiple tokens (like a definition) you'll need to \aftergroup each of these tokens separately. This includes things like braces ({}), so for instance

{\aftergroup\def\aftergroup\abc\aftergroup{\aftergroup A\aftergroup B\aftergroup C\aftergroup}}

is equivalent to {}\def\abc{ABC}.

This is quite a hassle, so the following may be more practical:

{\gdef\somethingunique{\def\abc{ABC}}\aftergroup\somethingunique}

This works by globally assigning \def\abc{ABC} to \somethingunique and inserting that after the end of the group. If ABC is replaced by some macro, say \ABC, that is only defined within the current group and that you want to be fully expanded then you'll want to use \xdef instead:

{%
  \newcommand*\ABC{ABC}%
  \xdef\somethingunique{\def\noexpand\abc{\ABC}}%
  \aftergroup\somethingunique
}

I've inserted \noexpand in front of \abc because we don't want \abc to be expanded. If you only want \ABC to be expanded once you can instead use the slightly more complicated

{
  \newcommand*\ABC{\somethingthatshouldntbeexpanded}%
  \xdef\somethingunique{\def\noexpand\abc{\unexpanded\expandafter{\ABC}}}%
  \aftergroup\somethingunique
}

(The primitives \noexpand, \unexpanded and \expandafter are all explained in this this answer.)

To smuggle the definition of \abc out of a group you can do what I just did above with \ABC replaced by \abc itself. That way \abc will be defined as itself (expanded once) immediately after the end of the group.

There's also \AfterGroup from the etextools package. It acts mostly like \aftergroup, but it takes an argument that can consist of any number of tokens. So, for instance, \Aftergroup{\def\abc{ABC}} inserts \def\abc{ABC} after the current group without all of the aforementioned hassle. There's also a starred version, \Aftergroup*, that does the same thing but first expands its arguments fully.

Don't use the etextools package though! It is apparently buggy and no longer maintained and it is incompatible with a bunch of other packages. (Thanks to Ulrike Fischer for pointing that out, here are a few examples: 1, 2, 3, 4.)

Even though you shouldn't use the package, \AfterGroup itself can be quite useful. It is defined as follows:

\makeatletter %% <- make @ usable in command names
\newcount\ettl@fter
\newrobustcmd\AfterGroup{\@ifstar{\ettl@AfterGroup\@firstofone}{\ettl@AfterGroup\unexpanded}}
\newrobustcmd\ettl@AfterGroup[2]{%
   \csxdef{ettl@fterGroup\number\numexpr\the\ettl@fter+1}%
      {\global\csundef{ettl@fterGroup\number\numexpr\the\ettl@fter+1}#1{#2}}%
   \global\advance\ettl@fter\@ne
   \expandafter\aftergroup\csname ettl@fterGroup\the\ettl@fter\endcsname}
\makeatother  %% <- revert @

Defining \smuggleone:

To smuggle a macro that was already defined past the end of a group, it may be more effective to use \let instead of \def. One advantage is that it will also works for macros with arguments:

{
  \newcommand*\abc[1]{``#1''}%
  \global\let\somethingunique\abc
  \aftergroup\let\aftergroup\abc\aftergroup\somethingunique
}
\abc{This works!}

This leads us to a possible definition of \smuggleone.

\documentclass{article}

\newcounter{smuggle}
\DeclareRobustCommand\smuggleone[1]{%
  \stepcounter{smuggle}%
  \expandafter\global\expandafter\let\csname smuggle@\arabic{smuggle}\endcsname#1%
  \aftergroup\let\aftergroup#1\expandafter\aftergroup\csname smuggle@\arabic{smuggle}\endcsname
}

\begin{document}

\newcommand*\abc[1]{\textbf{#1}}%
{%
  {%
    \renewcommand*\abc[1]{``#1''}%
    \smuggleone\abc
    \abc{Local definition}
  }\par
  \abc{Local definition}
}\par
\abc{Global definition}

\end{document}

output

The reason for the use of a counter here is that if you use \somethingunique every time you're smuggling something, it won't really be unique. Whenever multiple smuggling operations are happening consescutively, because you're using \smuggleone multiple times from within the same group or from a group contained in another one where \smuggleone is used, this will cause trouble. The above command therefore creates \smuggle@<n> the <n>-th time it is used.

This can be made more efficient (memory-wise) by reusing these command sequences as much as possible, as in jfbu's answer.


All of this applied to your MWE:

Here is your MWE with two changes: (1) I've added the definition of \smuggleone and (2) I've replaced %\smuggleoutone#1 by \smuggleone#2.

\documentclass[tikz,border=3.14mm]{standalone}
\usetikzlibrary{calc}

\newcounter{smuggle}
\DeclareRobustCommand\smuggleone[1]{%
  \stepcounter{smuggle}%
  \expandafter\global\expandafter\let\csname smuggle@\arabic{smuggle}\endcsname#1%
  \aftergroup\let\aftergroup#1\expandafter\aftergroup\csname smuggle@\arabic{smuggle}\endcsname
}

\begin{document}
\begin{tikzpicture}[globalize/.code n args={2}{\xdef#2{#1}},
localize/.code n args={2}{\pgfmathsetmacro{#2}{#1}\typeout{#2}
\smuggleone#2
}]
\begin{scope}[local bounding box=extra]
\path let \p1=($(2,1)-(0,0)$),\n1={atan2(\y1,\x1)} in 
\pgfextra{\xdef\myangle{\n1}};
\node at (1,0) {\myangle};
\end{scope}
\node[anchor=south] at (extra.north) {using \verb|\pgfextra|};
%
\begin{scope}[local bounding box=globalize,xshift=3cm]
\path let \p1=($(2,1)-(0,0)$),\n1={atan2(\y1,\x1)} in 
[globalize={\n1}{\myangle}];
\node at (1,0) {\myangle};
\end{scope}
\node[anchor=south] at (globalize.north) {using \texttt{globalize}};
%
\xdef\myangle{7}
\begin{scope}[local bounding box=localize,xshift=6cm]
\path let \p1=($(2,1)-(0,0)$),\n1={atan2(\y1,\x1)} in 
[localize={\n1}{\myangle}];
\node at (1,0) {\myangle};
\end{scope}
\node[anchor=south] at (localize.north) {attempt to smuggle};
%
\end{tikzpicture}
\end{document}
\node[anchor=south] at (globalize.north) {using \texttt{globalize}};
%
\xdef\myangle{7}
\begin{scope}[local bounding box=localize,xshift=6cm]
\path let \p1=($(2,1)-(0,0)$),\n1={atan2(\y1,\x1)} in 
[localize={\n1}{\myangle}];
\node at (1,0) {\myangle};
\end{scope}
\node[anchor=south] at (localize.north) {attempt to smuggle};
%
\end{tikzpicture}
\end{document}

output



Addendum

Here's a \smuggle macro that works up to depth 10. It doesn't let you smuggle anything across eleven borders because 10 is two tokens (yeah, that's a stupid reason). I could make it work for any depth, but I like how short the definition currently is and it seems unlikely that any sane person would need this.

The syntax is \smuggle[<depth>]{<macro>}, and the default <depth> is 1. It works by calling \smuggleone and then also \aftergrouping \smuggle[<depth-1>]{<macro>}.

\documentclass{article}

\newcounter{smuggle}
\DeclareRobustCommand\smuggleone[1]{%
  \stepcounter{smuggle}%
  \expandafter\global\expandafter\let\csname smuggle@\arabic{smuggle}\endcsname#1%
  \aftergroup\let\aftergroup#1\expandafter\aftergroup\csname smuggle@\arabic{smuggle}\endcsname
}
\DeclareRobustCommand\smuggle[2][1]{%
  \smuggleone{#2}%
  \ifnum#1>1
    \aftergroup\smuggle\aftergroup[\expandafter\aftergroup\the\numexpr#1-1\aftergroup]\aftergroup#2%
  \fi
}

\begin{document}

\newcommand*\abc[1]{\textbf{#1}}
{%
  {%
    {%
      \renewcommand*\abc[1]{``#1''}%
      \smuggle[2]{\abc}%
      Definition at depth 3: \abc{Local definition}
    }\par
    Definition of depth 2: \abc{Local definition}
  }\par
  Definition of depth 1: \abc{Local definition}
}\par
Definition at depth 0: \abc{Global definition}

\end{document}

output

5
  • 2
    Don't use etextools. Search the site for some of its incompabilies. Commented Jan 20, 2019 at 7:36
  • @UlrikeFischer: Oh, that is good to know. Do you have a suggestion about what to use instead? (I'll just copy the definition of \Aftergroup for now.) Commented Jan 20, 2019 at 9:35
  • come on, we all need \SmuggleOutTo{comma separated list of upper levels}\foo, e.g., \SmuggleOutTo{+1, +4, +6, +13..\infty}\Foo will mean let \Foo be known 1 level up, 4 levels up, 6 levels up and 13 levels up and all the way to infty, whereas \SmuggleOutTo{\infty}\Foo will smuggle only to top level. What are you waiting for to do the real job ? :)
    – user4686
    Commented Jan 20, 2019 at 15:17
  • Hah, indeed! :) Commented Jan 20, 2019 at 15:19
  • @Circumscribe No worries. It will take a while till I smuggle the information into my brain. ;-)
    – user121799
    Commented Jan 20, 2019 at 23:53
15

The usual approach is to \expandafter around the end-of-group

\begingroup
  % Various things
  \def\result{some-tokens-that-need-to-escape}%
\expandafter\endgroup
\expandafter\def\expandafter\result\expandafter{\result}

That can be expressed slightly more concisely if using expl3

\group_begin:
  % Stuff to set
  \tl_set:Nn \l_result_tl { some-tokens-that-need-to-escape }
\exp_args:NNNV \group_end:
\tl_set:Nn \l_result_tl \l_result_tl

In either case, one could define something like

\protected\def\smuggleone#1#2\endgroup{%
  #2%
  \expandafter\endgroup
  \expandafter\def\expandafter#1\expandafter{#1}%
}
1
  • 2
    I am wondering if you could add an example for how this is to be used in my above MWE?
    – user121799
    Commented Jan 20, 2019 at 16:02
13

This covers various things:

  • smuggling a (no-parameter; see bottom of answer for macros with parameters) macro one level up,

  • (bizarre, for fun) smuggle it two level up, but it remains undefined one level up,

  • (more useful) smuggle one level up the contents of some macro, which is way to execute after group closes arbitrarily many tokens (if they are still defined at that level of course).

There is some subtlety in the way some globally defined auxiliary macros are indexed (their index is never globally increased), but I think it is ok. I hesitated about adding extra code to let them globally undefined once used, but did not do that finally. The same index will be used multiple times, but I think no non-yet needed thing ever gets overwritten. (although I may need to think about it more).

\documentclass{article}

\newcount\goodiescount

\makeatletter
\def\SmuggleMacro{%
    \advance\goodiescount 1 % not done globally !
    \expandafter\SmuggleMacro@aux
                \csname Goodies\the\goodiescount\endcsname
}%
\def\SmuggleMacro@aux #1#2{%
    \expandafter\gdef\expandafter#1\expandafter
        {\expandafter\def\expandafter#2\expandafter{#2}}%
    \aftergroup#1%
}%
% This one will let the macro be known two levels higher,
% but not if only one level higher
\def\SmuggleMacroUpTwo{%
    \advance\goodiescount 1 % not done globally !
    \expandafter\SmuggleMacroUpTwo@aux
                \csname Goodies\the\goodiescount\endcsname
}%
\def\SmuggleMacroUpTwo@aux#1#2{%
    \expandafter\gdef\expandafter#1\expandafter
        {\expandafter\def\expandafter#2\expandafter{#2}}%
    \aftergroup\SmuggleValue
    \aftergroup#1%
}%
\def\SmuggleValue{%
    \advance\goodiescount 1 % not done globally ! 
    \expandafter\SmuggleValue@aux
                \csname Goodies\the\goodiescount\endcsname
}%
\def\SmuggleValue@aux #1#2{%
    \global\let#1#2%
    \aftergroup#1%
}%


\makeatother

\begin{document}

\tt

\begingroup
    \typeout{DEPTH 1}%
    \def\fuzz{FUZZ defined at depth 1 and smuggled}%
    \SmuggleMacro\fuzz
    \begingroup
        \typeout{DEPTH 2}%
        \def\baz{BAZ defined at depth 2 and smuggled up two}%
        \SmuggleMacroUpTwo\baz
        \begingroup
            \typeout{DEPTH 3}%
            DEPTH 3\par
            \def\foo{FOO defined at depth 3 and smuggled}%
            \SmuggleMacro\foo
            \def\bar{BAR defined at depth 3 and smuggled up two}%
            \SmuggleMacroUpTwo\bar
            END OF FIRST DEPTH 3\par
        \endgroup
        at depth 2 in-between the two depth 3\par
        \string\foo\space has meaning \meaning\foo\space and will be smuggled again\par
        \string\bar\space has meaning \meaning\bar\par
        \SmuggleMacro\foo
        \begingroup
            DEPTH 3\par
            \typeout{SECOND TIMES AT DEPTH 3}%
            \def\foofoo{FOOFOO defined at (second) depth 3 and smuggled}%
            \SmuggleMacro\foofoo
            \def\Truc{\par Hello, I am \string\Truc\space 
                      I was defined at depth 3, but got executed
                      at depth 2!\par
                      My own meaning is now: \meaning\Truc\par
                      \typeout{DEPTH 2 AFTER 3}}%
            \show\Truc
            \SmuggleValue\Truc
            END OF SECOND DEPTH 3\par
        \endgroup
        BACK TO DEPTH 2 (after executing aftergroup tokens)\par
        \show\Truc
        \show\fuzz
        \show\baz
        \show\foo
        \show\foofoo
        \show\bar
    \endgroup
    BACK TO DEPTH 1 (after executing aftergroup tokens)\par
    \string\foo\space has meaning \meaning\foo\par
    \string\bar\space has meaning \meaning\bar\par
    \typeout{DEPTH 1 AFTER 2}%
    \show\fuzz
    \show\baz
    \show\foo
    \show\foofoo
    \show\bar
\endgroup
BACK TO DEPTH 0 (after executing aftergroup tokens)\par
\string\foo\space has meaning \meaning\foo\par
\typeout{DEPTH 0 AFTER 1}
\show\fuzz
\show\baz
\show\foo
\show\foofoo
\show\bar

\end{document}

enter image description here

DEPTH 1
DEPTH 2
DEPTH 3

SECOND TIMES AT DEPTH 3
> \Truc=macro:
->\par Hello, I am \string \Truc \space I was defined at depth 3, but got executed at depth 2!\par My own meaning is now: \meaning \Truc \par \typeout {DEPTH 2 AFTER 3}.
l.77             \show\Truc


DEPTH 2 AFTER 3
> \Truc=undefined.
l.82         \show\Truc

> \fuzz=macro:
->FUZZ defined at depth 1 and smuggled.
l.83         \show\fuzz

> \baz=macro:
->BAZ defined at depth 2 and smuggled up two.
l.84         \show\baz

> \foo=macro:
->FOO defined at depth 3 and smuggled.
l.85         \show\foo

> \foofoo=macro:
->FOOFOO defined at (second) depth 3 and smuggled.
l.86         \show\foofoo

> \bar=macro:
->\mathaccent "7016\relax .
l.87         \show\bar

DEPTH 1 AFTER 2
> \fuzz=macro:
->FUZZ defined at depth 1 and smuggled.
l.92     \show\fuzz

> \baz=undefined.
l.93     \show\baz

> \foo=macro:
->FOO defined at depth 3 and smuggled.
l.94     \show\foo

> \foofoo=undefined.
l.95     \show\foofoo

> \bar=macro:
->BAR defined at depth 3 and smuggled up two.
l.96     \show\bar

DEPTH 0 AFTER 1
> \fuzz=macro:
->FUZZ defined at depth 1 and smuggled.
l.101 \show\fuzz

> \baz=macro:
->BAZ defined at depth 2 and smuggled up two.
l.102 \show\baz

> \foo=undefined.
l.103 \show\foo

> \foofoo=undefined.
l.104 \show\foofoo

> \bar=macro:
->\mathaccent "7016\relax .
l.105 \show\bar


Addendum

I am adding \SmuggleMacroNtimesUp <number>.\macro which will let the \macro be known <number> levels up (of course, to the extent that its meaning uses tokens known at these levels...). Currently only parameter less macros, because this is how I started this...

Not much tested. In fact only tested on the single example below...

\documentclass{article}

\newcount\goodiescount

\makeatletter
\def\SmuggleMacro{%
    \advance\goodiescount 1 % not done globally !
    \expandafter\SmuggleMacro@aux
                \csname Goodies\the\goodiescount\endcsname
}%
\def\SmuggleMacro@aux #1#2{%
    \expandafter\gdef\expandafter#1\expandafter
        {\expandafter\def\expandafter#2\expandafter{#2}}%
    \aftergroup#1%
}%
% This one will let the macro be known two levels higher,
% but not if only one level higher
\def\SmuggleMacroUpTwo{%
    \advance\goodiescount 1 % not done globally !
    \expandafter\SmuggleMacroUpTwo@aux
                \csname Goodies\the\goodiescount\endcsname
}%
\def\SmuggleMacroUpTwo@aux#1#2{%
    \expandafter\gdef\expandafter#1\expandafter
        {\expandafter\def\expandafter#2\expandafter{#2}}%
    \aftergroup\SmuggleValue
    \aftergroup#1%
}%
\def\SmuggleValue{%
    \advance\goodiescount 1 % not done globally ! 
    \expandafter\SmuggleValue@aux
                \csname Goodies\the\goodiescount\endcsname
}%
\def\SmuggleValue@aux #1#2{%
    \global\let#1#2%
    \aftergroup#1%
}%
%
% This one makes known the macros 1, 2, ..., N levels up.
% Syntax \SmuggleMacroNtimesUp<number>.\macro
\def\SmuggleMacroNtimesUp{%
    \advance\goodiescount 1 % not done globally!
    \expandafter\SmuggleMacroNtimesUp@aux
                \csname Goodies\the\goodiescount\endcsname
}%     
\def\SmuggleMacroNtimesUp@aux#1#2.#3{%
    \expandafter\gdef\expandafter#1\expandafter
        {\expandafter\def\expandafter#3\expandafter{#3}}%
    \aftergroup#1%
    \expandafter\SmuggleMacroNtimesUp@a\the\numexpr#2-1.#1%
}%
\def\SmuggleMacroNtimesUp@a#1{%
    \if0#1\expandafter\@gobbletwo
    \else
       \aftergroup\SmuggleValueNtimesUp
       \aftergroup #1%
       \expandafter\SmuggleNtimesUp@loop
    \fi
}%
\def\SmuggleNtimesUp@loop#1{%
    \aftergroup#1%
    \if.#1\expandafter\aftergroup
    \else
       \expandafter\SmuggleNtimesUp@loop
    \fi
}%
% This one makes **executes the macro**
% at all levels 1, 2, ..., N up.
% Syntax \SmuggleValueNtimesUp<number>.\macro
\def\SmuggleValueNtimesUp{%
    \advance\goodiescount 1 % not done globally!
    \expandafter\SmuggleValueNtimesUp@aux
                \csname Goodies\the\goodiescount\endcsname
}%     
\def\SmuggleValueNtimesUp@aux#1#2.#3{%
    \global\let#1#3%
    \aftergroup#1%
    \expandafter\SmuggleValueNtimesUp@a\the\numexpr#2-1.#1%
}%
\def\SmuggleValueNtimesUp@a#1{%
    \if0#1\expandafter\@gobbletwo
    \else
       \aftergroup\SmuggleValueNtimesUp
       \aftergroup #1%
       \expandafter\SmuggleNtimesUp@loop
    \fi
}%

\makeatother

\begin{document}

\ttfamily

\def\foo{}
{{{{{{{{% 8 deep
{{{{{{{{% 16 deep
   \def\foo{FOO defined at 16 will be made known all the way to 3}%
   \SmuggleMacroNtimesUp13.\foo
16.\foo\par}%
15.\foo\par}%+1
14.\foo\par}%+2
13.\foo\par}%+3
12.\foo\par}%+4
11.\foo\par}%
10.\foo\par}%
9.\foo\par}%
8.\foo\par}%+8
7.\foo\par}%
6.\foo\par}%
5.\foo\par}%
4.\foo\par}%+12
3.\foo\par}%+13
2.\foo\par}%
1.\foo\par}%
0.\foo\par

\end{document}

enter image description here



Final version

Under pressure of @Circumscribe example I have refactored to handle macros with parameters. Not much tested... Added @marmot query about moving meaning to top level (aka bottom level...)

Thus the defined things are

  • \SmuggleMacro \foo : makes \foo keep its meaning one level up,

  • \SmuggleMacroUpTwo \foo: makes \foo recover its meaning two levels up (but not one level up...)

  • \SmuggleMacroNtimesUp <number>.\foo : makes \foo keep its meaning for the <number> less nested levels. Must be used with <number> at least 1.

  • \SmuggleValueNtimesUp <number>.\foo : executes the meaning of \foo for the <number> less nested levels, via \aftergroup so as soon as a more nested level is left. \foo itselfs (if not globally defined) is not smuggled.

  • \SmuggleMacroToTop\foo : makes \foo known at bottom level (sic), but not at any intermediate level (of course while moving from inner to outer, once we reach bottom level, next time we enter a group \foo will be known).

(if the code looks crazy, it is also because it tries to define the less auxiliary storage macros, by keeping this idea of never globally stepping the index of these storage things)

\documentclass{article}
\usepackage{geometry}
\newcount\goodiescount

\makeatletter
\def\SmuggleMacro{%
    \advance\goodiescount 1 % not done globally !
    \expandafter\SmuggleMacro@aux
                \csname Goodies\the\goodiescount\endcsname
}%
\def\SmuggleMacro@aux#1#2{%
    \global\let#1#2%
    \aftergroup\let
    \aftergroup#2%
    \aftergroup#1%
}%
% This one will let the macro be known two levels higher,
% but not if only one level higher
\def\SmuggleMacroUpTwo{%
    \advance\goodiescount 1 % not done globally !
    \expandafter\SmuggleMacroUpTwo@aux
                \csname Goodies\the\goodiescount\endcsname
}%
\def\SmuggleMacroUpTwo@aux#1#2{%
    \global\let#1#2%
    \aftergroup\SmuggleLet
    \aftergroup#2%
    \aftergroup#1%
}%
\def\SmuggleLet{%
    \advance\goodiescount 1 % not done globally ! 
    \expandafter\SmuggleLet@aux
                \csname Goodies\the\goodiescount\endcsname
}%
\def\SmuggleLet@aux#1#2#3{%
    \global\let#1#3%
    \aftergroup\let
    \aftergroup#2%
    \aftergroup#1%
}%
%
% This one makes known the macros 1, 2, ..., N levels up.
% Syntax \SmuggleMacroNtimesUp<number>.\macro
\def\SmuggleMacroNtimesUp{%
    \advance\goodiescount 1 % not done globally!
    \expandafter\SmuggleMacroNtimesUp@aux
                \csname Goodies\the\goodiescount\endcsname
}%     
\def\SmuggleMacroNtimesUp@aux#1#2.#3{%
    \global\let#1#3%
    \aftergroup\let
    \aftergroup#3%
    \aftergroup#1%
    \expandafter\SmuggleMacroNtimesUp@a\the\numexpr#2-1.#3%
}%
%\long\def\@gobblethree#1#2#3{}%
\def\SmuggleMacroNtimesUp@a#1{%
    \if0#1\expandafter\@gobbletwo
    \else
       \aftergroup\SmuggleMacroNtimesUp
       \aftergroup #1%
       \expandafter\SmuggleNtimesUp@loop
    \fi
}%
\def\SmuggleNtimesUp@loop#1{%
    \aftergroup#1%
    \if.#1\expandafter\aftergroup
    \else
       \expandafter\SmuggleNtimesUp@loop
    \fi
}%
\def\SmuggleValueNtimesUp{%
    \advance\goodiescount 1 % not done globally!
    \expandafter\SmuggleValueNtimesUp@aux
                \csname Goodies\the\goodiescount\endcsname
}%     
\def\SmuggleValueNtimesUp@aux#1#2.#3{%
    \global\let#1#3%
    \aftergroup#1%
    \expandafter\SmuggleValueNtimesUp@a\the\numexpr#2-1.#1%
}%
\def\SmuggleValueNtimesUp@a#1{%
    \if0#1\expandafter\@gobbletwo
    \else
       \aftergroup\SmuggleValueNtimesUp
       \aftergroup #1%
       \expandafter\SmuggleNtimesUp@loop
    \fi
}%

% \SmuggleMacroToTop
\def\SmuggleMacroToTop{%
    \ifnum\currentgrouplevel=\z@
          \expandafter\@gobble
    \else
          \expandafter\SmuggleMacro@ToTop
    \fi
}%
\def\SmuggleMacro@ToTop{%
    \advance\goodiescount 1 % not done globally!
    \expandafter\SmuggleMacroToTop@aux
                \csname Goodies\the\goodiescount\endcsname
}%     
\def\SmuggleMacroToTop@aux#1#2{%
    \global\let#1#2%
    \aftergroup\SmuggleLetToTop
    \aftergroup#2%
    \aftergroup#1%
}%
\def\SmuggleLetToTop{%
    \ifnum\currentgrouplevel=\z@
          \expandafter\let
    \else
          \expandafter\SmuggleLet@ToTop
    \fi
}%
\def\SmuggleLet@ToTop{%
    \advance\goodiescount 1 % not done globally ! 
    \expandafter\SmuggleLetToTop@aux
                \csname Goodies\the\goodiescount\endcsname
}%
\def\SmuggleLetToTop@aux#1#2#3{%
    \global\let#1#3%
    \aftergroup\SmuggleLetToTop
    \aftergroup#2%
    \aftergroup#1%
}%
\makeatother

\begin{document}

\ttfamily

\def\foo{}
{{{{{{{{% 8 deep
{{{{{{{{% 16 deep
   \def\BAR#1#2#3{Hello, I am BAR}%
   \SmuggleMacro\BAR
   \SmuggleMacroToTop\BAR
   \def\BAZ#1#2#3#4{Hello, I am BAZ}%
   \SmuggleMacroUpTwo\BAZ
   \def\foo#1#2{FOO defined at 16 will be made known all the way to 3}%
   \SmuggleMacroNtimesUp13.\foo
16.FOO \meaning\foo\par
16.BAZ \meaning\BAZ\par
16.BAR \meaning\BAR\par
\def\x{\leavevmode\llap{aaa }}%
\SmuggleValueNtimesUp7.\x
\medskip}%
15.FOO \meaning\foo\par
15.BAZ \meaning\BAZ\par
15.BAR \meaning\BAR\par\medskip}%
14.FOO \meaning\foo\par
14.BAZ \meaning\BAZ\par
14.BAR \meaning\BAR\par\medskip}%
13.FOO \meaning\foo\par}%+3
12.FOO \meaning\foo\par}%+4
11.FOO \meaning\foo\par}%
10.FOO \meaning\foo\par}%
9.FOO \meaning\foo\par
9.BAR \meaning\BAR\par
}%
8.FOO \meaning\foo\par}%+8
7.FOO \meaning\foo\par}%
6.FOO \meaning\foo\par}%
5.FOO \meaning\foo\par
5.BAR \meaning\BAR\par}%
4.FOO \meaning\foo\par}%+12
3.FOO \meaning\foo\par}%+13
2.FOO \meaning\foo\par}%
1.FOO \meaning\foo\par}%
0.FOO \meaning\foo\par
0.BAR \meaning\BAR\par
\end{document}

enter image description here

6
  • although I may need to think about it more: I think it is fine, although I have not really thought at depth. (ah ah).
    – user4686
    Commented Jan 20, 2019 at 13:33
  • Trying to mentally parse \SmuggleMacroUpTwo makes my head hurt, but it appears to be airtight. You're really minimising the number of different macros that need to be assigned by keeping track of how many smuggling operations are currently ongoing and assigning them appropriate numbers :). Commented Jan 20, 2019 at 14:20
  • @Circumscribe yes, after a group closes, new definition of a given index can only happen after or at same time its old meaning is actually needed, and when you open an inner group you can only then define macros with a higher index than what you had stored earlier as to be used after the outer group. Thus, ..., it should be airtight indeed, but indeed a bit head-spinning :)
    – user4686
    Commented Jan 20, 2019 at 15:04
  • Did you read " Of course, \infty means that this macro has the value in all LaTeX documents of the world. ;-) " ? ;-)
    – user121799
    Commented Jan 20, 2019 at 17:56
  • @marmot I understood that I would become rich, did I get that right?
    – user4686
    Commented Jan 20, 2019 at 17:58
8

New in PGF 3.1.3

This version introduces \pgfutil@pushmacro and \pgfutil@popmacro from ConTeXt. With these two operation you can push macro onto and pop them from a stack. The underlying macro definition also use globals (the only way you could possibly get around that is probably using Lua), but the macro name is sufficiently obfuscated so it is unlikely to clash.

It's not so different from the other answers but this method is now built into PGF.

\documentclass{article}
\usepackage{pgf}
\begin{document}

\makeatletter

\def\zz{a}
\show\zz

{
    {
        \def\zz{b}
        \show\zz
        \pgfutil@pushmacro\zz
        \def\zz{c}
        \show\zz
    }
    \pgfutil@popmacro\zz
    \show\zz
}
\show\zz

\makeatother

\end{document}
> \zz=macro:
->a.
l.8 \show\zz

? 
> \zz=macro:
->b.
l.13         \show\zz

? 
> \zz=macro:
->c.
l.16         \show\zz

? 
> \zz=macro:
->b.
l.19     \show\zz

? 
> \zz=macro:
->a.
l.21 \show\zz

? 
6
  • 1
    @Schrödinger'scat Fixed in github.com/pgf-tikz/pgf/commit/… Commented May 24, 2020 at 1:57
  • Thanks a lot! (So I guess one has to wait till 3.1.6 before one can use it, right?)
    – user194703
    Commented May 24, 2020 at 2:02
  • 1
    @Schrödinger'scat Yes, or you backport the fix by copying the whole definition of \pgfutil@pushmacro into your preamble. Commented May 24, 2020 at 3:17
  • 1
    This is a really remarkable and useful tool. So one smuggle super easily out of \foreach loops and paths. Thanks a lot!
    – user194703
    Commented May 24, 2020 at 4:01
  • 1
    Actually I think that a remember outside key for the \foreach loops would be a really useful key. Using your push and pop methods I was able to construct something of that sort very quickly. Of course, to have something stable is more efforts, but I really think this would be useful so I plan to look into this in more detail.
    – user194703
    Commented May 24, 2020 at 5:05

You must log in to answer this question.