[Tex/LaTex] Macro to close all open environments, groups and argument delimiters

environmentsgroupingmacros

Initial Request

I am in search for a macro that would close all curly brackets and all open environments. Ideally, I could write something of the following sort:

\Open\begin{minipage}\begin{quote}\texttt{
      \textbf{Something
\Close % will close all open environments and curly brackets opened
       % since the most recent \Open

If such a beast can be created, I would map \Open to a thick curly brackets of Unicode and \close to a thick curly brackets, i.e., ❴❵ U+2773 and U+2774.

(Yes, I know some people thought this is an abomination, but then so did people
think of curly brackets in BCPL, especially when forced to use a digraph or trigraph for these)

With this macro, one can even remove more LaTeX textual "clutter" by writing

\Open\begin{minipage}\begin{quote}\texttt{
      \textbf{Something
\Close % will close all open environments and curly brackets opened
       % since the most recent \Open

or even

❴\minipage{\quote{\texttt{
      \textbf{Something
❵      % will close all open environments and curly brackets opened
       % since the most recent ❵

(and, yes, I realize that this would drive some LaTeX IDE's crazy)

With \Open opening a context, one could even write:

❴\minipage\quote\texttt{
      \textbf{Something
❵

i.e., removing the opening curly brackets of environments. For macros taking an arguments, the code has to be rewritten as

❴\minipage\quote\ttfamily\bfseries
   Something
❵    

which is not only neat. It is also more IDE friendly. Strangely enough, it would also work with ordinary curly brackets.

Here is another use case, should anyone be interested:
Instead of typing

\newcommand\kk[1]{\textcolor{RoyalBlue}{\text{\textup{\textbf{\texttt{#1}}}}}}

and carefully counting the closing parenthesis, we shall have code that is much easier to check visually:

\newcommand\kk[1]❴\textcolor{RoyalBlue}{\text{\textup{\textbf{\texttt{#1❵ 

Analysis

Based on the comment below, I understand that this question calls for up to four components of an answer:

  • A macro to close all pending environment which begun by \begin{env}
  • A macro to close all groups opened since the last drop of an anchor. The term group refers here to the use of { and } for delimiting scope, as in {\em something }
  • A macro to close all pending open argument delimiters. Argument delimiters refer to the use of { and } to collect tokens together, making them one for the purpose of macro application, as in \emph{something}
  • A macro to close all open { in the context of \def, in which it used for delimiting the macro body, but not name, as in \def\EM{\em}

I learn that the fourth is impossible without replacing TeX's lexical analyzer, and I presume that the third is equally difficult.
IMHO, the original sin is that TeX overloaded '{' for three distinct purposes: argument passing and for defining scope.

Compare with the classical

main() {
  printf("Hello, World!\n");
}

in which C uses () for function calling and {} for grouping or scoping. The round brackets in C are used for two similar purposes:

  1. **Defining functions* as in main(), which in TeX terms is \def\main{}
  2. Calling functions as in printf("Hello, World!\n");which in TeX could be
    written as emph{Hello, World!}

So, if messing with the {} for macro calling and definition is so difficult, then I would be very happy with answer that would support the original simple use case

\Open\begin{minipage}\begin{quote}\texttt{
      \textbf{Something
\Close % will close all open environments and curly brackets opened
       % since the most recent \Open

wtth the fist two requests only:

  • A macro to close all pending environment which begun by \begin{env}
  • A macro to close all groups opened since the last drop of an anchor. The term group refers here to the use of { and } for delimiting scope, as in {\em something }

Reflection

Without me intending to do so, the question highlighted the fact that {} are used for three different purposes: grouping, macro definition and macro invocation. In my mind, this overloading is a barrier for beginners who come to LaTeX from the programming world.

Motivation and Context

This question was not asked with the purpose of serving as an intellectual challenge. There is a particular need that a solution should serve.

I am engaged in writing many slides with beamer. By now, I believe I have over a thousand of these. The slides are not too complicated, but they do involve using a lot of environments. A slide with 10 lines or so will contain environments such as

  • \begin{frame}\end{frame}
  • \begin{quote}\end{quote}
  • \begin{block}\end{block}
  • \begin{columns}\end{columns}
  • \begin{column}\end{column}
  • \begin{definition}\end{definition}
  • \begin{uncoverenv}\end{uncoverenv}
  • \begin{itemize}\end{itemize}
  • \begin{lstlisting}\end{lstlisting}
  • \begin{centering}\end{centering}

The ratio of macros and environments to actual text is very high with input designed to make presentations. In a count I just did, I found that on overage I use four environments per slide, other than the obvious \begin{frame}.

Opening these environments may create some "clutter", but closing them can be a real pain. It is often the case that you open many environments, all of which end at the end of the frame. Being able to close all these at once, is not for
saving typing. In making presentations, typing is the least concern. The majority of work is in experimenting with numerous combinations of the visual layout components to create the desired visual effect. In doing that, it is convenient that the actual layout of the text, including indentation, makes it easy to grasp immediately the current slide design, with minimal need to mentally compile
and understand commands and other "clutter" which are required to make the input legal for beamer.

I believe that a macro to close all open environments, and more generally, open groups would be useful.

BTW, the idea is not new. Many years ago, I used to program in Lisp. I remember that it was possible to set an anchor by type '[' and then typing ']' to close all parenthesis opened since the anchor. This was a great help then. These days I do not use Lisp anymore, and I cannot find reference to this ancient trick.

Some Statistical Data

Just to get an initial idea of the potential use of this feature, I measured a presentation I just made, which was not overly complex, and without me being aware that such a macro could be made. I only examined the \end{frame} command.

Overall, there were 43 frames. In 6 of these (14%) the frame ended with a single \end{env}. In 26 frames (60%), there were two \end at the end, and in 11 frames (26%), there were three such commands. Seems as if such a feature would be useful in at least 86% of all frames. And, it is highly unlikely that \Close
at the end of frames would lead to many strange errors.

\Open/\Close and the Python Metaphor

It occurred to me that these two macros are similar, but not identical to the way
the Python programming language manages bracketing. In Python, indenting a line creates indentation/de-indentation tokens as explained by this quote:

Before the first line of the file is read, a single zero is pushed on the stack; this will never be popped off again. The numbers pushed on the stack will always be strictly increasing from bottom to top. At the beginning of each logical line, the line's indentation level is compared to the top of the stack. If it is equal, nothing happens. If it is larger, it is pushed on the stack, and one INDENT token is generated. If it is smaller, it must be one of the numbers occurring on the stack; all numbers on the stack that are larger are popped off, and for each number popped off a DEDENT token is generated. At the end of the file, a DEDENT token is generated for each number remaining on the stack that is larger than zero.

(Source is here: https://docs.python.org/release/2.5.1/ref/indentation.html)

The nice property of Python is that you do not really need to insert an \Open token. All you do is un-indent to the desired level.

Best Answer

I have said that you are inclined to plain TeX code. But your question seemed me to be interesting. So, I tried to do something despite this is LaTeX problem.

You can start with experimenting with my code:

\newcount\openLnum
\newtoks\currtext
\def\Open{\begingroup\let\bgroup=\relax \let\egroup=\relax 
   \expandafter\checkbracesJ\autobracelist\end 
   \let\ifIamInGroup=\iffalse \currtext={}\checkbracesA
}
\def\checkbracesA{\futurelet\tmp\checkbracesB}
\def\checkbracesB{%
   \let\next=\checkbracesN
   \ifx\tmp\spacetoken \let\next=\checkbracesC \let\nexxt=\checkbracesA \addtocurrtext{ }\fi
   \ifx\tmp\bgroupOri  \let\next=\checkbracesC \let\nexxt=\checkbracesD \fi
   \ifx\tmp\egroupOri  \let\next=\checkbracesC \let\nexxt=\checkbracesE \fi
   \ifx\tmp\autobraced \let\next=\checkbracesH \fi
   \ifx\tmp\Close \let\next=\checkbracesC \let\nexxt=\checkbracesF \fi
   \ifx\tmp\Open  \global\advance\openLnum by1 \let\next=\relax \fi
   \next
}
\def\checkbracesC{\afterassignment\nexxt \let\next= }
\long\def\checkbracesN#1{\addtocurrtext#1\checkbracesA}
\def\checkbracesD{\begingroup \let\ifIamInGroup=\iftrue \currtext={}\checkbracesA}
\def\checkbracesE{\ifIamInGroup \addtocurrtextclosebrace
   \else \currtext\expandafter{\expandafter{\the\currtext}}%
   \fi \checkbracesA
}
\def\checkbracesF{%
   \ifIamInGroup \addtocurrtextclosebrace \expandafter\checkbracesF
   \else \expandafter\checkbracesG \fi
}
\def\checkbracesG{%
   \ifnum\openLnum>0 \global\advance\openLnum by-1 
       \def\next{\expandafter\endgroup \expandafter 
          \currtext \expandafter\expandafter\expandafter
             {\expandafter\the\expandafter\currtext \the\currtext}\checkbracesA}%
   \else \def\next{\expandafter\endgroup \the\currtext}%
   \fi \next
}
\def\checkbracesH#1{\addtocurrtext#1\futurelet\tmp\checkbracesI}
\def\checkbracesI{\ifx\tmp\bgroupOri \expandafter\checkbracesB
                  \else \expandafter\checkbracesD \fi
}
\def\checkbracesJ#1{\ifx#1\end \else \let#1=\autobraced \expandafter\checkbracesJ \fi}

\def\addtocurrtextclosebrace{\expandafter\endgroup
   \expandafter\currtext\expandafter\expandafter\expandafter
      {\expandafter\the\expandafter\currtext\expandafter{\the\currtext}}%
}
\long\def\addtocurrtext#1{\currtext\expandafter{\the\currtext#1}}
\let\bgroupOri=\bgroup
\let\egroupOri=\egroup
\def\tmp/{\let\spacetoken= }\tmp/ %
\def\Close{^\Close^}
\def\autobraced{^\autobraced^}
\def\autobracelist{}

Save it to the file (say) openclose.tex and do the following tests:

\documentclass{article}

\input openclose

\long\def\Minipage#1{\begin{minipage}{\textwidth}#1\end{minipage}}
\long\def\Quote#1{\begin{quote}#1\end{quote}}

\begin{document}

\Open
\Minipage{\Quote{\texttt{\textbf{ 
    Something\Close
% This inserts the closing braces automatically. It means that
%  \Minipage{\Quote{\texttt{\textbf{ Something}}}} is processed.

\def\autobracelist{\Minipage \Quote \texttt \textbf}
\Open
\Minipage\Quote\texttt
      \textbf Something else\Close
% This inserts the opening braces after listed commands and 
% inserts the closing braces automatically. 

\def\autobracelist{}
\Open
\newcommand\kk[1]{\textcolor{RoyalBlue}{\text{\textup{\textbf{\texttt{#1\Close

\message{\meaning\kk}

% This defines the \kk macro as is your desire.

\end{document}

Note that my \Open doesn't insert the starting { immediatelly. This is needed because \Open command works at main TeX processor level. It cannot be inserted between [1] and \textcolor in your \newcommand example.

If you need insert the open brace immediatelly then you can define \def\Openbrace{\expandafter\Open\expandafter{\iffalse}\fi}.

Edit The \Open...\Close pair acts as the autobalancing procedure. If the text between \Open...\Close isn't balanced then the balancing braces are added. Else text si unchanged. You can try:

\Open abc\Close       % no change: ->  abc

\Open a{b}c{d}\Close  % no change: -> a{b}c{d}

\Open a{b{c\Close     % added braces: ->  a{b{c}}

\Open a}b}c\Close     % added braces: -> {{a}b}c

\Open a}b}c{d{e\Close % added braces: -> {{a}b}c{d{e}}

2nd Edit: I've added the support of nested \Open...\Close pairs. First, the inner pairs are processed and then the outer pairs (like normal parenthesis):

\Open text \Open inner1\Close text \Open inner2 \Open inner3\Close text \Close \Close
->
\Open text processed-inner1 text \Open inner2 processed-inner3 text \Close \Close
-> 
\Open text processed-inner1 text processed-(inner2 processed-inner3 text)\Close

Edit Aug.30 New version of openclose.tex: more efficient code and new feature: if the open brace already exists after control sequence listed in \autobracelist then new open brace isn't inserted here.

Related Question