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