181bd3c9587241e97fa69ce6cc513e1db7d5aac9
[pspp-builds.git] / src / error.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3    Written by Ben Pfaff <blp@gnu.org>.
4
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18    02111-1307, USA. */
19
20 /* AIX requires this to be the first thing in the file.  */
21 #include <config.h>
22 #if __GNUC__
23 #define alloca __builtin_alloca
24 #else
25 #if HAVE_ALLOCA_H
26 #include <alloca.h>
27 #else
28 #ifdef _AIX
29 #pragma alloca
30 #else
31 #ifndef alloca                  /* predefined by HP cc +Olibcalls */
32 char *alloca ();
33 #endif
34 #endif
35 #endif
36 #endif
37
38 #include <assert.h>
39 #include <ctype.h>
40 #include <stdarg.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include "alloc.h"
44 #include "command.h"
45 #include "error.h"
46 #include "getline.h"
47 #include "main.h"
48 #include "output.h"
49 #include "settings.h"
50 #include "str.h"
51 #include "var.h"
52
53 int err_error_count;
54 int err_warning_count;
55
56 int err_already_flagged;
57
58 int err_verbosity;
59
60 /* File locator stack. */
61 static const struct file_locator **file_loc;
62 static int nfile_loc, mfile_loc;
63 \f
64 /* Fairly common public functions. */
65
66 /* Writes error message in CLASS, with title TITLE and text FORMAT,
67    formatted with printf, to the standard places. */
68 void
69 tmsg (int class, const char *title, const char *format, ...)
70 {
71   char buf[1024];
72   
73   /* Format the message into BUF. */
74   {
75     va_list args;
76
77     va_start (args, format);
78     vsnprintf (buf, 1024, format, args);
79     va_end (args);
80   }
81   
82   /* Output the message. */
83   {
84     struct error e;
85
86     e.class = class;
87     err_location (&e.where);
88     e.title = title;
89     e.text = buf;
90     err_vmsg (&e);
91   }
92 }
93
94 /* Writes error message in CLASS, with text FORMAT, formatted with
95    printf, to the standard places. */
96 void
97 msg (int class, const char *format, ...)
98 {
99   char buf[1024];
100   
101   /* Format the message into BUF. */
102   {
103     va_list args;
104
105     va_start (args, format);
106     vsnprintf (buf, 1024, format, args);
107     va_end (args);
108   }
109   
110   /* Output the message. */
111   {
112     struct error e;
113
114     e.class = class;
115     err_location (&e.where);
116     e.title = NULL;
117     e.text = buf;
118     err_vmsg (&e);
119   }
120 }
121
122 /* Terminate due to fatal error in input. */
123 void
124 err_failure (void)
125 {
126   fflush (stdout);
127   fflush (stderr);
128
129   fprintf (stderr, "%s: %s\n", pgmname,
130            _("Terminating NOW due to a fatal error!"));
131
132   err_hcf (0);
133 }
134
135 /* Terminate unless we're interactive or will go interactive when the
136    file is over with. */
137 void
138 err_cond_fail (void)
139 {
140   if (getl_reading_script)
141     {
142       if (getl_interactive)
143         getl_close_all ();
144       else
145         err_failure ();
146     }
147 }
148 \f
149 /* File locator stack functions. */
150
151 /* Pushes F onto the stack of file locations. */
152 void
153 err_push_file_locator (const struct file_locator *f)
154 {
155   if (nfile_loc >= mfile_loc)
156     {
157       if (mfile_loc == 0)
158         mfile_loc = 8;
159       else
160         mfile_loc *= 2;
161
162       file_loc = xrealloc (file_loc, mfile_loc * sizeof *file_loc);
163     }
164
165   file_loc[nfile_loc++] = f;
166 }
167
168 /* Pops F off the stack of file locations.
169    Argument F is only used for verification that that is actually the
170    item on top of the stack. */
171 void
172 err_pop_file_locator (const struct file_locator *f)
173 {
174   assert (nfile_loc >= 0 && file_loc[nfile_loc - 1] == f);
175   nfile_loc--;
176 }
177
178 /* Puts the current file and line number in F, or NULL and -1 if
179    none. */
180 void
181 err_location (struct file_locator *f)
182 {
183   if (nfile_loc)
184     *f = *file_loc[nfile_loc - 1];
185   else
186     getl_location (&f->filename, &f->line_number);
187 }
188 \f
189 /* Obscure public functions. */
190
191 /* Writes a blank line to the error device(s).
192    FIXME: currently a no-op. */
193 void
194 err_break (void)
195 {
196 }
197
198 /* Checks whether we've had so many errors that it's time to quit
199    processing this syntax file.  If so, then take appropriate
200    action. */
201 void
202 err_check_count (void)
203 {
204   int error_class = getl_interactive ? MM : FE;
205
206   if (set_errorbreak && err_error_count)
207     msg (error_class, _("Terminating execution of syntax file due to error."));
208   else if (err_error_count > set_mxerrs)
209     msg (error_class, _("Errors (%d) exceeds limit (%d)."),
210          err_error_count, set_mxerrs);
211   else if (err_error_count + err_warning_count > set_mxwarns)
212     msg (error_class, _("Warnings (%d) exceed limit (%d)."),
213          err_error_count + err_warning_count, set_mxwarns);
214   else
215     return;
216
217   getl_close_all ();
218 }
219
220 /* Some machines are broken.  Compensate. */
221 #ifndef EXIT_SUCCESS
222 #define EXIT_SUCCESS 0
223 #endif
224
225 #ifndef EXIT_FAILURE
226 #define EXIT_FAILURE 1
227 #endif
228
229 static int terminating;
230
231 /* Halt-catch-fire.  SUCCESS should be nonzero if exiting successfully
232    or zero if not.  Despite the name, this is the usual way to finish,
233    successfully or not. */
234 void
235 err_hcf (int success)
236 {
237   terminating = 1;
238
239   getl_uninitialize ();
240
241   outp_done ();
242
243   exit (success ? EXIT_SUCCESS : EXIT_FAILURE);
244 }
245
246 static void puts_stdout (const char *s);
247 static void dump_message (char *errbuf, unsigned indent,
248                           void (*func) (const char *), unsigned width);
249
250 void
251 err_vmsg (const struct error *e)
252 {
253   /* Class flags. */
254   enum
255     {
256       ERR_IN_PROCEDURE = 01,    /* 1=Display name of current procedure. */
257       ERR_WITH_FILE = 02,       /* 1=Display filename and line number. */
258     };
259
260   /* Describes one class of error. */
261   struct error_class
262     {
263       int flags;                /* Zero or more of MSG_*. */
264       int *count;               /* Counting category. */
265       const char *banner;       /* Banner. */
266     };
267
268   static const struct error_class error_classes[ERR_CLASS_COUNT] =
269     {
270       {0, NULL, N_("fatal")},                   /* FE */
271
272       {3, &err_error_count, N_("error")},       /* SE */
273       {3, &err_warning_count, N_("warning")},   /* SW */
274       {3, NULL, N_("note")},                    /* SM */
275
276       {0, NULL, N_("installation error")},      /* IE */
277       {2, NULL, N_("installation error")},      /* IS */
278
279       {2, &err_error_count, N_("error")},       /* DE */
280       {2, &err_warning_count, N_("warning")},   /* DW */
281
282       {0, &err_error_count, N_("error")},       /* ME */
283       {0, &err_warning_count, N_("warning")},   /* MW */
284       {0, NULL, N_("note")},                    /* MM */
285     };
286
287   struct string msg;
288   int class;
289
290   /* Check verbosity level. */
291   class = e->class;
292   if (((class >> ERR_VERBOSITY_SHIFT) & ERR_VERBOSITY_MASK) > err_verbosity)
293     return;
294   class &= ERR_CLASS_MASK;
295   
296   assert (class >= 0 && class < ERR_CLASS_COUNT);
297   assert (e->text != NULL);
298   
299   ds_init (NULL, &msg, 64);
300   if (e->where.filename && (error_classes[class].flags & ERR_WITH_FILE))
301     {
302       ds_printf (&msg, "%s:", e->where.filename);
303       if (e->where.line_number != -1)
304         ds_printf (&msg, "%d:", e->where.line_number);
305       ds_putchar (&msg, ' ');
306     }
307
308   ds_printf (&msg, "%s: ", gettext (error_classes[class].banner));
309   
310   {
311     int *count = error_classes[class].count;
312     if (count)
313       (*count)++;
314   }
315   
316   if (cur_proc && (error_classes[class].flags & ERR_IN_PROCEDURE))
317     ds_printf (&msg, "%s: ", cur_proc);
318
319   if (e->title)
320     ds_concat (&msg, e->title);
321
322   ds_concat (&msg, e->text);
323
324   /* FIXME: Check set_messages and set_errors to determine where to
325      send errors and messages.
326
327      Please note that this is not trivial.  We have to avoid an
328      infinite loop in reporting errors that originate in the output
329      section. */
330   dump_message (ds_value (&msg), 8, puts_stdout, set_viewwidth);
331
332   ds_destroy (&msg);
333
334   if (e->class == FE && !terminating)
335     err_hcf (0);
336 }
337 \f
338 /* Private functions. */
339
340 #if 0
341 /* Write S followed by a newline to stderr. */
342 static void
343 puts_stderr (const char *s)
344 {
345   fputs (s, stderr);
346   fputc ('\n', stderr);
347 }
348 #endif
349
350 /* Write S followed by a newline to stdout. */
351 static void
352 puts_stdout (const char *s)
353 {
354   puts (s);
355 }
356
357 /* Returns 1 if C is a `break character', that is, if it is a good
358    place to break a message into lines. */
359 static inline int
360 char_is_break (int quote, int c)
361 {
362   return ((quote && c == DIR_SEPARATOR)
363           || (!quote && (isspace (c) || c == '-' || c == '/')));
364 }
365
366 /* Returns 1 if C is a break character where the break should be made
367    BEFORE the character. */
368 static inline int
369 break_before (int quote, int c)
370 {
371   return !quote && isspace (c);
372 }
373
374 /* If C is a break character, returns 1 if the break should be made
375    AFTER the character.  Does not return a meaningful result if C is
376    not a break character. */
377 static inline int
378 break_after (int quote, int c)
379 {
380   return !break_before (quote, c);
381 }
382
383 /* If you want very long words that occur at a bad break point to be
384    broken into two lines even if they're shorter than a whole line by
385    themselves, define as 2/3, or 4/5, or whatever fraction of a whole
386    line you think is necessary in order to consider a word long enough
387    to break into pieces.  Otherwise, define as 0.  See code to grok
388    the details.  Do NOT parenthesize the expression!  */
389 #define BREAK_LONG_WORD 0
390 /* #define BREAK_LONG_WORD 2/3 */
391 /* #define BREAK_LONG_WORD 4/5 */
392
393 /* Divides MSG into lines of WIDTH width for the first line and WIDTH
394    - INDENT width for each succeeding line.  Each line is dumped
395    through FUNC, which may do with the string what it will. */
396 static void
397 dump_message (char *msg, unsigned indent, void (*func) (const char *),
398               unsigned width)
399 {
400   char *cp;
401
402   /* 1 when at a position inside double quotes ("). */
403   int quote = 0;
404
405   /* Buffer for a single line. */
406   char *buf;
407
408   /* If the message is short, just print the full thing. */
409   if (strlen (msg) < width)
410     {
411       func (msg);
412       return;
413     }
414
415   /* Make sure the indent isn't too big relative to the page width. */
416   if (indent > width / 3)
417     indent = width / 3;
418   
419   buf = local_alloc (width + 1);
420
421   /* Advance WIDTH characters into MSG.
422      If that's a valid breakpoint, keep it; otherwise, back up.
423      Output the line. */
424   for (cp = msg; (unsigned) (cp - msg) < width - 1; cp++)
425     if (*cp == '"')
426       quote ^= 1;
427
428   if (break_after (quote, (unsigned char) *cp))
429     {
430       for (cp--; !char_is_break (quote, (unsigned char) *cp) && cp > msg; cp--)
431         if (*cp == '"')
432           quote ^= 1;
433       
434       if (break_after (quote, (unsigned char) *cp))
435         cp++;
436     }
437
438   if (cp <= msg + width * BREAK_LONG_WORD)
439     for (; cp < msg + width - 1; cp++)
440       if (*cp == '"')
441         quote ^= 1;
442   
443   {
444     int c = *cp;
445     *cp = '\0';
446     func (msg);
447     *cp = c;
448   }
449
450   /* Repeat above procedure for remaining lines. */
451   for (;;)
452     {
453       char *cp2;
454
455       /* Advance past whitespace. */
456       while (isspace ((unsigned char) *cp))
457         cp++;
458       if (*cp == 0)
459         break;
460
461       /* Advance WIDTH - INDENT characters. */
462       for (cp2 = cp; (unsigned) (cp2 - cp) < width - indent && *cp2; cp2++)
463         if (*cp2 == '"')
464           quote ^= 1;
465
466       /* Back up if this isn't a breakpoint. */
467       {
468         unsigned w = cp2 - cp;
469         if (*cp2)
470           for (cp2--; !char_is_break (quote, (unsigned char) *cp2) && cp2 > cp;
471                cp2--)
472             if (*cp2 == '"')
473               quote ^= 1;
474
475         if (w == width - indent
476             && (unsigned) (cp2 - cp) <= (width - indent) * BREAK_LONG_WORD)
477           for (; (unsigned) (cp2 - cp) < width - indent && *cp2; cp2++)
478             if (*cp2 == '"')
479               quote ^= 1;
480       }
481
482       /* Write out the line. */
483       memset (buf, ' ', indent);
484       memcpy (&buf[indent], cp, cp2 - cp);
485       buf[indent + cp2 - cp] = '\0';
486       func (buf);
487
488       cp = cp2;
489     }
490
491   local_free (buf);
492 }