Use gnulib exit module.
[pspp-builds.git] / src / message.c
index b77e7b627fe1e9eacb5eae6b3a110f1ebcee72f9..a7f226a8b4b31248949f03ed0c2c2961bbde132b 100644 (file)
    02110-1301, USA. */
 
 #include <config.h>
-#include "message.h"
+#include <libpspp/message.h>
 #include <ctype.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include "alloc.h"
-#include "filename.h"
-#include "line-buffer.h"
-#include "lexer.h"
-#include "settings.h"
-#include "read-line.h"
-#include "version.h"
+#include <libpspp/alloc.h>
+#include <data/file-name.h>
+#include <language/line-buffer.h>
+#include <language/lexer/lexer.h>
+#include <data/settings.h>
+#include <ui/terminal/read-line.h>
+#include <libpspp/version.h>
+#include "exit.h"
+#include "linebreak.h"
+#include "progname.h"
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
@@ -44,40 +47,41 @@ int err_verbosity;
 
 static char *command_name;
 \f
-/* Fairly common public functions. */
+/* Public functions. */
 
-/* Writes error message in CLASS, with title TITLE and text FORMAT,
-   formatted with printf, to the standard places. */
+/* Writes error message in CLASS, with text FORMAT, formatted with
+   printf, to the standard places. */
 void
-tmsg (int class, const char *title, const char *format, ...)
+msg (enum msg_class class, const char *format, ...)
 {
   struct error e;
   va_list args;
 
-  e.class = class;
+  e.category = msg_class_to_category (class);
+  e.severity = msg_class_to_severity (class);
   err_location (&e.where);
-  e.title = title;
-
   va_start (args, format);
-  err_vmsg (&e, format, args);
+  e.text = xvasprintf (format, args);
   va_end (args);
+
+  err_msg (&e);
 }
 
-/* Writes error message in CLASS, with text FORMAT, formatted with
-   printf, to the standard places. */
+/* Writes MESSAGE formatted with printf, to stderr, if the
+   verbosity level is at least LEVEL. */
 void
-msg (int class, const char *format, ...)
+verbose_msg (int level, const char *format, ...)
 {
-  struct error e;
-  va_list args;
-
-  e.class = class;
-  err_location (&e.where);
-  e.title = NULL;
-
-  va_start (args, format);
-  err_vmsg (&e, format, args);
-  va_end (args);
+  if (err_verbosity >= level)
+    {
+      va_list args;
+  
+      va_start (args, format);
+      fprintf (stderr, "%s: ", program_name);
+      vfprintf (stderr, format, args);
+      putc ('\n', stderr);
+      va_end (args);
+    }
 }
 
 /* Checks whether we've had so many errors that it's time to quit
@@ -86,12 +90,12 @@ void
 err_check_count (void)
 {
   if (get_errorbreak() && err_error_count)
-    msg (MM, _("Terminating execution of syntax file due to error."));
+    msg (MN, _("Terminating execution of syntax file due to error."));
   else if (err_error_count > get_mxerrs() )
-    msg (MM, _("Errors (%d) exceeds limit (%d)."),
+    msg (MN, _("Errors (%d) exceeds limit (%d)."),
         err_error_count, get_mxerrs());
   else if (err_error_count + err_warning_count > get_mxwarns() )
-    msg (MM, _("Warnings (%d) exceed limit (%d)."),
+    msg (MN, _("Warnings (%d) exceed limit (%d)."),
         err_error_count + err_warning_count, get_mxwarns() );
   else
     return;
@@ -99,18 +103,11 @@ err_check_count (void)
   getl_abort_noninteractive ();
 }
 
-/* Some machines are broken.  Compensate. */
-#ifndef EXIT_SUCCESS
-#define EXIT_SUCCESS 0
-#endif
-
-#ifndef EXIT_FAILURE
-#define EXIT_FAILURE 1
-#endif
-
-static void puts_stdout (const char *s);
-static void dump_message (char *errbuf, unsigned indent,
-                         void (*func) (const char *), unsigned width);
+static void puts_stdout (int line_indent, const char *line, size_t length);
+static void dump_message (char *msg,
+                          void (*func) (int line_indent,
+                                        const char *line, size_t length),
+                          unsigned width, unsigned indent);
 
 void
 err_done (void) 
