Push/Pop or save a length/dimension?

Is it possible to push or pop a length like parindent? I want to change it temporarily but reset it soon after.

I have a solution but looking for something a little nicer. I'll post it as an answer.

-
What's the intention of your question? For example you can change the length inside an environment or group and the effect is local. –  Marco Daniel Apr 7 '12 at 10:14
@MarcoDaniel No, not necessarily, although that might be a another method. A stack. \push(\parindent) and \pop(\parindent). So I can change the parindent at will and restore it at will. –  Uiy Apr 7 '12 at 11:09

You can implement a stack without using LaTeX3 as well, by using a token list and some macros. That would work as follows:

\documentclass{article}
\newtoks\paridstack
\paridstack={\empty}
\def\push#1#2{%
\begingroup\toks0={{#1}}%
\edef\act{\endgroup\global#2={\the\toks0 \the#2}}\act
}% push #1 onto #2
\def\pop#1{%
\begingroup
\edef\act{\endgroup\noexpand\splitList\the#1(tail)#1}\act
}% pop from #1
\def\splitList#1#2(tail)#3{%
\ifx#1\empty Can't pop an empty stack.\else#1\global#3={#2}\fi
}
\begin{document}
\noindent
\push{200pt}{\paridstack}%
\pop{\paridstack}\\
\push{200pt}{\paridstack}%
\push{300pt}{\paridstack}%
\push{400pt}{\paridstack}%
\pop{\paridstack}\\
\pop{\paridstack}\\
\pop{\paridstack}\\
\pop{\paridstack}
\end{document}


It will print 200pt 400pt 300pt 200pt Can't pop from an empty stack.. You can of course replace the error message with some error handling code, or some default value to be returned. This is all based on an example in chapter 14 of TeX by Topic. If you want to set \parindent directly instead of just printing the value, simply change #1 in the \else of \splitList to \parindent=#1.

-
This would be rather difficult to manage if groups are involved and probably global assignments should be used. –  egreg Apr 7 '12 at 12:56
@egreg: good point. I added two \globals. I think this should be sufficient to handle groups. Of course, the assignment to \parindent would have to be made global as well for it to persist after the end of a group. –  Roelof Spijker Apr 7 '12 at 13:05
It's unclear, from what the OP says, whether the \parindent assignment should be global or not. –  egreg Apr 7 '12 at 13:11
@egreg can you tell me to me to a resource that explains what a latex group is? There seems to be something special about it than my basic understanding... –  Uiy Apr 8 '12 at 10:49
@RoelofSpijker Basically what I want assuming it all works out ;) –  Uiy Apr 8 '12 at 10:49

\newlength{\savedparindent}


and, when you want to change the \parindent, say

\setlength{\savedparindent}{\parindent}
\setlength{\parindent}{<dimen>}


Then you can restore the previous \parindent by

\setlength{\parindent}{\savedparindent}


This is even unnecessary if you use the environment structure:

\newenvironment{otherparindent}[1]
{\par\setlength{\parindent}{#1}}
{\par}


so that the change to \parindent is confined to the environment. Of course it's not necessary to use such an environment: you can put the \setlength{\parindent}{<dimen>} code in the definition of any environment.

A stack based solution is easily obtained with LaTeX3:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\seq_new:N \g_uiy_parindent_seq

\NewDocumentCommand{\pushparindent}{m}
{
\seq_gpush:Nx \g_uiy_parindent_seq {\the\parindent}
\dim_gset:Nn \parindent {#1}
}
\NewDocumentCommand{\popparindent}{}
{
\seq_gpop:NN \g_uiy_parindent_seq \l_tmpa_tl
\dim_gset:Nn \parindent {\l_tmpa_tl}
}
\ExplSyntaxOff

\begin{document}

\showthe\parindent

\pushparindent{100pt}

\showthe\parindent

\pushparindent{50pt}

\showthe\parindent

\popparindent

\showthe\parindent

\popparindent

\showthe\parindent

\end{document}


This will show in succession 15.0pt, 100.0pt, 50.0pt, 100.0pt, 15.0pt; the command \pushparindent stores the current value and sets \parindent to the value specified in the argument. With \popparindent you get the last stored value.

Here's a version with "standard LaTeX":

\newtoks\parindentstack
\newcommand{\pushparindent}[1]{%
\edef\temp{\noexpand\listelement{\the\parindent}\the\parindentstack}%
\global\parindentstack=\expandafter{\temp}%
\global\parindent=#1\relax
}
\newcommand{\popparindent}{%
\if\relax\detokenize\expandafter{\the\parindentstack}\relax
\errmessage{Empty stack}%
\else
\expandafter\getlistelement\the\parindentstack\getlistelement
\fi
}
\def\getlistelement\listelement#1#2\getlistelement{%
\global\parindentstack{#2}\global\parindent=#1\relax}

-
Well, yes, but this is basically my answer. The problem is such a method doesn't work with nesting. i.e., you can easily overwrite savedparindent and everything could get out of whack. Hence the need for a good stack based solution. One way would be to create \savedparindentk and use them... but of course automatically. –  Uiy Apr 7 '12 at 10:51
@Uiy Saying "doesn't work with nesting" is not very helpful: without seeing at least a sketch of what you want to do it's difficult to say more than "use the built in group structure to confine the changes". –  egreg Apr 7 '12 at 10:55
Nesting is when you do something inside of something else. It is a common problem. Your code basically like: –  Uiy Apr 7 '12 at 10:58
x = 1; tmp1 = x; { x = 3; tmp2 = x; { x = 5; tmp3 = x; x = tmp3; } x = tmp2; } x = tmp1; print x; except with yours all the temp's are the same. –  Uiy Apr 7 '12 at 11:03
so the final print should print 1 but with your code will print the last value stored in tmp, which is 5. The code makes more sense as a stack. x = 1; push(x) { push(x) { push(x) pop(x) } pop(x) } pop(x) print x; in this case x will be 1 like it should. –  Uiy Apr 7 '12 at 11:04

You can create a new macro length and add \parindent to it which creates a copy of \parindent

\newlength{\mylen}
\setlength\mylen{\parindent}
\setlength\parindent{0pt}


When you are ready to restore \parindent you can use

\setlength\parindent{\mylen}


Note this method does not allow nesting.

There is also a stack based solution but maybe less efficient.

With lualatex you can simply use a stack in lua with a tex macro wrapper. Should be pretty self-explanatory.

-
Why \addtolength and not \setlength? –  egreg Apr 7 '12 at 10:12
@egreg: Either one will work. I used set at first but something didn't work. setlength would be more semantical. (they are equivalent since x = a + 0 is the same as x = a. (assuming \mylen is 0) –  Uiy Apr 7 '12 at 10:44
Nowhere in the LaTeX manuals it is suggested that \newlength behaves like \newcommand: indeed it doesn't; it just allocates a register to store a length. –  egreg Apr 7 '12 at 11:18
saying "Note this method does not allow nesting." is rather misleading: it does allow nesting if you use the standard Tex mechanism for nesting using groups, either {} groups or a latex environment. You only need the explicit stack implementations if you need global definitions with a nested structure that doesn't use Tex groups. Needing this isn't that uncommon (for example in tables you often want a logical group corresponding to a row, but you can not use a Tex group there) But your question gives no indication that that is needed in your case. –  David Carlisle Apr 7 '12 at 16:15
3pt the value is restored at the group end, as you would easily see by executimg \parindent=3pt { \parindent=10pt } \showthe\parindent all local assignments are restored at the end of a group. You can do \global\parindent=5pt then it is not restored –  David Carlisle Apr 8 '12 at 8:07

Here is another solution for pushing and popping dimensions. It allows many measures to be pushed or popped at once.

\documentclass{article}
\usepackage{catoptions}
\makeatletter
\robust@def*\pushlengths#1{%
\cptdocommalist{#1}{%
\cptexpanded{%
\gdef\noexpandcsn{pushlength@\cptgobblescape##1}{%
\noexpand\pushstop{##1\the##1\relax}%
\ifcsndefTF{pushlength@\cptgobblescape##1}{%
\ifcsnnullTF{pushlength@\cptgobblescape##1}{%
\noexpand\pushstop{}%
}{%
\expandcsnonce{pushlength@\cptgobblescape##1}%
}%
}{%
\noexpand\pushstop{}%
}%
}%
}%
}%
}
\robust@def*\poplengths#1{%
\cptdocommalist{#1}{%
\cptexpanded{%
\cpt@poplength\expandcsnonce{pushlength@\cptgobblescape##1}%
}%
\pushstopp##1%
}%
}
\robust@def*\cpt@poplength\pushstop#1#2\pushstopp#3{%
\ifblankTF{#1}{%
\cpt@err{Can't pop empty stack
\noexpandcsn{pushlength@\cptgobblescape#3}}\@ehd
}{%
#1\csn@gdef{pushlength@\cptgobblescape#3}{#2}%
}%
}
\makeatother

% Tests:
\begin{document}
\parindent20pt
\pushlengths{\parindent,\textwidth,\textheight,\rightmargin,\leftmargin}
\parindent0pt
\pushlengths\parindent
\poplengths\parindent
\poplengths{\parindent,\textwidth,\textheight,\rightmargin,\leftmargin}
% \poplengths\parindent % -> Can't pop empty stack.
\end{document}

-

This solution is just a response to Roelof. I hope it is appropriate here. I replace \empty by \roelofstackbegin in his solution. This is safer than using \empty. Roelof's stack looks like an all-purpose stack. So any possible source of failure should be avoided.

\documentclass{article}
\makeatletter
\newtoks\roelofstack
\roelofstack={\roelofstackbegin}
\def\roelofstackbegin{\empty}
% push #1 onto #2:
\def\push#1#2{%
\begingroup
\toks0={{#1}}%
\edef\act{\endgroup\global#2={\the\toks0 \the#2}}%
\act
}
% pop from #1:
\def\pop#1{%
\begingroup
\edef\act{\endgroup\noexpand\SplitOff\the#1(tail)#1}%
\act
}
\def\SplitOff#1#2(tail)#3{%
\ifx#1\roelofstackbegin
\errhelp{Attempting to pop empty stack #3.}%
\errmessage{You can't pop an empty stack.}%
\else
#1\global#3={#2}%
\fi
}
\makeatother

% Tests:
\begin{document}
\noindent
\push{\empty}{\roelofstack}
\pop{\roelofstack}

\push{200pt}{\roelofstack}
\pop{\roelofstack}\par
\push{200pt}{\roelofstack}
\push{300pt}{\roelofstack}
\push{400pt}{\roelofstack}
\pop{\roelofstack}\par
\pop{\roelofstack}\par
\pop{\roelofstack}\par
% \pop{\roelofstack} % Popping empty stack.
\end{document}

-