/* Declaration for error-reporting function for Bison.

   Copyright (C) 2000-2002, 2004-2006, 2009-2015, 2018-2019 Free
   Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

/* Based on error.c and error.h,
   written by David MacKenzie <djm@gnu.ai.mit.edu>.  */

#include <config.h>
#include "system.h"

#include <argmatch.h>
#include <stdarg.h>
#include <progname.h>

#include "complain.h"
#include "files.h"
#include "fixits.h"
#include "getargs.h"
#include "quote.h"

err_status complaint_status = status_none;

bool warnings_are_errors = false;

/** Whether -Werror/-Wno-error was applied to a warning.  */
typedef enum
  {
    errority_unset = 0,     /** No explict status.  */
    errority_disabled = 1,  /** Explictly disabled with -Wno-error=foo.  */
    errority_enabled = 2    /** Explictly enabled with -Werror=foo. */
  } errority;

/** For each warning type, its errority.  */
static errority errority_flag[warnings_size];

/** Diagnostics severity.  */
typedef enum
  {
    severity_disabled = 0, /**< Explicitly disabled via -Wno-foo.  */
    severity_unset = 1,    /**< Unspecified status.  */
    severity_warning = 2,  /**< A warning.  */
    severity_error = 3,    /**< An error (continue, but die soon).  */
    severity_fatal = 4     /**< Fatal error (die now).  */
  } severity;


/** For each warning type, its severity.  */
static severity warnings_flag[warnings_size];

static unsigned *indent_ptr = 0;

/*------------------------.
| --warnings's handling.  |
`------------------------*/

static const char * const warnings_args[] =
{
  "none",
  "midrule-values",
  "yacc",
  "conflicts-sr",
  "conflicts-rr",
  "deprecated",
  "empty-rule",
  "precedence",
  "other",
  "all",
  "error",
  "everything",
  0
};

static const warnings warnings_types[] =
{
  Wnone,
  Wmidrule_values,
  Wyacc,
  Wconflicts_sr,
  Wconflicts_rr,
  Wdeprecated,
  Wempty_rule,
  Wprecedence,
  Wother,
  Wall,
  Werror,
  Weverything
};

ARGMATCH_VERIFY (warnings_args, warnings_types);

void
warning_argmatch (char const *arg, size_t no, size_t err)
{
  int value = XARGMATCH ("--warning", arg + no + err,
                         warnings_args, warnings_types);

  /* -Wnone == -Wno-everything, and -Wno-none == -Weverything.  */
  if (!value)
    {
      value = Weverything;
      no = !no;
    }

  for (size_t b = 0; b < warnings_size; ++b)
    if (value & 1 << b)
      {
        if (err && no)
          /* -Wno-error=foo.  */
          errority_flag[b] = errority_disabled;
        else if (err && !no)
          {
            /* -Werror=foo: enables -Wfoo. */
            errority_flag[b] = errority_enabled;
            warnings_flag[b] = severity_warning;
          }
        else if (no)
          /* -Wno-foo.  */
          warnings_flag[b] = severity_disabled;
        else
          /* -Wfoo. */
          warnings_flag[b] = severity_warning;
      }
}

/** Decode a comma-separated list of arguments from -W.
 *
 *  \param args     comma separated list of effective subarguments to decode.
 *                  If 0, then activate all the flags.
 */

void
warnings_argmatch (char *args)
{
  if (args)
    for (args = strtok (args, ","); args; args = strtok (NULL, ","))
      if (STREQ (args, "error"))
        warnings_are_errors = true;
      else if (STREQ (args, "no-error"))
        warnings_are_errors = false;
      else
        {
          /* The length of the possible 'no-' prefix: 3, or 0.  */
          size_t no = STRPREFIX_LIT ("no-", args) ? 3 : 0;
          /* The length of the possible 'error=' (possibly after
             'no-') prefix: 6, or 0. */
          size_t err = STRPREFIX_LIT ("error=", args + no) ? 6 : 0;

          warning_argmatch (args, no, err);
        }
  else
    warning_argmatch ("all", 0, 0);
}


/*-----------.
| complain.  |
`-----------*/

void
complain_init (void)
{
  warnings warnings_default =
    Wconflicts_sr | Wconflicts_rr | Wdeprecated | Wother;

  for (size_t b = 0; b < warnings_size; ++b)
    {
      warnings_flag[b] = (1 << b & warnings_default
                          ? severity_warning
                          : severity_unset);
      errority_flag[b] = errority_unset;
    }
}


/* A diagnostic with FLAGS is about to be issued.  With what severity?
   (severity_fatal, severity_error, severity_disabled, or
   severity_warning.) */

static severity
warning_severity (warnings flags)
{
  if (flags & fatal)
    /* Diagnostics about fatal errors.  */
    return severity_fatal;
  else if (flags & complaint)
    /* Diagnostics about errors.  */
    return severity_error;
  else
    {
      /* Diagnostics about warnings.  */
      severity res = severity_disabled;
      for (size_t b = 0; b < warnings_size; ++b)
        if (flags & 1 << b)
          {
            res = res < warnings_flag[b] ? warnings_flag[b] : res;
            /* If the diagnostic is enabled, and -Werror is enabled,
               and -Wno-error=foo was not explicitly requested, this
               is an error. */
            if (res == severity_warning
                && (errority_flag[b] == errority_enabled
                    || (warnings_are_errors
                        && errority_flag[b] != errority_disabled)))
              res = severity_error;
          }
      return res;
    }
}

bool
warning_is_unset (warnings flags)
{
  for (size_t b = 0; b < warnings_size; ++b)
    if (flags & 1 << b && warnings_flag[b] != severity_unset)
      return false;
  return true;
}