@@ -120,273 +117,145 @@ err_done (void)
   readln_uninitialize();
 }
 
+/* Emits E as an error message.
+   Frees `text' member in E. */
 void
-err_vmsg (const struct error *e, const char *format, va_list args)
+err_msg (const struct error *e)
 {
-  /* Class flags. */
-  enum
+  struct category 
     {
-      ERR_IN_PROCEDURE = 01,   /* 1=Display name of current procedure. */
-      ERR_WITH_FILE = 02,      /* 1=Display filename and line number. */
+      bool show_command_name;   /* Show command name with error? */
+      bool show_file_location;  /* Show syntax file location? */
     };
 
-  /* Describes one class of error. */
-  struct error_class
+  static const struct category categories[] = 
     {
-      int flags;               /* Zero or more of ERR_*. */
-      int *count;              /* Counting category. */
-      const char *banner;      /* Banner. */
+      {false, false},           /* MSG_GENERAL. */
+      {true, true},             /* MSG_SYNTAX. */
+      {false, true},            /* MSG_DATA. */
     };
 
-  static const struct error_class error_classes[ERR_CLASS_COUNT] =
+  struct severity 
     {
-      {3, &err_error_count, N_("error")},      /* SE */
-      {3, &err_warning_count, N_("warning")},  /* SW */
-      {3, NULL, N_("note")},                   /* SM */
-
-      {0, NULL, N_("installation error")},     /* IE */
-      {2, NULL, N_("installation error")},     /* IS */
-
-      {2, &err_error_count, N_("error")},      /* DE */
-      {2, &err_warning_count, N_("warning")},  /* DW */
-
-      {0, &err_error_count, N_("error")},      /* ME */
-      {0, &err_warning_count, N_("warning")},  /* MW */
-      {0, NULL, N_("note")},                   /* MM */
+      const char *name;         /* How to identify this severity. */
+      int *count;               /* Number of msgs with this severity so far. */
+    };
+  
+  static struct severity severities[] = 
+    {
+      {N_("error"), &err_error_count},          /* MSG_ERROR. */
+      {N_("warning"), &err_warning_count},      /* MSG_WARNING. */
+      {NULL, NULL},                             /* MSG_NOTE. */
     };
 
-  struct string msg;
-  int class;
+  const struct category *category = &categories[e->category];
+  const struct severity *severity = &severities[e->severity];
+  struct string msg = DS_INITIALIZER;
 
-  /* Check verbosity level. */
-  class = e->class;
-  if (((class >> ERR_VERBOSITY_SHIFT) & ERR_VERBOSITY_MASK) > err_verbosity)
-    return;
-  class &= ERR_CLASS_MASK;
-  
-  assert (class >= 0 && class < ERR_CLASS_COUNT);
-  assert (format != NULL);
-  
-  ds_init (&msg, 64);
-  if (e->where.filename && (error_classes[class].flags & ERR_WITH_FILE))
+  if (category->show_file_location && e->where.file_name)
     {
-      ds_printf (&msg, "%s:", e->where.filename);
+      ds_printf (&msg, "%s:", e->where.file_name);
       if (e->where.line_number != -1)
        ds_printf (&msg, "%d:", e->where.line_number);
       ds_putc (&msg, ' ');
     }
 
-  ds_printf (&msg, "%s: ", gettext (error_classes[class].banner));
+  if (severity->name != NULL)
+    ds_printf (&msg, "%s: ", gettext (severity->name));
   
-  {
-    int *count = error_classes[class].count;
-    if (count)
-      (*count)++;
-  }
+  if (severity->count != NULL)
+    ++*severity->count;
   
-  if (command_name != NULL && (error_classes[class].flags & ERR_IN_PROCEDURE))
+  if (category->show_command_name && command_name != NULL)
     ds_printf (&msg, "%s: ", command_name);
 
-  if (e->title)
-    ds_puts (&msg, e->title);
-
-  ds_vprintf (&msg, format, args);
+  ds_puts (&msg, e->text);
 
   /* FIXME: Check set_messages and set_errors to determine where to
-     send errors and messages.
-
-     Please note that this is not trivial.  We have to avoid an
-     infinite loop in reporting errors that originate in the output
-     section. */
-  dump_message (ds_c_str (&msg), 8, puts_stdout, get_viewwidth());
+     send errors and messages. */
+  dump_message (ds_c_str (&msg), puts_stdout, get_viewwidth (), 8);
 
   ds_destroy (&msg);
+  free (e->text);
 }
 \f
 /* Private functions. */
 
