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