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