-#if 0
-/* Write S followed by a newline to stderr. */
-static void
-puts_stderr (const char *s)
+/* Write LINE_INDENT spaces, the LENGTH characters in LINE, then
+   a new-line to stdout. */
+static void puts_stdout (int line_indent,
+                         const char *line, size_t length)
 {
-  fputs (s, stderr);
-  fputc ('\n', stderr);
+  int i;
+  for (i = 0; i < line_indent; i++)
+    putchar (' ');
+  fwrite (line, 1, length, stdout);
+  putchar ('\n');
 }
-#endif
 
-/* Write S followed by a newline to stdout. */
+/* Divides MSG into lines of WIDTH width for the first line and
+   WIDTH - INDENT width for each succeeding line.  Each line is
+   passed to FUNC as a null-terminated string (no new-line
+   character is included in the string). */
 static void
-puts_stdout (const char *s)
+dump_message (char *msg,
+              void (*func) (int line_indent, const char *line, size_t length),
+             unsigned width, unsigned indent)
 {
-  puts (s);
-}
-
-/* Returns 1 if the line must be broken here */
-static int
-compulsory_break(int c)
-{
-  return ( c == '\n' );
-}
-
-/* Returns 1 if C is a `break character', that is, if it is a good
-   place to break a message into lines. */
-static inline int
-char_is_break (int quote, int c)
-{
-  return ((quote && c == DIR_SEPARATOR)
-         || (!quote && (isspace (c) || c == '-' || c == '/'))); 
-}
-
-/* Returns 1 if C is a break character where the break should be made
-   BEFORE the character. */
-static inline int
-break_before (int quote, int c)
-{
-  return !quote && isspace (c);
-}
-
-/* If C is a break character, returns 1 if the break should be made
-   AFTER the character.  Does not return a meaningful result if C is
-   not a break character. */
-static inline int
-break_after (int quote, int c)
-{
-  return !break_before (quote, c);
-}
-
-/* If you want very long words that occur at a bad break point to be
-   broken into two lines even if they're shorter than a whole line by
-   themselves, define as 2/3, or 4/5, or whatever fraction of a whole
-   line you think is necessary in order to consider a word long enough
-   to break into pieces.  Otherwise, define as 0.  See code to grok
-   the details.  Do NOT parenthesize the expression!  */
-#define BREAK_LONG_WORD 0
-/* #define BREAK_LONG_WORD 2/3 */
-/* #define BREAK_LONG_WORD 4/5 */
-
-/* Divides MSG into lines of WIDTH width for the first line and WIDTH
-   - INDENT width for each succeeding line.  Each line is dumped
-   through FUNC, which may do with the string what it will. */
-static void
-dump_message (char *msg, unsigned indent, void (*func) (const char *),
-             unsigned width)
-{
-  char *cp;
-
-  /* 1 when at a position inside double quotes ("). */
-  int quote = 0;
-
-  /* Buffer for a single line. */
-  char *buf;
-
-  /* If the message is short, just print the full thing. */
-  if (strlen (msg) < width)
+  size_t length = strlen (msg);
+  char *string, *breaks;
+  int line_indent;
+  size_t line_start, i;
+
+  /* Allocate temporary buffers.
+     If we can't get memory for them, then just dump the whole
+     message. */
+  string = strdup (msg);
+  breaks = malloc (length);
+  if (string == NULL || breaks == NULL)
     {
-      func (msg);
+      free (string);
+      free (breaks);
+      func (0, msg, length);
       return;
     }
 
-  /* Make sure the indent isn't too big relative to the page width. */
+  /* Break into lines. */
   if (indent > width / 3)
     indent = width / 3;
-  
-  buf = local_alloc (width + 2);
-
-  /* Advance WIDTH characters into MSG.
-     If that's a valid breakpoint, keep it; otherwise, back up.
-     Output the line. */
-  for (cp = msg; (unsigned) (cp - msg) < width - 1 && 
-        ! compulsory_break(*cp); cp++)
-    if (*cp == '"')
-      quote ^= 1;
-
-  if (break_after (quote, (unsigned char) *cp))
-    {
-      for (cp--; !char_is_break (quote, (unsigned char) *cp) && cp > msg; cp--)
-       if (*cp == '"')
-         quote ^= 1;
-      
-      if (break_after (quote, (unsigned char) *cp))
-       cp++;
-    }
-
-  if (cp <= msg + width * BREAK_LONG_WORD)
-    for (; cp < msg + width - 1; cp++)
-      if (*cp == '"')
-       quote ^= 1;
-  
-  {
-    int c = *cp;
-    *cp = '\0';
-    func (msg);
-    *cp = c;
-  }
-
-
-  /* Repeat above procedure for remaining lines. */
-  for (;;)
-    {
-      static int hard_break=0;
-
-      int idx=0;
-      char *cp2;
-
-      /* Advance past whitespace. */
-      if (! hard_break ) 
-       while ( isspace ((unsigned char) *cp) )
-         cp++;
-      else
-       cp++;
-
-      if (*cp == 0)
-         break; 
-
-
-      /* Advance WIDTH - INDENT characters. */
-      for (cp2 = cp; (unsigned) (cp2 - cp) < width - indent && 
-            *cp2 && !compulsory_break(*cp2);  cp2++)
-       if (*cp2 == '"')
-         quote ^= 1;
-      
-      if ( compulsory_break(*cp2) )
-       hard_break = 1;
-      else
-       hard_break = 0;
-
-
-      /* Back up if this isn't a breakpoint. */
+  mbs_width_linebreaks (string, length,
+                        width - indent, -indent, 0,
+                        NULL, locale_charset (), breaks);
+
+  /* Pass lines to FUNC. */
+  line_start = 0;
+  line_indent = 0;
+  for (i = 0; i < length; i++)
+    switch (breaks[i]) 
       {
-       unsigned w = cp2 - cp;
-       if (*cp2 && ! compulsory_break(*cp2) )
-       for (cp2--; !char_is_break (quote, (unsigned char) *cp2) && 
-              cp2 > cp;
-              cp2--)
-         {
-
-           if (*cp2 == '"')
-             quote ^= 1;
-         }
-
-       if (w == width - indent
-           && (unsigned) (cp2 - cp) <= (width - indent) * BREAK_LONG_WORD)
-         for (; (unsigned) (cp2 - cp) < width - indent && *cp2 ; cp2++)
-           if (*cp2 == '"')
-             quote ^= 1;
+      case UC_BREAK_POSSIBLE:
+        /* Break before this character,
+           and include this character in the next line. */
+        func (line_indent, &string[line_start], i - line_start);
+        line_start = i;
+        line_indent = indent;
+        break;
+      case UC_BREAK_MANDATORY:
+        /* Break before this character,
+           but don't include this character in the next line
+           (because it'string a new-line). */
+        func (line_indent, &string[line_start], i - line_start);
+        line_start = i + 1;
+        line_indent = indent;
+        break;
+      default:
+        break;
       }
+  if (line_start < length)
+    func (line_indent, &string[line_start], length - line_start);
 
-      
-      /* Write out the line. */
-
-      memset (buf, ' ', indent);
-      memcpy (&buf[indent], cp, cp2 - cp);
-
-      buf[indent + idx + cp2 - cp] = '\0';
-      func (buf);
-      cp = cp2;
-    }
-
-  local_free (buf);
+  free (string);
+  free (breaks);
 }
 
 /* Sets COMMAND_NAME as the command name included in some kinds