[The addendum at bottom was not posted with the answer but added in my archives later ---mjd] Date: 22 Jul 1993 15:54:57 -0400 (EDT) From: Michael Downes Subject: Around the Bend #8 answers To: info-tex@shsu.edu X-ListName: TeX-Related Network Discussion List Exercise 8 asked for a way to trap missing }, \endgroup, or \fi at the end of a [La]TeX document, in order to give error messages instead of the warning messages issued by TeX: (\end occurred inside a group at level 1) (\end occurred when \iffalse on line 6 was incomplete) This review of solutions is posted later than expected because I needed time to try out and understand solutions submitted by Peter Schmitt last week. For clarity's sake, I have split the solutions into two parts, one dealing with groups, the other with conditionals. GROUPS Peter Schmitt remarked that if TeX can give a warning message for a missing endgroup there is nothing to prevent it from giving an error message except the choice of TeX's author. In some cursory perusal of TeX: the Program, I wasn't able to find any explanation from Knuth as to why he didn't make it a real error message instead of just a warning. Perhaps someone else can shed some light here? Now for solutions. The first one was submitted by Peter Schmitt. My commentary: Assume the body of the TeX document is enclosed within start and end commands (here named \BEGIN and \END), with the starting command contributing a \begingroup and the closing command providing the matching \endgroup, with some juggling to make a group mismatch trigger an error. If the document contains any unclosed groups that were opened with { or \bgroup, the \endgroup will trigger TeX's low-level error recovery, which is to insert matching }s (`Missing } inserted'). Thus only the case of an unmatched \begingroup needs to be handled. Schmitt does this by (essentially) making a local redefinition of \end that produces an error message; if all groups are closed properly, the local definition will disappear, restoring the normal definition, which will execute a normal endgame. Here now Schmitt's submitted solution. I have simplified it slightly by disentangling some other stuff that will be discussed later below. >>Solution 1 (Peter Schmitt) [a8131dal@awiuni11.edvz.univie.ac.at, schmitt@awirap.bitnet] \catcode`_11 \let\standard_end\end % save original meaning of end % define modified end \def\unexpected_end{% {\errorcontextlines=0 % minimize errormessage \errmessage{Unexpected \string\END\space inside group}% errormessage }\standard_end % continue with \standard_end } \let\End\standard_end \def\END{\endgroup\End} \def\BEGIN{\begingroup \let\End\unexpected_end } \BEGIN %%% some tests: % \bgroup\egroup\end % balanced \begingroup\end \endgroup % unbalanced % \bgroup\end % unbalanced % { \end % unbalanced % } \begingroup \end % this is reported % \endgroup \begingroup \end % this is not reported >>EndSolution >>Solution 2 (mine) This solution uses a rather dirty trick with \batchmode. Jonathan Fine also found the same idea, though in his mail to me he did not elaborate it into a fully wrapped solution. Enclosing the entire document inside a \begingroup \endgroup places an extra burden on the save stack (one would presume this is why LaTeX's \begin{document} and \end{document} take some pains to avoid constructing such a group, although the comments in latex.tex don't provide an explicit reason). (Extra credit question: Just how much of a burden would it place on the save stack in, say, an average LaTeX document?) So my solution seeks to trap unmatched { or \begingroup without enclosing the document body in a group. The reason the \batchmode trick is `dirty' is that it leaves a spurious extra error message in the log file. On screen for the typical interactive user, this error message is hidden by the temporary switch to \batchmode, but if for example the user has as part of their TeX system an editor setup that automatically proceeds through the .log file to help the user take care of all error messages, then the spurious error message will be somewhat inconvenient. The following clip shows what a user would typically see on screen if their document contained an unmatched {. ! Missing } added. \bgrouperr ...ffalse {\fi \string } added} \enddocument ...rgroup \bgrouperr \egroup \if \errorstopping \batchmo... l.50 \enddocument ? h There appears to be an unmatched opening brace or \bgroup somewhere in your document. ? ) No pages of output. Here then is the code for the solution. As it stands, only the most recent unmatched open-group is dealt with in the error message. As the on-screen result from the test section marked as `test 2' will indicate, a recursive definition for \bgrouperr would be better for maximum robustness, but I haven't had the spare time to work out the extra details. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \def\enddocument{% % Go into \batchmode to suppress possible error messages that we % don't want to bring to the user's attention. \batchmode % Set a flag to enable us to handle the \endgroup properly if the % \egroup pairs up with an unmatched { or \bgroup. \def\errorstopping{TF}% % If the following \egroup matches with a preceding unmatched { or % \bgroup in the user document, then the aftergroup tokens % \errorstopmode \bgrouperr will be executed. Otherwise they will % go away into uncharted limbo. \aftergroup\errorstopmode\aftergroup\bgrouperr \egroup % If there was no unmatched { or \bgroup, then the preceding % \egroup was discarded by TeX. And \errorstopping is still false. % Otherwise we need to insert some new \aftergroup tokens. \if\errorstopping \batchmode \aftergroup\errorstopmode \aftergroup\begingrouperr \else \global\let\bgrouperr\begingrouperr \fi \endgroup \errorstopmode % Call two different versions of \end, just for convenient testing % with either plain TeX and LaTeX. \csname\string @\string @end\endcsname \end } \def\bgrouperr{% \def\errorstopping{TT}% \errhelp{% There appears to be an unmatched opening brace or \bgroup somewhere^^J% in your document.}% \errmessage{Missing \iffalse{\fi\string} added}% } \def\begingrouperr{% \errhelp{% There appears to be an unmatched \begingroup somewhere in your document.}% \errmessage{Missing \noexpand\endgroup added}% } \newlinechar=`\^^J % % Test 0: Leave the following three lines commented out. %{ % Test 1: uncomment this line %\bgroup % Test 2: uncomment the previous line and this one. %\begingroup % Test 3: uncomment all three lines. \enddocument >>EndSolution %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CONDITIONALS Now, what about \if...\fi matching? Can a method analogous to the one for groups be applied here? Well, it seems not, since there is no \afterfi primitive that works like \aftergroup. If you insert an `extra' \fi it will generate an error message in the case when it is not needed, and nothing in the case when it is needed; I would have sworn there's no *detectable* change of state between before the nonextra \fi and after the nonextra \fi. But Peter Schmitt found a scintillating idea, which is to make sure the \fi is never extra but use the need or non-need of an \else to control the triggering of an error message. This is done by enclosing the entire document in a pair of conditions: \iftrue\iffalse\else ... \fi...\else\fi If the \if's and \fi's in the body of the document are properly matched, then the branch will be skipped over without execution. But if an unmatched \ifsomething in the document body uses up the \fi that is supposed to match up with the \iffalse\else, then the following \else will trigger an error message (which Schmitt hides with \batchmode, using the same trick as discussed above in Solution 2), then be discarded, and the branch will now be true. The extra two conditional structures place no significant burden on any of TeX's stacks, only a little bit of main memory to keep track of the line number and type of \if. Peter had the group and conditional trapping combined in his original solution; here is the conditional trapping part as I disentangled it. >>Solution 3 (Peter Schmitt): \catcode`_11 \def\fi_message{{\newlinechar`|% % | is used to format screen messages \errorcontextlines=0 % minimize errormessage \errhelp{% % help text (if requested by the user) \END occurred inside a conditional group. |% You probably have forgotten to close some \fi before. }% \errmessage{Unexpected \string\END\space inside conditon} % errormessage }} \def\BEGIN{\def\END{\fi\batchmode\else\errorstopmode\fi_message\fi \errorstopmode\end}% \iftrue\iffalse\else } \BEGIN %%% some tests: % \iftrue \fi \END % balanced \iftrue \END \fi % error message % \iffalse \else \END \fi % error message % \iftrue \iffalse \else \END \fi \fi % warning only % \iftrue \iffalse \else \fi \END \fi % error message % \iffalse \else \iffalse \else \END \fi \fi % error message % \iffalse \else \iffalse \else \END \fi \fi % error message >>EndSolution In closing, I want to point out that missing \fi's or \endgroup's are more likely to arise from a TeX programmer's error than from ordinary use of a macro package like LaTeX. So it might be minimally sufficient to trap only the missing } case, if the goal is to provide an explicit error message to end users of such a package. Michael Downes PS. Hint for Exercise 10: Run the body of the posting through plain TeX. ASCII 32--64,65--126: !"#$%&'()*+,-./0123456789:;<=>?@ ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Addendum: Found this in comp.text.tex. The line number question is significant; in Schmitt's solution for handling missing \fi's, you lose information about the line number where the unmatched \if really started. =========================================================================== Archive-Date: Wed, 04 Aug 1993 13:30:24 CST Sender: bed_gdg@SHSU.EDU From: morje@math.ohio-state.edu (Prabhav Morje) Reply-To: morje@math.ohio-state.edu (Prabhav Morje) Subject: "end occurs inside a group" error in LaTeX Date: 3 Aug 1993 22:36:30 -0400 Message-ID: <23n7be$e32@math.mps.ohio-state.edu> Keywords: line number group error To: tex-news@SHSU.EDU Hi, I sometimes get the error "\end occured while inside a group on level 1" while running LaTeX. I know it means there is an extra "{" somewhere. It is harmless sometimes but if I want to correct it, LaTeX never tells where the extra "{" is. Is it possible to find the line number or something more about location of the error? Any pointers will be greatly appreciated. - Prabhav