/** Display a "[-Wyacc]" like message on \a f.  */

static void
warnings_print_categories (warnings warn_flags, FILE *f)
{
  /* Display only the first match, the second is "-Wall".  */
  for (size_t i = 0; warnings_args[i]; ++i)
    if (warn_flags & warnings_types[i])
      {
        severity s = warning_severity (warnings_types[i]);
        fprintf (f, " [-W%s%s]",
                 s == severity_error ? "error=" : "",
                 warnings_args[i]);
        return;
      }
}

/** Report an error message.
 *
 * \param loc     the location, defaulting to the current file,
 *                or the program name.
 * \param flags   the category for this message.
 * \param prefix  put before the message (e.g., "warning").
 * \param message the error message, a printf format string.  Iff it
 *                ends with ": ", then no trailing newline is printed,
 *                and the caller should print the remaining
 *                newline-terminated message to stderr.
 * \param args    the arguments of the format string.
 */
static
void
error_message (const location *loc, warnings flags, const char *prefix,
               const char *message, va_list args)
{
  unsigned pos = 0;

  if (loc)
    pos += location_print (*loc, stderr);
  else
    pos += fprintf (stderr, "%s", current_file ? current_file : program_name);
  pos += fprintf (stderr, ": ");

  if (indent_ptr)
    {
      if (*indent_ptr)
        prefix = NULL;
      if (!*indent_ptr)
        *indent_ptr = pos;
      else if (*indent_ptr > pos)
        fprintf (stderr, "%*s", *indent_ptr - pos, "");
      indent_ptr = NULL;
    }

  if (prefix)
    fprintf (stderr, "%s: ", prefix);

  vfprintf (stderr, message, args);
  /* Print the type of warning, only if this is not a sub message
     (in which case the prefix is null).  */
  if (! (flags & silent) && prefix)
    warnings_print_categories (flags, stderr);

  {
    size_t l = strlen (message);
    if (l < 2 || message[l - 2] != ':' || message[l - 1] != ' ')
      {
        putc ('\n', stderr);
        fflush (stderr);
        if (loc && feature_flag & feature_caret && !(flags & no_caret))
          location_caret (*loc, stderr);
      }
  }
  fflush (stderr);
}

/** Raise a complaint. That can be a fatal error, an error or just a
    warning.  */

static void
complains (const location *loc, warnings flags, const char *message,
           va_list args)
{
  severity s = warning_severity (flags);
  if ((flags & complaint) && complaint_status < status_complaint)
    complaint_status = status_complaint;

  if (severity_warning <= s)
    {
      const char* prefix =
        s == severity_fatal ? _("fatal error")
        : s == severity_error ? _("error")
        : _("warning");
      if (severity_error <= s && ! complaint_status)
        complaint_status = status_warning_as_error;
      error_message (loc, flags, prefix, message, args);
    }

  if (flags & fatal)
    exit (EXIT_FAILURE);
}

void
complain (location const *loc, warnings flags, const char *message, ...)
{
  va_list args;
  va_start (args, message);
  complains (loc, flags, message, args);
  va_end (args);
}

void
complain_indent (location const *loc, warnings flags, unsigned *indent,
                 const char *message, ...)
{
  va_list args;
  indent_ptr = indent;
  va_start (args, message);
  complains (loc, flags, message, args);
  va_end (args);
}

void
complain_args (location const *loc, warnings w, unsigned *indent,
               int argc, char *argv[])
{
  switch (argc)
  {
  case 1:
    complain_indent (loc, w, indent, "%s", _(argv[0]));
    break;
  case 2:
    complain_indent (loc, w, indent, _(argv[0]), argv[1]);
    break;
  case 3:
    complain_indent (loc, w, indent, _(argv[0]), argv[1], argv[2]);
    break;
  case 4:
    complain_indent (loc, w, indent, _(argv[0]), argv[1], argv[2], argv[3]);
    break;
  case 5:
    complain_indent (loc, w, indent, _(argv[0]), argv[1], argv[2], argv[3],
                     argv[4]);
    break;
  default:
    complain (loc, fatal, "too many arguments for complains");
    break;
  }
}


void
bison_directive (location const *loc, char const *directive)
{
  complain (loc, Wyacc,
            _("POSIX Yacc does not support %s"), directive);
}

void
deprecated_directive (location const *loc, char const *old, char const *upd)
{
  if (feature_flag & feature_caret)
    complain (loc, Wdeprecated,
              _("deprecated directive, use %s"),
              quote_n (1, upd));
  else
    complain (loc, Wdeprecated,
              _("deprecated directive: %s, use %s"),
              quote (old), quote_n (1, upd));
  /* Register updates only if -Wdeprecated is enabled.  */
  if (warnings_flag[warning_deprecated] != severity_disabled)
    fixits_register (loc, upd);
}

void
duplicate_directive (char const *directive,
                     location first, location second)
{
  unsigned i = 0;
  if (feature_flag & feature_caret)
    complain_indent (&second, Wother, &i, _("duplicate directive"));
  else
    complain_indent (&second, Wother, &i, _("duplicate directive: %s"), quote (directive));
  i += SUB_INDENT;
  complain_indent (&first, Wother, &i, _("previous declaration"));
  fixits_register (&second, "");
}

void
duplicate_rule_directive (char const *directive,
                          location first, location second)
{
  unsigned i = 0;
  complain_indent (&second, complaint, &i,
                   _("only one %s allowed per rule"), directive);
  i += SUB_INDENT;
  complain_indent (&first, complaint, &i,
                   _("previous declaration"));
  fixits_register (&second, "");
}
