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