checkin of 0.3.0
[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 #if __CHECKER__
221 static void induce_segfault (void);
222 #endif
223
224 /* Some machines are broken.  Compensate. */
225 #ifndef EXIT_SUCCESS
226 #define EXIT_SUCCESS 0
227 #endif
228
229 #ifndef EXIT_FAILURE
230 #define EXIT_FAILURE 1
231 #endif
232
233 static int terminating;
234
235 /* Halt-catch-fire.  SUCCESS should be nonzero if exiting successfully
236    or zero if not.  Despite the name, this is the usual way to finish,
237    successfully or not. */
238 void
239 err_hcf (int success)
240 {
241   terminating = 1;
242
243   getl_uninitialize ();
244
245   outp_done ();
246
247 #if __CHECKER__
248   if (!success)
249     induce_segfault ();
250 #endif
251
252   exit (success ? EXIT_SUCCESS : EXIT_FAILURE);
253 }
254
255 static void puts_stdout (const char *s);
256 static void dump_message (char *errbuf, unsigned indent,
257                           void (*func) (const char *), unsigned width);
258
259 void
260 err_vmsg (const struct error *e)
261 {
262   /* Class flags. */
263   enum
264     {
265       ERR_IN_PROCEDURE = 01,    /* 1=Display name of current procedure. */
266       ERR_WITH_FILE = 02,       /* 1=Display filename and line number. */
267     };
268
269   /* Describes one class of error. */
270   struct error_class
271     {
272       int flags;                /* Zero or more of MSG_*. */
273       int *count;               /* Counting category. */
274       const char *banner;       /* Banner. */
275     };
276
277   static const struct error_class error_classes[ERR_CLASS_COUNT] =
278     {
279       {0, NULL, N_("fatal")},                   /* FE */
280
281       {3, &err_error_count, N_("error")},       /* SE */
282       {3, &err_warning_count, N_("warning")},   /* SW */
283       {3, NULL, N_("note")},                    /* SM */
284
285       {0, NULL, N_("installation error")},      /* IE */
286       {2, NULL, N_("installation error")},      /* IS */
287
288       {2, NULL, N_("error")},                   /* DE */
289       {2, NULL, N_("warning")},                 /* DW */
290
291       {0, NULL, N_("error")},                   /* ME */
292       {0, NULL, N_("warning")},                 /* MW */
293       {0, NULL, N_("note")},                    /* MM */
294     };
295
296   struct string msg;
297   int class;
298
299   /* Check verbosity level. */
300   class = e->class;
301   if (((class >> ERR_VERBOSITY_SHIFT) & ERR_VERBOSITY_MASK) > err_verbosity)
302     return;
303   class &= ERR_CLASS_MASK;
304   
305   assert (class >= 0 && class < ERR_CLASS_COUNT);
306   assert (e->text != NULL);
307   
308   ds_init (NULL, &msg, 64);
309   if (e->where.filename && (error_classes[class].flags & ERR_WITH_FILE))
310     {
311       ds_printf (&msg, "%s:", e->where.filename);
312       if (e->where.line_number != -1)
313         ds_printf (&msg, "%d:", e->where.line_number);
314       ds_putchar (&msg, ' ');
315     }
316
317   ds_printf (&msg, "%s: ", gettext (error_classes[class].banner));
318   
319   {
320     int *count = error_classes[class].count;
321     if (count)
322       (*count)++;
323   }
324   
325   if (cur_proc && (error_classes[class].flags & ERR_IN_PROCEDURE))
326     ds_printf (&msg, "%s: ", cur_proc);
327
328   if (e->title)
329     ds_concat (&msg, e->title);
330
331   ds_concat (&msg, e->text);
332
333   /* FIXME: Check set_messages and set_errors to determine where to
334      send errors and messages.
335
336      Please note that this is not trivial.  We have to avoid an
337      infinite loop in reporting errors that originate in the output
338      section. */
339   dump_message (ds_value (&msg), 8, puts_stdout, set_viewwidth);
340
341   ds_destroy (&msg);
342
343   if (e->class == FE && !terminating)
344     err_hcf (0);
345 }
346 \f
347 /* Private functions. */
348
349 #if 0
350 /* Write S followed by a newline to stderr. */
351 static void
352 puts_stderr (const char *s)
353 {
354   fputs (s, stderr);
355   fputc ('\n', stderr);
356 }
357 #endif
358
359 /* Write S followed by a newline to stdout. */
360 static void
361 puts_stdout (const char *s)
362 {
363   puts (s);
364 }
365
366 /* Returns 1 if C is a `break character', that is, if it is a good
367    place to break a message into lines. */
368 static inline int
369 char_is_break (int quote, int c)
370 {
371   return ((quote && c == DIR_SEPARATOR)
372           || (!quote && (isspace (c) || c == '-' || c == '/')));
373 }
374
375 /* Returns 1 if C is a break character where the break should be made
376    BEFORE the character. */
377 static inline int
378 break_before (int quote, int c)
379 {
380   return !quote && isspace (c);
381 }
382
383 /* If C is a break character, returns 1 if the break should be made
384    AFTER the character.  Does not return a meaningful result if C is
385    not a break character. */
386 static inline int
387 break_after (int quote, int c)
388 {
389   return !break_before (quote, c);
390 }
391
392 /* If you want very long words that occur at a bad break point to be
393    broken into two lines even if they're shorter than a whole line by
394    themselves, define as 2/3, or 4/5, or whatever fraction of a whole
395    line you think is necessary in order to consider a word long enough
396    to break into pieces.  Otherwise, define as 0.  See code to grok
397    the details.  Do NOT parenthesize the expression!  */
398 #define BREAK_LONG_WORD 0
399 /* #define BREAK_LONG_WORD 2/3 */
400 /* #define BREAK_LONG_WORD 4/5 */
401
402 /* Divides MSG into lines of WIDTH width for the first line and WIDTH
403    - INDENT width for each succeeding line.  Each line is dumped
404    through FUNC, which may do with the string what it will. */
405 static void
406 dump_message (char *msg, unsigned indent, void (*func) (const char *),
407               unsigned width)
408 {
409   char *cp;
410
411   /* 1 when at a position inside double quotes ("). */
412   int quote = 0;
413
414   /* Buffer for a single line. */
415   char *buf;
416
417   /* If the message is short, just print the full thing. */
418   if (strlen (msg) < width)
419     {
420       func (msg);
421       return;
422     }
423
424   /* Make sure the indent isn't too big relative to the page width. */
425   if (indent > width / 3)
426     indent = width / 3;
427   
428   buf = local_alloc (width + 1);
429
430   /* Advance WIDTH characters into MSG.
431      If that's a valid breakpoint, keep it; otherwise, back up.
432      Output the line. */
433   for (cp = msg; (unsigned) (cp - msg) < width - 1; cp++)
434     if (*cp == '"')
435       quote ^= 1;
436
437   if (break_after (quote, (unsigned char) *cp))
438     {
439       for (cp--; !char_is_break (quote, (unsigned char) *cp) && cp > msg; cp--)
440         if (*cp == '"')
441           quote ^= 1;
442       
443       if (break_after (quote, (unsigned char) *cp))
444         cp++;
445     }
446
447   if (cp <= msg + width * BREAK_LONG_WORD)
448     for (; cp < msg + width - 1; cp++)
449       if (*cp == '"')
450         quote ^= 1;
451   
452   {
453     int c = *cp;
454     *cp = '\0';
455     func (msg);
456     *cp = c;
457   }
458
459   /* Repeat above procedure for remaining lines. */
460   for (;;)
461     {
462       char *cp2;
463
464       /* Advance past whitespace. */
465       while (isspace ((unsigned char) *cp))
466         cp++;
467       if (*cp == 0)
468         break;
469
470       /* Advance WIDTH - INDENT characters. */
471       for (cp2 = cp; (unsigned) (cp2 - cp) < width - indent && *cp2; cp2++)
472         if (*cp2 == '"')
473           quote ^= 1;
474
475       /* Back up if this isn't a breakpoint. */
476       {
477         unsigned w = cp2 - cp;
478         if (*cp2)
479           for (cp2--; !char_is_break (quote, (unsigned char) *cp2) && cp2 > cp;
480                cp2--)
481             if (*cp2 == '"')
482               quote ^= 1;
483
484         if (w == width - indent
485             && (unsigned) (cp2 - cp) <= (width - indent) * BREAK_LONG_WORD)
486           for (; (unsigned) (cp2 - cp) < width - indent && *cp2; cp2++)
487             if (*cp2 == '"')
488               quote ^= 1;
489       }
490
491       /* Write out the line. */
492       memset (buf, ' ', indent);
493       memcpy (&buf[indent], cp, cp2 - cp);
494       buf[indent + cp2 - cp] = '\0';
495       func (buf);
496
497       cp = cp2;
498     }
499
500   local_free (buf);
501 }
502
503 #if __CHECKER__
504 /* Causes a segfault in order to force Checker to print a stack
505    backtrace. */
506 static void
507 induce_segfault (void)
508 {
509   fputs (_("\n"
510            "\t*********************\n"
511            "\t* INDUCING SEGFAULT *\n"
512            "\t*********************\n"), stdout);
513   fflush (stdout);
514   fflush (stderr);
515   abort ();
516 }
517 #endif