summaryrefslogtreecommitdiff
path: root/info/challenges/aro-bend/answer.008
blob: b333a5c567cf107e9a03a579ec80c501567c872f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
[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 <MJD@MATH.AMS.ORG>
Subject: Around the Bend #8 answers
To: info-tex@shsu.edu
X-ListName: TeX-Related Network Discussion List <INFO-TeX@SHSU.edu>

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<error>\fi

If the \if's and \fi's in the body of the document are properly
matched, then the <error> 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 <error> 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